mirror of
https://github.com/QIDITECH/QIDISlicer.git
synced 2026-02-01 08:28:42 +03:00
QIDISlicer1.0.0
This commit is contained in:
178
src/slic3r/GUI/2DBed.cpp
Normal file
178
src/slic3r/GUI/2DBed.cpp
Normal file
@@ -0,0 +1,178 @@
|
||||
#include "2DBed.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
|
||||
#include <wx/dcbuffer.h>
|
||||
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
|
||||
Bed_2D::Bed_2D(wxWindow* parent) :
|
||||
wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(25 * wxGetApp().em_unit(), -1), wxTAB_TRAVERSAL)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
m_user_drawn_background = false;
|
||||
#else
|
||||
SetBackgroundStyle(wxBG_STYLE_PAINT); // to avoid assert message after wxAutoBufferedPaintDC
|
||||
#endif /*__APPLE__*/
|
||||
}
|
||||
|
||||
void Bed_2D::repaint(const std::vector<Vec2d>& shape)
|
||||
{
|
||||
wxAutoBufferedPaintDC dc(this);
|
||||
auto cw = GetSize().GetWidth();
|
||||
auto ch = GetSize().GetHeight();
|
||||
// when canvas is not rendered yet, size is 0, 0
|
||||
if (cw == 0) return ;
|
||||
|
||||
if (m_user_drawn_background) {
|
||||
// On all systems the AutoBufferedPaintDC() achieves double buffering.
|
||||
// On MacOS the background is erased, on Windows the background is not erased
|
||||
// and on Linux / GTK the background is erased to gray color.
|
||||
// Fill DC with the background on Windows & Linux / GTK.
|
||||
#ifdef _WIN32
|
||||
auto color = wxGetApp().get_highlight_default_clr();
|
||||
#else
|
||||
auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); //GetSystemColour
|
||||
#endif
|
||||
dc.SetPen(wxPen(color, 1, wxPENSTYLE_SOLID));
|
||||
dc.SetBrush(wxBrush(color, wxBRUSHSTYLE_SOLID));
|
||||
auto rect = GetUpdateRegion().GetBox();
|
||||
dc.DrawRectangle(rect.GetLeft(), rect.GetTop(), rect.GetWidth(), rect.GetHeight());
|
||||
}
|
||||
|
||||
if (shape.empty())
|
||||
return;
|
||||
|
||||
// reduce size to have some space around the drawn shape
|
||||
cw -= (2 * Border);
|
||||
ch -= (2 * Border);
|
||||
|
||||
auto cbb = BoundingBoxf(Vec2d(0, 0),Vec2d(cw, ch));
|
||||
auto ccenter = cbb.center();
|
||||
|
||||
// get bounding box of bed shape in G - code coordinates
|
||||
auto bed_polygon = Polygon::new_scale(shape);
|
||||
auto bb = BoundingBoxf(shape);
|
||||
bb.merge(Vec2d(0, 0)); // origin needs to be in the visible area
|
||||
auto bw = bb.size()(0);
|
||||
auto bh = bb.size()(1);
|
||||
auto bcenter = bb.center();
|
||||
|
||||
// calculate the scaling factor for fitting bed shape in canvas area
|
||||
auto sfactor = std::min(cw/bw, ch/bh);
|
||||
auto shift = Vec2d(
|
||||
ccenter(0) - bcenter(0) * sfactor,
|
||||
ccenter(1) - bcenter(1) * sfactor
|
||||
);
|
||||
|
||||
m_scale_factor = sfactor;
|
||||
m_shift = Vec2d(shift(0) + cbb.min(0), shift(1) - (cbb.max(1) - ch));
|
||||
|
||||
// draw bed fill
|
||||
dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxBRUSHSTYLE_SOLID));
|
||||
|
||||
wxPointList pt_list;
|
||||
const size_t pt_cnt = shape.size();
|
||||
std::vector<wxPoint> points;
|
||||
points.reserve(pt_cnt);
|
||||
for (const auto& shape_pt : shape) {
|
||||
Point pt_pix = to_pixels(shape_pt, ch);
|
||||
points.emplace_back(wxPoint(pt_pix(0), pt_pix(1)));
|
||||
pt_list.Append(&points.back());
|
||||
}
|
||||
dc.DrawPolygon(&pt_list, 0, 0);
|
||||
|
||||
// draw grid
|
||||
auto step = 10; // 1cm grid
|
||||
Polylines polylines;
|
||||
for (auto x = bb.min(0) - fmod(bb.min(0), step) + step; x < bb.max(0); x += step) {
|
||||
polylines.push_back(Polyline::new_scale({ Vec2d(x, bb.min(1)), Vec2d(x, bb.max(1)) }));
|
||||
}
|
||||
for (auto y = bb.min(1) - fmod(bb.min(1), step) + step; y < bb.max(1); y += step) {
|
||||
polylines.push_back(Polyline::new_scale({ Vec2d(bb.min(0), y), Vec2d(bb.max(0), y) }));
|
||||
}
|
||||
polylines = intersection_pl(polylines, bed_polygon);
|
||||
|
||||
dc.SetPen(wxPen(wxColour(230, 230, 230), 1, wxPENSTYLE_SOLID));
|
||||
for (auto pl : polylines)
|
||||
{
|
||||
for (size_t i = 0; i < pl.points.size()-1; i++) {
|
||||
Point pt1 = to_pixels(unscale(pl.points[i]), ch);
|
||||
Point pt2 = to_pixels(unscale(pl.points[i + 1]), ch);
|
||||
dc.DrawLine(pt1(0), pt1(1), pt2(0), pt2(1));
|
||||
}
|
||||
}
|
||||
|
||||
// draw bed contour
|
||||
dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxPENSTYLE_SOLID));
|
||||
dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxBRUSHSTYLE_TRANSPARENT));
|
||||
dc.DrawPolygon(&pt_list, 0, 0);
|
||||
|
||||
auto origin_px = to_pixels(Vec2d(0, 0), ch);
|
||||
|
||||
// draw axes
|
||||
auto axes_len = 50;
|
||||
auto arrow_len = 6;
|
||||
auto arrow_angle = Geometry::deg2rad(45.0);
|
||||
dc.SetPen(wxPen(wxColour(255, 0, 0), 2, wxPENSTYLE_SOLID)); // red
|
||||
auto x_end = Vec2d(origin_px(0) + axes_len, origin_px(1));
|
||||
dc.DrawLine(wxPoint(origin_px(0), origin_px(1)), wxPoint(x_end(0), x_end(1)));
|
||||
for (auto angle : { -arrow_angle, arrow_angle }) {
|
||||
auto end = Eigen::Translation2d(x_end) * Eigen::Rotation2Dd(angle) * Eigen::Translation2d(- x_end) * Eigen::Vector2d(x_end(0) - arrow_len, x_end(1));
|
||||
dc.DrawLine(wxPoint(x_end(0), x_end(1)), wxPoint(end(0), end(1)));
|
||||
}
|
||||
|
||||
dc.SetPen(wxPen(wxColour(0, 255, 0), 2, wxPENSTYLE_SOLID)); // green
|
||||
auto y_end = Vec2d(origin_px(0), origin_px(1) - axes_len);
|
||||
dc.DrawLine(wxPoint(origin_px(0), origin_px(1)), wxPoint(y_end(0), y_end(1)));
|
||||
for (auto angle : { -arrow_angle, arrow_angle }) {
|
||||
auto end = Eigen::Translation2d(y_end) * Eigen::Rotation2Dd(angle) * Eigen::Translation2d(- y_end) * Eigen::Vector2d(y_end(0), y_end(1) + arrow_len);
|
||||
dc.DrawLine(wxPoint(y_end(0), y_end(1)), wxPoint(end(0), end(1)));
|
||||
}
|
||||
|
||||
// draw origin
|
||||
dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxPENSTYLE_SOLID));
|
||||
dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxBRUSHSTYLE_SOLID));
|
||||
dc.DrawCircle(origin_px(0), origin_px(1), 3);
|
||||
|
||||
static const auto origin_label = wxString("(0,0)");
|
||||
dc.SetTextForeground(wxColour(0, 0, 0));
|
||||
dc.SetFont(wxFont(10, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL));
|
||||
auto extent = dc.GetTextExtent(origin_label);
|
||||
const auto origin_label_x = origin_px(0) <= cw / 2 ? origin_px(0) + 1 : origin_px(0) - 1 - extent.GetWidth();
|
||||
const auto origin_label_y = origin_px(1) <= ch / 2 ? origin_px(1) + 1 : origin_px(1) - 1 - extent.GetHeight();
|
||||
dc.DrawText(origin_label, origin_label_x, origin_label_y);
|
||||
|
||||
// draw current position
|
||||
if (m_pos!= Vec2d(0, 0)) {
|
||||
auto pos_px = to_pixels(m_pos, ch);
|
||||
dc.SetPen(wxPen(wxColour(200, 0, 0), 2, wxPENSTYLE_SOLID));
|
||||
dc.SetBrush(wxBrush(wxColour(200, 0, 0), wxBRUSHSTYLE_TRANSPARENT));
|
||||
dc.DrawCircle(pos_px(0), pos_px(1), 5);
|
||||
|
||||
dc.DrawLine(pos_px(0) - 15, pos_px(1), pos_px(0) + 15, pos_px(1));
|
||||
dc.DrawLine(pos_px(0), pos_px(1) - 15, pos_px(0), pos_px(1) + 15);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// convert G - code coordinates into pixels
|
||||
Point Bed_2D::to_pixels(const Vec2d& point, int height)
|
||||
{
|
||||
auto p = point * m_scale_factor + m_shift;
|
||||
return Point(p(0) + Border, height - p(1) + Border);
|
||||
}
|
||||
|
||||
void Bed_2D::set_pos(const Vec2d& pos)
|
||||
{
|
||||
m_pos = pos;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
33
src/slic3r/GUI/2DBed.hpp
Normal file
33
src/slic3r/GUI/2DBed.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#ifndef slic3r_2DBed_hpp_
|
||||
#define slic3r_2DBed_hpp_
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include "libslic3r/Config.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class Bed_2D : public wxPanel
|
||||
{
|
||||
static const int Border = 10;
|
||||
|
||||
bool m_user_drawn_background = true;
|
||||
|
||||
double m_scale_factor;
|
||||
Vec2d m_shift = Vec2d::Zero();
|
||||
Vec2d m_pos = Vec2d::Zero();
|
||||
|
||||
Point to_pixels(const Vec2d& point, int height);
|
||||
void set_pos(const Vec2d& pos);
|
||||
|
||||
public:
|
||||
explicit Bed_2D(wxWindow* parent);
|
||||
|
||||
void repaint(const std::vector<Vec2d>& shape);
|
||||
};
|
||||
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
#endif /* slic3r_2DBed_hpp_ */
|
||||
579
src/slic3r/GUI/3DBed.cpp
Normal file
579
src/slic3r/GUI/3DBed.cpp
Normal file
@@ -0,0 +1,579 @@
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include "3DBed.hpp"
|
||||
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/Geometry/Circle.hpp"
|
||||
#include "libslic3r/Tesselate.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
|
||||
#include "GUI_App.hpp"
|
||||
#include "GLCanvas3D.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "Camera.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
static const float GROUND_Z = -0.02f;
|
||||
static const Slic3r::ColorRGBA DEFAULT_MODEL_COLOR = Slic3r::ColorRGBA::DARK_GRAY();
|
||||
static const Slic3r::ColorRGBA PICKING_MODEL_COLOR = Slic3r::ColorRGBA::BLACK();
|
||||
static const Slic3r::ColorRGBA DEFAULT_SOLID_GRID_COLOR = { 0.9f, 0.9f, 0.9f, 1.0f };
|
||||
static const Slic3r::ColorRGBA DEFAULT_TRANSPARENT_GRID_COLOR = { 0.9f, 0.9f, 0.9f, 0.6f };
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
bool Bed3D::set_shape(const Pointfs& bed_shape, const double max_print_height, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom)
|
||||
{
|
||||
auto check_texture = [](const std::string& texture) {
|
||||
boost::system::error_code ec; // so the exists call does not throw (e.g. after a permission problem)
|
||||
return !texture.empty() && (boost::algorithm::iends_with(texture, ".png") || boost::algorithm::iends_with(texture, ".svg")) && boost::filesystem::exists(texture, ec);
|
||||
};
|
||||
|
||||
auto check_model = [](const std::string& model) {
|
||||
boost::system::error_code ec;
|
||||
return !model.empty() && boost::algorithm::iends_with(model, ".stl") && boost::filesystem::exists(model, ec);
|
||||
};
|
||||
|
||||
Type type;
|
||||
std::string model;
|
||||
std::string texture;
|
||||
if (force_as_custom)
|
||||
type = Type::Custom;
|
||||
else {
|
||||
auto [new_type, system_model, system_texture] = detect_type(bed_shape);
|
||||
type = new_type;
|
||||
model = system_model;
|
||||
texture = system_texture;
|
||||
}
|
||||
|
||||
std::string texture_filename = custom_texture.empty() ? texture : custom_texture;
|
||||
if (! texture_filename.empty() && ! check_texture(texture_filename)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Unable to load bed texture: " << texture_filename;
|
||||
texture_filename.clear();
|
||||
}
|
||||
|
||||
std::string model_filename = custom_model.empty() ? model : custom_model;
|
||||
if (! model_filename.empty() && ! check_model(model_filename)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Unable to load bed model: " << model_filename;
|
||||
model_filename.clear();
|
||||
}
|
||||
|
||||
|
||||
if (m_build_volume.bed_shape() == bed_shape && m_build_volume.max_print_height() == max_print_height && m_type == type && m_texture_filename == texture_filename && m_model_filename == model_filename)
|
||||
// No change, no need to update the UI.
|
||||
return false;
|
||||
|
||||
m_type = type;
|
||||
m_build_volume = BuildVolume { bed_shape, max_print_height };
|
||||
m_texture_filename = texture_filename;
|
||||
m_model_filename = model_filename;
|
||||
m_extended_bounding_box = this->calc_extended_bounding_box();
|
||||
|
||||
m_contour = ExPolygon(Polygon::new_scale(bed_shape));
|
||||
const BoundingBox bbox = m_contour.contour.bounding_box();
|
||||
if (!bbox.defined)
|
||||
throw RuntimeError(std::string("Invalid bed shape"));
|
||||
m_polygon = offset(m_contour.contour, (float)bbox.radius() * 1.7f, jtRound, scale_(0.5)).front();
|
||||
|
||||
m_triangles.reset();
|
||||
m_gridlines.reset();
|
||||
m_contourlines.reset();
|
||||
m_texture.reset();
|
||||
m_model.reset();
|
||||
|
||||
// Set the origin and size for rendering the coordinate system axes.
|
||||
m_axes.set_origin({ 0.0, 0.0, static_cast<double>(GROUND_Z) });
|
||||
m_axes.set_stem_length(0.1f * static_cast<float>(m_build_volume.bounding_volume().max_size()));
|
||||
|
||||
// unregister from picking
|
||||
wxGetApp().plater()->canvas3D()->remove_raycasters_for_picking(SceneRaycaster::EType::Bed);
|
||||
|
||||
// Let the calee to update the UI.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bed3D::contains(const Point& point) const
|
||||
{
|
||||
return m_polygon.contains(point);
|
||||
}
|
||||
|
||||
Point Bed3D::point_projection(const Point& point) const
|
||||
{
|
||||
return m_polygon.point_projection(point);
|
||||
}
|
||||
|
||||
void Bed3D::render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_texture)
|
||||
{
|
||||
render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, show_texture, false);
|
||||
}
|
||||
|
||||
void Bed3D::render_for_picking(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor)
|
||||
{
|
||||
render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, false, true);
|
||||
}
|
||||
|
||||
void Bed3D::render_internal(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor,
|
||||
bool show_texture, bool picking)
|
||||
{
|
||||
m_scale_factor = scale_factor;
|
||||
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
|
||||
m_model.model.set_color(picking ? PICKING_MODEL_COLOR : DEFAULT_MODEL_COLOR);
|
||||
|
||||
switch (m_type)
|
||||
{
|
||||
case Type::System: { render_system(canvas, view_matrix, projection_matrix, bottom, show_texture); break; }
|
||||
default:
|
||||
case Type::Custom: { render_custom(canvas, view_matrix, projection_matrix, bottom, show_texture, picking); break; }
|
||||
}
|
||||
|
||||
glsafe(::glDisable(GL_DEPTH_TEST));
|
||||
}
|
||||
|
||||
// Calculate an extended bounding box from axes and current model for visualization purposes.
|
||||
BoundingBoxf3 Bed3D::calc_extended_bounding_box() const
|
||||
{
|
||||
BoundingBoxf3 out { m_build_volume.bounding_volume() };
|
||||
const Vec3d size = out.size();
|
||||
// ensures that the bounding box is set as defined or the following calls to merge() will not work as intented
|
||||
if (size.x() > 0.0 && size.y() > 0.0 && !out.defined)
|
||||
out.defined = true;
|
||||
// Reset the build volume Z, we don't want to zoom to the top of the build volume if it is empty.
|
||||
out.min.z() = 0.0;
|
||||
out.max.z() = 0.0;
|
||||
// extend to origin in case origin is off bed
|
||||
out.merge(m_axes.get_origin());
|
||||
// extend to contain axes
|
||||
out.merge(m_axes.get_origin() + m_axes.get_total_length() * Vec3d::Ones());
|
||||
out.merge(out.min + Vec3d(-m_axes.get_tip_radius(), -m_axes.get_tip_radius(), out.max.z()));
|
||||
// extend to contain model, if any
|
||||
BoundingBoxf3 model_bb = m_model.model.get_bounding_box();
|
||||
if (model_bb.defined) {
|
||||
model_bb.translate(m_model_offset);
|
||||
out.merge(model_bb);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void Bed3D::init_triangles()
|
||||
{
|
||||
if (m_triangles.is_initialized())
|
||||
return;
|
||||
|
||||
if (m_contour.empty())
|
||||
return;
|
||||
|
||||
const std::vector<Vec2f> triangles = triangulate_expolygon_2f(m_contour, NORMALS_UP);
|
||||
if (triangles.empty() || triangles.size() % 3 != 0)
|
||||
return;
|
||||
|
||||
GLModel::Geometry init_data;
|
||||
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3T2 };
|
||||
init_data.reserve_vertices(triangles.size());
|
||||
init_data.reserve_indices(triangles.size() / 3);
|
||||
|
||||
Vec2f min = triangles.front();
|
||||
Vec2f max = min;
|
||||
for (const Vec2f& v : triangles) {
|
||||
min = min.cwiseMin(v).eval();
|
||||
max = max.cwiseMax(v).eval();
|
||||
}
|
||||
|
||||
const Vec2f size = max - min;
|
||||
if (size.x() <= 0.0f || size.y() <= 0.0f)
|
||||
return;
|
||||
|
||||
Vec2f inv_size = size.cwiseInverse();
|
||||
inv_size.y() *= -1.0f;
|
||||
|
||||
// vertices + indices
|
||||
unsigned int vertices_counter = 0;
|
||||
for (const Vec2f& v : triangles) {
|
||||
const Vec3f p = { v.x(), v.y(), GROUND_Z };
|
||||
init_data.add_vertex(p, (Vec2f)(v - min).cwiseProduct(inv_size).eval());
|
||||
++vertices_counter;
|
||||
if (vertices_counter % 3 == 0)
|
||||
init_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1);
|
||||
}
|
||||
|
||||
if (m_model.model.get_filename().empty() && m_model.mesh_raycaster == nullptr)
|
||||
// register for picking
|
||||
register_raycasters_for_picking(init_data, Transform3d::Identity());
|
||||
|
||||
m_triangles.init_from(std::move(init_data));
|
||||
m_triangles.set_color(DEFAULT_MODEL_COLOR);
|
||||
}
|
||||
|
||||
void Bed3D::init_gridlines()
|
||||
{
|
||||
if (m_gridlines.is_initialized())
|
||||
return;
|
||||
|
||||
if (m_contour.empty())
|
||||
return;
|
||||
|
||||
const BoundingBox& bed_bbox = m_contour.contour.bounding_box();
|
||||
const coord_t step = scale_(10.0);
|
||||
|
||||
Polylines axes_lines;
|
||||
for (coord_t x = bed_bbox.min.x(); x <= bed_bbox.max.x(); x += step) {
|
||||
Polyline line;
|
||||
line.append(Point(x, bed_bbox.min.y()));
|
||||
line.append(Point(x, bed_bbox.max.y()));
|
||||
axes_lines.push_back(line);
|
||||
}
|
||||
for (coord_t y = bed_bbox.min.y(); y <= bed_bbox.max.y(); y += step) {
|
||||
Polyline line;
|
||||
line.append(Point(bed_bbox.min.x(), y));
|
||||
line.append(Point(bed_bbox.max.x(), y));
|
||||
axes_lines.push_back(line);
|
||||
}
|
||||
|
||||
// clip with a slightly grown expolygon because our lines lay on the contours and may get erroneously clipped
|
||||
Lines gridlines = to_lines(intersection_pl(axes_lines, offset(m_contour, float(SCALED_EPSILON))));
|
||||
|
||||
// append bed contours
|
||||
Lines contour_lines = to_lines(m_contour);
|
||||
std::copy(contour_lines.begin(), contour_lines.end(), std::back_inserter(gridlines));
|
||||
|
||||
GLModel::Geometry init_data;
|
||||
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
|
||||
init_data.reserve_vertices(2 * gridlines.size());
|
||||
init_data.reserve_indices(2 * gridlines.size());
|
||||
|
||||
for (const Slic3r::Line& l : gridlines) {
|
||||
init_data.add_vertex(Vec3f(unscale<float>(l.a.x()), unscale<float>(l.a.y()), GROUND_Z));
|
||||
init_data.add_vertex(Vec3f(unscale<float>(l.b.x()), unscale<float>(l.b.y()), GROUND_Z));
|
||||
const unsigned int vertices_counter = (unsigned int)init_data.vertices_count();
|
||||
init_data.add_line(vertices_counter - 2, vertices_counter - 1);
|
||||
}
|
||||
|
||||
m_gridlines.init_from(std::move(init_data));
|
||||
}
|
||||
|
||||
void Bed3D::init_contourlines()
|
||||
{
|
||||
if (m_contourlines.is_initialized())
|
||||
return;
|
||||
|
||||
if (m_contour.empty())
|
||||
return;
|
||||
|
||||
const Lines contour_lines = to_lines(m_contour);
|
||||
|
||||
GLModel::Geometry init_data;
|
||||
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P3 };
|
||||
init_data.reserve_vertices(2 * contour_lines.size());
|
||||
init_data.reserve_indices(2 * contour_lines.size());
|
||||
|
||||
for (const Slic3r::Line& l : contour_lines) {
|
||||
init_data.add_vertex(Vec3f(unscale<float>(l.a.x()), unscale<float>(l.a.y()), GROUND_Z));
|
||||
init_data.add_vertex(Vec3f(unscale<float>(l.b.x()), unscale<float>(l.b.y()), GROUND_Z));
|
||||
const unsigned int vertices_counter = (unsigned int)init_data.vertices_count();
|
||||
init_data.add_line(vertices_counter - 2, vertices_counter - 1);
|
||||
}
|
||||
|
||||
m_contourlines.init_from(std::move(init_data));
|
||||
m_contourlines.set_color({ 1.0f, 1.0f, 1.0f, 0.5f });
|
||||
}
|
||||
|
||||
// Try to match the print bed shape with the shape of an active profile. If such a match exists,
|
||||
// return the print bed model.
|
||||
std::tuple<Bed3D::Type, std::string, std::string> Bed3D::detect_type(const Pointfs& shape)
|
||||
{
|
||||
auto bundle = wxGetApp().preset_bundle;
|
||||
if (bundle != nullptr) {
|
||||
const Preset* curr = &bundle->printers.get_selected_preset();
|
||||
while (curr != nullptr) {
|
||||
if (curr->config.has("bed_shape")) {
|
||||
if (shape == dynamic_cast<const ConfigOptionPoints*>(curr->config.option("bed_shape"))->values) {
|
||||
std::string model_filename = PresetUtils::system_printer_bed_model(*curr);
|
||||
std::string texture_filename = PresetUtils::system_printer_bed_texture(*curr);
|
||||
if (!model_filename.empty() && !texture_filename.empty())
|
||||
return { Type::System, model_filename, texture_filename };
|
||||
}
|
||||
}
|
||||
|
||||
curr = bundle->printers.get_preset_parent(*curr);
|
||||
}
|
||||
}
|
||||
|
||||
return { Type::Custom, {}, {} };
|
||||
}
|
||||
|
||||
void Bed3D::render_axes()
|
||||
{
|
||||
if (m_build_volume.valid())
|
||||
m_axes.render(Transform3d::Identity(), 0.25f);
|
||||
}
|
||||
|
||||
void Bed3D::render_system(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_texture)
|
||||
{
|
||||
if (!bottom)
|
||||
render_model(view_matrix, projection_matrix);
|
||||
|
||||
if (show_texture)
|
||||
render_texture(bottom, canvas, view_matrix, projection_matrix);
|
||||
else if (bottom)
|
||||
render_contour(view_matrix, projection_matrix);
|
||||
}
|
||||
|
||||
void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix)
|
||||
{
|
||||
if (m_texture_filename.empty()) {
|
||||
m_texture.reset();
|
||||
render_default(bottom, false, true, view_matrix, projection_matrix);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_texture.get_id() == 0 || m_texture.get_source() != m_texture_filename) {
|
||||
m_texture.reset();
|
||||
|
||||
if (boost::algorithm::iends_with(m_texture_filename, ".svg")) {
|
||||
// use higher resolution images if graphic card and opengl version allow
|
||||
GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size();
|
||||
if (m_temp_texture.get_id() == 0 || m_temp_texture.get_source() != m_texture_filename) {
|
||||
// generate a temporary lower resolution texture to show while no main texture levels have been compressed
|
||||
if (!m_temp_texture.load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8)) {
|
||||
render_default(bottom, false, true, view_matrix, projection_matrix);
|
||||
return;
|
||||
}
|
||||
canvas.request_extra_frame();
|
||||
}
|
||||
|
||||
// starts generating the main texture, compression will run asynchronously
|
||||
if (!m_texture.load_from_svg_file(m_texture_filename, true, true, true, max_tex_size)) {
|
||||
render_default(bottom, false, true, view_matrix, projection_matrix);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (boost::algorithm::iends_with(m_texture_filename, ".png")) {
|
||||
// generate a temporary lower resolution texture to show while no main texture levels have been compressed
|
||||
if (m_temp_texture.get_id() == 0 || m_temp_texture.get_source() != m_texture_filename) {
|
||||
if (!m_temp_texture.load_from_file(m_texture_filename, false, GLTexture::None, false)) {
|
||||
render_default(bottom, false, true, view_matrix, projection_matrix);
|
||||
return;
|
||||
}
|
||||
canvas.request_extra_frame();
|
||||
}
|
||||
|
||||
// starts generating the main texture, compression will run asynchronously
|
||||
if (!m_texture.load_from_file(m_texture_filename, true, GLTexture::MultiThreaded, true)) {
|
||||
render_default(bottom, false, true, view_matrix, projection_matrix);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
render_default(bottom, false, true, view_matrix, projection_matrix);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (m_texture.unsent_compressed_data_available()) {
|
||||
// sends to gpu the already available compressed levels of the main texture
|
||||
m_texture.send_compressed_data_to_gpu();
|
||||
|
||||
// the temporary texture is not needed anymore, reset it
|
||||
if (m_temp_texture.get_id() != 0)
|
||||
m_temp_texture.reset();
|
||||
|
||||
canvas.request_extra_frame();
|
||||
}
|
||||
|
||||
init_triangles();
|
||||
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("printbed");
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
shader->set_uniform("view_model_matrix", view_matrix);
|
||||
shader->set_uniform("projection_matrix", projection_matrix);
|
||||
shader->set_uniform("transparent_background", bottom);
|
||||
shader->set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg"));
|
||||
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
if (bottom)
|
||||
glsafe(::glDepthMask(GL_FALSE));
|
||||
|
||||
glsafe(::glEnable(GL_BLEND));
|
||||
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
|
||||
|
||||
if (bottom)
|
||||
glsafe(::glFrontFace(GL_CW));
|
||||
|
||||
// show the temporary texture while no compressed data is available
|
||||
GLuint tex_id = (GLuint)m_temp_texture.get_id();
|
||||
if (tex_id == 0)
|
||||
tex_id = (GLuint)m_texture.get_id();
|
||||
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id));
|
||||
m_triangles.render();
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
|
||||
if (bottom)
|
||||
glsafe(::glFrontFace(GL_CCW));
|
||||
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
if (bottom)
|
||||
glsafe(::glDepthMask(GL_TRUE));
|
||||
|
||||
shader->stop_using();
|
||||
}
|
||||
}
|
||||
|
||||
void Bed3D::render_model(const Transform3d& view_matrix, const Transform3d& projection_matrix)
|
||||
{
|
||||
if (m_model_filename.empty())
|
||||
return;
|
||||
|
||||
if (m_model.model.get_filename() != m_model_filename && m_model.model.init_from_file(m_model_filename)) {
|
||||
m_model.model.set_color(DEFAULT_MODEL_COLOR);
|
||||
|
||||
// move the model so that its origin (0.0, 0.0, 0.0) goes into the bed shape center and a bit down to avoid z-fighting with the texture quad
|
||||
m_model_offset = to_3d(m_build_volume.bounding_volume2d().center(), -0.03);
|
||||
|
||||
// register for picking
|
||||
const std::vector<std::shared_ptr<SceneRaycasterItem>>* const raycaster = wxGetApp().plater()->canvas3D()->get_raycasters_for_picking(SceneRaycaster::EType::Bed);
|
||||
if (!raycaster->empty()) {
|
||||
// The raycaster may have been set by the call to init_triangles() made from render_texture() if the printbed was
|
||||
// changed while the camera was pointing upward.
|
||||
// In this case we need to remove it before creating a new using the model geometry
|
||||
wxGetApp().plater()->canvas3D()->remove_raycasters_for_picking(SceneRaycaster::EType::Bed);
|
||||
m_model.mesh_raycaster.reset();
|
||||
}
|
||||
register_raycasters_for_picking(m_model.model.get_geometry(), Geometry::translation_transform(m_model_offset));
|
||||
|
||||
// update extended bounding box
|
||||
m_extended_bounding_box = this->calc_extended_bounding_box();
|
||||
}
|
||||
|
||||
if (!m_model.model.get_filename().empty()) {
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
shader->set_uniform("emission_factor", 0.0f);
|
||||
const Transform3d model_matrix = Geometry::translation_transform(m_model_offset);
|
||||
shader->set_uniform("view_model_matrix", view_matrix * model_matrix);
|
||||
shader->set_uniform("projection_matrix", projection_matrix);
|
||||
const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
|
||||
shader->set_uniform("view_normal_matrix", view_normal_matrix);
|
||||
m_model.model.render();
|
||||
shader->stop_using();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Bed3D::render_custom(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_texture, bool picking)
|
||||
{
|
||||
if (m_texture_filename.empty() && m_model_filename.empty()) {
|
||||
render_default(bottom, picking, show_texture, view_matrix, projection_matrix);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!bottom)
|
||||
render_model(view_matrix, projection_matrix);
|
||||
|
||||
if (show_texture)
|
||||
render_texture(bottom, canvas, view_matrix, projection_matrix);
|
||||
else if (bottom)
|
||||
render_contour(view_matrix, projection_matrix);
|
||||
}
|
||||
|
||||
void Bed3D::render_default(bool bottom, bool picking, bool show_texture, const Transform3d& view_matrix, const Transform3d& projection_matrix)
|
||||
{
|
||||
m_texture.reset();
|
||||
|
||||
init_gridlines();
|
||||
init_triangles();
|
||||
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("flat");
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
|
||||
shader->set_uniform("view_model_matrix", view_matrix);
|
||||
shader->set_uniform("projection_matrix", projection_matrix);
|
||||
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
glsafe(::glEnable(GL_BLEND));
|
||||
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
|
||||
|
||||
const bool has_model = !m_model.model.get_filename().empty();
|
||||
if (!has_model && !bottom) {
|
||||
// draw background
|
||||
glsafe(::glDepthMask(GL_FALSE));
|
||||
m_triangles.render();
|
||||
glsafe(::glDepthMask(GL_TRUE));
|
||||
}
|
||||
|
||||
if (show_texture) {
|
||||
// draw grid
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
if (!OpenGLManager::get_gl_info().is_core_profile())
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
glsafe(::glLineWidth(1.5f * m_scale_factor));
|
||||
m_gridlines.set_color(has_model && !bottom ? DEFAULT_SOLID_GRID_COLOR : DEFAULT_TRANSPARENT_GRID_COLOR);
|
||||
m_gridlines.render();
|
||||
}
|
||||
else
|
||||
render_contour(view_matrix, projection_matrix);
|
||||
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
|
||||
shader->stop_using();
|
||||
}
|
||||
}
|
||||
|
||||
void Bed3D::render_contour(const Transform3d& view_matrix, const Transform3d& projection_matrix)
|
||||
{
|
||||
init_contourlines();
|
||||
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("flat");
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
shader->set_uniform("view_model_matrix", view_matrix);
|
||||
shader->set_uniform("projection_matrix", projection_matrix);
|
||||
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
glsafe(::glEnable(GL_BLEND));
|
||||
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
|
||||
|
||||
// draw contour
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
if (!OpenGLManager::get_gl_info().is_core_profile())
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
glsafe(::glLineWidth(1.5f * m_scale_factor));
|
||||
m_contourlines.render();
|
||||
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
|
||||
shader->stop_using();
|
||||
}
|
||||
}
|
||||
|
||||
void Bed3D::register_raycasters_for_picking(const GLModel::Geometry& geometry, const Transform3d& trafo)
|
||||
{
|
||||
assert(m_model.mesh_raycaster == nullptr);
|
||||
|
||||
indexed_triangle_set its;
|
||||
its.vertices.reserve(geometry.vertices_count());
|
||||
for (size_t i = 0; i < geometry.vertices_count(); ++i) {
|
||||
its.vertices.emplace_back(geometry.extract_position_3(i));
|
||||
}
|
||||
its.indices.reserve(geometry.indices_count() / 3);
|
||||
for (size_t i = 0; i < geometry.indices_count() / 3; ++i) {
|
||||
const size_t tri_id = i * 3;
|
||||
its.indices.emplace_back(geometry.extract_index(tri_id), geometry.extract_index(tri_id + 1), geometry.extract_index(tri_id + 2));
|
||||
}
|
||||
|
||||
m_model.mesh_raycaster = std::make_unique<MeshRaycaster>(std::make_shared<const TriangleMesh>(std::move(its)));
|
||||
wxGetApp().plater()->canvas3D()->add_raycaster_for_picking(SceneRaycaster::EType::Bed, 0, *m_model.mesh_raycaster, trafo);
|
||||
}
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
106
src/slic3r/GUI/3DBed.hpp
Normal file
106
src/slic3r/GUI/3DBed.hpp
Normal file
@@ -0,0 +1,106 @@
|
||||
#ifndef slic3r_3DBed_hpp_
|
||||
#define slic3r_3DBed_hpp_
|
||||
|
||||
#include "GLTexture.hpp"
|
||||
#include "3DScene.hpp"
|
||||
#include "CoordAxes.hpp"
|
||||
#include "MeshUtils.hpp"
|
||||
|
||||
#include "libslic3r/BuildVolume.hpp"
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
|
||||
#include <tuple>
|
||||
#include <array>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class GLCanvas3D;
|
||||
|
||||
class Bed3D
|
||||
{
|
||||
public:
|
||||
enum class Type : unsigned char
|
||||
{
|
||||
// The print bed model and texture are available from some printer preset.
|
||||
System,
|
||||
// The print bed model is unknown, thus it is rendered procedurally.
|
||||
Custom
|
||||
};
|
||||
|
||||
private:
|
||||
BuildVolume m_build_volume;
|
||||
Type m_type{ Type::Custom };
|
||||
std::string m_texture_filename;
|
||||
std::string m_model_filename;
|
||||
// Print volume bounding box exteded with axes and model.
|
||||
BoundingBoxf3 m_extended_bounding_box;
|
||||
// Print bed polygon
|
||||
ExPolygon m_contour;
|
||||
// Slightly expanded print bed polygon, for collision detection.
|
||||
Polygon m_polygon;
|
||||
GLModel m_triangles;
|
||||
GLModel m_gridlines;
|
||||
GLModel m_contourlines;
|
||||
GLTexture m_texture;
|
||||
// temporary texture shown until the main texture has still no levels compressed
|
||||
GLTexture m_temp_texture;
|
||||
PickingModel m_model;
|
||||
Vec3d m_model_offset{ Vec3d::Zero() };
|
||||
CoordAxes m_axes;
|
||||
|
||||
float m_scale_factor{ 1.0f };
|
||||
|
||||
public:
|
||||
Bed3D() = default;
|
||||
~Bed3D() = default;
|
||||
|
||||
// Update print bed model from configuration.
|
||||
// Return true if the bed shape changed, so the calee will update the UI.
|
||||
//FIXME if the build volume max print height is updated, this function still returns zero
|
||||
// as this class does not use it, thus there is no need to update the UI.
|
||||
bool set_shape(const Pointfs& bed_shape, const double max_print_height, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false);
|
||||
|
||||
// Build volume geometry for various collision detection tasks.
|
||||
const BuildVolume& build_volume() const { return m_build_volume; }
|
||||
|
||||
// Was the model provided, or was it generated procedurally?
|
||||
Type get_type() const { return m_type; }
|
||||
// Was the model generated procedurally?
|
||||
bool is_custom() const { return m_type == Type::Custom; }
|
||||
|
||||
// Bounding box around the print bed, axes and model, for rendering.
|
||||
const BoundingBoxf3& extended_bounding_box() const { return m_extended_bounding_box; }
|
||||
|
||||
// Check against an expanded 2d bounding box.
|
||||
//FIXME shall one check against the real build volume?
|
||||
bool contains(const Point& point) const;
|
||||
Point point_projection(const Point& point) const;
|
||||
|
||||
void render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_texture);
|
||||
void render_axes();
|
||||
void render_for_picking(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor);
|
||||
|
||||
private:
|
||||
// Calculate an extended bounding box from axes and current model for visualization purposes.
|
||||
BoundingBoxf3 calc_extended_bounding_box() const;
|
||||
void init_triangles();
|
||||
void init_gridlines();
|
||||
void init_contourlines();
|
||||
static std::tuple<Type, std::string, std::string> detect_type(const Pointfs& shape);
|
||||
void render_internal(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor,
|
||||
bool show_texture, bool picking);
|
||||
void render_system(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_texture);
|
||||
void render_texture(bool bottom, GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix);
|
||||
void render_model(const Transform3d& view_matrix, const Transform3d& projection_matrix);
|
||||
void render_custom(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool show_texture, bool picking);
|
||||
void render_default(bool bottom, bool picking, bool show_texture, const Transform3d& view_matrix, const Transform3d& projection_matrix);
|
||||
void render_contour(const Transform3d& view_matrix, const Transform3d& projection_matrix);
|
||||
|
||||
void register_raycasters_for_picking(const GLModel::Geometry& geometry, const Transform3d& trafo);
|
||||
};
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
#endif // slic3r_3DBed_hpp_
|
||||
1517
src/slic3r/GUI/3DScene.cpp
Normal file
1517
src/slic3r/GUI/3DScene.cpp
Normal file
File diff suppressed because it is too large
Load Diff
488
src/slic3r/GUI/3DScene.hpp
Normal file
488
src/slic3r/GUI/3DScene.hpp
Normal file
@@ -0,0 +1,488 @@
|
||||
#ifndef slic3r_3DScene_hpp_
|
||||
#define slic3r_3DScene_hpp_
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Line.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/Color.hpp"
|
||||
|
||||
#include "GLModel.hpp"
|
||||
#include "MeshUtils.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define HAS_GLSAFE
|
||||
#endif // NDEBUG
|
||||
|
||||
#ifdef HAS_GLSAFE
|
||||
extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name);
|
||||
inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); }
|
||||
#define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
|
||||
#define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
|
||||
#else // HAS_GLSAFE
|
||||
inline void glAssertRecentCall() { }
|
||||
#define glsafe(cmd) cmd
|
||||
#define glcheck()
|
||||
#endif // HAS_GLSAFE
|
||||
|
||||
namespace Slic3r {
|
||||
class SLAPrintObject;
|
||||
enum SLAPrintObjectStep : unsigned int;
|
||||
class BuildVolume;
|
||||
class DynamicPrintConfig;
|
||||
class ExtrusionPath;
|
||||
class ExtrusionMultiPath;
|
||||
class ExtrusionLoop;
|
||||
class ExtrusionEntity;
|
||||
class ExtrusionEntityCollection;
|
||||
class ModelObject;
|
||||
class ModelVolume;
|
||||
enum ModelInstanceEPrintVolumeState : unsigned char;
|
||||
|
||||
// Return appropriate color based on the ModelVolume.
|
||||
extern ColorRGBA color_from_model_volume(const ModelVolume& model_volume);
|
||||
|
||||
class GLVolume {
|
||||
public:
|
||||
static const ColorRGBA SELECTED_COLOR;
|
||||
static const ColorRGBA HOVER_SELECT_COLOR;
|
||||
static const ColorRGBA HOVER_DESELECT_COLOR;
|
||||
static const ColorRGBA OUTSIDE_COLOR;
|
||||
static const ColorRGBA SELECTED_OUTSIDE_COLOR;
|
||||
static const ColorRGBA DISABLED_COLOR;
|
||||
static const ColorRGBA SLA_SUPPORT_COLOR;
|
||||
static const ColorRGBA SLA_PAD_COLOR;
|
||||
static const ColorRGBA NEUTRAL_COLOR;
|
||||
static const std::array<ColorRGBA, 4> MODEL_COLOR;
|
||||
|
||||
enum EHoverState : unsigned char
|
||||
{
|
||||
HS_None,
|
||||
HS_Hover,
|
||||
HS_Select,
|
||||
HS_Deselect
|
||||
};
|
||||
|
||||
GLVolume(float r = 1.0f, float g = 1.0f, float b = 1.0f, float a = 1.0f);
|
||||
GLVolume(const ColorRGBA& color) : GLVolume(color.r(), color.g(), color.b(), color.a()) {}
|
||||
|
||||
private:
|
||||
Geometry::Transformation m_instance_transformation;
|
||||
Geometry::Transformation m_volume_transformation;
|
||||
|
||||
// Shift in z required by sla supports+pad
|
||||
double m_sla_shift_z;
|
||||
// Bounding box of this volume, in unscaled coordinates.
|
||||
std::optional<BoundingBoxf3> m_transformed_bounding_box;
|
||||
// Convex hull of the volume, if any.
|
||||
std::shared_ptr<const TriangleMesh> m_convex_hull;
|
||||
// Bounding box of this volume, in unscaled coordinates.
|
||||
std::optional<BoundingBoxf3> m_transformed_convex_hull_bounding_box;
|
||||
// Bounding box of the non sinking part of this volume, in unscaled coordinates.
|
||||
std::optional<BoundingBoxf3> m_transformed_non_sinking_bounding_box;
|
||||
|
||||
class SinkingContours
|
||||
{
|
||||
static const float HalfWidth;
|
||||
GLVolume& m_parent;
|
||||
GUI::GLModel m_model;
|
||||
BoundingBoxf3 m_old_box;
|
||||
Vec3d m_shift{ Vec3d::Zero() };
|
||||
|
||||
public:
|
||||
SinkingContours(GLVolume& volume) : m_parent(volume) {}
|
||||
void render();
|
||||
|
||||
private:
|
||||
void update();
|
||||
};
|
||||
|
||||
SinkingContours m_sinking_contours;
|
||||
|
||||
class NonManifoldEdges
|
||||
{
|
||||
GLVolume& m_parent;
|
||||
GUI::GLModel m_model;
|
||||
bool m_update_needed{ true };
|
||||
|
||||
public:
|
||||
NonManifoldEdges(GLVolume& volume) : m_parent(volume) {}
|
||||
void render();
|
||||
void set_as_dirty() { m_update_needed = true; }
|
||||
|
||||
private:
|
||||
void update();
|
||||
};
|
||||
|
||||
NonManifoldEdges m_non_manifold_edges;
|
||||
|
||||
public:
|
||||
// Color of the triangles / quads held by this volume.
|
||||
ColorRGBA color;
|
||||
// Color used to render this volume.
|
||||
ColorRGBA render_color;
|
||||
|
||||
struct CompositeID {
|
||||
CompositeID(int object_id, int volume_id, int instance_id) : object_id(object_id), volume_id(volume_id), instance_id(instance_id) {}
|
||||
CompositeID() : object_id(-1), volume_id(-1), instance_id(-1) {}
|
||||
// Object ID, which is equal to the index of the respective ModelObject in Model.objects array.
|
||||
int object_id;
|
||||
// Volume ID, which is equal to the index of the respective ModelVolume in ModelObject.volumes array.
|
||||
// If negative, it is an index of a geometry produced by the PrintObject for the respective ModelObject,
|
||||
// and which has no associated ModelVolume in ModelObject.volumes. For example, SLA supports.
|
||||
// Volume with a negative volume_id cannot be picked independently, it will pick the associated instance.
|
||||
int volume_id;
|
||||
// Instance ID, which is equal to the index of the respective ModelInstance in ModelObject.instances array.
|
||||
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,
|
||||
// for generated volumes it is the timestamp generated by PrintState::invalidate() or PrintState::set_done(),
|
||||
// and the associated ModelInstanceID.
|
||||
// Valid geometry_id should always be positive.
|
||||
std::pair<size_t, size_t> geometry_id;
|
||||
// An ID containing the extruder ID (used to select color).
|
||||
int extruder_id;
|
||||
|
||||
// Various boolean flags.
|
||||
struct {
|
||||
// Is this object selected?
|
||||
bool selected : 1;
|
||||
// Is this object disabled from selection?
|
||||
bool disabled : 1;
|
||||
// Is this object printable?
|
||||
bool printable : 1;
|
||||
// Whether or not this volume is active for rendering
|
||||
bool is_active : 1;
|
||||
// Whether or not to use this volume when applying zoom_to_volumes()
|
||||
bool zoom_to_volumes : 1;
|
||||
// Wheter or not this volume is enabled for outside print volume detection in shader.
|
||||
bool shader_outside_printer_detection_enabled : 1;
|
||||
// Wheter or not this volume is outside print volume.
|
||||
bool is_outside : 1;
|
||||
// Wheter or not this volume has been generated from a modifier
|
||||
bool is_modifier : 1;
|
||||
// Wheter or not this volume has been generated from the wipe tower
|
||||
bool is_wipe_tower : 1;
|
||||
// Wheter or not this volume has been generated from an extrusion path
|
||||
bool is_extrusion_path : 1;
|
||||
// Whether or not always use the volume's own color (not using SELECTED/HOVER/DISABLED/OUTSIDE)
|
||||
bool force_native_color : 1;
|
||||
// Whether or not render this volume in neutral
|
||||
bool force_neutral_color : 1;
|
||||
// Whether or not to force rendering of sinking contours
|
||||
bool force_sinking_contours : 1;
|
||||
}; // this gets instantiated automatically in the parent struct
|
||||
|
||||
// Is mouse or rectangle selection over this object to select/deselect it ?
|
||||
EHoverState hover;
|
||||
|
||||
GUI::GLModel model;
|
||||
// raycaster used for picking
|
||||
std::unique_ptr<GUI::MeshRaycaster> mesh_raycaster;
|
||||
// Ranges of triangle and quad indices to be rendered.
|
||||
std::pair<size_t, size_t> tverts_range;
|
||||
|
||||
// If the qverts or tverts contain thick extrusions, then offsets keeps pointers of the starts
|
||||
// of the extrusions per layer.
|
||||
std::vector<coordf_t> print_zs;
|
||||
// Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer.
|
||||
std::vector<size_t> offsets;
|
||||
|
||||
// Bounding box of this volume, in unscaled coordinates.
|
||||
BoundingBoxf3 bounding_box() const {
|
||||
return this->model.get_bounding_box();
|
||||
}
|
||||
|
||||
void set_color(const ColorRGBA& rgba) { color = rgba; }
|
||||
void set_render_color(const ColorRGBA& rgba) { render_color = rgba; }
|
||||
// Sets render color in dependence of current state
|
||||
void set_render_color(bool force_transparent);
|
||||
// set color according to model volume
|
||||
void set_color_from_model_volume(const ModelVolume& model_volume);
|
||||
|
||||
const Geometry::Transformation& get_instance_transformation() const { return m_instance_transformation; }
|
||||
void set_instance_transformation(const Geometry::Transformation& transformation) { m_instance_transformation = transformation; set_bounding_boxes_as_dirty(); }
|
||||
void set_instance_transformation(const Transform3d& transform) { m_instance_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); }
|
||||
|
||||
Vec3d get_instance_offset() const { return m_instance_transformation.get_offset(); }
|
||||
double get_instance_offset(Axis axis) const { return m_instance_transformation.get_offset(axis); }
|
||||
|
||||
void set_instance_offset(const Vec3d& offset) { m_instance_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); }
|
||||
void set_instance_offset(Axis axis, double offset) { m_instance_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); }
|
||||
|
||||
Vec3d get_instance_rotation() const { return m_instance_transformation.get_rotation(); }
|
||||
double get_instance_rotation(Axis axis) const { return m_instance_transformation.get_rotation(axis); }
|
||||
|
||||
void set_instance_rotation(const Vec3d& rotation) { m_instance_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); }
|
||||
void set_instance_rotation(Axis axis, double rotation) { m_instance_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); }
|
||||
|
||||
Vec3d get_instance_scaling_factor() const { return m_instance_transformation.get_scaling_factor(); }
|
||||
double get_instance_scaling_factor(Axis axis) const { return m_instance_transformation.get_scaling_factor(axis); }
|
||||
|
||||
void set_instance_scaling_factor(const Vec3d& scaling_factor) { m_instance_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); }
|
||||
void set_instance_scaling_factor(Axis axis, double scaling_factor) { m_instance_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); }
|
||||
|
||||
Vec3d get_instance_mirror() const { return m_instance_transformation.get_mirror(); }
|
||||
double get_instance_mirror(Axis axis) const { return m_instance_transformation.get_mirror(axis); }
|
||||
|
||||
void set_instance_mirror(const Vec3d& mirror) { m_instance_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); }
|
||||
void set_instance_mirror(Axis axis, double mirror) { m_instance_transformation.set_mirror(axis, mirror); set_bounding_boxes_as_dirty(); }
|
||||
|
||||
const Geometry::Transformation& get_volume_transformation() const { return m_volume_transformation; }
|
||||
void set_volume_transformation(const Geometry::Transformation& transformation) { m_volume_transformation = transformation; set_bounding_boxes_as_dirty(); }
|
||||
void set_volume_transformation(const Transform3d& transform) { m_volume_transformation.set_matrix(transform); set_bounding_boxes_as_dirty(); }
|
||||
|
||||
Vec3d get_volume_offset() const { return m_volume_transformation.get_offset(); }
|
||||
double get_volume_offset(Axis axis) const { return m_volume_transformation.get_offset(axis); }
|
||||
|
||||
void set_volume_offset(const Vec3d& offset) { m_volume_transformation.set_offset(offset); set_bounding_boxes_as_dirty(); }
|
||||
void set_volume_offset(Axis axis, double offset) { m_volume_transformation.set_offset(axis, offset); set_bounding_boxes_as_dirty(); }
|
||||
|
||||
Vec3d get_volume_rotation() const { return m_volume_transformation.get_rotation(); }
|
||||
double get_volume_rotation(Axis axis) const { return m_volume_transformation.get_rotation(axis); }
|
||||
|
||||
void set_volume_rotation(const Vec3d& rotation) { m_volume_transformation.set_rotation(rotation); set_bounding_boxes_as_dirty(); }
|
||||
void set_volume_rotation(Axis axis, double rotation) { m_volume_transformation.set_rotation(axis, rotation); set_bounding_boxes_as_dirty(); }
|
||||
|
||||
Vec3d get_volume_scaling_factor() const { return m_volume_transformation.get_scaling_factor(); }
|
||||
double get_volume_scaling_factor(Axis axis) const { return m_volume_transformation.get_scaling_factor(axis); }
|
||||
|
||||
void set_volume_scaling_factor(const Vec3d& scaling_factor) { m_volume_transformation.set_scaling_factor(scaling_factor); set_bounding_boxes_as_dirty(); }
|
||||
void set_volume_scaling_factor(Axis axis, double scaling_factor) { m_volume_transformation.set_scaling_factor(axis, scaling_factor); set_bounding_boxes_as_dirty(); }
|
||||
|
||||
Vec3d get_volume_mirror() const { return m_volume_transformation.get_mirror(); }
|
||||
double get_volume_mirror(Axis axis) const { return m_volume_transformation.get_mirror(axis); }
|
||||
|
||||
void set_volume_mirror(const Vec3d& mirror) { m_volume_transformation.set_mirror(mirror); set_bounding_boxes_as_dirty(); }
|
||||
void set_volume_mirror(Axis axis, double mirror) { m_volume_transformation.set_mirror(axis, mirror); set_bounding_boxes_as_dirty(); }
|
||||
|
||||
double get_sla_shift_z() const { return m_sla_shift_z; }
|
||||
void set_sla_shift_z(double z) { m_sla_shift_z = z; }
|
||||
|
||||
void set_convex_hull(std::shared_ptr<const TriangleMesh> convex_hull) { m_convex_hull = std::move(convex_hull); }
|
||||
void set_convex_hull(const TriangleMesh &convex_hull) { m_convex_hull = std::make_shared<const TriangleMesh>(convex_hull); }
|
||||
void set_convex_hull(TriangleMesh &&convex_hull) { m_convex_hull = std::make_shared<const TriangleMesh>(std::move(convex_hull)); }
|
||||
|
||||
int object_idx() const { return this->composite_id.object_id; }
|
||||
int volume_idx() const { return this->composite_id.volume_id; }
|
||||
int instance_idx() const { return this->composite_id.instance_id; }
|
||||
|
||||
Transform3d world_matrix() const;
|
||||
bool is_left_handed() const;
|
||||
|
||||
const BoundingBoxf3& transformed_bounding_box() const;
|
||||
// non-caching variant
|
||||
BoundingBoxf3 transformed_convex_hull_bounding_box(const Transform3d &trafo) const;
|
||||
// caching variant
|
||||
const BoundingBoxf3& transformed_convex_hull_bounding_box() const;
|
||||
// non-caching variant
|
||||
BoundingBoxf3 transformed_non_sinking_bounding_box(const Transform3d& trafo) const;
|
||||
// caching variant
|
||||
const BoundingBoxf3& transformed_non_sinking_bounding_box() const;
|
||||
// convex hull
|
||||
const TriangleMesh* convex_hull() const { return m_convex_hull.get(); }
|
||||
|
||||
bool empty() const { return this->model.is_empty(); }
|
||||
|
||||
void set_range(double low, double high);
|
||||
|
||||
void render();
|
||||
|
||||
void set_bounding_boxes_as_dirty() {
|
||||
m_transformed_bounding_box.reset();
|
||||
m_transformed_convex_hull_bounding_box.reset();
|
||||
m_transformed_non_sinking_bounding_box.reset();
|
||||
}
|
||||
|
||||
bool is_sla_support() const;
|
||||
bool is_sla_pad() const;
|
||||
|
||||
bool is_sinking() const;
|
||||
bool is_below_printbed() const;
|
||||
void render_sinking_contours();
|
||||
void render_non_manifold_edges();
|
||||
|
||||
// Return an estimate of the memory consumed by this class.
|
||||
size_t cpu_memory_used() const {
|
||||
return sizeof(*this) + this->model.cpu_memory_used() + this->print_zs.capacity() * sizeof(coordf_t) +
|
||||
this->offsets.capacity() * sizeof(size_t);
|
||||
}
|
||||
// Return an estimate of the memory held by GPU vertex buffers.
|
||||
size_t gpu_memory_used() const { return this->model.gpu_memory_used(); }
|
||||
size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); }
|
||||
};
|
||||
|
||||
typedef std::vector<GLVolume*> GLVolumePtrs;
|
||||
typedef std::pair<GLVolume*, std::pair<unsigned int, double>> GLVolumeWithIdAndZ;
|
||||
typedef std::vector<GLVolumeWithIdAndZ> GLVolumeWithIdAndZList;
|
||||
|
||||
class GLVolumeCollection
|
||||
{
|
||||
public:
|
||||
enum class ERenderType : unsigned char
|
||||
{
|
||||
Opaque,
|
||||
Transparent,
|
||||
All
|
||||
};
|
||||
|
||||
struct PrintVolume
|
||||
{
|
||||
// see: Bed3D::EShapeType
|
||||
int type{ 0 };
|
||||
// data contains:
|
||||
// Rectangle:
|
||||
// [0] = min.x, [1] = min.y, [2] = max.x, [3] = max.y
|
||||
// Circle:
|
||||
// [0] = center.x, [1] = center.y, [3] = radius
|
||||
std::array<float, 4> data;
|
||||
// [0] = min z, [1] = max z
|
||||
std::array<float, 2> zs;
|
||||
};
|
||||
|
||||
private:
|
||||
PrintVolume m_print_volume;
|
||||
|
||||
// z range for clipping in shaders
|
||||
std::array<float, 2> m_z_range;
|
||||
|
||||
// plane coeffs for clipping in shaders
|
||||
std::array<double, 4> m_clipping_plane;
|
||||
|
||||
// plane coeffs for render volumes with different colors in shaders
|
||||
// used by cut gizmo
|
||||
std::array<double, 4> m_color_clip_plane;
|
||||
bool m_use_color_clip_plane{ false };
|
||||
std::array<ColorRGBA, 2> m_color_clip_plane_colors{ ColorRGBA::RED(), ColorRGBA::BLUE() };
|
||||
|
||||
struct Slope
|
||||
{
|
||||
// toggle for slope rendering
|
||||
bool active{ false };
|
||||
float normal_z;
|
||||
};
|
||||
|
||||
Slope m_slope;
|
||||
bool m_show_sinking_contours{ false };
|
||||
bool m_show_non_manifold_edges{ true };
|
||||
bool m_use_raycasters{ true };
|
||||
|
||||
public:
|
||||
GLVolumePtrs volumes;
|
||||
|
||||
GLVolumeCollection() { set_default_slope_normal_z(); }
|
||||
~GLVolumeCollection() { clear(); }
|
||||
|
||||
std::vector<int> load_object(
|
||||
const ModelObject* model_object,
|
||||
int obj_idx,
|
||||
const std::vector<int>& instance_idxs);
|
||||
|
||||
int load_object_volume(
|
||||
const ModelObject* model_object,
|
||||
int obj_idx,
|
||||
int volume_idx,
|
||||
int instance_idx);
|
||||
|
||||
#if ENABLE_OPENGL_ES
|
||||
int load_wipe_tower_preview(
|
||||
float pos_x, float pos_y, float width, float depth, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width, TriangleMesh* out_mesh = nullptr);
|
||||
#else
|
||||
int load_wipe_tower_preview(
|
||||
float pos_x, float pos_y, float width, float depth, float height, float cone_angle, float rotation_angle, bool size_unknown, float brim_width);
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
|
||||
// Load SLA auxiliary GLVolumes (for support trees or pad).
|
||||
void load_object_auxiliary(
|
||||
const SLAPrintObject* print_object,
|
||||
int obj_idx,
|
||||
// pairs of <instance_idx, print_instance_idx>
|
||||
const std::vector<std::pair<size_t, size_t>>& instances,
|
||||
SLAPrintObjectStep milestone,
|
||||
// Timestamp of the last change of the milestone
|
||||
size_t timestamp);
|
||||
|
||||
GLVolume* new_toolpath_volume(const ColorRGBA& rgba);
|
||||
GLVolume* new_nontoolpath_volume(const ColorRGBA& rgba);
|
||||
// Render the volumes by OpenGL.
|
||||
void render(ERenderType type, bool disable_cullface, const Transform3d& view_matrix, const Transform3d& projection_matrix,
|
||||
std::function<bool(const GLVolume&)> filter_func = std::function<bool(const GLVolume&)>()) const;
|
||||
|
||||
// Clear the geometry
|
||||
void clear() { for (auto *v : volumes) delete v; volumes.clear(); }
|
||||
|
||||
bool empty() const { return volumes.empty(); }
|
||||
void set_range(double low, double high) { for (GLVolume* vol : this->volumes) vol->set_range(low, high); }
|
||||
|
||||
void set_use_raycasters(bool value) { m_use_raycasters = value; }
|
||||
void set_print_volume(const PrintVolume& print_volume) { m_print_volume = print_volume; }
|
||||
|
||||
void set_z_range(float min_z, float max_z) { m_z_range[0] = min_z; m_z_range[1] = max_z; }
|
||||
void set_clipping_plane(const std::array<double, 4>& coeffs) { m_clipping_plane = coeffs; }
|
||||
|
||||
const std::array<float, 2>& get_z_range() const { return m_z_range; }
|
||||
const std::array<double, 4>& get_clipping_plane() const { return m_clipping_plane; }
|
||||
|
||||
void set_use_color_clip_plane(bool use) { m_use_color_clip_plane = use; }
|
||||
void set_color_clip_plane(const Vec3d& cp_normal, double offset) {
|
||||
for (int i = 0; i < 3; ++i)
|
||||
m_color_clip_plane[i] = -cp_normal[i];
|
||||
m_color_clip_plane[3] = offset;
|
||||
}
|
||||
void set_color_clip_plane_colors(const std::array<ColorRGBA, 2>& colors) { m_color_clip_plane_colors = colors; }
|
||||
|
||||
bool is_slope_active() const { return m_slope.active; }
|
||||
void set_slope_active(bool active) { m_slope.active = active; }
|
||||
|
||||
float get_slope_normal_z() const { return m_slope.normal_z; }
|
||||
void set_slope_normal_z(float normal_z) { m_slope.normal_z = normal_z; }
|
||||
void set_default_slope_normal_z() { m_slope.normal_z = -::cos(Geometry::deg2rad(90.0f - 45.0f)); }
|
||||
void set_show_sinking_contours(bool show) { m_show_sinking_contours = show; }
|
||||
void set_show_non_manifold_edges(bool show) { m_show_non_manifold_edges = show; }
|
||||
|
||||
void reset_outside_state();
|
||||
|
||||
void update_colors_by_extruder(const DynamicPrintConfig* config);
|
||||
|
||||
// Returns a vector containing the sorted list of all the print_zs of the volumes contained in this collection
|
||||
std::vector<double> get_current_print_zs(bool active_only) const;
|
||||
|
||||
// Return an estimate of the memory consumed by this class.
|
||||
size_t cpu_memory_used() const;
|
||||
// Return an estimate of the memory held by GPU vertex buffers.
|
||||
size_t gpu_memory_used() const;
|
||||
size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); }
|
||||
// Return CPU, GPU and total memory log line.
|
||||
std::string log_memory_info() const;
|
||||
|
||||
private:
|
||||
GLVolumeCollection(const GLVolumeCollection &other);
|
||||
GLVolumeCollection& operator=(const GLVolumeCollection &);
|
||||
};
|
||||
|
||||
GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func = nullptr);
|
||||
|
||||
struct _3DScene
|
||||
{
|
||||
static void thick_lines_to_verts(const Lines& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, double top_z, GUI::GLModel::Geometry& geometry);
|
||||
static void thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, GUI::GLModel::Geometry& geometry);
|
||||
static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
|
||||
static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
|
||||
static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
|
||||
static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
|
||||
static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
369
src/slic3r/GUI/AboutDialog.cpp
Normal file
369
src/slic3r/GUI/AboutDialog.cpp
Normal file
@@ -0,0 +1,369 @@
|
||||
#include "AboutDialog.hpp"
|
||||
#include "I18N.hpp"
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/Color.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
#include <wx/clipbrd.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
AboutDialogLogo::AboutDialogLogo(wxWindow* parent)
|
||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
|
||||
{
|
||||
this->SetBackgroundColour(*wxWHITE);
|
||||
this->logo = wxBitmap(from_u8(Slic3r::var("QIDISlicer_192px.png")), wxBITMAP_TYPE_PNG);
|
||||
this->SetMinSize(this->logo.GetSize());
|
||||
|
||||
this->Bind(wxEVT_PAINT, &AboutDialogLogo::onRepaint, this);
|
||||
}
|
||||
|
||||
void AboutDialogLogo::onRepaint(wxEvent &event)
|
||||
{
|
||||
wxPaintDC dc(this);
|
||||
dc.SetBackgroundMode(wxTRANSPARENT);
|
||||
|
||||
wxSize size = this->GetSize();
|
||||
int logo_w = this->logo.GetWidth();
|
||||
int logo_h = this->logo.GetHeight();
|
||||
dc.DrawBitmap(this->logo, (size.GetWidth() - logo_w)/2, (size.GetHeight() - logo_h)/2, true);
|
||||
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
|
||||
// -----------------------------------------
|
||||
// CopyrightsDialog
|
||||
// -----------------------------------------
|
||||
CopyrightsDialog::CopyrightsDialog()
|
||||
: DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, format_wxstr("%1% - %2%"
|
||||
, wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME
|
||||
, _L("Portions copyright")),
|
||||
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||
{
|
||||
this->SetFont(wxGetApp().normal_font());
|
||||
#ifdef _WIN32
|
||||
wxGetApp().UpdateDarkUI(this);
|
||||
#else
|
||||
this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
#endif
|
||||
|
||||
auto sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
fill_entries();
|
||||
|
||||
m_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition,
|
||||
wxSize(40 * em_unit(), 20 * em_unit()), wxHW_SCROLLBAR_AUTO);
|
||||
|
||||
wxFont font = get_default_font(this);
|
||||
const int fs = font.GetPointSize();
|
||||
const int fs2 = static_cast<int>(1.2f*fs);
|
||||
int size[] = { fs, fs, fs, fs, fs2, fs2, fs2 };
|
||||
|
||||
m_html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
|
||||
m_html->SetBorders(2);
|
||||
m_html->SetPage(get_html_text());
|
||||
|
||||
sizer->Add(m_html, 1, wxEXPAND | wxALL, 15);
|
||||
m_html->Bind(wxEVT_HTML_LINK_CLICKED, &CopyrightsDialog::onLinkClicked, this);
|
||||
|
||||
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE);
|
||||
wxGetApp().UpdateDlgDarkUI(this, true);
|
||||
this->SetEscapeId(wxID_CLOSE);
|
||||
this->Bind(wxEVT_BUTTON, &CopyrightsDialog::onCloseDialog, this, wxID_CLOSE);
|
||||
sizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
|
||||
|
||||
SetSizer(sizer);
|
||||
sizer->SetSizeHints(this);
|
||||
|
||||
}
|
||||
|
||||
void CopyrightsDialog::fill_entries()
|
||||
{
|
||||
m_entries = {
|
||||
{ "wxWidgets" , "2019 wxWidgets" , "https://www.wxwidgets.org/" },
|
||||
{ "OpenGL" , "1997-2019 The Khronos™ Group Inc" , "https://www.opengl.org/" },
|
||||
{ "GNU gettext" , "1998, 2019 Free Software Foundation, Inc." , "https://www.gnu.org/software/gettext/" },
|
||||
{ "PoEdit" , "2019 Václav Slavík" , "https://poedit.net/" },
|
||||
{ "ImGUI" , "2014-2019 Omar Cornut" , "https://github.com/ocornut/imgui" },
|
||||
{ "Eigen" , "" , "http://eigen.tuxfamily.org" },
|
||||
{ "ADMesh" , "1995, 1996 Anthony D. Martin; "
|
||||
"2015, ADMesh contributors" , "https://admesh.readthedocs.io/en/latest/" },
|
||||
{ "Anti-Grain Geometry"
|
||||
, "2002-2005 Maxim Shemanarev (McSeem)" , "http://antigrain.com" },
|
||||
{ "Boost" , "1998-2005 Beman Dawes, David Abrahams; "
|
||||
"2004 - 2007 Rene Rivera" , "https://www.boost.org/" },
|
||||
{ "Clipper" , "2010-2015 Angus Johnson " , "http://www.angusj.com " },
|
||||
{ "GLEW (The OpenGL Extension Wrangler Library)",
|
||||
"2002 - 2007, Milan Ikits; "
|
||||
"2002 - 2007, Marcelo E.Magallon; "
|
||||
"2002, Lev Povalahev" , "http://glew.sourceforge.net/" },
|
||||
{ "Libigl" , "2013 Alec Jacobson and others" , "https://libigl.github.io/" },
|
||||
{ "Qhull" , "1993-2015 C.B.Barber Arlington and "
|
||||
"University of Minnesota" , "http://qhull.org/" },
|
||||
{ "SemVer" , "2015-2017 Tomas Aparicio" , "https://semver.org/" },
|
||||
{ "Nanosvg" , "2013-14 Mikko Mononen" , "https://github.com/memononen/nanosvg" },
|
||||
{ "Miniz" , "2013-2014 RAD Game Tools and Valve Software; "
|
||||
"2010-2014 Rich Geldreich and Tenacious Software LLC"
|
||||
, "https://github.com/richgel999/miniz" },
|
||||
{ "Expat" , "1998-2000 Thai Open Source Software Center Ltd and Clark Cooper"
|
||||
"2001-2016 Expat maintainers" , "http://www.libexpat.org/" },
|
||||
{ "AVRDUDE" , "2018 Free Software Foundation, Inc." , "http://savannah.nongnu.org/projects/avrdude" },
|
||||
{ "Real-Time DXT1/DXT5 C compression library"
|
||||
, "Based on original by fabian \"ryg\" giesen v1.04. "
|
||||
"Custom version, modified by Yann Collet" , "https://github.com/Cyan4973/RygsDXTc" },
|
||||
{ "Icons for STL and GCODE files."
|
||||
, "Akira Yasuda" , "http://3dp0.com/icons-for-stl-and-gcode/" },
|
||||
{ "AppImage packaging for Linux using AppImageKit"
|
||||
, "2004-2019 Simon Peter and contributors" , "https://appimage.org/" },
|
||||
{ "lib_fts"
|
||||
, "Forrest Smith" , "https://www.forrestthewoods.com/" },
|
||||
{ "fast_float"
|
||||
, "Daniel Lemire, João Paulo Magalhaes and contributors", "https://github.com/fastfloat/fast_float" },
|
||||
{ "CuraEngine (Arachne, etc.)"
|
||||
, "Ultimaker", "https://github.com/Ultimaker/CuraEngine" },
|
||||
{ "Open CASCADE Technology"
|
||||
, "Open Cascade SAS", "https://github.com/Open-Cascade-SAS/OCCT" }
|
||||
};
|
||||
}
|
||||
|
||||
wxString CopyrightsDialog::get_html_text()
|
||||
{
|
||||
wxColour bgr_clr = wxGetApp().get_window_default_clr();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
|
||||
const auto text_clr = wxGetApp().get_label_clr_default();
|
||||
const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue()));
|
||||
const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()));
|
||||
|
||||
const wxString copyright_str = _L("Copyright") + "© ";
|
||||
const wxString header_str = _L("License agreements of all following programs (libraries) are part of application license agreement");
|
||||
|
||||
wxString text = wxString::Format(
|
||||
"<html>"
|
||||
"<body bgcolor= %s link= %s>"
|
||||
"<font color=%s>"
|
||||
"<font size=\"5\">%s.</font>"
|
||||
"<br /><br />"
|
||||
"<font size=\"3\">"
|
||||
, bgr_clr_str, text_clr_str
|
||||
, text_clr_str
|
||||
, header_str);
|
||||
|
||||
for (const auto& entry : m_entries) {
|
||||
text += wxString::Format(
|
||||
"<a href=\"%s\">%s</a><br/>"
|
||||
, entry.link, entry.lib_name);
|
||||
|
||||
if (!entry.copyright.empty())
|
||||
text += format_wxstr(
|
||||
"%1% %2%<br/><br/>"
|
||||
, copyright_str, entry.copyright);
|
||||
}
|
||||
|
||||
text += wxString(
|
||||
"</font>"
|
||||
"</font>"
|
||||
"</body>"
|
||||
"</html>");
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
void CopyrightsDialog::on_dpi_changed(const wxRect &suggested_rect)
|
||||
{
|
||||
const wxFont& font = GetFont();
|
||||
const int fs = font.GetPointSize();
|
||||
const int fs2 = static_cast<int>(1.2f*fs);
|
||||
int font_size[] = { fs, fs, fs, fs, fs2, fs2, fs2 };
|
||||
|
||||
m_html->SetFonts(font.GetFaceName(), font.GetFaceName(), font_size);
|
||||
|
||||
const int& em = em_unit();
|
||||
|
||||
msw_buttons_rescale(this, em, { wxID_CLOSE });
|
||||
|
||||
const wxSize& size = wxSize(40 * em, 20 * em);
|
||||
|
||||
m_html->SetMinSize(size);
|
||||
m_html->Refresh();
|
||||
|
||||
SetMinSize(size);
|
||||
Fit();
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void CopyrightsDialog::onLinkClicked(wxHtmlLinkEvent &event)
|
||||
{
|
||||
wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref());
|
||||
event.Skip(false);
|
||||
}
|
||||
|
||||
void CopyrightsDialog::onCloseDialog(wxEvent &)
|
||||
{
|
||||
this->EndModal(wxID_CLOSE);
|
||||
}
|
||||
|
||||
AboutDialog::AboutDialog()
|
||||
: DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, format_wxstr(_L("About %s"), wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME), wxDefaultPosition,
|
||||
wxDefaultSize, /*wxCAPTION*/wxDEFAULT_DIALOG_STYLE)
|
||||
{
|
||||
SetFont(wxGetApp().normal_font());
|
||||
|
||||
wxColour bgr_clr = wxGetApp().get_window_default_clr();//wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
SetBackgroundColour(bgr_clr);
|
||||
wxBoxSizer* hsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 0);
|
||||
|
||||
// logo
|
||||
m_logo = new wxStaticBitmap(this, wxID_ANY, *get_bmp_bundle("QIDI_Back", 99));
|
||||
//hsizer->Add(m_logo, 1, wxALIGN_CENTER_VERTICAL);
|
||||
//Rectangle
|
||||
|
||||
wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
vsizer->Add(m_logo, 1, wxTOP | wxLEFT | wxRIGHT | wxEXPAND, -50);
|
||||
hsizer->AddSpacer(15);
|
||||
hsizer->Add(vsizer, 2, wxEXPAND|wxLEFT, 0);
|
||||
|
||||
// title
|
||||
/*{
|
||||
wxStaticText* title = new wxStaticText(this, wxID_ANY, wxGetApp().is_editor() ? SLIC3R_APP_NAME : GCODEVIEWER_APP_NAME, wxDefaultPosition, wxDefaultSize);
|
||||
wxFont title_font = GUI::wxGetApp().bold_font();
|
||||
title_font.SetFamily(wxFONTFAMILY_ROMAN);
|
||||
title_font.SetPointSize(24);
|
||||
title->SetFont(title_font);
|
||||
title->SetForegroundColour(wxColour(68, 121, 251));
|
||||
vsizer->Add(title, 0, wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL);
|
||||
}*/
|
||||
|
||||
// version
|
||||
/*{
|
||||
auto version_string = std::string(SLIC3R_APP_NAME) + " " + std::string(SLIC3R_VERSION) + _L(" is based on Slic3r by Alessandro Ranellucci and the RepRap community.");
|
||||
wxStaticText* version = new wxStaticText(this, wxID_ANY, version_string.c_str(), wxDefaultPosition, wxDefaultSize);
|
||||
wxFont version_font = GetFont();
|
||||
#ifdef __WXMSW__
|
||||
version_font.SetPointSize(version_font.GetPointSize());
|
||||
#else
|
||||
version_font.SetPointSize(11);
|
||||
#endif
|
||||
version->SetFont(version_font);
|
||||
vsizer->Add(version, 0, wxALIGN_LEFT | wxTOP | wxLEFT | wxRIGHT, 10);
|
||||
}*/
|
||||
|
||||
// text
|
||||
m_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO/*NEVER*/);
|
||||
{
|
||||
m_html->SetMinSize(wxSize(-1, 16 * wxGetApp().em_unit()));
|
||||
wxFont font = get_default_font(this);
|
||||
const auto text_clr = wxGetApp().get_label_clr_default();
|
||||
const auto text_clr_str = encode_color(ColorRGB(text_clr.Red(), text_clr.Green(), text_clr.Blue()));
|
||||
const auto bgr_clr_str = encode_color(ColorRGB(bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue()));
|
||||
|
||||
const int fs = font.GetPointSize();
|
||||
int size[] = {fs,fs,fs,fs,fs,fs,fs};
|
||||
m_html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
|
||||
m_html->SetBorders(2);
|
||||
const wxString copyright_str = _L("Copyright");
|
||||
// TRN AboutDialog: "Slic3r %1% GNU Affero General Public License"
|
||||
const wxString a_url_str = _L("Amazon : https://www.amazon.com/stores/page/220AF7CA-5334-4ECA-8E62-B6C8A068E7AC");
|
||||
const wxString s_url_str = _L("Shopify : https://qidi3d.com/");
|
||||
const wxString is_lecensed_str = _L("is licensed under the");
|
||||
const wxString license_str = _L("GNU Affero General Public License, version 3");
|
||||
const wxString version_str = std::string(SLIC3R_APP_NAME) + " " + std::string(SLIC3R_VERSION) + _L(" is based on PrusaSlicer.");
|
||||
const wxString based_on_str = _L("QIDISlicer is based on Slic3r by Alessandro Ranellucci and the RepRap community.");
|
||||
const wxString contributors_str = _L("QIDISlicer has made targeted adjustment and adaptation to QIDITech 3D printers, so that QIDITech 3D printers are more friendly to novices. It is recommended to use QIDITech 3D printers.");
|
||||
const auto text = format_wxstr(
|
||||
"<html>"
|
||||
"<body bgcolor= %1% link= %2%>"
|
||||
"<font color=%3%>"
|
||||
"%4%"
|
||||
"<br /><br />"
|
||||
"<a href=\"https://www.amazon.com/stores/page/220AF7CA-5334-4ECA-8E62-B6C8A068E7AC\">%5%</a> <br />"
|
||||
"<a href=\"https://qidi3d.com/\">%6%</a>"
|
||||
"<br /><br />"
|
||||
"%7%<br />"
|
||||
"%8% © 2016-2023 Prusa Research. <br />"
|
||||
"%9% © 2011-2018 Alessandro Ranellucci. <br />"
|
||||
"<a href=\"http://slic3r.org/\">Slic3r</a> %10% "
|
||||
"<a href=\"http://www.gnu.org/licenses/agpl-3.0.html\">%11%</a>."
|
||||
"</font>"
|
||||
"</body>"
|
||||
"</html>", bgr_clr_str, text_clr_str, text_clr_str
|
||||
, contributors_str, a_url_str, s_url_str ,version_str
|
||||
, copyright_str, copyright_str
|
||||
, is_lecensed_str, license_str);
|
||||
m_html->SetPage(text);
|
||||
m_html->SetForegroundColour(wxColour(68, 121, 251));
|
||||
vsizer->Add(m_html, 1, wxEXPAND | wxTOP, -30);
|
||||
m_html->Bind(wxEVT_HTML_LINK_CLICKED, &AboutDialog::onLinkClicked, this);
|
||||
}
|
||||
|
||||
|
||||
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE);
|
||||
|
||||
m_copy_rights_btn_id = NewControlId();
|
||||
auto copy_rights_btn = new wxButton(this, m_copy_rights_btn_id, _L("Portions copyright")+dots);
|
||||
buttons->Insert(0, copy_rights_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
|
||||
copy_rights_btn->Bind(wxEVT_BUTTON, &AboutDialog::onCopyrightBtn, this);
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this, true);
|
||||
|
||||
this->SetEscapeId(wxID_CLOSE);
|
||||
this->Bind(wxEVT_BUTTON, &AboutDialog::onCloseDialog, this, wxID_CLOSE);
|
||||
vsizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxTOP | wxBOTTOM, 15);
|
||||
|
||||
SetSizer(main_sizer);
|
||||
main_sizer->SetSizeHints(this);
|
||||
}
|
||||
|
||||
void AboutDialog::on_dpi_changed(const wxRect &suggested_rect)
|
||||
{
|
||||
// m_logo_bitmap.msw_rescale();
|
||||
// m_logo->SetBitmap(m_logo_bitmap.bmp());
|
||||
|
||||
const wxFont& font = GetFont();
|
||||
const int fs = font.GetPointSize() - 1;
|
||||
int font_size[] = { fs, fs, fs, fs, fs, fs, fs };
|
||||
m_html->SetFonts(font.GetFaceName(), font.GetFaceName(), font_size);
|
||||
|
||||
const int& em = em_unit();
|
||||
|
||||
msw_buttons_rescale(this, em, { wxID_CLOSE, m_copy_rights_btn_id });
|
||||
|
||||
m_html->SetMinSize(wxSize(-1, 16 * em));
|
||||
m_html->Refresh();
|
||||
|
||||
const wxSize& size = wxSize(65 * em, 30 * em);
|
||||
|
||||
SetMinSize(size);
|
||||
Fit();
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void AboutDialog::onLinkClicked(wxHtmlLinkEvent &event)
|
||||
{
|
||||
wxGetApp().open_browser_with_warning_dialog(event.GetLinkInfo().GetHref());
|
||||
event.Skip(false);
|
||||
}
|
||||
|
||||
void AboutDialog::onCloseDialog(wxEvent &)
|
||||
{
|
||||
this->EndModal(wxID_CLOSE);
|
||||
}
|
||||
|
||||
void AboutDialog::onCopyrightBtn(wxEvent &)
|
||||
{
|
||||
CopyrightsDialog dlg;
|
||||
dlg.ShowModal();
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
80
src/slic3r/GUI/AboutDialog.hpp
Normal file
80
src/slic3r/GUI/AboutDialog.hpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#ifndef slic3r_GUI_AboutDialog_hpp_
|
||||
#define slic3r_GUI_AboutDialog_hpp_
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/intl.h>
|
||||
#include <wx/html/htmlwin.h>
|
||||
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class AboutDialogLogo : public wxPanel
|
||||
{
|
||||
public:
|
||||
AboutDialogLogo(wxWindow* parent);
|
||||
|
||||
private:
|
||||
wxBitmap logo;
|
||||
void onRepaint(wxEvent &event);
|
||||
};
|
||||
|
||||
|
||||
|
||||
class CopyrightsDialog : public DPIDialog
|
||||
{
|
||||
public:
|
||||
CopyrightsDialog();
|
||||
~CopyrightsDialog() {}
|
||||
|
||||
struct Entry {
|
||||
Entry(const std::string &lib_name, const std::string ©right, const std::string &link) :
|
||||
lib_name(lib_name), copyright(copyright), link(link) {}
|
||||
|
||||
std::string lib_name;
|
||||
std::string copyright;
|
||||
std::string link;
|
||||
};
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect &suggested_rect) override;
|
||||
|
||||
private:
|
||||
wxHtmlWindow* m_html;
|
||||
std::vector<Entry> m_entries;
|
||||
|
||||
void onLinkClicked(wxHtmlLinkEvent &event);
|
||||
void onCloseDialog(wxEvent &);
|
||||
|
||||
void fill_entries();
|
||||
wxString get_html_text();
|
||||
};
|
||||
|
||||
|
||||
|
||||
class AboutDialog : public DPIDialog
|
||||
{
|
||||
ScalableBitmap m_logo_bitmap;
|
||||
wxHtmlWindow* m_html;
|
||||
wxStaticBitmap* m_logo;
|
||||
int m_copy_rights_btn_id { wxID_ANY };
|
||||
int m_copy_version_btn_id { wxID_ANY };
|
||||
public:
|
||||
AboutDialog();
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect &suggested_rect) override;
|
||||
|
||||
private:
|
||||
void onLinkClicked(wxHtmlLinkEvent &event);
|
||||
void onCloseDialog(wxEvent &);
|
||||
void onCopyrightBtn(wxEvent &);
|
||||
void onCopyToClipboard(wxEvent&);
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
754
src/slic3r/GUI/BackgroundSlicingProcess.cpp
Normal file
754
src/slic3r/GUI/BackgroundSlicingProcess.cpp
Normal file
@@ -0,0 +1,754 @@
|
||||
#include "BackgroundSlicingProcess.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/stdpaths.h>
|
||||
|
||||
// For zipped archive creation
|
||||
#include <wx/stdstream.h>
|
||||
#include <wx/wfstream.h>
|
||||
#include <wx/zipstrm.h>
|
||||
|
||||
#include <miniz.h>
|
||||
|
||||
// Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx.
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/GCode/PostProcessor.hpp"
|
||||
#include "libslic3r/Format/SL1.hpp"
|
||||
#include "libslic3r/Thread.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <stdexcept>
|
||||
#include <cctype>
|
||||
|
||||
#include <boost/format/format_fwd.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include "I18N.hpp"
|
||||
#include "RemovableDriveManager.hpp"
|
||||
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
bool SlicingProcessCompletedEvent::critical_error() const
|
||||
{
|
||||
try {
|
||||
this->rethrow_exception();
|
||||
} catch (const Slic3r::SlicingError &) {
|
||||
// Exception derived from SlicingError is non-critical.
|
||||
return false;
|
||||
} catch (...) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SlicingProcessCompletedEvent::invalidate_plater() const
|
||||
{
|
||||
if (critical_error())
|
||||
{
|
||||
try {
|
||||
this->rethrow_exception();
|
||||
}
|
||||
catch (const Slic3r::ExportError&) {
|
||||
// Exception thrown by copying file does not ivalidate plater
|
||||
return false;
|
||||
}
|
||||
catch (...) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::pair<std::string, bool> SlicingProcessCompletedEvent::format_error_message() const
|
||||
{
|
||||
std::string error;
|
||||
bool monospace = false;
|
||||
try {
|
||||
this->rethrow_exception();
|
||||
} catch (const std::bad_alloc &ex) {
|
||||
error = GUI::format(_L("%s has encountered an error. It was likely caused by running out of memory. "
|
||||
"If you are sure you have enough RAM on your system, this may also be a bug and we would "
|
||||
"be glad if you reported it."), SLIC3R_APP_NAME);
|
||||
error += "\n\n" + std::string(ex.what());
|
||||
} catch (const HardCrash &ex) {
|
||||
error = GUI::format(_L("QIDISlicer has encountered a fatal error: \"%1%\""), ex.what()) + "\n\n" +
|
||||
_u8L("Please save your project and restart QIDISlicer. "
|
||||
"We would be glad if you reported the issue.");
|
||||
} catch (PlaceholderParserError &ex) {
|
||||
error = ex.what();
|
||||
monospace = true;
|
||||
} catch (std::exception &ex) {
|
||||
error = ex.what();
|
||||
} catch (...) {
|
||||
error = "Unknown C++ exception.";
|
||||
}
|
||||
return std::make_pair(std::move(error), monospace);
|
||||
}
|
||||
|
||||
BackgroundSlicingProcess::BackgroundSlicingProcess()
|
||||
{
|
||||
boost::filesystem::path temp_path(wxStandardPaths::Get().GetTempDir().utf8_str().data());
|
||||
temp_path /= (boost::format(".%1%.gcode") % get_current_pid()).str();
|
||||
m_temp_output_path = temp_path.string();
|
||||
}
|
||||
|
||||
BackgroundSlicingProcess::~BackgroundSlicingProcess()
|
||||
{
|
||||
this->stop();
|
||||
this->join_background_thread();
|
||||
boost::nowide::remove(m_temp_output_path.c_str());
|
||||
}
|
||||
|
||||
bool BackgroundSlicingProcess::select_technology(PrinterTechnology tech)
|
||||
{
|
||||
bool changed = false;
|
||||
if (m_print == nullptr || m_print->technology() != tech) {
|
||||
if (m_print != nullptr)
|
||||
this->reset();
|
||||
switch (tech) {
|
||||
case ptFFF: m_print = m_fff_print; break;
|
||||
case ptSLA: m_print = m_sla_print; break;
|
||||
default: assert(false); break;
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
assert(m_print != nullptr);
|
||||
return changed;
|
||||
}
|
||||
|
||||
PrinterTechnology BackgroundSlicingProcess::current_printer_technology() const
|
||||
{
|
||||
return m_print->technology();
|
||||
}
|
||||
|
||||
std::string BackgroundSlicingProcess::output_filepath_for_project(const boost::filesystem::path &project_path)
|
||||
{
|
||||
assert(m_print != nullptr);
|
||||
if (project_path.empty())
|
||||
return m_print->output_filepath("");
|
||||
return m_print->output_filepath(project_path.parent_path().string(), project_path.stem().string());
|
||||
}
|
||||
|
||||
// This function may one day be merged into the Print, but historically the print was separated
|
||||
// from the G-code generator.
|
||||
void BackgroundSlicingProcess::process_fff()
|
||||
{
|
||||
assert(m_print == m_fff_print);
|
||||
m_print->process();
|
||||
wxCommandEvent evt(m_event_slicing_completed_id);
|
||||
// Post the Slicing Finished message for the G-code viewer to update.
|
||||
// Passing the timestamp
|
||||
evt.SetInt((int)(m_fff_print->step_state_with_timestamp(PrintStep::psSlicingFinished).timestamp));
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
|
||||
m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, [this](const ThumbnailsParams& params) { return this->render_thumbnails(params); });
|
||||
if (this->set_step_started(bspsGCodeFinalize)) {
|
||||
if (! m_export_path.empty()) {
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
|
||||
finalize_gcode();
|
||||
} else if (! m_upload_job.empty()) {
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
|
||||
prepare_upload();
|
||||
} else {
|
||||
m_print->set_status(100, _u8L("Slicing complete"));
|
||||
}
|
||||
this->set_step_done(bspsGCodeFinalize);
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundSlicingProcess::process_sla()
|
||||
{
|
||||
assert(m_print == m_sla_print);
|
||||
m_print->process();
|
||||
if (this->set_step_started(bspsGCodeFinalize)) {
|
||||
if (! m_export_path.empty()) {
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
|
||||
|
||||
const std::string export_path = m_sla_print->print_statistics().finalize_output_path(m_export_path);
|
||||
|
||||
ThumbnailsList thumbnails = this->render_thumbnails(
|
||||
ThumbnailsParams{current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true});
|
||||
|
||||
m_sla_print->export_print(export_path, thumbnails);
|
||||
|
||||
m_print->set_status(100, GUI::format(_L("Masked SLA file exported to %1%"), export_path));
|
||||
} else if (! m_upload_job.empty()) {
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
|
||||
prepare_upload();
|
||||
} else {
|
||||
m_print->set_status(100, _u8L("Slicing complete"));
|
||||
}
|
||||
this->set_step_done(bspsGCodeFinalize);
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundSlicingProcess::thread_proc()
|
||||
{
|
||||
set_current_thread_name("slic3r_BgSlcPcs");
|
||||
name_tbb_thread_pool_threads_set_locale();
|
||||
|
||||
// Set "C" locales and enforce OSX QoS level on all threads entering an arena.
|
||||
// The cost of the callback is quite low: The callback is called once per thread
|
||||
// entering a parallel loop and the callback is guarded with a thread local
|
||||
// variable to be executed just once.
|
||||
TBBLocalesSetter setter;
|
||||
|
||||
assert(m_print != nullptr);
|
||||
assert(m_print == m_fff_print || m_print == m_sla_print);
|
||||
std::unique_lock<std::mutex> lck(m_mutex);
|
||||
// Let the caller know we are ready to run the background processing task.
|
||||
m_state = STATE_IDLE;
|
||||
lck.unlock();
|
||||
m_condition.notify_one();
|
||||
for (;;) {
|
||||
assert(m_state == STATE_IDLE || m_state == STATE_CANCELED || m_state == STATE_FINISHED);
|
||||
// Wait until a new task is ready to be executed, or this thread should be finished.
|
||||
lck.lock();
|
||||
m_condition.wait(lck, [this](){ return m_state == STATE_STARTED || m_state == STATE_EXIT; });
|
||||
if (m_state == STATE_EXIT)
|
||||
// Exiting this thread.
|
||||
break;
|
||||
// Process the background slicing task.
|
||||
m_state = STATE_RUNNING;
|
||||
lck.unlock();
|
||||
std::exception_ptr exception;
|
||||
#ifdef _WIN32
|
||||
this->call_process_seh_throw(exception);
|
||||
#else
|
||||
this->call_process(exception);
|
||||
#endif
|
||||
m_print->finalize();
|
||||
lck.lock();
|
||||
m_state = m_print->canceled() ? STATE_CANCELED : STATE_FINISHED;
|
||||
if (m_print->cancel_status() != Print::CANCELED_INTERNAL) {
|
||||
// Only post the canceled event, if canceled by user.
|
||||
// Don't post the canceled event, if canceled from Print::apply().
|
||||
SlicingProcessCompletedEvent evt(m_event_finished_id, 0,
|
||||
(m_state == STATE_CANCELED) ? SlicingProcessCompletedEvent::Cancelled :
|
||||
exception ? SlicingProcessCompletedEvent::Error : SlicingProcessCompletedEvent::Finished, exception);
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
|
||||
// Cancelled by the user, not internally, thus cleanup() was not called yet.
|
||||
// Otherwise cleanup() is called from Print::apply()
|
||||
m_print->cleanup();
|
||||
}
|
||||
m_print->restart();
|
||||
lck.unlock();
|
||||
// Let the UI thread wake up if it is waiting for the background task to finish.
|
||||
m_condition.notify_one();
|
||||
// Let the UI thread see the result.
|
||||
}
|
||||
m_state = STATE_EXITED;
|
||||
lck.unlock();
|
||||
// End of the background processing thread. The UI thread should join m_thread now.
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// Only these SEH exceptions will be catched and turned into Slic3r::HardCrash C++ exceptions.
|
||||
static bool is_win32_seh_harware_exception(unsigned long ex) throw() {
|
||||
return
|
||||
ex == STATUS_ACCESS_VIOLATION ||
|
||||
ex == STATUS_DATATYPE_MISALIGNMENT ||
|
||||
ex == STATUS_FLOAT_DIVIDE_BY_ZERO ||
|
||||
ex == STATUS_FLOAT_OVERFLOW ||
|
||||
ex == STATUS_FLOAT_UNDERFLOW ||
|
||||
#ifdef STATUS_FLOATING_RESEVERED_OPERAND
|
||||
ex == STATUS_FLOATING_RESEVERED_OPERAND ||
|
||||
#endif // STATUS_FLOATING_RESEVERED_OPERAND
|
||||
ex == STATUS_ILLEGAL_INSTRUCTION ||
|
||||
ex == STATUS_PRIVILEGED_INSTRUCTION ||
|
||||
ex == STATUS_INTEGER_DIVIDE_BY_ZERO ||
|
||||
ex == STATUS_INTEGER_OVERFLOW ||
|
||||
ex == STATUS_STACK_OVERFLOW;
|
||||
}
|
||||
|
||||
// Rethrow some SEH exceptions as Slic3r::HardCrash C++ exceptions.
|
||||
static void rethrow_seh_exception(unsigned long win32_seh_catched)
|
||||
{
|
||||
if (win32_seh_catched) {
|
||||
// Rethrow SEH exception as Slicer::HardCrash.
|
||||
if (win32_seh_catched == STATUS_ACCESS_VIOLATION || win32_seh_catched == STATUS_DATATYPE_MISALIGNMENT)
|
||||
throw Slic3r::HardCrash(_u8L("Access violation"));
|
||||
if (win32_seh_catched == STATUS_ILLEGAL_INSTRUCTION || win32_seh_catched == STATUS_PRIVILEGED_INSTRUCTION)
|
||||
throw Slic3r::HardCrash(_u8L("Illegal instruction"));
|
||||
if (win32_seh_catched == STATUS_FLOAT_DIVIDE_BY_ZERO || win32_seh_catched == STATUS_INTEGER_DIVIDE_BY_ZERO)
|
||||
throw Slic3r::HardCrash(_u8L("Divide by zero"));
|
||||
if (win32_seh_catched == STATUS_FLOAT_OVERFLOW || win32_seh_catched == STATUS_INTEGER_OVERFLOW)
|
||||
throw Slic3r::HardCrash(_u8L("Overflow"));
|
||||
if (win32_seh_catched == STATUS_FLOAT_UNDERFLOW)
|
||||
throw Slic3r::HardCrash(_u8L("Underflow"));
|
||||
#ifdef STATUS_FLOATING_RESEVERED_OPERAND
|
||||
if (win32_seh_catched == STATUS_FLOATING_RESEVERED_OPERAND)
|
||||
throw Slic3r::HardCrash(_u8L("Floating reserved operand"));
|
||||
#endif // STATUS_FLOATING_RESEVERED_OPERAND
|
||||
if (win32_seh_catched == STATUS_STACK_OVERFLOW)
|
||||
throw Slic3r::HardCrash(_u8L("Stack overflow"));
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper for Win32 structured exceptions. Win32 structured exception blocks and C++ exception blocks cannot be mixed in the same function.
|
||||
unsigned long BackgroundSlicingProcess::call_process_seh(std::exception_ptr &ex) throw()
|
||||
{
|
||||
unsigned long win32_seh_catched = 0;
|
||||
__try {
|
||||
this->call_process(ex);
|
||||
} __except (is_win32_seh_harware_exception(GetExceptionCode())) {
|
||||
win32_seh_catched = GetExceptionCode();
|
||||
}
|
||||
return win32_seh_catched;
|
||||
}
|
||||
void BackgroundSlicingProcess::call_process_seh_throw(std::exception_ptr &ex) throw()
|
||||
{
|
||||
unsigned long win32_seh_catched = this->call_process_seh(ex);
|
||||
if (win32_seh_catched) {
|
||||
// Rethrow SEH exception as Slicer::HardCrash.
|
||||
try {
|
||||
rethrow_seh_exception(win32_seh_catched);
|
||||
} catch (...) {
|
||||
ex = std::current_exception();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
void BackgroundSlicingProcess::call_process(std::exception_ptr &ex) throw()
|
||||
{
|
||||
try {
|
||||
assert(m_print != nullptr);
|
||||
switch (m_print->technology()) {
|
||||
case ptFFF: this->process_fff(); break;
|
||||
case ptSLA: this->process_sla(); break;
|
||||
default: m_print->process(); break;
|
||||
}
|
||||
} catch (CanceledException& /* ex */) {
|
||||
// Canceled, this is all right.
|
||||
assert(m_print->canceled());
|
||||
ex = std::current_exception();
|
||||
} catch (...) {
|
||||
ex = std::current_exception();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
unsigned long BackgroundSlicingProcess::thread_proc_safe_seh() throw()
|
||||
{
|
||||
unsigned long win32_seh_catched = 0;
|
||||
__try {
|
||||
this->thread_proc_safe();
|
||||
} __except (is_win32_seh_harware_exception(GetExceptionCode())) {
|
||||
win32_seh_catched = GetExceptionCode();
|
||||
}
|
||||
return win32_seh_catched;
|
||||
}
|
||||
void BackgroundSlicingProcess::thread_proc_safe_seh_throw() throw()
|
||||
{
|
||||
unsigned long win32_seh_catched = this->thread_proc_safe_seh();
|
||||
if (win32_seh_catched) {
|
||||
// Rethrow SEH exception as Slicer::HardCrash.
|
||||
try {
|
||||
rethrow_seh_exception(win32_seh_catched);
|
||||
} catch (...) {
|
||||
wxTheApp->OnUnhandledException();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
void BackgroundSlicingProcess::thread_proc_safe() throw()
|
||||
{
|
||||
try {
|
||||
this->thread_proc();
|
||||
} catch (...) {
|
||||
wxTheApp->OnUnhandledException();
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundSlicingProcess::join_background_thread()
|
||||
{
|
||||
std::unique_lock<std::mutex> lck(m_mutex);
|
||||
if (m_state == STATE_INITIAL) {
|
||||
// Worker thread has not been started yet.
|
||||
assert(! m_thread.joinable());
|
||||
} else {
|
||||
assert(m_state == STATE_IDLE);
|
||||
assert(m_thread.joinable());
|
||||
// Notify the worker thread to exit.
|
||||
m_state = STATE_EXIT;
|
||||
lck.unlock();
|
||||
m_condition.notify_one();
|
||||
// Wait until the worker thread exits.
|
||||
m_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
bool BackgroundSlicingProcess::start()
|
||||
{
|
||||
if (m_print->empty())
|
||||
// The print is empty (no object in Model, or all objects are out of the print bed).
|
||||
return false;
|
||||
|
||||
std::unique_lock<std::mutex> lck(m_mutex);
|
||||
if (m_state == STATE_INITIAL) {
|
||||
// The worker thread is not running yet. Start it.
|
||||
assert(! m_thread.joinable());
|
||||
m_thread = create_thread([this]{
|
||||
#ifdef _WIN32
|
||||
this->thread_proc_safe_seh_throw();
|
||||
#else // _WIN32
|
||||
this->thread_proc_safe();
|
||||
#endif // _WIN32
|
||||
});
|
||||
// Wait until the worker thread is ready to execute the background processing task.
|
||||
m_condition.wait(lck, [this](){ return m_state == STATE_IDLE; });
|
||||
}
|
||||
assert(m_state == STATE_IDLE || this->running());
|
||||
if (this->running())
|
||||
// The background processing thread is already running.
|
||||
return false;
|
||||
if (! this->idle())
|
||||
throw Slic3r::RuntimeError("Cannot start a background task, the worker thread is not idle.");
|
||||
m_state = STATE_STARTED;
|
||||
m_print->set_cancel_callback([this](){ this->stop_internal(); });
|
||||
lck.unlock();
|
||||
m_condition.notify_one();
|
||||
return true;
|
||||
}
|
||||
|
||||
// To be called on the UI thread.
|
||||
bool BackgroundSlicingProcess::stop()
|
||||
{
|
||||
// m_print->state_mutex() shall NOT be held. Unfortunately there is no interface to test for it.
|
||||
std::unique_lock<std::mutex> lck(m_mutex);
|
||||
if (m_state == STATE_INITIAL) {
|
||||
// m_export_path.clear();
|
||||
return false;
|
||||
}
|
||||
// assert(this->running());
|
||||
if (m_state == STATE_STARTED || m_state == STATE_RUNNING) {
|
||||
// Cancel any task planned by the background thread on UI thread.
|
||||
cancel_ui_task(m_ui_task);
|
||||
m_print->cancel();
|
||||
// Wait until the background processing stops by being canceled.
|
||||
m_condition.wait(lck, [this](){ return m_state == STATE_CANCELED; });
|
||||
// In the "Canceled" state. Reset the state to "Idle".
|
||||
m_state = STATE_IDLE;
|
||||
m_print->set_cancel_callback([](){});
|
||||
} else if (m_state == STATE_FINISHED || m_state == STATE_CANCELED) {
|
||||
// In the "Finished" or "Canceled" state. Reset the state to "Idle".
|
||||
m_state = STATE_IDLE;
|
||||
m_print->set_cancel_callback([](){});
|
||||
}
|
||||
// m_export_path.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BackgroundSlicingProcess::reset()
|
||||
{
|
||||
bool stopped = this->stop();
|
||||
this->reset_export();
|
||||
m_print->clear();
|
||||
this->invalidate_all_steps();
|
||||
return stopped;
|
||||
}
|
||||
|
||||
// To be called by Print::apply() on the UI thread through the Print::m_cancel_callback to stop the background
|
||||
// processing before changing any data of running or finalized milestones.
|
||||
// This function shall not trigger any UI update through the wxWidgets event.
|
||||
void BackgroundSlicingProcess::stop_internal()
|
||||
{
|
||||
// m_print->state_mutex() shall be held. Unfortunately there is no interface to test for it.
|
||||
if (m_state == STATE_IDLE)
|
||||
// The worker thread is waiting on m_mutex/m_condition for wake up. The following lock of the mutex would block.
|
||||
return;
|
||||
std::unique_lock<std::mutex> lck(m_mutex);
|
||||
assert(m_state == STATE_STARTED || m_state == STATE_RUNNING || m_state == STATE_FINISHED || m_state == STATE_CANCELED);
|
||||
if (m_state == STATE_STARTED || m_state == STATE_RUNNING) {
|
||||
// Cancel any task planned by the background thread on UI thread.
|
||||
cancel_ui_task(m_ui_task);
|
||||
// At this point of time the worker thread may be blocking on m_print->state_mutex().
|
||||
// Set the print state to canceled before unlocking the state_mutex(), so when the worker thread wakes up,
|
||||
// it throws the CanceledException().
|
||||
m_print->cancel_internal();
|
||||
// Allow the worker thread to wake up if blocking on a milestone.
|
||||
m_print->state_mutex().unlock();
|
||||
// Wait until the background processing stops by being canceled.
|
||||
m_condition.wait(lck, [this](){ return m_state == STATE_CANCELED; });
|
||||
// Lock it back to be in a consistent state.
|
||||
m_print->state_mutex().lock();
|
||||
}
|
||||
// In the "Canceled" state. Reset the state to "Idle".
|
||||
m_state = STATE_IDLE;
|
||||
m_print->set_cancel_callback([](){});
|
||||
}
|
||||
|
||||
// Execute task from background thread on the UI thread. Returns true if processed, false if cancelled.
|
||||
bool BackgroundSlicingProcess::execute_ui_task(std::function<void()> task)
|
||||
{
|
||||
bool running = false;
|
||||
if (m_mutex.try_lock()) {
|
||||
// Cancellation is either not in process, or already canceled and waiting for us to finish.
|
||||
// There must be no UI task planned.
|
||||
assert(! m_ui_task);
|
||||
if (! m_print->canceled()) {
|
||||
running = true;
|
||||
m_ui_task = std::make_shared<UITask>();
|
||||
}
|
||||
m_mutex.unlock();
|
||||
} else {
|
||||
// Cancellation is in process.
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
if (running) {
|
||||
std::shared_ptr<UITask> ctx = m_ui_task;
|
||||
GUI::wxGetApp().mainframe->m_plater->CallAfter([task, ctx]() {
|
||||
// Running on the UI thread, thus ctx->state does not need to be guarded with mutex against ::cancel_ui_task().
|
||||
assert(ctx->state == UITask::Planned || ctx->state == UITask::Canceled);
|
||||
if (ctx->state == UITask::Planned) {
|
||||
task();
|
||||
std::unique_lock<std::mutex> lck(ctx->mutex);
|
||||
ctx->state = UITask::Finished;
|
||||
}
|
||||
// Wake up the worker thread from the UI thread.
|
||||
ctx->condition.notify_all();
|
||||
});
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(ctx->mutex);
|
||||
ctx->condition.wait(lock, [&ctx]{ return ctx->state == UITask::Finished || ctx->state == UITask::Canceled; });
|
||||
}
|
||||
result = ctx->state == UITask::Finished;
|
||||
m_ui_task.reset();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// To be called on the UI thread from ::stop() and ::stop_internal().
|
||||
void BackgroundSlicingProcess::cancel_ui_task(std::shared_ptr<UITask> task)
|
||||
{
|
||||
if (task) {
|
||||
std::unique_lock<std::mutex> lck(task->mutex);
|
||||
task->state = UITask::Canceled;
|
||||
lck.unlock();
|
||||
task->condition.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
bool BackgroundSlicingProcess::empty() const
|
||||
{
|
||||
assert(m_print != nullptr);
|
||||
return m_print->empty();
|
||||
}
|
||||
|
||||
std::string BackgroundSlicingProcess::validate(std::vector<std::string>* warnings)
|
||||
{
|
||||
assert(m_print != nullptr);
|
||||
return m_print->validate(warnings);
|
||||
}
|
||||
|
||||
// Apply config over the print. Returns false, if the new config values caused any of the already
|
||||
// processed steps to be invalidated, therefore the task will need to be restarted.
|
||||
Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const DynamicPrintConfig &config)
|
||||
{
|
||||
assert(m_print != nullptr);
|
||||
assert(config.opt_enum<PrinterTechnology>("printer_technology") == m_print->technology());
|
||||
Print::ApplyStatus invalidated = m_print->apply(model, config);
|
||||
if ((invalidated & PrintBase::APPLY_STATUS_INVALIDATED) != 0 && m_print->technology() == ptFFF &&
|
||||
!m_fff_print->is_step_done(psGCodeExport)) {
|
||||
// Some FFF status was invalidated, and the G-code was not exported yet.
|
||||
// Let the G-code preview UI know that the final G-code preview is not valid.
|
||||
// In addition, this early memory deallocation reduces memory footprint.
|
||||
if (m_gcode_result != nullptr)
|
||||
m_gcode_result->reset();
|
||||
}
|
||||
return invalidated;
|
||||
}
|
||||
|
||||
void BackgroundSlicingProcess::set_task(const PrintBase::TaskParams ¶ms)
|
||||
{
|
||||
assert(m_print != nullptr);
|
||||
m_print->set_task(params);
|
||||
}
|
||||
|
||||
// Set the output path of the G-code.
|
||||
void BackgroundSlicingProcess::schedule_export(const std::string &path, bool export_path_on_removable_media)
|
||||
{
|
||||
assert(m_export_path.empty());
|
||||
if (! m_export_path.empty())
|
||||
return;
|
||||
|
||||
// Guard against entering the export step before changing the export path.
|
||||
std::scoped_lock<std::mutex> lock(m_print->state_mutex());
|
||||
this->invalidate_step(bspsGCodeFinalize);
|
||||
m_export_path = path;
|
||||
m_export_path_on_removable_media = export_path_on_removable_media;
|
||||
}
|
||||
|
||||
void BackgroundSlicingProcess::schedule_upload(Slic3r::PrintHostJob upload_job)
|
||||
{
|
||||
assert(m_export_path.empty());
|
||||
if (! m_export_path.empty())
|
||||
return;
|
||||
|
||||
// Guard against entering the export step before changing the export path.
|
||||
std::scoped_lock<std::mutex> lock(m_print->state_mutex());
|
||||
this->invalidate_step(bspsGCodeFinalize);
|
||||
m_export_path.clear();
|
||||
m_upload_job = std::move(upload_job);
|
||||
}
|
||||
|
||||
void BackgroundSlicingProcess::reset_export()
|
||||
{
|
||||
assert(! this->running());
|
||||
if (! this->running()) {
|
||||
m_export_path.clear();
|
||||
m_export_path_on_removable_media = false;
|
||||
// invalidate_step expects the mutex to be locked.
|
||||
std::scoped_lock<std::mutex> lock(m_print->state_mutex());
|
||||
this->invalidate_step(bspsGCodeFinalize);
|
||||
}
|
||||
}
|
||||
|
||||
bool BackgroundSlicingProcess::set_step_started(BackgroundSlicingProcessStep step)
|
||||
{
|
||||
return m_step_state.set_started(step, m_print->state_mutex(), [this](){ this->throw_if_canceled(); });
|
||||
}
|
||||
|
||||
void BackgroundSlicingProcess::set_step_done(BackgroundSlicingProcessStep step)
|
||||
{
|
||||
m_step_state.set_done(step, m_print->state_mutex(), [this](){ this->throw_if_canceled(); });
|
||||
}
|
||||
|
||||
bool BackgroundSlicingProcess::is_step_done(BackgroundSlicingProcessStep step) const
|
||||
{
|
||||
return m_step_state.is_done(step, m_print->state_mutex());
|
||||
}
|
||||
|
||||
bool BackgroundSlicingProcess::invalidate_step(BackgroundSlicingProcessStep step)
|
||||
{
|
||||
bool invalidated = m_step_state.invalidate(step, [this](){ this->stop_internal(); });
|
||||
return invalidated;
|
||||
}
|
||||
|
||||
bool BackgroundSlicingProcess::invalidate_all_steps()
|
||||
{
|
||||
return m_step_state.invalidate_all([this](){ this->stop_internal(); });
|
||||
}
|
||||
|
||||
// G-code is generated in m_temp_output_path.
|
||||
// Optionally run a post-processing script on a copy of m_temp_output_path.
|
||||
// Copy the final G-code to target location (possibly a SD card, if it is a removable media, then verify that the file was written without an error).
|
||||
void BackgroundSlicingProcess::finalize_gcode()
|
||||
{
|
||||
m_print->set_status(95, _u8L("Running post-processing scripts"));
|
||||
|
||||
// Perform the final post-processing of the export path by applying the print statistics over the file name.
|
||||
std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
|
||||
std::string output_path = m_temp_output_path;
|
||||
// Both output_path and export_path ar in-out parameters.
|
||||
// If post processed, output_path will differ from m_temp_output_path as run_post_process_scripts() will make a copy of the G-code to not
|
||||
// collide with the G-code viewer memory mapping of the unprocessed G-code. G-code viewer maps unprocessed G-code, because m_gcode_result
|
||||
// is calculated for the unprocessed G-code and it references lines in the memory mapped G-code file by line numbers.
|
||||
// export_path may be changed by the post-processing script as well if the post processing script decides so, see GH #6042.
|
||||
bool post_processed = run_post_process_scripts(output_path, true, "File", export_path, m_fff_print->full_print_config());
|
||||
auto remove_post_processed_temp_file = [post_processed, &output_path]() {
|
||||
if (post_processed)
|
||||
try {
|
||||
boost::filesystem::remove(output_path);
|
||||
} catch (const std::exception &ex) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to remove temp file " << output_path << ": " << ex.what();
|
||||
}
|
||||
};
|
||||
|
||||
//FIXME localize the messages
|
||||
std::string error_message;
|
||||
int copy_ret_val = CopyFileResult::SUCCESS;
|
||||
try
|
||||
{
|
||||
copy_ret_val = copy_file(output_path, export_path, error_message, m_export_path_on_removable_media);
|
||||
remove_post_processed_temp_file();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
remove_post_processed_temp_file();
|
||||
throw Slic3r::ExportError(_u8L("Unknown error occured during exporting G-code."));
|
||||
}
|
||||
switch (copy_ret_val) {
|
||||
case CopyFileResult::SUCCESS: break; // no error
|
||||
case CopyFileResult::FAIL_COPY_FILE:
|
||||
throw Slic3r::ExportError(GUI::format(_L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\nError message: %1%"), error_message));
|
||||
break;
|
||||
case CopyFileResult::FAIL_FILES_DIFFERENT:
|
||||
throw Slic3r::ExportError(GUI::format(_L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."), export_path));
|
||||
break;
|
||||
case CopyFileResult::FAIL_RENAMING:
|
||||
throw Slic3r::ExportError(GUI::format(_L("Renaming of the G-code after copying to the selected destination folder has failed. Current path is %1%.tmp. Please try exporting again."), export_path));
|
||||
break;
|
||||
case CopyFileResult::FAIL_CHECK_ORIGIN_NOT_OPENED:
|
||||
throw Slic3r::ExportError(GUI::format(_L("Copying of the temporary G-code has finished but the original code at %1% couldn't be opened during copy check. The output G-code is at %2%.tmp."), output_path, export_path));
|
||||
break;
|
||||
case CopyFileResult::FAIL_CHECK_TARGET_NOT_OPENED:
|
||||
throw Slic3r::ExportError(GUI::format(_L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."), export_path));
|
||||
break;
|
||||
default:
|
||||
throw Slic3r::ExportError(_u8L("Unknown error occured during exporting G-code."));
|
||||
BOOST_LOG_TRIVIAL(error) << "Unexpected fail code(" << (int)copy_ret_val << ") durring copy_file() to " << export_path << ".";
|
||||
break;
|
||||
}
|
||||
|
||||
m_print->set_status(100, GUI::format(_L("G-code file exported to %1%"), export_path));
|
||||
}
|
||||
|
||||
// A print host upload job has been scheduled, enqueue it to the printhost job queue
|
||||
void BackgroundSlicingProcess::prepare_upload()
|
||||
{
|
||||
// Generate a unique temp path to which the gcode/zip file is copied/exported
|
||||
boost::filesystem::path source_path = boost::filesystem::temp_directory_path()
|
||||
/ boost::filesystem::unique_path("." SLIC3R_APP_KEY ".upload.%%%%-%%%%-%%%%-%%%%");
|
||||
|
||||
if (m_print == m_fff_print) {
|
||||
m_print->set_status(95, _u8L("Running post-processing scripts"));
|
||||
std::string error_message;
|
||||
if (copy_file(m_temp_output_path, source_path.string(), error_message) != SUCCESS)
|
||||
throw Slic3r::RuntimeError("Copying of the temporary G-code to the output G-code failed");
|
||||
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
|
||||
// Make a copy of the source path, as run_post_process_scripts() is allowed to change it when making a copy of the source file
|
||||
// (not here, but when the final target is a file).
|
||||
std::string source_path_str = source_path.string();
|
||||
std::string output_name_str = m_upload_job.upload_data.upload_path.string();
|
||||
if (run_post_process_scripts(source_path_str, false, m_upload_job.printhost->get_name(), output_name_str, m_fff_print->full_print_config()))
|
||||
m_upload_job.upload_data.upload_path = output_name_str;
|
||||
} else {
|
||||
m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
|
||||
|
||||
ThumbnailsList thumbnails = this->render_thumbnails(
|
||||
ThumbnailsParams{current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, true, true});
|
||||
m_sla_print->export_print(source_path.string(),thumbnails, m_upload_job.upload_data.upload_path.filename().string());
|
||||
}
|
||||
|
||||
m_print->set_status(100, GUI::format(_L("Scheduling upload to `%1%`. See Window -> Print Host Upload Queue"), m_upload_job.printhost->get_host()));
|
||||
|
||||
m_upload_job.upload_data.source_path = std::move(source_path);
|
||||
|
||||
GUI::wxGetApp().printhost_job_queue().enqueue(std::move(m_upload_job));
|
||||
}
|
||||
|
||||
// Executed by the background thread, to start a task on the UI thread.
|
||||
ThumbnailsList BackgroundSlicingProcess::render_thumbnails(const ThumbnailsParams ¶ms)
|
||||
{
|
||||
ThumbnailsList thumbnails;
|
||||
if (m_thumbnail_cb)
|
||||
this->execute_ui_task([this, ¶ms, &thumbnails](){ thumbnails = m_thumbnail_cb(params); });
|
||||
return thumbnails;
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
284
src/slic3r/GUI/BackgroundSlicingProcess.hpp
Normal file
284
src/slic3r/GUI/BackgroundSlicingProcess.hpp
Normal file
@@ -0,0 +1,284 @@
|
||||
#ifndef slic3r_GUI_BackgroundSlicingProcess_hpp_
|
||||
#define slic3r_GUI_BackgroundSlicingProcess_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
#include <wx/event.h>
|
||||
|
||||
#include "libslic3r/PrintBase.hpp"
|
||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||
#include "slic3r/Utils/PrintHost.hpp"
|
||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||
|
||||
|
||||
namespace boost { namespace filesystem { class path; } }
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class Model;
|
||||
class SLAPrint;
|
||||
|
||||
class SlicingStatusEvent : public wxEvent
|
||||
{
|
||||
public:
|
||||
SlicingStatusEvent(wxEventType eventType, int winid, const PrintBase::SlicingStatus &status) :
|
||||
wxEvent(winid, eventType), status(std::move(status)) {}
|
||||
virtual wxEvent *Clone() const { return new SlicingStatusEvent(*this); }
|
||||
|
||||
PrintBase::SlicingStatus status;
|
||||
};
|
||||
|
||||
class SlicingProcessCompletedEvent : public wxEvent
|
||||
{
|
||||
public:
|
||||
enum StatusType {
|
||||
Finished,
|
||||
Cancelled,
|
||||
Error
|
||||
};
|
||||
|
||||
SlicingProcessCompletedEvent(wxEventType eventType, int winid, StatusType status, std::exception_ptr exception) :
|
||||
wxEvent(winid, eventType), m_status(status), m_exception(exception) {}
|
||||
virtual wxEvent* Clone() const { return new SlicingProcessCompletedEvent(*this); }
|
||||
|
||||
StatusType status() const { return m_status; }
|
||||
bool finished() const { return m_status == Finished; }
|
||||
bool success() const { return m_status == Finished; }
|
||||
bool cancelled() const { return m_status == Cancelled; }
|
||||
bool error() const { return m_status == Error; }
|
||||
// Unhandled error produced by stdlib or a Win32 structured exception, or unhandled Slic3r's own critical exception.
|
||||
bool critical_error() const;
|
||||
// Critical errors does invalidate plater except CopyFileError.
|
||||
bool invalidate_plater() const;
|
||||
// Only valid if error()
|
||||
void rethrow_exception() const { assert(this->error()); assert(m_exception); std::rethrow_exception(m_exception); }
|
||||
// Produce a human readable message to be displayed by a notification or a message box.
|
||||
// 2nd parameter defines whether the output should be displayed with a monospace font.
|
||||
std::pair<std::string, bool> format_error_message() const;
|
||||
|
||||
private:
|
||||
StatusType m_status;
|
||||
std::exception_ptr m_exception;
|
||||
};
|
||||
|
||||
wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent);
|
||||
|
||||
// Print step IDs for keeping track of the print state.
|
||||
enum BackgroundSlicingProcessStep {
|
||||
bspsGCodeFinalize, bspsCount,
|
||||
};
|
||||
|
||||
// Support for the GUI background processing (Slicing and G-code generation).
|
||||
// As of now this class is not declared in Slic3r::GUI due to the Perl bindings limits.
|
||||
class BackgroundSlicingProcess
|
||||
{
|
||||
public:
|
||||
BackgroundSlicingProcess();
|
||||
// Stop the background processing and finalize the bacgkround processing thread, remove temp files.
|
||||
~BackgroundSlicingProcess();
|
||||
|
||||
void set_fff_print(Print *print) { m_fff_print = print; }
|
||||
void set_sla_print(SLAPrint *print) { m_sla_print = print; }
|
||||
void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; }
|
||||
void set_gcode_result(GCodeProcessorResult* result) { m_gcode_result = result; }
|
||||
|
||||
// The following wxCommandEvent will be sent to the UI thread / Plater window, when the slicing is finished
|
||||
// and the background processing will transition into G-code export.
|
||||
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
|
||||
void set_slicing_completed_event(int event_id) { m_event_slicing_completed_id = event_id; }
|
||||
// The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code export is finished.
|
||||
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
|
||||
void set_finished_event(int event_id) { m_event_finished_id = event_id; }
|
||||
// The following wxCommandEvent will be sent to the UI thread / Plater window, when the G-code is being exported to
|
||||
// specified path or uploaded.
|
||||
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
|
||||
void set_export_began_event(int event_id) { m_event_export_began_id = event_id; }
|
||||
|
||||
// Activate either m_fff_print or m_sla_print.
|
||||
// Return true if changed.
|
||||
bool select_technology(PrinterTechnology tech);
|
||||
|
||||
// Get the currently active printer technology.
|
||||
PrinterTechnology current_printer_technology() const;
|
||||
// Get the current print. It is either m_fff_print or m_sla_print.
|
||||
const PrintBase* current_print() const { return m_print; }
|
||||
const Print* fff_print() const { return m_fff_print; }
|
||||
const SLAPrint* sla_print() const { return m_sla_print; }
|
||||
// Take the project path (if provided), extract the name of the project, run it through the macro processor and save it next to the project file.
|
||||
// If the project_path is empty, just run output_filepath().
|
||||
std::string output_filepath_for_project(const boost::filesystem::path &project_path);
|
||||
|
||||
// Start the background processing. Returns false if the background processing was already running.
|
||||
bool start();
|
||||
// Cancel the background processing. Returns false if the background processing was not running.
|
||||
// A stopped background processing may be restarted with start().
|
||||
bool stop();
|
||||
// Cancel the background processing and reset the print. Returns false if the background processing was not running.
|
||||
// Useful when the Model or configuration is being changed drastically.
|
||||
bool reset();
|
||||
|
||||
// Apply config over the print. Returns false, if the new config values caused any of the already
|
||||
// processed steps to be invalidated, therefore the task will need to be restarted.
|
||||
PrintBase::ApplyStatus apply(const Model &model, const DynamicPrintConfig &config);
|
||||
// After calling the apply() function, set_task() may be called to limit the task to be processed by process().
|
||||
// This is useful for calculating SLA supports for a single object only.
|
||||
void set_task(const PrintBase::TaskParams ¶ms);
|
||||
// After calling apply, the empty() call will report whether there is anything to slice.
|
||||
bool empty() const;
|
||||
// Validate the print. Returns an empty string if valid, returns an error message if invalid.
|
||||
// Call validate before calling start().
|
||||
std::string validate(std::vector<std::string>* warnings = nullptr);
|
||||
|
||||
// Set the export path of the G-code.
|
||||
// Once the path is set, the G-code
|
||||
void schedule_export(const std::string &path, bool export_path_on_removable_media);
|
||||
// Set print host upload job data to be enqueued to the PrintHostJobQueue
|
||||
// after current print slicing is complete
|
||||
void schedule_upload(Slic3r::PrintHostJob upload_job);
|
||||
// Clear m_export_path.
|
||||
void reset_export();
|
||||
// Once the G-code export is scheduled, the apply() methods will do nothing.
|
||||
bool is_export_scheduled() const { return ! m_export_path.empty(); }
|
||||
bool is_upload_scheduled() const { return ! m_upload_job.empty(); }
|
||||
|
||||
enum State {
|
||||
// m_thread is not running yet, or it did not reach the STATE_IDLE yet (it does not wait on the condition yet).
|
||||
STATE_INITIAL = 0,
|
||||
// m_thread is waiting for the task to execute.
|
||||
STATE_IDLE,
|
||||
STATE_STARTED,
|
||||
// m_thread is executing a task.
|
||||
STATE_RUNNING,
|
||||
// m_thread finished executing a task, and it is waiting until the UI thread picks up the results.
|
||||
STATE_FINISHED,
|
||||
// m_thread finished executing a task, the task has been canceled by the UI thread, therefore the UI thread will not be notified.
|
||||
STATE_CANCELED,
|
||||
// m_thread exited the loop and it is going to finish. The UI thread should join on m_thread.
|
||||
STATE_EXIT,
|
||||
STATE_EXITED,
|
||||
};
|
||||
State state() const { return m_state; }
|
||||
bool idle() const { return m_state == STATE_IDLE; }
|
||||
bool running() const { return m_state == STATE_STARTED || m_state == STATE_RUNNING || m_state == STATE_FINISHED || m_state == STATE_CANCELED; }
|
||||
// Returns true if the last step of the active print was finished with success.
|
||||
// The "finished" flag is reset by the apply() method, if it changes the state of the print.
|
||||
// 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(); }
|
||||
|
||||
private:
|
||||
void thread_proc();
|
||||
// Calls thread_proc(), catches all C++ exceptions and shows them using wxApp::OnUnhandledException().
|
||||
void thread_proc_safe() throw();
|
||||
#ifdef _WIN32
|
||||
// Wrapper for Win32 structured exceptions. Win32 structured exception blocks and C++ exception blocks cannot be mixed in the same function.
|
||||
// Catch a SEH exception and return its ID or zero if no SEH exception has been catched.
|
||||
unsigned long thread_proc_safe_seh() throw();
|
||||
// Calls thread_proc_safe_seh(), rethrows a Slic3r::HardCrash exception based on SEH exception
|
||||
// returned by thread_proc_safe_seh() and lets wxApp::OnUnhandledException() display it.
|
||||
void thread_proc_safe_seh_throw() throw();
|
||||
#endif // _WIN32
|
||||
void join_background_thread();
|
||||
// To be called by Print::apply() through the Print::m_cancel_callback to stop the background
|
||||
// processing before changing any data of running or finalized milestones.
|
||||
// This function shall not trigger any UI update through the wxWidgets event.
|
||||
void stop_internal();
|
||||
|
||||
// Helper to wrap the FFF slicing & G-code generation.
|
||||
void process_fff();
|
||||
|
||||
// Temporary: for mimicking the fff file export behavior with the raster output
|
||||
void process_sla();
|
||||
|
||||
// Call Print::process() and catch all exceptions into ex, thus no exception could be thrown
|
||||
// by this method. This exception behavior is required to combine C++ exceptions with Win32 SEH exceptions
|
||||
// on the same thread.
|
||||
void call_process(std::exception_ptr &ex) throw();
|
||||
|
||||
#ifdef _WIN32
|
||||
// Wrapper for Win32 structured exceptions. Win32 structured exception blocks and C++ exception blocks cannot be mixed in the same function.
|
||||
// Catch a SEH exception and return its ID or zero if no SEH exception has been catched.
|
||||
unsigned long call_process_seh(std::exception_ptr &ex) throw();
|
||||
// Calls call_process_seh(), rethrows a Slic3r::HardCrash exception based on SEH exception
|
||||
// returned by call_process_seh().
|
||||
void call_process_seh_throw(std::exception_ptr &ex) throw();
|
||||
#endif // _WIN32
|
||||
|
||||
// Currently active print. It is one of m_fff_print and m_sla_print.
|
||||
PrintBase *m_print = nullptr;
|
||||
// Non-owned pointers to Print instances.
|
||||
Print *m_fff_print = nullptr;
|
||||
SLAPrint *m_sla_print = nullptr;
|
||||
// Data structure, to which the G-code export writes its annotations.
|
||||
GCodeProcessorResult *m_gcode_result = nullptr;
|
||||
// Callback function, used to write thumbnails into gcode.
|
||||
ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
|
||||
// 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;
|
||||
// Output path provided by the user. The output path may be set even if the slicing is running,
|
||||
// but once set, it cannot be re-set.
|
||||
std::string m_export_path;
|
||||
bool m_export_path_on_removable_media = false;
|
||||
// Print host upload job to schedule after slicing is complete, used by schedule_upload(),
|
||||
// empty by default (ie. no upload to schedule)
|
||||
PrintHostJob m_upload_job;
|
||||
// Thread, on which the background processing is executed. The thread will always be present
|
||||
// and ready to execute the slicing process.
|
||||
boost::thread m_thread;
|
||||
// Mutex and condition variable to synchronize m_thread with the UI thread.
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_condition;
|
||||
State m_state = STATE_INITIAL;
|
||||
|
||||
// For executing tasks from the background thread on UI thread synchronously (waiting for result) using wxWidgets CallAfter().
|
||||
// When the background proces is canceled, the UITask has to be invalidated as well, so that it will not be
|
||||
// executed on the UI thread referencing invalid data.
|
||||
struct UITask {
|
||||
enum State {
|
||||
Planned,
|
||||
Finished,
|
||||
Canceled,
|
||||
};
|
||||
State state = Planned;
|
||||
std::mutex mutex;
|
||||
std::condition_variable condition;
|
||||
};
|
||||
// Only one UI task may be planned by the background thread to be executed on the UI thread, as the background
|
||||
// thread is blocking until the UI thread calculation finishes.
|
||||
std::shared_ptr<UITask> m_ui_task;
|
||||
|
||||
PrintState<BackgroundSlicingProcessStep, bspsCount> m_step_state;
|
||||
bool set_step_started(BackgroundSlicingProcessStep step);
|
||||
void set_step_done(BackgroundSlicingProcessStep step);
|
||||
bool is_step_done(BackgroundSlicingProcessStep step) const;
|
||||
bool invalidate_step(BackgroundSlicingProcessStep step);
|
||||
bool invalidate_all_steps();
|
||||
// If the background processing stop was requested, throw CanceledException.
|
||||
void throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); }
|
||||
void finalize_gcode();
|
||||
void prepare_upload();
|
||||
// To be executed at the background thread.
|
||||
ThumbnailsList render_thumbnails(const ThumbnailsParams ¶ms);
|
||||
// Execute task from background thread on the UI thread synchronously. Returns true if processed, false if cancelled before executing the task.
|
||||
bool execute_ui_task(std::function<void()> task);
|
||||
// To be called from inside m_mutex to cancel a planned UI task.
|
||||
static void cancel_ui_task(std::shared_ptr<BackgroundSlicingProcess::UITask> task);
|
||||
|
||||
// wxWidgets command ID to be sent to the plater to inform that the slicing is finished, and the G-code export will continue.
|
||||
int m_event_slicing_completed_id = 0;
|
||||
// wxWidgets command ID to be sent to the plater to inform that the task finished.
|
||||
int m_event_finished_id = 0;
|
||||
// wxWidgets command ID to be sent to the plater to inform that the G-code is being exported.
|
||||
int m_event_export_began_id = 0;
|
||||
|
||||
};
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_GUI_BackgroundSlicingProcess_hpp_ */
|
||||
596
src/slic3r/GUI/BedShapeDialog.cpp
Normal file
596
src/slic3r/GUI/BedShapeDialog.cpp
Normal file
@@ -0,0 +1,596 @@
|
||||
#include "BedShapeDialog.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "OptionsGroup.hpp"
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/numformatter.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/statbox.h>
|
||||
#include <wx/tooltip.h>
|
||||
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
BedShape::BedShape(const ConfigOptionPoints& points)
|
||||
{
|
||||
m_build_volume = { points.values, 0. };
|
||||
}
|
||||
|
||||
static std::string get_option_label(BedShape::Parameter param)
|
||||
{
|
||||
switch (param) {
|
||||
case BedShape::Parameter::RectSize : return L("Size");
|
||||
case BedShape::Parameter::RectOrigin: return L("Origin");
|
||||
case BedShape::Parameter::Diameter : return L("Diameter");
|
||||
default: assert(false); return {};
|
||||
}
|
||||
}
|
||||
|
||||
void BedShape::append_option_line(ConfigOptionsGroupShp optgroup, Parameter param)
|
||||
{
|
||||
ConfigOptionDef def;
|
||||
t_config_option_key key;
|
||||
switch (param) {
|
||||
case Parameter::RectSize:
|
||||
def.type = coPoints;
|
||||
def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) });
|
||||
def.min = 0;
|
||||
def.max = 1200;
|
||||
def.label = get_option_label(param);
|
||||
def.tooltip = L("Size in X and Y of the rectangular plate.");
|
||||
key = "rect_size";
|
||||
break;
|
||||
case Parameter::RectOrigin:
|
||||
def.type = coPoints;
|
||||
def.set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) });
|
||||
def.min = -600;
|
||||
def.max = 600;
|
||||
def.label = get_option_label(param);
|
||||
def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.");
|
||||
key = "rect_origin";
|
||||
break;
|
||||
case Parameter::Diameter:
|
||||
def.type = coFloat;
|
||||
def.set_default_value(new ConfigOptionFloat(200));
|
||||
def.sidetext = L("mm");
|
||||
def.label = get_option_label(param);
|
||||
def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center.");
|
||||
key = "diameter";
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
optgroup->append_single_option_line({ def, std::move(key) });
|
||||
}
|
||||
|
||||
wxString BedShape::get_name(PageType type)
|
||||
{
|
||||
switch (type) {
|
||||
case PageType::Rectangle: return _L("Rectangular");
|
||||
case PageType::Circle: return _L("Circular");
|
||||
case PageType::Custom: return _L("Custom");
|
||||
}
|
||||
// make visual studio happy
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
|
||||
BedShape::PageType BedShape::get_page_type()
|
||||
{
|
||||
switch (m_build_volume.type()) {
|
||||
case BuildVolume::Type::Rectangle:
|
||||
case BuildVolume::Type::Invalid: return PageType::Rectangle;
|
||||
case BuildVolume::Type::Circle: return PageType::Circle;
|
||||
case BuildVolume::Type::Convex:
|
||||
case BuildVolume::Type::Custom: return PageType::Custom;
|
||||
}
|
||||
// make visual studio happy
|
||||
assert(false);
|
||||
return PageType::Rectangle;
|
||||
}
|
||||
|
||||
wxString BedShape::get_full_name_with_params()
|
||||
{
|
||||
wxString out = _L("Shape") + ": " + get_name(this->get_page_type());
|
||||
switch (m_build_volume.type()) {
|
||||
case BuildVolume::Type::Circle:
|
||||
out += "\n" + _L(get_option_label(Parameter::Diameter)) + ": [" + double_to_string(2. * unscaled<double>(m_build_volume.circle().radius)) + "]";
|
||||
break;
|
||||
default:
|
||||
// rectangle, convex, concave...
|
||||
out += "\n" + _(get_option_label(Parameter::RectSize)) + ": [" + ConfigOptionPoint(to_2d(m_build_volume.bounding_volume().size())).serialize() + "]";
|
||||
out += "\n" + _(get_option_label(Parameter::RectOrigin)) + ": [" + ConfigOptionPoint(- to_2d(m_build_volume.bounding_volume().min)).serialize() + "]";
|
||||
break;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void BedShape::apply_optgroup_values(ConfigOptionsGroupShp optgroup)
|
||||
{
|
||||
switch (m_build_volume.type()) {
|
||||
case BuildVolume::Type::Circle:
|
||||
optgroup->set_value("diameter", double_to_string(2. * unscaled<double>(m_build_volume.circle().radius)));
|
||||
break;
|
||||
default:
|
||||
// rectangle, convex, concave...
|
||||
optgroup->set_value("rect_size" , to_2d(m_build_volume.bounding_volume().size()));
|
||||
optgroup->set_value("rect_origin" , to_2d(-1 * m_build_volume.bounding_volume().min));
|
||||
}
|
||||
}
|
||||
|
||||
BedShapeDialog::BedShapeDialog(wxWindow* parent) : DPIDialog(parent, wxID_ANY, _(L("Bed Shape")),
|
||||
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {}
|
||||
|
||||
void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model)
|
||||
{
|
||||
SetFont(wxGetApp().normal_font());
|
||||
|
||||
m_panel = new BedShapePanel(this);
|
||||
m_panel->build_panel(default_pt, custom_texture, custom_model);
|
||||
|
||||
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
main_sizer->Add(m_panel, 1, wxEXPAND);
|
||||
main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this, true);
|
||||
|
||||
SetSizer(main_sizer);
|
||||
SetMinSize(GetSize());
|
||||
main_sizer->SetSizeHints(this);
|
||||
|
||||
this->Bind(wxEVT_CLOSE_WINDOW, ([this](wxCloseEvent& evt) {
|
||||
EndModal(wxID_CANCEL);
|
||||
}));
|
||||
}
|
||||
|
||||
void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect)
|
||||
{
|
||||
const int& em = em_unit();
|
||||
m_panel->m_shape_options_book->SetMinSize(wxSize(25 * em, -1));
|
||||
|
||||
for (auto og : m_panel->m_optgroups)
|
||||
og->msw_rescale();
|
||||
|
||||
const wxSize& size = wxSize(50 * em, -1);
|
||||
|
||||
SetMinSize(size);
|
||||
SetSize(size);
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
const std::string BedShapePanel::NONE = "None";
|
||||
const std::string BedShapePanel::EMPTY_STRING = "";
|
||||
|
||||
void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model)
|
||||
{
|
||||
wxGetApp().UpdateDarkUI(this);
|
||||
m_shape = default_pt.values;
|
||||
m_custom_texture = custom_texture.value.empty() ? NONE : custom_texture.value;
|
||||
m_custom_model = custom_model.value.empty() ? NONE : custom_model.value;
|
||||
|
||||
auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _L("Shape"));
|
||||
sbsizer->GetStaticBox()->SetFont(wxGetApp().bold_font());
|
||||
wxGetApp().UpdateDarkUI(sbsizer->GetStaticBox());
|
||||
|
||||
// shape options
|
||||
m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP);
|
||||
wxGetApp().UpdateDarkUI(m_shape_options_book->GetChoiceCtrl());
|
||||
|
||||
sbsizer->Add(m_shape_options_book);
|
||||
|
||||
auto optgroup = init_shape_options_page(BedShape::get_name(BedShape::PageType::Rectangle));
|
||||
BedShape::append_option_line(optgroup, BedShape::Parameter::RectSize);
|
||||
BedShape::append_option_line(optgroup, BedShape::Parameter::RectOrigin);
|
||||
activate_options_page(optgroup);
|
||||
|
||||
optgroup = init_shape_options_page(BedShape::get_name(BedShape::PageType::Circle));
|
||||
BedShape::append_option_line(optgroup, BedShape::Parameter::Diameter);
|
||||
activate_options_page(optgroup);
|
||||
|
||||
optgroup = init_shape_options_page(BedShape::get_name(BedShape::PageType::Custom));
|
||||
|
||||
Line line{ "", "" };
|
||||
line.full_width = 1;
|
||||
line.widget = [this](wxWindow* parent) {
|
||||
wxButton* shape_btn = new wxButton(parent, wxID_ANY, _L("Load shape from STL..."));
|
||||
wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
shape_sizer->Add(shape_btn, 1, wxEXPAND);
|
||||
|
||||
wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(shape_sizer, 1, wxEXPAND);
|
||||
|
||||
shape_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& e) {
|
||||
load_stl();
|
||||
});
|
||||
|
||||
return sizer;
|
||||
};
|
||||
optgroup->append_line(line);
|
||||
activate_options_page(optgroup);
|
||||
|
||||
wxPanel* texture_panel = init_texture_panel();
|
||||
wxPanel* model_panel = init_model_panel();
|
||||
|
||||
Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e) { update_shape(); }));
|
||||
|
||||
// right pane with preview canvas
|
||||
m_canvas = new Bed_2D(this);
|
||||
m_canvas->Bind(wxEVT_PAINT, [this](wxPaintEvent& e) { m_canvas->repaint(m_shape); });
|
||||
m_canvas->Bind(wxEVT_SIZE, [this](wxSizeEvent& e) { m_canvas->Refresh(); });
|
||||
|
||||
wxSizer* left_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
left_sizer->Add(sbsizer, 0, wxEXPAND);
|
||||
left_sizer->Add(texture_panel, 1, wxEXPAND);
|
||||
left_sizer->Add(model_panel, 1, wxEXPAND);
|
||||
|
||||
wxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
top_sizer->Add(left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 10);
|
||||
top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10);
|
||||
|
||||
SetSizerAndFit(top_sizer);
|
||||
|
||||
set_shape(default_pt);
|
||||
update_preview();
|
||||
}
|
||||
|
||||
// Called from the constructor.
|
||||
// Create a panel for a rectangular / circular / custom bed shape.
|
||||
ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title)
|
||||
{
|
||||
wxPanel* panel = new wxPanel(m_shape_options_book);
|
||||
ConfigOptionsGroupShp optgroup = std::make_shared<ConfigOptionsGroup>(panel, _L("Settings"));
|
||||
|
||||
optgroup->label_width = 10;
|
||||
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
||||
update_shape();
|
||||
};
|
||||
|
||||
m_optgroups.push_back(optgroup);
|
||||
// panel->SetSizerAndFit(optgroup->sizer);
|
||||
m_shape_options_book->AddPage(panel, title);
|
||||
|
||||
return optgroup;
|
||||
}
|
||||
|
||||
void BedShapePanel::activate_options_page(ConfigOptionsGroupShp options_group)
|
||||
{
|
||||
options_group->activate();
|
||||
options_group->parent()->SetSizerAndFit(options_group->sizer);
|
||||
}
|
||||
|
||||
wxPanel* BedShapePanel::init_texture_panel()
|
||||
{
|
||||
wxPanel* panel = new wxPanel(this);
|
||||
wxGetApp().UpdateDarkUI(panel, true);
|
||||
ConfigOptionsGroupShp optgroup = std::make_shared<ConfigOptionsGroup>(panel, _L("Texture"));
|
||||
|
||||
optgroup->label_width = 10;
|
||||
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
||||
update_shape();
|
||||
};
|
||||
|
||||
Line line{ "", "" };
|
||||
line.full_width = 1;
|
||||
line.widget = [this](wxWindow* parent) {
|
||||
wxButton* load_btn = new wxButton(parent, wxID_ANY, _L("Load..."));
|
||||
wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
load_sizer->Add(load_btn, 1, wxEXPAND);
|
||||
|
||||
wxStaticText* filename_lbl = new wxStaticText(parent, wxID_ANY, _(NONE));
|
||||
|
||||
wxSizer* filename_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
filename_sizer->Add(filename_lbl, 1, wxEXPAND);
|
||||
|
||||
wxButton* remove_btn = new wxButton(parent, wxID_ANY, _L("Remove"));
|
||||
wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
remove_sizer->Add(remove_btn, 1, wxEXPAND);
|
||||
|
||||
wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(filename_sizer, 1, wxEXPAND);
|
||||
sizer->Add(load_sizer, 1, wxEXPAND);
|
||||
sizer->Add(remove_sizer, 1, wxEXPAND | wxTOP, 2);
|
||||
|
||||
load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) { load_texture(); }));
|
||||
remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) {
|
||||
m_custom_texture = NONE;
|
||||
update_shape();
|
||||
}));
|
||||
|
||||
filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) {
|
||||
e.SetText(_(boost::filesystem::path(m_custom_texture).filename().string()));
|
||||
wxStaticText* lbl = dynamic_cast<wxStaticText*>(e.GetEventObject());
|
||||
if (lbl != nullptr) {
|
||||
bool exists = (m_custom_texture == NONE) || boost::filesystem::exists(m_custom_texture);
|
||||
lbl->SetForegroundColour(exists ? wxGetApp().get_label_clr_default() : wxColor(*wxRED));
|
||||
|
||||
wxString tooltip_text = "";
|
||||
if (m_custom_texture != NONE) {
|
||||
if (!exists)
|
||||
tooltip_text += _L("Not found:") + " ";
|
||||
|
||||
tooltip_text += _(m_custom_texture);
|
||||
}
|
||||
|
||||
wxToolTip* tooltip = lbl->GetToolTip();
|
||||
if ((tooltip == nullptr) || (tooltip->GetTip() != tooltip_text))
|
||||
lbl->SetToolTip(tooltip_text);
|
||||
}
|
||||
}));
|
||||
|
||||
remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) { e.Enable(m_custom_texture != NONE); }));
|
||||
|
||||
return sizer;
|
||||
};
|
||||
optgroup->append_line(line);
|
||||
optgroup->activate();
|
||||
|
||||
panel->SetSizerAndFit(optgroup->sizer);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
wxPanel* BedShapePanel::init_model_panel()
|
||||
{
|
||||
wxPanel* panel = new wxPanel(this);
|
||||
wxGetApp().UpdateDarkUI(panel, true);
|
||||
ConfigOptionsGroupShp optgroup = std::make_shared<ConfigOptionsGroup>(panel, _L("Model"));
|
||||
|
||||
optgroup->label_width = 10;
|
||||
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
||||
update_shape();
|
||||
};
|
||||
|
||||
Line line{ "", "" };
|
||||
line.full_width = 1;
|
||||
line.widget = [this](wxWindow* parent) {
|
||||
wxButton* load_btn = new wxButton(parent, wxID_ANY, _L("Load..."));
|
||||
wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
load_sizer->Add(load_btn, 1, wxEXPAND);
|
||||
|
||||
wxStaticText* filename_lbl = new wxStaticText(parent, wxID_ANY, _(NONE));
|
||||
wxSizer* filename_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
filename_sizer->Add(filename_lbl, 1, wxEXPAND);
|
||||
|
||||
wxButton* remove_btn = new wxButton(parent, wxID_ANY, _L("Remove"));
|
||||
wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
remove_sizer->Add(remove_btn, 1, wxEXPAND);
|
||||
|
||||
wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(filename_sizer, 1, wxEXPAND);
|
||||
sizer->Add(load_sizer, 1, wxEXPAND);
|
||||
sizer->Add(remove_sizer, 1, wxEXPAND | wxTOP, 2);
|
||||
|
||||
load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) { load_model(); }));
|
||||
|
||||
remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) {
|
||||
m_custom_model = NONE;
|
||||
update_shape();
|
||||
}));
|
||||
|
||||
filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) {
|
||||
e.SetText(_(boost::filesystem::path(m_custom_model).filename().string()));
|
||||
wxStaticText* lbl = dynamic_cast<wxStaticText*>(e.GetEventObject());
|
||||
if (lbl != nullptr) {
|
||||
bool exists = (m_custom_model == NONE) || boost::filesystem::exists(m_custom_model);
|
||||
lbl->SetForegroundColour(exists ? wxGetApp().get_label_clr_default() : wxColor(*wxRED));
|
||||
|
||||
wxString tooltip_text = "";
|
||||
if (m_custom_model != NONE) {
|
||||
if (!exists)
|
||||
tooltip_text += _L("Not found:") + " ";
|
||||
|
||||
tooltip_text += _(m_custom_model);
|
||||
}
|
||||
|
||||
wxToolTip* tooltip = lbl->GetToolTip();
|
||||
if ((tooltip == nullptr) || (tooltip->GetTip() != tooltip_text))
|
||||
lbl->SetToolTip(tooltip_text);
|
||||
}
|
||||
}));
|
||||
|
||||
remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) { e.Enable(m_custom_model != NONE); }));
|
||||
|
||||
return sizer;
|
||||
};
|
||||
optgroup->append_line(line);
|
||||
optgroup->activate();
|
||||
|
||||
panel->SetSizerAndFit(optgroup->sizer);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
// Called from the constructor.
|
||||
// Set the initial bed shape from a list of points.
|
||||
// Deduce the bed shape type(rect, circle, custom)
|
||||
// This routine shall be smart enough if the user messes up
|
||||
// with the list of points in the ini file directly.
|
||||
void BedShapePanel::set_shape(const ConfigOptionPoints& points)
|
||||
{
|
||||
BedShape shape(points);
|
||||
|
||||
m_shape_options_book->SetSelection(int(shape.get_page_type()));
|
||||
shape.apply_optgroup_values(m_optgroups[int(shape.get_page_type())]);
|
||||
|
||||
// Copy the polygon to the canvas, make a copy of the array, if custom shape is selected
|
||||
if (shape.is_custom())
|
||||
m_loaded_shape = points.values;
|
||||
|
||||
update_shape();
|
||||
}
|
||||
|
||||
void BedShapePanel::update_preview()
|
||||
{
|
||||
if (m_canvas) m_canvas->Refresh();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
// Update the bed shape from the dialog fields.
|
||||
void BedShapePanel::update_shape()
|
||||
{
|
||||
auto page_idx = m_shape_options_book->GetSelection();
|
||||
auto opt_group = m_optgroups[page_idx];
|
||||
|
||||
switch (static_cast<BedShape::PageType>(page_idx)) {
|
||||
case BedShape::PageType::Rectangle:
|
||||
{
|
||||
Vec2d rect_size(Vec2d::Zero());
|
||||
Vec2d rect_origin(Vec2d::Zero());
|
||||
|
||||
try { rect_size = boost::any_cast<Vec2d>(opt_group->get_value("rect_size")); }
|
||||
catch (const std::exception& /* e */) { return; }
|
||||
|
||||
try { rect_origin = boost::any_cast<Vec2d>(opt_group->get_value("rect_origin")); }
|
||||
catch (const std::exception & /* e */) { return; }
|
||||
|
||||
auto x = rect_size(0);
|
||||
auto y = rect_size(1);
|
||||
// empty strings or '-' or other things
|
||||
if (x == 0 || y == 0) return;
|
||||
double x0 = 0.0;
|
||||
double y0 = 0.0;
|
||||
double x1 = x;
|
||||
double y1 = y;
|
||||
|
||||
auto dx = rect_origin(0);
|
||||
auto dy = rect_origin(1);
|
||||
|
||||
x0 -= dx;
|
||||
x1 -= dx;
|
||||
y0 -= dy;
|
||||
y1 -= dy;
|
||||
m_shape = { Vec2d(x0, y0),
|
||||
Vec2d(x1, y0),
|
||||
Vec2d(x1, y1),
|
||||
Vec2d(x0, y1) };
|
||||
break;
|
||||
}
|
||||
case BedShape::PageType::Circle:
|
||||
{
|
||||
double diameter;
|
||||
try { diameter = boost::any_cast<double>(opt_group->get_value("diameter")); }
|
||||
catch (const std::exception & /* e */) { return; }
|
||||
|
||||
if (diameter == 0.0) return ;
|
||||
auto r = diameter / 2;
|
||||
auto twopi = 2 * PI;
|
||||
// Don't change this value without adjusting BuildVolume constructor detecting circle diameter!
|
||||
auto edges = 72;
|
||||
std::vector<Vec2d> points;
|
||||
for (int i = 1; i <= edges; ++i) {
|
||||
auto angle = i * twopi / edges;
|
||||
points.push_back(Vec2d(r*cos(angle), r*sin(angle)));
|
||||
}
|
||||
m_shape = points;
|
||||
break;
|
||||
}
|
||||
case BedShape::PageType::Custom:
|
||||
m_shape = m_loaded_shape;
|
||||
break;
|
||||
}
|
||||
|
||||
update_preview();
|
||||
}
|
||||
|
||||
// Loads an stl file, projects it to the XY plane and calculates a polygon.
|
||||
void BedShapePanel::load_stl()
|
||||
{
|
||||
wxFileDialog dialog(this, _L("Choose an STL file to import bed shape from:"), "", "", file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dialog.ShowModal() != wxID_OK)
|
||||
return;
|
||||
|
||||
std::string file_name = dialog.GetPath().ToUTF8().data();
|
||||
if (!boost::algorithm::iends_with(file_name, ".stl")) {
|
||||
show_error(this, _L("Invalid file format."));
|
||||
return;
|
||||
}
|
||||
|
||||
wxBusyCursor wait;
|
||||
|
||||
Model model;
|
||||
try {
|
||||
model = Model::read_from_file(file_name);
|
||||
}
|
||||
catch (std::exception &) {
|
||||
show_error(this, _L("Error! Invalid model"));
|
||||
return;
|
||||
}
|
||||
|
||||
auto mesh = model.mesh();
|
||||
auto expolygons = mesh.horizontal_projection();
|
||||
|
||||
if (expolygons.size() == 0) {
|
||||
show_error(this, _L("The selected file contains no geometry."));
|
||||
return;
|
||||
}
|
||||
if (expolygons.size() > 1) {
|
||||
show_error(this, _L("The selected file contains several disjoint areas. This is not supported."));
|
||||
return;
|
||||
}
|
||||
|
||||
auto polygon = expolygons[0].contour;
|
||||
std::vector<Vec2d> points;
|
||||
for (auto pt : polygon.points)
|
||||
points.push_back(unscale(pt));
|
||||
|
||||
m_loaded_shape = points;
|
||||
update_shape();
|
||||
}
|
||||
|
||||
void BedShapePanel::load_texture()
|
||||
{
|
||||
wxFileDialog dialog(this, _L("Choose a file to import bed texture from (PNG/SVG):"), "", "",
|
||||
file_wildcards(FT_TEX), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
|
||||
if (dialog.ShowModal() != wxID_OK)
|
||||
return;
|
||||
|
||||
m_custom_texture = NONE;
|
||||
|
||||
std::string file_name = dialog.GetPath().ToUTF8().data();
|
||||
if (!boost::algorithm::iends_with(file_name, ".png") && !boost::algorithm::iends_with(file_name, ".svg")) {
|
||||
show_error(this, _L("Invalid file format."));
|
||||
return;
|
||||
}
|
||||
|
||||
wxBusyCursor wait;
|
||||
|
||||
m_custom_texture = file_name;
|
||||
update_shape();
|
||||
}
|
||||
|
||||
void BedShapePanel::load_model()
|
||||
{
|
||||
wxFileDialog dialog(this, _L("Choose an STL file to import bed model from:"), "", "",
|
||||
file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
|
||||
if (dialog.ShowModal() != wxID_OK)
|
||||
return;
|
||||
|
||||
m_custom_model = NONE;
|
||||
|
||||
std::string file_name = dialog.GetPath().ToUTF8().data();
|
||||
if (!boost::algorithm::iends_with(file_name, ".stl")) {
|
||||
show_error(this, _L("Invalid file format."));
|
||||
return;
|
||||
}
|
||||
|
||||
wxBusyCursor wait;
|
||||
|
||||
m_custom_model = file_name;
|
||||
update_shape();
|
||||
}
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
111
src/slic3r/GUI/BedShapeDialog.hpp
Normal file
111
src/slic3r/GUI/BedShapeDialog.hpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#ifndef slic3r_BedShapeDialog_hpp_
|
||||
#define slic3r_BedShapeDialog_hpp_
|
||||
// The bed shape dialog.
|
||||
// The dialog opens from Print Settins tab->Bed Shape : Set...
|
||||
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "2DBed.hpp"
|
||||
|
||||
#include <libslic3r/BuildVolume.hpp>
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/choicebk.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class ConfigOptionsGroup;
|
||||
|
||||
using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>;
|
||||
using ConfigOptionsGroupWkp = std::weak_ptr<ConfigOptionsGroup>;
|
||||
|
||||
struct BedShape
|
||||
{
|
||||
enum class PageType {
|
||||
Rectangle,
|
||||
Circle,
|
||||
Custom
|
||||
};
|
||||
|
||||
enum class Parameter {
|
||||
RectSize,
|
||||
RectOrigin,
|
||||
Diameter
|
||||
};
|
||||
|
||||
BedShape(const ConfigOptionPoints& points);
|
||||
|
||||
bool is_custom() { return m_build_volume.type() == BuildVolume::Type::Convex || m_build_volume.type() == BuildVolume::Type::Custom; }
|
||||
|
||||
static void append_option_line(ConfigOptionsGroupShp optgroup, Parameter param);
|
||||
static wxString get_name(PageType type);
|
||||
|
||||
PageType get_page_type();
|
||||
|
||||
wxString get_full_name_with_params();
|
||||
void apply_optgroup_values(ConfigOptionsGroupShp optgroup);
|
||||
|
||||
private:
|
||||
BuildVolume m_build_volume;
|
||||
};
|
||||
|
||||
class BedShapePanel : public wxPanel
|
||||
{
|
||||
static const std::string NONE;
|
||||
static const std::string EMPTY_STRING;
|
||||
|
||||
Bed_2D* m_canvas;
|
||||
std::vector<Vec2d> m_shape;
|
||||
std::vector<Vec2d> m_loaded_shape;
|
||||
std::string m_custom_texture;
|
||||
std::string m_custom_model;
|
||||
|
||||
public:
|
||||
BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY), m_custom_texture(NONE), m_custom_model(NONE) {}
|
||||
|
||||
void build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model);
|
||||
|
||||
// Returns the resulting bed shape polygon. This value will be stored to the ini file.
|
||||
const std::vector<Vec2d>& get_shape() const { return m_shape; }
|
||||
const std::string& get_custom_texture() const { return (m_custom_texture != NONE) ? m_custom_texture : EMPTY_STRING; }
|
||||
const std::string& get_custom_model() const { return (m_custom_model != NONE) ? m_custom_model : EMPTY_STRING; }
|
||||
|
||||
private:
|
||||
ConfigOptionsGroupShp init_shape_options_page(const wxString& title);
|
||||
void activate_options_page(ConfigOptionsGroupShp options_group);
|
||||
wxPanel* init_texture_panel();
|
||||
wxPanel* init_model_panel();
|
||||
void set_shape(const ConfigOptionPoints& points);
|
||||
void update_preview();
|
||||
void update_shape();
|
||||
void load_stl();
|
||||
void load_texture();
|
||||
void load_model();
|
||||
|
||||
wxChoicebook* m_shape_options_book;
|
||||
std::vector <ConfigOptionsGroupShp> m_optgroups;
|
||||
|
||||
friend class BedShapeDialog;
|
||||
};
|
||||
|
||||
class BedShapeDialog : public DPIDialog
|
||||
{
|
||||
BedShapePanel* m_panel;
|
||||
public:
|
||||
BedShapeDialog(wxWindow* parent);
|
||||
|
||||
void build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model);
|
||||
|
||||
const std::vector<Vec2d>& get_shape() const { return m_panel->get_shape(); }
|
||||
const std::string& get_custom_texture() const { return m_panel->get_custom_texture(); }
|
||||
const std::string& get_custom_model() const { return m_panel->get_custom_model(); }
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect &suggested_rect) override;
|
||||
};
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
|
||||
#endif /* slic3r_BedShapeDialog_hpp_ */
|
||||
628
src/slic3r/GUI/BitmapCache.cpp
Normal file
628
src/slic3r/GUI/BitmapCache.cpp
Normal file
@@ -0,0 +1,628 @@
|
||||
#include "BitmapCache.hpp"
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "../Utils/MacDarkMode.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_Utils.hpp"
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
||||
#ifdef __WXGTK2__
|
||||
// Broken alpha workaround
|
||||
#include <wx/mstream.h>
|
||||
#include <wx/rawbmp.h>
|
||||
#endif /* __WXGTK2__ */
|
||||
|
||||
#include <nanosvg/nanosvg.h>
|
||||
#include <nanosvg/nanosvgrast.h>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
BitmapCache::BitmapCache()
|
||||
{
|
||||
#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.
|
||||
m_scale = mac_max_scaling_factor();
|
||||
#endif
|
||||
}
|
||||
|
||||
void BitmapCache::clear()
|
||||
{
|
||||
for (std::pair<const std::string, wxBitmap*> &bitmap : m_map)
|
||||
delete bitmap.second;
|
||||
m_map.clear();
|
||||
|
||||
for (std::pair<const std::string, wxBitmapBundle*> &bitmap_bundle : m_bndl_map)
|
||||
delete bitmap_bundle.second;
|
||||
m_bndl_map.clear();
|
||||
}
|
||||
|
||||
static wxBitmap wxImage_to_wxBitmap_with_alpha(wxImage &&image, float scale = 1.0f)
|
||||
{
|
||||
#ifdef __WXGTK2__
|
||||
// Broken alpha workaround
|
||||
wxMemoryOutputStream stream;
|
||||
image.SaveFile(stream, wxBITMAP_TYPE_PNG);
|
||||
wxStreamBuffer *buf = stream.GetOutputStreamBuffer();
|
||||
return wxBitmap::NewFromPNGData(buf->GetBufferStart(), buf->GetBufferSize());
|
||||
#else
|
||||
#ifdef __APPLE__
|
||||
// This is a c-tor native to Mac OS. We need to let the Mac OS wxBitmap implementation
|
||||
// know that the image may already be scaled appropriately for Retina,
|
||||
// and thereby that it's not supposed to upscale it.
|
||||
// Contrary to intuition, the `scale` argument isn't "please scale this to such and such"
|
||||
// but rather "the wxImage is sized for backing scale such and such".
|
||||
return wxBitmap(std::move(image), -1, scale);
|
||||
#else
|
||||
return wxBitmap(std::move(image));
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
wxBitmapBundle* BitmapCache::insert_bndl(const std::string& name, const std::vector<wxBitmapBundle*>& bmps)
|
||||
{
|
||||
wxVector<wxBitmap> bitmaps;
|
||||
|
||||
std::set<double> scales = {1.0};
|
||||
#ifndef __linux__
|
||||
|
||||
#ifdef __APPLE__
|
||||
scales.emplace(m_scale);
|
||||
#else
|
||||
size_t disp_cnt = wxDisplay::GetCount();
|
||||
for (size_t disp = 0; disp < disp_cnt; ++disp)
|
||||
scales.emplace(wxDisplay(disp).GetScaleFactor());
|
||||
#endif
|
||||
|
||||
#endif // !__linux__
|
||||
|
||||
for (double scale : scales) {
|
||||
size_t width = 0;
|
||||
size_t height = 0;
|
||||
for (const wxBitmapBundle* bmp_bndl : bmps) {
|
||||
#ifdef __APPLE__
|
||||
wxSize size = bmp_bndl->GetDefaultSize();
|
||||
#else
|
||||
wxSize size = bmp_bndl->GetPreferredBitmapSizeAtScale(scale);
|
||||
#endif
|
||||
width += size.GetWidth();
|
||||
height = std::max<size_t>(height, size.GetHeight());
|
||||
}
|
||||
|
||||
std::string bitmap_key = name + "," +float_to_string_decimal_point(scale);
|
||||
|
||||
#ifdef __WXGTK2__
|
||||
// Broken alpha workaround
|
||||
wxImage image(width, height);
|
||||
image.InitAlpha();
|
||||
// Fill in with a white color.
|
||||
memset(image.GetData(), 0x0ff, width * height * 3);
|
||||
// Fill in with full transparency.
|
||||
memset(image.GetAlpha(), 0, width * height);
|
||||
size_t x = 0;
|
||||
for (const wxBitmapBundle* bmp_bndl : bmps) {
|
||||
wxBitmap bmp = bmp_bndl->GetBitmap(bmp_bndl->GetDefaultSize());
|
||||
if (bmp.GetWidth() > 0) {
|
||||
if (bmp.GetDepth() == 32) {
|
||||
wxAlphaPixelData data(bmp);
|
||||
//FIXME The following method is missing from wxWidgets 3.1.1.
|
||||
// It looks like the wxWidgets 3.0.3 called the wrapped bitmap's UseAlpha().
|
||||
//data.UseAlpha();
|
||||
if (data) {
|
||||
for (int r = 0; r < bmp.GetHeight(); ++r) {
|
||||
wxAlphaPixelData::Iterator src(data);
|
||||
src.Offset(data, 0, r);
|
||||
unsigned char* dst_pixels = image.GetData() + (x + r * width) * 3;
|
||||
unsigned char* dst_alpha = image.GetAlpha() + x + r * width;
|
||||
for (int c = 0; c < bmp.GetWidth(); ++c, ++src) {
|
||||
*dst_pixels++ = src.Red();
|
||||
*dst_pixels++ = src.Green();
|
||||
*dst_pixels++ = src.Blue();
|
||||
*dst_alpha++ = src.Alpha();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (bmp.GetDepth() == 24) {
|
||||
wxNativePixelData data(bmp);
|
||||
if (data) {
|
||||
for (int r = 0; r < bmp.GetHeight(); ++r) {
|
||||
wxNativePixelData::Iterator src(data);
|
||||
src.Offset(data, 0, r);
|
||||
unsigned char* dst_pixels = image.GetData() + (x + r * width) * 3;
|
||||
unsigned char* dst_alpha = image.GetAlpha() + x + r * width;
|
||||
for (int c = 0; c < bmp.GetWidth(); ++c, ++src) {
|
||||
*dst_pixels++ = src.Red();
|
||||
*dst_pixels++ = src.Green();
|
||||
*dst_pixels++ = src.Blue();
|
||||
*dst_alpha++ = wxALPHA_OPAQUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
x += bmp.GetScaledWidth();
|
||||
}
|
||||
|
||||
bitmaps.push_back(* this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image))));
|
||||
|
||||
#else
|
||||
|
||||
wxBitmap* bitmap = this->insert(bitmap_key, width, height, scale);
|
||||
wxMemoryDC memDC;
|
||||
memDC.SelectObject(*bitmap);
|
||||
memDC.SetBackground(*wxTRANSPARENT_BRUSH);
|
||||
memDC.Clear();
|
||||
size_t x = 0;
|
||||
for (const wxBitmapBundle* bmp_bndl : bmps) {
|
||||
wxBitmap bmp = bmp_bndl->GetBitmap(bmp_bndl->GetPreferredBitmapSizeAtScale(scale));
|
||||
|
||||
if (bmp.GetWidth() > 0)
|
||||
memDC.DrawBitmap(bmp, x, 0, true);
|
||||
// we should "move" with step equal to non-scaled width
|
||||
#ifdef __APPLE__
|
||||
x += bmp.GetScaledWidth();
|
||||
#else
|
||||
x += bmp.GetWidth();
|
||||
#endif
|
||||
}
|
||||
memDC.SelectObject(wxNullBitmap);
|
||||
bitmaps.push_back(*bitmap);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
return insert_bndl(name, bitmaps);
|
||||
}
|
||||
|
||||
wxBitmapBundle* BitmapCache::insert_bndl(const std::string &bitmap_key, const char* data, size_t width, size_t height)
|
||||
{
|
||||
wxBitmapBundle* bndl = nullptr;
|
||||
auto it = m_bndl_map.find(bitmap_key);
|
||||
if (it == m_bndl_map.end()) {
|
||||
bndl = new wxBitmapBundle(wxBitmapBundle::FromSVG(data, wxSize(width, height)));
|
||||
m_bndl_map[bitmap_key] = bndl;
|
||||
}
|
||||
else {
|
||||
bndl = it->second;
|
||||
*bndl = wxBitmapBundle::FromSVG(data, wxSize(width, height));
|
||||
}
|
||||
return bndl;
|
||||
}
|
||||
|
||||
wxBitmapBundle* BitmapCache::insert_bndl(const std::string& bitmap_key, const wxBitmapBundle& bmp)
|
||||
{
|
||||
wxBitmapBundle* bndl = nullptr;
|
||||
auto it = m_bndl_map.find(bitmap_key);
|
||||
if (it == m_bndl_map.end()) {
|
||||
bndl = new wxBitmapBundle(bmp);
|
||||
m_bndl_map[bitmap_key] = bndl;
|
||||
}
|
||||
else {
|
||||
bndl = it->second;
|
||||
*bndl = wxBitmapBundle(bmp);
|
||||
}
|
||||
return bndl;
|
||||
}
|
||||
|
||||
wxBitmapBundle* BitmapCache::insert_bndl(const std::string& bitmap_key, const wxVector<wxBitmap>& bmps)
|
||||
{
|
||||
wxBitmapBundle* bndl = nullptr;
|
||||
auto it = m_bndl_map.find(bitmap_key);
|
||||
if (it == m_bndl_map.end()) {
|
||||
bndl = new wxBitmapBundle(wxBitmapBundle::FromBitmaps(bmps));
|
||||
m_bndl_map[bitmap_key] = bndl;
|
||||
}
|
||||
else {
|
||||
bndl = it->second;
|
||||
*bndl = wxBitmapBundle::FromBitmaps(bmps);
|
||||
}
|
||||
return bndl;
|
||||
}
|
||||
|
||||
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_t height, double scale/* = -1.0*/)
|
||||
{
|
||||
wxBitmap *bitmap = nullptr;
|
||||
auto it = m_map.find(bitmap_key);
|
||||
if (it == m_map.end()) {
|
||||
bitmap = new wxBitmap(width, height
|
||||
#ifdef __WXGTK3__
|
||||
, 32
|
||||
#endif
|
||||
);
|
||||
#ifdef __APPLE__
|
||||
// Contrary to intuition, the `scale` argument isn't "please scale this to such and such"
|
||||
// but rather "the wxImage is sized for backing scale such and such".
|
||||
// So, We need to let the Mac OS wxBitmap implementation
|
||||
// know that the image may already be scaled appropriately for Retina,
|
||||
// and thereby that it's not supposed to upscale it.
|
||||
bitmap->CreateScaled(width, height, -1, scale < 0.0 ? m_scale : scale);
|
||||
#endif
|
||||
m_map[bitmap_key] = bitmap;
|
||||
} else {
|
||||
bitmap = it->second;
|
||||
if (size_t(bitmap->GetWidth()) != width || size_t(bitmap->GetHeight()) != height)
|
||||
bitmap->Create(width, height);
|
||||
}
|
||||
#if defined(WIN32) || defined(__APPLE__)
|
||||
// Not needed or harmful for GTK2 and GTK3.
|
||||
bitmap->UseAlpha();
|
||||
#endif
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp)
|
||||
{
|
||||
wxBitmap *bitmap = nullptr;
|
||||
auto it = m_map.find(bitmap_key);
|
||||
if (it == m_map.end()) {
|
||||
bitmap = new wxBitmap(bmp);
|
||||
m_map[bitmap_key] = bitmap;
|
||||
} else {
|
||||
bitmap = it->second;
|
||||
*bitmap = bmp;
|
||||
}
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
wxBitmap* BitmapCache::insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, const bool grayscale/* = false*/)
|
||||
{
|
||||
wxImage image(width, height);
|
||||
image.InitAlpha();
|
||||
unsigned char *rgb = image.GetData();
|
||||
unsigned char *alpha = image.GetAlpha();
|
||||
unsigned int pixels = width * height;
|
||||
for (unsigned int i = 0; i < pixels; ++ i) {
|
||||
*rgb ++ = *raw_data ++;
|
||||
*rgb ++ = *raw_data ++;
|
||||
*rgb ++ = *raw_data ++;
|
||||
*alpha ++ = *raw_data ++;
|
||||
}
|
||||
|
||||
if (grayscale)
|
||||
image = image.ConvertToGreyscale(m_gs, m_gs, m_gs);
|
||||
|
||||
return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image), m_scale));
|
||||
}
|
||||
|
||||
wxBitmap* BitmapCache::load_png(const std::string &bitmap_name, unsigned width, unsigned height,
|
||||
const bool grayscale/* = false*/)
|
||||
{
|
||||
std::string bitmap_key = bitmap_name + ( height !=0 ?
|
||||
"-h" + std::to_string(height) :
|
||||
"-w" + std::to_string(width))
|
||||
+ (grayscale ? "-gs" : "");
|
||||
|
||||
auto it = m_map.find(bitmap_key);
|
||||
if (it != m_map.end())
|
||||
return it->second;
|
||||
|
||||
wxImage image;
|
||||
if (! image.LoadFile(Slic3r::GUI::from_u8(Slic3r::var(bitmap_name + ".png")), wxBITMAP_TYPE_PNG) ||
|
||||
image.GetWidth() == 0 || image.GetHeight() == 0)
|
||||
return nullptr;
|
||||
|
||||
if (height != 0 && unsigned(image.GetHeight()) != height)
|
||||
width = unsigned(0.5f + float(image.GetWidth()) * height / image.GetHeight());
|
||||
else if (width != 0 && unsigned(image.GetWidth()) != width)
|
||||
height = unsigned(0.5f + float(image.GetHeight()) * width / image.GetWidth());
|
||||
|
||||
if (height != 0 && width != 0)
|
||||
image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR);
|
||||
|
||||
if (grayscale)
|
||||
image = image.ConvertToGreyscale(m_gs, m_gs, m_gs);
|
||||
|
||||
return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image)));
|
||||
}
|
||||
|
||||
NSVGimage* BitmapCache::nsvgParseFromFileWithReplace(const char* filename, const char* units, float dpi, const std::map<std::string, std::string>& replaces)
|
||||
{
|
||||
std::string str;
|
||||
FILE* fp = NULL;
|
||||
size_t size;
|
||||
char* data = NULL;
|
||||
NSVGimage* image = NULL;
|
||||
|
||||
fp = boost::nowide::fopen(filename, "rb");
|
||||
if (!fp) goto error;
|
||||
fseek(fp, 0, SEEK_END);
|
||||
size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
data = (char*)malloc(size + 1);
|
||||
if (data == NULL) goto error;
|
||||
if (fread(data, 1, size, fp) != size) goto error;
|
||||
data[size] = '\0'; // Must be null terminated.
|
||||
fclose(fp);
|
||||
|
||||
if (replaces.empty())
|
||||
image = nsvgParse(data, units, dpi);
|
||||
else {
|
||||
str.assign(data);
|
||||
for (auto val : replaces)
|
||||
boost::replace_all(str, val.first, val.second);
|
||||
image = nsvgParse(str.data(), units, dpi);
|
||||
}
|
||||
free(data);
|
||||
return image;
|
||||
|
||||
error:
|
||||
if (fp) fclose(fp);
|
||||
if (data) free(data);
|
||||
if (image) nsvgDelete(image);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void BitmapCache::nsvgGetDataFromFileWithReplace(const char* filename, std::string& data_str, const std::map<std::string, std::string>& replaces)
|
||||
{
|
||||
FILE* fp = NULL;
|
||||
size_t size;
|
||||
char* data = NULL;
|
||||
|
||||
fp = boost::nowide::fopen(filename, "rb");
|
||||
if (!fp) goto error;
|
||||
fseek(fp, 0, SEEK_END);
|
||||
size = ftell(fp);
|
||||
fseek(fp, 0, SEEK_SET);
|
||||
data = (char*)malloc(size + 1);
|
||||
if (data == NULL) goto error;
|
||||
if (fread(data, 1, size, fp) != size) goto error;
|
||||
data[size] = '\0'; // Must be null terminated.
|
||||
fclose(fp);
|
||||
|
||||
data_str.assign(data);
|
||||
for (auto val : replaces)
|
||||
boost::replace_all(data_str, val.first, val.second);
|
||||
|
||||
free(data);
|
||||
return;
|
||||
|
||||
error:
|
||||
if (fp) fclose(fp);
|
||||
if (data) free(data);
|
||||
return;
|
||||
}
|
||||
|
||||
wxBitmapBundle* BitmapCache::from_svg(const std::string& bitmap_name, unsigned target_width, unsigned target_height,
|
||||
const bool dark_mode, const std::string& new_color /*= ""*/)
|
||||
{
|
||||
if (target_width == 0)
|
||||
target_width = target_height;
|
||||
std::string bitmap_key = bitmap_name + (target_height != 0 ?
|
||||
"-h" + std::to_string(target_height) :
|
||||
"-w" + std::to_string(target_width))
|
||||
+ (dark_mode ? "-dm" : "")
|
||||
+ new_color;
|
||||
|
||||
auto it = m_bndl_map.find(bitmap_key);
|
||||
if (it != m_bndl_map.end())
|
||||
return it->second;
|
||||
|
||||
// map of color replaces
|
||||
std::map<std::string, std::string> replaces;
|
||||
if (dark_mode)
|
||||
replaces["\"#808080\""] = "\"#FFFFFF\"";
|
||||
if (!new_color.empty())
|
||||
replaces["\"#ED6B21\""] = "\"" + new_color + "\"";
|
||||
|
||||
std::string str;
|
||||
nsvgGetDataFromFileWithReplace(Slic3r::var(bitmap_name + ".svg").c_str(), str, replaces);
|
||||
if (str.empty())
|
||||
return nullptr;
|
||||
|
||||
return insert_bndl(bitmap_key, str.data(), target_width, target_height);
|
||||
}
|
||||
|
||||
wxBitmapBundle* BitmapCache::from_png(const std::string& bitmap_name, unsigned width, unsigned height)
|
||||
{
|
||||
std::string bitmap_key = bitmap_name + (height != 0 ?
|
||||
"-h" + std::to_string(height) :
|
||||
"-w" + std::to_string(width));
|
||||
|
||||
auto it = m_bndl_map.find(bitmap_key);
|
||||
if (it != m_bndl_map.end())
|
||||
return it->second;
|
||||
|
||||
wxImage image;
|
||||
if (!image.LoadFile(Slic3r::GUI::from_u8(Slic3r::var(bitmap_name + ".png")), wxBITMAP_TYPE_PNG) ||
|
||||
image.GetWidth() == 0 || image.GetHeight() == 0)
|
||||
return nullptr;
|
||||
|
||||
if (height != 0 && unsigned(image.GetHeight()) != height)
|
||||
width = unsigned(0.5f + float(image.GetWidth()) * height / image.GetHeight());
|
||||
else if (width != 0 && unsigned(image.GetWidth()) != width)
|
||||
height = unsigned(0.5f + float(image.GetHeight()) * width / image.GetWidth());
|
||||
|
||||
if (height != 0 && width != 0)
|
||||
image.Rescale(width, height, wxIMAGE_QUALITY_BILINEAR);
|
||||
|
||||
return this->insert_bndl(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image)));
|
||||
}
|
||||
|
||||
wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_width, unsigned target_height,
|
||||
const bool grayscale/* = false*/, const bool dark_mode/* = false*/, const std::string& new_color /*= ""*/)
|
||||
{
|
||||
std::string bitmap_key = bitmap_name + ( target_height !=0 ?
|
||||
"-h" + std::to_string(target_height) :
|
||||
"-w" + std::to_string(target_width))
|
||||
+ (m_scale != 1.0f ? "-s" + float_to_string_decimal_point(m_scale) : "")
|
||||
+ (dark_mode ? "-dm" : "")
|
||||
+ (grayscale ? "-gs" : "")
|
||||
+ new_color;
|
||||
|
||||
auto it = m_map.find(bitmap_key);
|
||||
if (it != m_map.end())
|
||||
return it->second;
|
||||
|
||||
// map of color replaces
|
||||
std::map<std::string, std::string> replaces;
|
||||
if (dark_mode)
|
||||
replaces["\"#808080\""] = "\"#FFFFFF\"";
|
||||
if (!new_color.empty())
|
||||
replaces["\"#ED6B21\""] = "\"" + new_color + "\"";
|
||||
|
||||
NSVGimage *image = nsvgParseFromFileWithReplace(Slic3r::var(bitmap_name + ".svg").c_str(), "px", 96.0f, replaces);
|
||||
if (image == nullptr)
|
||||
return nullptr;
|
||||
|
||||
target_height != 0 ? target_height *= m_scale : target_width *= m_scale;
|
||||
|
||||
float svg_scale = target_height != 0 ?
|
||||
(float)target_height / image->height : target_width != 0 ?
|
||||
(float)target_width / image->width : 1;
|
||||
|
||||
int width = (int)(svg_scale * image->width + 0.5f);
|
||||
int height = (int)(svg_scale * image->height + 0.5f);
|
||||
int n_pixels = width * height;
|
||||
if (n_pixels <= 0) {
|
||||
::nsvgDelete(image);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NSVGrasterizer *rast = ::nsvgCreateRasterizer();
|
||||
if (rast == nullptr) {
|
||||
::nsvgDelete(image);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> data(n_pixels * 4, 0);
|
||||
::nsvgRasterize(rast, image, 0, 0, svg_scale, data.data(), width, height, width * 4);
|
||||
::nsvgDeleteRasterizer(rast);
|
||||
::nsvgDelete(image);
|
||||
|
||||
return this->insert_raw_rgba(bitmap_key, width, height, data.data(), grayscale);
|
||||
}
|
||||
|
||||
//we make scaled solid bitmaps only for the cases, when its will be used with scaled SVG icon in one output bitmap
|
||||
//wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, bool suppress_scaling/* = false* /, size_t border_width /*= 0* /, bool dark_mode/* = false* /)
|
||||
//{
|
||||
// double scale = suppress_scaling ? 1.0f : m_scale;
|
||||
// width *= scale;
|
||||
// height *= scale;
|
||||
//
|
||||
// wxImage image(width, height);
|
||||
// image.InitAlpha();
|
||||
// unsigned char* imgdata = image.GetData();
|
||||
// unsigned char* imgalpha = image.GetAlpha();
|
||||
// for (size_t i = 0; i < width * height; ++ i) {
|
||||
// *imgdata ++ = r;
|
||||
// *imgdata ++ = g;
|
||||
// *imgdata ++ = b;
|
||||
// *imgalpha ++ = transparency;
|
||||
// }
|
||||
//
|
||||
// // Add border, make white/light spools easier to see
|
||||
// if (border_width > 0) {
|
||||
//
|
||||
// // Restrict to width of image
|
||||
// if (border_width > height) border_width = height - 1;
|
||||
// if (border_width > width) border_width = width - 1;
|
||||
//
|
||||
// auto px_data = (uint8_t*)image.GetData();
|
||||
// auto a_data = (uint8_t*)image.GetAlpha();
|
||||
//
|
||||
// for (size_t x = 0; x < width; ++x) {
|
||||
// for (size_t y = 0; y < height; ++y) {
|
||||
// if (x < border_width || y < border_width ||
|
||||
// x >= (width - border_width) || y >= (height - border_width)) {
|
||||
// const size_t idx = (x + y * width);
|
||||
// const size_t idx_rgb = (x + y * width) * 3;
|
||||
// px_data[idx_rgb] = px_data[idx_rgb + 1] = px_data[idx_rgb + 2] = dark_mode ? 245u : 110u;
|
||||
// a_data[idx] = 255u;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return wxImage_to_wxBitmap_with_alpha(std::move(image), scale);
|
||||
//}
|
||||
|
||||
//we make scaled solid bitmaps only for the cases, when its will be used with scaled SVG icon in one output bitmap
|
||||
wxBitmapBundle BitmapCache::mksolid(size_t width_in, size_t height_in, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, size_t border_width /*= 0*/, bool dark_mode/* = false*/)
|
||||
{
|
||||
wxVector<wxBitmap> bitmaps;
|
||||
|
||||
std::set<double> scales = { 1.0 };
|
||||
#ifndef __linux__
|
||||
|
||||
#ifdef __APPLE__
|
||||
scales.emplace(m_scale);
|
||||
#else
|
||||
size_t disp_cnt = wxDisplay::GetCount();
|
||||
for (size_t disp = 0; disp < disp_cnt; ++disp)
|
||||
scales.emplace(wxDisplay(disp).GetScaleFactor());
|
||||
#endif
|
||||
|
||||
#endif // !__linux__
|
||||
|
||||
for (double scale : scales) {
|
||||
size_t width = width_in * scale;
|
||||
size_t height = height_in * scale;
|
||||
|
||||
wxImage image(width, height);
|
||||
image.InitAlpha();
|
||||
unsigned char* imgdata = image.GetData();
|
||||
unsigned char* imgalpha = image.GetAlpha();
|
||||
for (size_t i = 0; i < width * height; ++i) {
|
||||
*imgdata++ = r;
|
||||
*imgdata++ = g;
|
||||
*imgdata++ = b;
|
||||
*imgalpha++ = transparency;
|
||||
}
|
||||
|
||||
// Add border, make white/light spools easier to see
|
||||
if (border_width > 0) {
|
||||
|
||||
// Restrict to width of image
|
||||
if (border_width > height) border_width = height - 1;
|
||||
if (border_width > width) border_width = width - 1;
|
||||
|
||||
auto px_data = (uint8_t*)image.GetData();
|
||||
auto a_data = (uint8_t*)image.GetAlpha();
|
||||
|
||||
for (size_t x = 0; x < width; ++x) {
|
||||
for (size_t y = 0; y < height; ++y) {
|
||||
if (x < border_width || y < border_width ||
|
||||
x >= (width - border_width) || y >= (height - border_width)) {
|
||||
const size_t idx = (x + y * width);
|
||||
const size_t idx_rgb = (x + y * width) * 3;
|
||||
px_data[idx_rgb] = px_data[idx_rgb + 1] = px_data[idx_rgb + 2] = dark_mode ? 245u : 110u;
|
||||
a_data[idx] = 255u;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bitmaps.push_back(wxImage_to_wxBitmap_with_alpha(std::move(image), scale));
|
||||
}
|
||||
return wxBitmapBundle::FromBitmaps(bitmaps);
|
||||
}
|
||||
|
||||
wxBitmapBundle* BitmapCache::mksolid_bndl(size_t width, size_t height, const std::string& color, size_t border_width, bool dark_mode)
|
||||
{
|
||||
std::string bitmap_key = (color.empty() ? "empty" : color) + "-h" + std::to_string(height) + "-w" + std::to_string(width) + (dark_mode ? "-dm" : "");
|
||||
|
||||
wxBitmapBundle* bndl = nullptr;
|
||||
auto it = m_bndl_map.find(bitmap_key);
|
||||
if (it == m_bndl_map.end()) {
|
||||
if (color.empty())
|
||||
bndl = new wxBitmapBundle(mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT, size_t(0)));
|
||||
else {
|
||||
ColorRGB rgb;// [3] ;
|
||||
decode_color(color, rgb);
|
||||
bndl = new wxBitmapBundle(mksolid(width, height, rgb.r_uchar(), rgb.g_uchar(), rgb.b_uchar(), wxALPHA_OPAQUE, border_width, dark_mode));
|
||||
}
|
||||
m_bndl_map[bitmap_key] = bndl;
|
||||
}
|
||||
else
|
||||
return it->second;
|
||||
|
||||
return bndl;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
71
src/slic3r/GUI/BitmapCache.hpp
Normal file
71
src/slic3r/GUI/BitmapCache.hpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#ifndef SLIC3R_GUI_BITMAP_CACHE_HPP
|
||||
#define SLIC3R_GUI_BITMAP_CACHE_HPP
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include <wx/wxprec.h>
|
||||
#ifndef WX_PRECOMP
|
||||
#include <wx/wx.h>
|
||||
#endif
|
||||
|
||||
#include "libslic3r/Color.hpp"
|
||||
|
||||
struct NSVGimage;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class BitmapCache
|
||||
{
|
||||
public:
|
||||
BitmapCache();
|
||||
~BitmapCache() { clear(); }
|
||||
void clear();
|
||||
double scale() { return m_scale; }
|
||||
|
||||
wxBitmapBundle* find_bndl(const std::string &name) { auto it = m_bndl_map.find(name); return (it == m_bndl_map.end()) ? nullptr : it->second; }
|
||||
const wxBitmapBundle* find_bndl(const std::string &name) const { return const_cast<BitmapCache*>(this)->find_bndl(name); }
|
||||
wxBitmap* find(const std::string &name) { auto it = m_map.find(name); return (it == m_map.end()) ? nullptr : it->second; }
|
||||
const wxBitmap* find(const std::string &name) const { return const_cast<BitmapCache*>(this)->find(name); }
|
||||
|
||||
wxBitmapBundle* insert_bndl(const std::string& bitmap_key, const char* data, size_t width, size_t height);
|
||||
wxBitmapBundle* insert_bndl(const std::string& bitmap_key, const wxBitmapBundle &bmp);
|
||||
wxBitmapBundle* insert_bndl(const std::string& bitmap_key, const wxVector<wxBitmap>& bmps);
|
||||
wxBitmapBundle* insert_bndl(const std::string& name, const std::vector<wxBitmapBundle*>& bmps);
|
||||
wxBitmapBundle* insert_raw_rgba_bndl(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, const bool grayscale = false);
|
||||
|
||||
wxBitmap* insert(const std::string &name, size_t width, size_t height, double scale = -1.0);
|
||||
wxBitmap* insert(const std::string &name, const wxBitmap &bmp);
|
||||
wxBitmap* insert_raw_rgba(const std::string &bitmap_key, unsigned width, unsigned height, const unsigned char *raw_data, const bool grayscale = false);
|
||||
|
||||
// Load png from resources/icons. bitmap_key is given without the .png suffix. Bitmap will be rescaled to provided height/width if nonzero.
|
||||
wxBitmap* load_png(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false);
|
||||
|
||||
// Parses SVG file from a file, returns SVG image as paths.
|
||||
// And makes replases befor parsing
|
||||
// replace_map containes old_value->new_value
|
||||
static NSVGimage* nsvgParseFromFileWithReplace(const char* filename, const char* units, float dpi, const std::map<std::string, std::string>& replaces);
|
||||
// Gets a data from SVG file and makes replases
|
||||
// replace_map containes old_value->new_value
|
||||
static void nsvgGetDataFromFileWithReplace(const char* filename, std::string& data_str, const std::map<std::string, std::string>& replaces);
|
||||
wxBitmapBundle* from_svg(const std::string& bitmap_name, unsigned target_width, unsigned target_height, const bool dark_mode, const std::string& new_color = "");
|
||||
wxBitmapBundle* from_png(const std::string& bitmap_name, unsigned width, unsigned height);
|
||||
// Load svg from resources/icons. bitmap_key is given without the .svg suffix. SVG will be rasterized to provided height/width.
|
||||
wxBitmap* load_svg(const std::string &bitmap_key, unsigned width = 0, unsigned height = 0, const bool grayscale = false, const bool dark_mode = false, const std::string& new_color = "");
|
||||
|
||||
wxBitmapBundle mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency, size_t border_width = 0, bool dark_mode = false);
|
||||
wxBitmapBundle* mksolid_bndl(size_t width, size_t height, const std::string& color = std::string(), size_t border_width = 0, bool dark_mode = false);
|
||||
wxBitmapBundle* mkclear_bndl(size_t width, size_t height) { return mksolid_bndl(width, height); }
|
||||
|
||||
private:
|
||||
std::map<std::string, wxBitmap*> m_map;
|
||||
std::map<std::string, wxBitmapBundle*> m_bndl_map;
|
||||
double m_gs = 0.2; // value, used for image.ConvertToGreyscale(m_gs, m_gs, m_gs)
|
||||
double m_scale = 1.0; // value, used for correct scaling of SVG icons on Retina display
|
||||
};
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
#endif /* SLIC3R_GUI_BITMAP_CACHE_HPP */
|
||||
202
src/slic3r/GUI/BitmapComboBox.cpp
Normal file
202
src/slic3r/GUI/BitmapComboBox.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
#include "BitmapComboBox.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/statbox.h>
|
||||
#include <wx/colordlg.h>
|
||||
#include <wx/wupdlock.h>
|
||||
#include <wx/menu.h>
|
||||
#include <wx/odcombo.h>
|
||||
#include <wx/listbook.h>
|
||||
#include <wx/window.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <wx/msw/dcclient.h>
|
||||
#include <wx/msw/private.h>
|
||||
#ifdef _MSW_DARK_MODE
|
||||
#include <wx/msw/dark_mode.h>
|
||||
#endif //_MSW_DARK_MODE
|
||||
#endif
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
// A workaround for a set of issues related to text fitting into gtk widgets:
|
||||
// See e.g.: https://github.com/qidi3d/QIDISlicer/issues/4584
|
||||
#if defined(__WXGTK20__) || defined(__WXGTK3__)
|
||||
#include <glib-2.0/glib-object.h>
|
||||
#include <pango-1.0/pango/pango-layout.h>
|
||||
#include <gtk/gtk.h>
|
||||
#endif
|
||||
|
||||
using Slic3r::GUI::format_wxstr;
|
||||
|
||||
#define BORDER_W 10
|
||||
|
||||
// ---------------------------------
|
||||
// *** BitmapComboBox ***
|
||||
// ---------------------------------
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
BitmapComboBox::BitmapComboBox(wxWindow* parent,
|
||||
wxWindowID id/* = wxID_ANY*/,
|
||||
const wxString& value/* = wxEmptyString*/,
|
||||
const wxPoint& pos/* = wxDefaultPosition*/,
|
||||
const wxSize& size/* = wxDefaultSize*/,
|
||||
int n/* = 0*/,
|
||||
const wxString choices[]/* = NULL*/,
|
||||
long style/* = 0*/) :
|
||||
wxBitmapComboBox(parent, id, value, pos, size, n, choices, style)
|
||||
{
|
||||
SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||
#ifdef _WIN32
|
||||
// Workaround for ignoring CBN_EDITCHANGE events, which are processed after the content of the combo box changes, so that
|
||||
// the index of the item inside CBN_EDITCHANGE may no more be valid.
|
||||
EnableTextChangedEvents(false);
|
||||
wxGetApp().UpdateDarkUI(this);
|
||||
if (!HasFlag(wxCB_READONLY))
|
||||
wxTextEntry::SetMargins(0,0);
|
||||
#endif /* _WIN32 */
|
||||
}
|
||||
|
||||
BitmapComboBox::~BitmapComboBox()
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
int BitmapComboBox::Append(const wxString& item)
|
||||
{
|
||||
// Workaround for a correct rendering of the control without Bitmap (under MSW):
|
||||
//1. We should create small Bitmap to fill Bitmaps RefData,
|
||||
// ! in this case wxBitmap.IsOK() return true.
|
||||
//2. But then set width to 0 value for no using of bitmap left and right spacing
|
||||
//3. Set this empty bitmap to the at list one item and BitmapCombobox will be recreated correct
|
||||
|
||||
wxBitmapBundle bitmap = *get_empty_bmp_bundle(1, 16);
|
||||
OnAddBitmap(bitmap);
|
||||
|
||||
const int n = wxComboBox::Append(item);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
enum OwnerDrawnComboBoxPaintingFlags
|
||||
{
|
||||
ODCB_PAINTING_DISABLED = 0x0004,
|
||||
};
|
||||
|
||||
bool BitmapComboBox::MSWOnDraw(WXDRAWITEMSTRUCT* item)
|
||||
{
|
||||
LPDRAWITEMSTRUCT lpDrawItem = (LPDRAWITEMSTRUCT)item;
|
||||
int pos = lpDrawItem->itemID;
|
||||
|
||||
// Draw default for item -1, which means 'focus rect only'
|
||||
if (pos == -1)
|
||||
return false;
|
||||
|
||||
int flags = 0;
|
||||
if (lpDrawItem->itemState & ODS_COMBOBOXEDIT)
|
||||
flags |= wxODCB_PAINTING_CONTROL;
|
||||
if (lpDrawItem->itemState & ODS_SELECTED)
|
||||
flags |= wxODCB_PAINTING_SELECTED;
|
||||
if (lpDrawItem->itemState & ODS_DISABLED)
|
||||
flags |= ODCB_PAINTING_DISABLED;
|
||||
|
||||
wxPaintDCEx dc(this, lpDrawItem->hDC);
|
||||
wxRect rect = wxRectFromRECT(lpDrawItem->rcItem);
|
||||
|
||||
DrawBackground_(dc, rect, pos, flags);
|
||||
|
||||
wxString text;
|
||||
|
||||
if (flags & wxODCB_PAINTING_CONTROL)
|
||||
{
|
||||
// Don't draw anything in the editable selection field.
|
||||
//if (!HasFlag(wxCB_READONLY))
|
||||
// return true;
|
||||
|
||||
pos = GetSelection();
|
||||
// Skip drawing if there is nothing selected.
|
||||
if (pos < 0)
|
||||
return true;
|
||||
|
||||
text = GetValue();
|
||||
}
|
||||
else
|
||||
{
|
||||
text = GetString(pos);
|
||||
}
|
||||
|
||||
wxBitmapComboBoxBase::DrawItem(dc, rect, pos, text, flags);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BitmapComboBox::DrawBackground_(wxDC& dc, const wxRect& rect, int WXUNUSED(item), int flags) const
|
||||
{
|
||||
if (flags & wxODCB_PAINTING_SELECTED)
|
||||
{
|
||||
const int vSizeDec = 0; // Vertical size reduction of selection rectangle edges
|
||||
|
||||
dc.SetTextForeground(wxGetApp().get_label_highlight_clr());
|
||||
|
||||
wxColour selCol = wxGetApp().get_highlight_default_clr();
|
||||
dc.SetPen(selCol);
|
||||
dc.SetBrush(selCol);
|
||||
dc.DrawRectangle(rect.x,
|
||||
rect.y + vSizeDec,
|
||||
rect.width,
|
||||
rect.height - (vSizeDec * 2));
|
||||
}
|
||||
else
|
||||
{
|
||||
dc.SetTextForeground(flags & ODCB_PAINTING_DISABLED ? wxColour(108,108,108) : wxGetApp().get_label_clr_default());
|
||||
|
||||
wxColour selCol = flags & ODCB_PAINTING_DISABLED ?
|
||||
#ifdef _MSW_DARK_MODE
|
||||
wxRGBToColour(NppDarkMode::GetSofterBackgroundColor()) :
|
||||
#else
|
||||
wxGetApp().get_highlight_default_clr() :
|
||||
#endif
|
||||
wxGetApp().get_window_default_clr();
|
||||
dc.SetPen(selCol);
|
||||
dc.SetBrush(selCol);
|
||||
dc.DrawRectangle(rect);
|
||||
}
|
||||
}
|
||||
|
||||
void BitmapComboBox::Rescale()
|
||||
{
|
||||
// Next workaround: To correct scaling of a BitmapCombobox
|
||||
// we need to refill control with new bitmaps
|
||||
const wxString selection = this->GetValue();
|
||||
std::vector<wxString> items;
|
||||
for (size_t i = 0; i < GetCount(); i++)
|
||||
items.push_back(GetString(i));
|
||||
|
||||
this->Clear();
|
||||
for (const wxString& item : items)
|
||||
Append(item);
|
||||
this->SetValue(selection);
|
||||
}
|
||||
#endif
|
||||
|
||||
}}
|
||||
|
||||
49
src/slic3r/GUI/BitmapComboBox.hpp
Normal file
49
src/slic3r/GUI/BitmapComboBox.hpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef slic3r_BitmapComboBox_hpp_
|
||||
#define slic3r_BitmapComboBox_hpp_
|
||||
|
||||
#include <wx/bmpcbox.h>
|
||||
#include <wx/gdicmn.h>
|
||||
|
||||
#include "GUI_Utils.hpp"
|
||||
|
||||
// ---------------------------------
|
||||
// *** BitmapComboBox ***
|
||||
// ---------------------------------
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
// BitmapComboBox used to presets list on Sidebar and Tabs
|
||||
class BitmapComboBox : public wxBitmapComboBox
|
||||
{
|
||||
public:
|
||||
BitmapComboBox(wxWindow* parent,
|
||||
wxWindowID id = wxID_ANY,
|
||||
const wxString& value = wxEmptyString,
|
||||
const wxPoint& pos = wxDefaultPosition,
|
||||
const wxSize& size = wxDefaultSize,
|
||||
int n = 0,
|
||||
const wxString choices[] = NULL,
|
||||
long style = 0);
|
||||
~BitmapComboBox();
|
||||
|
||||
#ifdef _WIN32
|
||||
int Append(const wxString& item);
|
||||
#endif
|
||||
int Append(const wxString& item, const wxBitmapBundle& bitmap)
|
||||
{
|
||||
return wxBitmapComboBox::Append(item, bitmap);
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
#ifdef _WIN32
|
||||
bool MSWOnDraw(WXDRAWITEMSTRUCT* item) override;
|
||||
void DrawBackground_(wxDC& dc, const wxRect& rect, int WXUNUSED(item), int flags) const;
|
||||
public:
|
||||
void Rescale();
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
}}
|
||||
#endif
|
||||
284
src/slic3r/GUI/BonjourDialog.cpp
Normal file
284
src/slic3r/GUI/BonjourDialog.cpp
Normal file
@@ -0,0 +1,284 @@
|
||||
#include "slic3r/Utils/Bonjour.hpp" // On Windows, boost needs to be included before wxWidgets headers
|
||||
|
||||
#include "BonjourDialog.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <mutex>
|
||||
|
||||
#include <boost/nowide/convert.hpp>
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/listctrl.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/wupdlock.h>
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "slic3r/Utils/Bonjour.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
class BonjourReplyEvent : public wxEvent
|
||||
{
|
||||
public:
|
||||
BonjourReply reply;
|
||||
|
||||
BonjourReplyEvent(wxEventType eventType, int winid, BonjourReply &&reply) :
|
||||
wxEvent(winid, eventType),
|
||||
reply(std::move(reply))
|
||||
{}
|
||||
|
||||
virtual wxEvent *Clone() const
|
||||
{
|
||||
return new BonjourReplyEvent(*this);
|
||||
}
|
||||
};
|
||||
|
||||
wxDEFINE_EVENT(EVT_BONJOUR_REPLY, BonjourReplyEvent);
|
||||
|
||||
wxDECLARE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_BONJOUR_COMPLETE, wxCommandEvent);
|
||||
|
||||
class ReplySet: public std::set<BonjourReply> {};
|
||||
|
||||
struct LifetimeGuard
|
||||
{
|
||||
std::mutex mutex;
|
||||
BonjourDialog *dialog;
|
||||
|
||||
LifetimeGuard(BonjourDialog *dialog) : dialog(dialog) {}
|
||||
};
|
||||
|
||||
BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech)
|
||||
: wxDialog(parent, wxID_ANY, _(L("Network lookup")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
|
||||
, list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT|wxSIMPLE_BORDER))
|
||||
, replies(new ReplySet)
|
||||
, label(new wxStaticText(this, wxID_ANY, ""))
|
||||
, timer(new wxTimer())
|
||||
, timer_state(0)
|
||||
, tech(tech)
|
||||
{
|
||||
const int em = GUI::wxGetApp().em_unit();
|
||||
list->SetMinSize(wxSize(80 * em, 30 * em));
|
||||
|
||||
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, em);
|
||||
|
||||
list->SetSingleStyle(wxLC_SINGLE_SEL);
|
||||
list->SetSingleStyle(wxLC_SORT_DESCENDING);
|
||||
list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 5 * em);
|
||||
list->AppendColumn(_(L("Hostname")), wxLIST_FORMAT_LEFT, 10 * em);
|
||||
list->AppendColumn(_(L("Service name")), wxLIST_FORMAT_LEFT, 20 * em);
|
||||
if (tech == ptFFF) {
|
||||
list->AppendColumn(_(L("OctoPrint version")), wxLIST_FORMAT_LEFT, 5 * em);
|
||||
}
|
||||
|
||||
vsizer->Add(list, 1, wxEXPAND | wxALL, em);
|
||||
|
||||
wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, em);
|
||||
button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, em);
|
||||
// ^ Note: The Ok/Cancel labels are translated by wxWidgets
|
||||
|
||||
vsizer->Add(button_sizer, 0, wxALIGN_CENTER);
|
||||
SetSizerAndFit(vsizer);
|
||||
|
||||
Bind(EVT_BONJOUR_REPLY, &BonjourDialog::on_reply, this);
|
||||
|
||||
Bind(EVT_BONJOUR_COMPLETE, [this](wxCommandEvent &) {
|
||||
this->timer_state = 0;
|
||||
});
|
||||
|
||||
Bind(wxEVT_TIMER, &BonjourDialog::on_timer, this);
|
||||
GUI::wxGetApp().UpdateDlgDarkUI(this);
|
||||
}
|
||||
|
||||
BonjourDialog::~BonjourDialog()
|
||||
{
|
||||
// Needed bacuse of forward defs
|
||||
}
|
||||
|
||||
bool BonjourDialog::show_and_lookup()
|
||||
{
|
||||
Show(); // Because we need GetId() to work before ShowModal()
|
||||
|
||||
timer->Stop();
|
||||
timer->SetOwner(this);
|
||||
timer_state = 1;
|
||||
timer->Start(1000);
|
||||
on_timer_process();
|
||||
|
||||
// The background thread needs to queue messages for this dialog
|
||||
// and for that it needs a valid pointer to it (mandated by the wxWidgets API).
|
||||
// Here we put the pointer under a shared_ptr and protect it by a mutex,
|
||||
// so that both threads can access it safely.
|
||||
auto dguard = std::make_shared<LifetimeGuard>(this);
|
||||
|
||||
// Note: More can be done here when we support discovery of hosts other than Octoprint and SL1
|
||||
Bonjour::TxtKeys txt_keys { "version", "model" };
|
||||
|
||||
bonjour = Bonjour("octoprint")
|
||||
.set_txt_keys(std::move(txt_keys))
|
||||
.set_retries(3)
|
||||
.set_timeout(4)
|
||||
.on_reply([dguard](BonjourReply &&reply) {
|
||||
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
|
||||
auto dialog = dguard->dialog;
|
||||
if (dialog != nullptr) {
|
||||
auto evt = new BonjourReplyEvent(EVT_BONJOUR_REPLY, dialog->GetId(), std::move(reply));
|
||||
wxQueueEvent(dialog, evt);
|
||||
}
|
||||
})
|
||||
.on_complete([dguard]() {
|
||||
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
|
||||
auto dialog = dguard->dialog;
|
||||
if (dialog != nullptr) {
|
||||
auto evt = new wxCommandEvent(EVT_BONJOUR_COMPLETE, dialog->GetId());
|
||||
wxQueueEvent(dialog, evt);
|
||||
}
|
||||
})
|
||||
.lookup();
|
||||
|
||||
bool res = ShowModal() == wxID_OK && list->GetFirstSelected() >= 0;
|
||||
{
|
||||
// Tell the background thread the dialog is going away...
|
||||
std::lock_guard<std::mutex> lock_guard(dguard->mutex);
|
||||
dguard->dialog = nullptr;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
wxString BonjourDialog::get_selected() const
|
||||
{
|
||||
auto sel = list->GetFirstSelected();
|
||||
return sel >= 0 ? list->GetItemText(sel) : wxString();
|
||||
}
|
||||
|
||||
|
||||
// Private
|
||||
|
||||
void BonjourDialog::on_reply(BonjourReplyEvent &e)
|
||||
{
|
||||
if (replies->find(e.reply) != replies->end()) {
|
||||
// We already have this reply
|
||||
return;
|
||||
}
|
||||
|
||||
// Filter replies based on selected technology
|
||||
const auto model = e.reply.txt_data.find("model");
|
||||
const bool sl1 = model != e.reply.txt_data.end() && model->second == "SL1";
|
||||
if ((tech == ptFFF && sl1) || (tech == ptSLA && !sl1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
replies->insert(std::move(e.reply));
|
||||
|
||||
auto selected = get_selected();
|
||||
|
||||
wxWindowUpdateLocker freeze_guard(this);
|
||||
(void)freeze_guard;
|
||||
|
||||
list->DeleteAllItems();
|
||||
|
||||
// The whole list is recreated so that we benefit from it already being sorted in the set.
|
||||
// (And also because wxListView's sorting API is bananas.)
|
||||
for (const auto &reply : *replies) {
|
||||
auto item = list->InsertItem(0, reply.full_address);
|
||||
list->SetItem(item, 1, reply.hostname);
|
||||
list->SetItem(item, 2, reply.service_name);
|
||||
|
||||
if (tech == ptFFF) {
|
||||
const auto it = reply.txt_data.find("version");
|
||||
if (it != reply.txt_data.end()) {
|
||||
list->SetItem(item, 3, GUI::from_u8(it->second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const int em = GUI::wxGetApp().em_unit();
|
||||
|
||||
for (int i = 0; i < list->GetColumnCount(); i++) {
|
||||
list->SetColumnWidth(i, wxLIST_AUTOSIZE);
|
||||
if (list->GetColumnWidth(i) < 10 * em) { list->SetColumnWidth(i, 10 * em); }
|
||||
}
|
||||
|
||||
if (!selected.IsEmpty()) {
|
||||
// Attempt to preserve selection
|
||||
auto hit = list->FindItem(-1, selected);
|
||||
if (hit >= 0) { list->SetItemState(hit, wxLIST_STATE_SELECTED, wxLIST_STATE_SELECTED); }
|
||||
}
|
||||
}
|
||||
|
||||
void BonjourDialog::on_timer(wxTimerEvent &)
|
||||
{
|
||||
on_timer_process();
|
||||
}
|
||||
|
||||
// This is here so the function can be bound to wxEVT_TIMER and also called
|
||||
// explicitly (wxTimerEvent should not be created by user code).
|
||||
void BonjourDialog::on_timer_process()
|
||||
{
|
||||
const auto search_str = _L("Searching for devices");
|
||||
|
||||
if (timer_state > 0) {
|
||||
const std::string dots(timer_state, '.');
|
||||
label->SetLabel(search_str + dots);
|
||||
timer_state = (timer_state) % 3 + 1;
|
||||
} else {
|
||||
label->SetLabel(search_str + ": " + _L("Finished") + ".");
|
||||
timer->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
IPListDialog::IPListDialog(wxWindow* parent, const wxString& hostname, const std::vector<boost::asio::ip::address>& ips, size_t& selected_index)
|
||||
: wxDialog(parent, wxID_ANY, _(L("Multiple resolved IP addresses")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||
, m_list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxSIMPLE_BORDER))
|
||||
, m_selected_index (selected_index)
|
||||
{
|
||||
const int em = GUI::wxGetApp().em_unit();
|
||||
m_list->SetMinSize(wxSize(40 * em, 30 * em));
|
||||
|
||||
wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto* label = new wxStaticText(this, wxID_ANY, GUI::format_wxstr(_L("There are several IP addresses resolving to hostname %1%.\nPlease select one that should be used."), hostname));
|
||||
vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, em);
|
||||
|
||||
m_list->SetSingleStyle(wxLC_SINGLE_SEL);
|
||||
m_list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 40 * em);
|
||||
|
||||
for (size_t i = 0; i < ips.size(); i++)
|
||||
m_list->InsertItem(i, boost::nowide::widen(ips[i].to_string()));
|
||||
|
||||
m_list->Select(0);
|
||||
|
||||
vsizer->Add(m_list, 1, wxEXPAND | wxALL, em);
|
||||
|
||||
wxBoxSizer* button_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, em);
|
||||
button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, em);
|
||||
|
||||
vsizer->Add(button_sizer, 0, wxALIGN_CENTER);
|
||||
SetSizerAndFit(vsizer);
|
||||
|
||||
GUI::wxGetApp().UpdateDlgDarkUI(this);
|
||||
}
|
||||
|
||||
IPListDialog::~IPListDialog()
|
||||
{
|
||||
}
|
||||
|
||||
void IPListDialog::EndModal(int retCode)
|
||||
{
|
||||
if (retCode == wxID_OK) {
|
||||
m_selected_index = (size_t)m_list->GetFirstSelected();
|
||||
}
|
||||
wxDialog::EndModal(retCode);
|
||||
}
|
||||
|
||||
}
|
||||
71
src/slic3r/GUI/BonjourDialog.hpp
Normal file
71
src/slic3r/GUI/BonjourDialog.hpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#ifndef slic3r_BonjourDialog_hpp_
|
||||
#define slic3r_BonjourDialog_hpp_
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/asio/ip/address.hpp>
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/string.h>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
class wxListView;
|
||||
class wxStaticText;
|
||||
class wxTimer;
|
||||
class wxTimerEvent;
|
||||
class address;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Bonjour;
|
||||
class BonjourReplyEvent;
|
||||
class ReplySet;
|
||||
|
||||
|
||||
class BonjourDialog: public wxDialog
|
||||
{
|
||||
public:
|
||||
BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology);
|
||||
BonjourDialog(BonjourDialog &&) = delete;
|
||||
BonjourDialog(const BonjourDialog &) = delete;
|
||||
BonjourDialog &operator=(BonjourDialog &&) = delete;
|
||||
BonjourDialog &operator=(const BonjourDialog &) = delete;
|
||||
~BonjourDialog();
|
||||
|
||||
bool show_and_lookup();
|
||||
wxString get_selected() const;
|
||||
private:
|
||||
wxListView *list;
|
||||
std::unique_ptr<ReplySet> replies;
|
||||
wxStaticText *label;
|
||||
std::shared_ptr<Bonjour> bonjour;
|
||||
std::unique_ptr<wxTimer> timer;
|
||||
unsigned timer_state;
|
||||
Slic3r::PrinterTechnology tech;
|
||||
|
||||
virtual void on_reply(BonjourReplyEvent &);
|
||||
void on_timer(wxTimerEvent &);
|
||||
void on_timer_process();
|
||||
};
|
||||
|
||||
class IPListDialog : public wxDialog
|
||||
{
|
||||
public:
|
||||
IPListDialog(wxWindow* parent, const wxString& hostname, const std::vector<boost::asio::ip::address>& ips, size_t& selected_index);
|
||||
IPListDialog(IPListDialog&&) = delete;
|
||||
IPListDialog(const IPListDialog&) = delete;
|
||||
IPListDialog& operator=(IPListDialog&&) = delete;
|
||||
IPListDialog& operator=(const IPListDialog&) = delete;
|
||||
~IPListDialog();
|
||||
|
||||
virtual void EndModal(int retCode) wxOVERRIDE;
|
||||
private:
|
||||
wxListView* m_list;
|
||||
size_t& m_selected_index;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
1518
src/slic3r/GUI/BugWizard.cpp
Normal file
1518
src/slic3r/GUI/BugWizard.cpp
Normal file
File diff suppressed because it is too large
Load Diff
64
src/slic3r/GUI/BugWizard.hpp
Normal file
64
src/slic3r/GUI/BugWizard.hpp
Normal file
@@ -0,0 +1,64 @@
|
||||
#ifndef slic3r_BugWizard_hpp_
|
||||
#define slic3r_BugWizard_hpp_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <wx/dialog.h>
|
||||
|
||||
#include "GUI_Utils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class BugPresetBundle;
|
||||
class PresetUpdater;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
|
||||
class BugWizard: public DPIDialog
|
||||
{
|
||||
public:
|
||||
// Why is the Wizard run
|
||||
enum BugRunReason {
|
||||
RR_DATA_EMPTY, // No or empty datadir
|
||||
RR_DATA_LEGACY, // Pre-updating datadir
|
||||
RR_DATA_INCOMPAT, // Incompatible datadir - Slic3r downgrade situation
|
||||
RR_USER, // User requested the Wizard from the menus
|
||||
};
|
||||
|
||||
// What page should wizard start on
|
||||
enum BugStartPage {
|
||||
SP_WELCOME,
|
||||
SP_PRINTERS,
|
||||
SP_FILAMENTS,
|
||||
SP_MATERIALS,
|
||||
};
|
||||
|
||||
BugWizard(wxWindow *parent);
|
||||
BugWizard(BugWizard &&) = delete;
|
||||
BugWizard(const BugWizard &) = delete;
|
||||
BugWizard &operator=(BugWizard &&) = delete;
|
||||
BugWizard &operator=(const BugWizard &) = delete;
|
||||
~BugWizard();
|
||||
|
||||
// Run the Wizard. Return whether it was completed.
|
||||
bool run(BugRunReason reason, BugStartPage start_page = SP_WELCOME);
|
||||
|
||||
static const wxString& name(const bool from_menu = false);
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect &suggested_rect) override ;
|
||||
void on_sys_color_changed() override;
|
||||
|
||||
private:
|
||||
struct priv;
|
||||
std::unique_ptr<priv> p;
|
||||
|
||||
friend struct BugWizardPage;
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
641
src/slic3r/GUI/BugWizard_private.hpp
Normal file
641
src/slic3r/GUI/BugWizard_private.hpp
Normal file
@@ -0,0 +1,641 @@
|
||||
#ifndef slic3r_BugWizard_private_hpp_
|
||||
#define slic3r_BugWizard_private_hpp_
|
||||
|
||||
#include "BugWizard.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <wx/panel.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/spinctrl.h>
|
||||
#include <wx/listbox.h>
|
||||
#include <wx/checklst.h>
|
||||
#include <wx/radiobut.h>
|
||||
#include <wx/html/htmlwin.h>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "slic3r/Utils/PresetUpdater.hpp"
|
||||
#include "BedShapeDialog.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "SavePresetDialog.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
enum {
|
||||
WRAP_WIDTH = 500,
|
||||
MODEL_MIN_WRAP = 150,
|
||||
|
||||
DIALOG_MARGIN = 15,
|
||||
INDEX_MARGIN = 40,
|
||||
BTN_SPACING = 10,
|
||||
INDENT_SPACING = 30,
|
||||
VERTICAL_SPACING = 10,
|
||||
|
||||
MAX_COLS = 4,
|
||||
ROW_SPACING = 75,
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Configuration data structures extensions needed for the wizard
|
||||
|
||||
enum BugTechnology {
|
||||
// Bitflag equivalent of PrinterBugTechnology
|
||||
T_FFF = 0x1,
|
||||
T_SLA = 0x2,
|
||||
T_ANY = ~0,
|
||||
};
|
||||
enum BugBundleLocation{
|
||||
IN_VENDOR,
|
||||
IN_ARCHIVE,
|
||||
IN_RESOURCES
|
||||
};
|
||||
|
||||
struct BugBundle
|
||||
{
|
||||
std::unique_ptr<PresetBundle> preset_bundle;
|
||||
VendorProfile* vendor_profile{ nullptr };
|
||||
//bool is_in_resources{ false };
|
||||
BugBundleLocation location;
|
||||
bool is_qidi_bundle{ false };
|
||||
|
||||
BugBundle() = default;
|
||||
BugBundle(BugBundle &&other);
|
||||
|
||||
// Returns false if not loaded. Reason for that is logged as boost::log error.
|
||||
bool load(fs::path source_path, BugBundleLocation location, bool is_qidi_bundle = false);
|
||||
|
||||
const std::string& vendor_id() const { return vendor_profile->id; }
|
||||
};
|
||||
|
||||
struct BugBundleMap : std::unordered_map<std::string /* = vendor ID */, BugBundle>
|
||||
{
|
||||
static BugBundleMap load();
|
||||
|
||||
BugBundle& qidi_bundle();
|
||||
const BugBundle& qidi_bundle() const;
|
||||
};
|
||||
|
||||
struct BugMaterials
|
||||
{
|
||||
BugTechnology technology;
|
||||
// use vector for the presets to purpose of save of presets sorting in the bundle
|
||||
std::vector<const Preset*> presets;
|
||||
// String is alias of material, size_t number of compatible counters
|
||||
std::vector<std::pair<std::string, size_t>> compatibility_counter;
|
||||
std::set<std::string> types;
|
||||
std::set<const Preset*> printers;
|
||||
|
||||
BugMaterials(BugTechnology technology) : technology(technology) {}
|
||||
|
||||
void push(const Preset *preset);
|
||||
void add_printer(const Preset* preset);
|
||||
void clear();
|
||||
bool containts(const Preset *preset) const {
|
||||
//return std::find(presets.begin(), presets.end(), preset) != presets.end();
|
||||
return std::find_if(presets.begin(), presets.end(),
|
||||
[preset](const Preset* element) { return element == preset; }) != presets.end();
|
||||
|
||||
}
|
||||
|
||||
bool get_omnipresent(const Preset* preset) {
|
||||
return get_printer_counter(preset) == printers.size();
|
||||
}
|
||||
|
||||
const std::vector<const Preset*> get_presets_by_alias(const std::string name) {
|
||||
std::vector<const Preset*> ret_vec;
|
||||
for (auto it = presets.begin(); it != presets.end(); ++it) {
|
||||
if ((*it)->alias == name)
|
||||
ret_vec.push_back((*it));
|
||||
}
|
||||
return ret_vec;
|
||||
}
|
||||
|
||||
|
||||
|
||||
size_t get_printer_counter(const Preset* preset) {
|
||||
for (auto it : compatibility_counter) {
|
||||
if (it.first == preset->alias)
|
||||
return it.second;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const std::string& appconfig_section() const;
|
||||
const std::string& get_type(const Preset *preset) const;
|
||||
const std::string& get_vendor(const Preset *preset) const;
|
||||
|
||||
template<class F> void filter_presets(const Preset* printer, const std::string& type, const std::string& vendor, F cb) {
|
||||
for (auto preset : presets) {
|
||||
const Preset& prst = *(preset);
|
||||
const Preset& prntr = *printer;
|
||||
if ((printer == nullptr || is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) &&
|
||||
(type.empty() || get_type(preset) == type) &&
|
||||
(vendor.empty() || get_vendor(preset) == vendor)) {
|
||||
|
||||
cb(preset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
static const std::string& get_material_type(const Preset *preset);
|
||||
static const std::string& get_material_vendor(const Preset *preset);
|
||||
};
|
||||
|
||||
|
||||
struct BugPrinterPickerEvent;
|
||||
|
||||
// GUI elements
|
||||
|
||||
typedef std::function<bool(const VendorProfile::PrinterModel&)> BugModelFilter;
|
||||
|
||||
struct BugPrinterPicker: wxPanel
|
||||
{
|
||||
struct Checkbox : wxCheckBox
|
||||
{
|
||||
Checkbox(wxWindow *parent, const wxString &label, const std::string &model, const std::string &variant) :
|
||||
wxCheckBox(parent, wxID_ANY, label),
|
||||
model(model),
|
||||
variant(variant)
|
||||
{}
|
||||
|
||||
std::string model;
|
||||
std::string variant;
|
||||
};
|
||||
|
||||
const std::string vendor_id;
|
||||
std::vector<Checkbox*> cboxes;
|
||||
std::vector<Checkbox*> cboxes_alt;
|
||||
|
||||
BugPrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const BugModelFilter &filter);
|
||||
BugPrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig);
|
||||
|
||||
void select_all(bool select, bool alternates = false);
|
||||
void select_one(size_t i, bool select);
|
||||
bool any_selected() const;
|
||||
std::set<std::string> get_selected_models() const ;
|
||||
|
||||
int get_width() const { return width; }
|
||||
const std::vector<int>& get_button_indexes() { return m_button_indexes; }
|
||||
|
||||
static const std::string PRINTER_PLACEHOLDER;
|
||||
private:
|
||||
int width;
|
||||
std::vector<int> m_button_indexes;
|
||||
|
||||
void on_checkbox(const Checkbox *cbox, bool checked);
|
||||
};
|
||||
|
||||
struct BugWizardPage: wxPanel
|
||||
{
|
||||
BugWizard *parent;
|
||||
const wxString shortname;
|
||||
wxBoxSizer *content;
|
||||
const unsigned indent;
|
||||
|
||||
BugWizardPage(BugWizard *parent, wxString title, wxString shortname, unsigned indent = 0);
|
||||
virtual ~BugWizardPage();
|
||||
|
||||
template<class T>
|
||||
T* append(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10)
|
||||
{
|
||||
content->Add(thing, proportion, flag, border);
|
||||
return thing;
|
||||
}
|
||||
|
||||
wxStaticText* append_text(wxString text);
|
||||
void append_spacer(int space);
|
||||
|
||||
BugWizard::priv *wizard_p() const { return parent->p.get(); }
|
||||
|
||||
virtual void apply_custom_config(DynamicPrintConfig &config) {}
|
||||
virtual void set_run_reason(BugWizard::BugRunReason run_reason) {}
|
||||
virtual void on_activate() {}
|
||||
};
|
||||
|
||||
struct BugPageWelcome: BugWizardPage
|
||||
{
|
||||
wxStaticText *welcome_text;
|
||||
wxCheckBox *cbox_reset;
|
||||
wxCheckBox *cbox_integrate;
|
||||
|
||||
BugPageWelcome(BugWizard *parent);
|
||||
|
||||
bool reset_user_profile() const { return cbox_reset != nullptr ? cbox_reset->GetValue() : false; }
|
||||
bool integrate_desktop() const { return cbox_integrate != nullptr ? cbox_integrate->GetValue() : false; }
|
||||
|
||||
virtual void set_run_reason(BugWizard::BugRunReason run_reason) override;
|
||||
};
|
||||
|
||||
struct BugPagePrinters: BugWizardPage
|
||||
{
|
||||
std::vector<BugPrinterPicker *> printer_pickers;
|
||||
BugTechnology technology;
|
||||
bool install;
|
||||
|
||||
BugPagePrinters(BugWizard *parent,
|
||||
wxString title,
|
||||
wxString shortname,
|
||||
const VendorProfile &vendor,
|
||||
unsigned indent, BugTechnology technology);
|
||||
|
||||
void select_all(bool select, bool alternates = false);
|
||||
int get_width() const;
|
||||
bool any_selected() const;
|
||||
std::set<std::string> get_selected_models();
|
||||
|
||||
std::string get_vendor_id() const { return printer_pickers.empty() ? "" : printer_pickers[0]->vendor_id; }
|
||||
|
||||
virtual void set_run_reason(BugWizard::BugRunReason run_reason) override;
|
||||
|
||||
bool has_printers { false };
|
||||
bool is_primary_printer_page { false };
|
||||
};
|
||||
|
||||
// Here we extend wxListBox and wxCheckListBox
|
||||
// to make the client data API much easier to use.
|
||||
template<class T, class D> struct DataList : public T
|
||||
{
|
||||
DataList(wxWindow *parent) : T(parent, wxID_ANY) {}
|
||||
DataList(wxWindow* parent, int style) : T(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, style) {}
|
||||
|
||||
// Note: We're _not_ using wxLB_SORT here because it doesn't do the right thing,
|
||||
// eg. "ABS" is sorted before "(All)"
|
||||
|
||||
int append(const std::string &label, const D *data) {
|
||||
void *ptr = reinterpret_cast<void*>(const_cast<D*>(data));
|
||||
return this->Append(from_u8(label), ptr);
|
||||
}
|
||||
|
||||
int append(const wxString &label, const D *data) {
|
||||
void *ptr = reinterpret_cast<void*>(const_cast<D*>(data));
|
||||
return this->Append(label, ptr);
|
||||
}
|
||||
|
||||
const D& get_data(int n) {
|
||||
return *reinterpret_cast<const D*>(this->GetClientData(n));
|
||||
}
|
||||
|
||||
int find(const D &data) {
|
||||
for (unsigned i = 0; i < this->GetCount(); i++) {
|
||||
if (get_data(i) == data) { return i; }
|
||||
}
|
||||
|
||||
return wxNOT_FOUND;
|
||||
}
|
||||
|
||||
int size() { return this->GetCount(); }
|
||||
|
||||
void on_mouse_move(const wxPoint& position) {
|
||||
int item = T::HitTest(position);
|
||||
|
||||
if(item == wxHitTest::wxHT_WINDOW_INSIDE)
|
||||
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_INSIDE";
|
||||
else if (item == wxHitTest::wxHT_WINDOW_OUTSIDE)
|
||||
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_OUTSIDE";
|
||||
else if(item == wxHitTest::wxHT_WINDOW_CORNER)
|
||||
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_CORNER";
|
||||
else if (item == wxHitTest::wxHT_WINDOW_VERT_SCROLLBAR)
|
||||
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_VERT_SCROLLBAR";
|
||||
else if (item == wxHitTest::wxHT_NOWHERE)
|
||||
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_NOWHERE";
|
||||
else if (item == wxHitTest::wxHT_MAX)
|
||||
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_MAX";
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(error) << "hit test: " << item;
|
||||
}
|
||||
};
|
||||
|
||||
typedef DataList<wxListBox, std::string> BugStringList;
|
||||
typedef DataList<wxCheckListBox, std::string> BugPresetList;
|
||||
|
||||
struct BugProfilePrintData
|
||||
{
|
||||
std::reference_wrapper<const std::string> name;
|
||||
bool omnipresent;
|
||||
bool checked;
|
||||
BugProfilePrintData(const std::string& n, bool o, bool c) : name(n), omnipresent(o), checked(c) {}
|
||||
};
|
||||
|
||||
struct BugPageMaterials: BugWizardPage
|
||||
{
|
||||
BugMaterials *materials;
|
||||
BugStringList *list_printer, *list_type, *list_vendor;
|
||||
BugPresetList *list_profile;
|
||||
wxArrayInt sel_printers_prev;
|
||||
int sel_type_prev, sel_vendor_prev;
|
||||
bool presets_loaded;
|
||||
|
||||
wxFlexGridSizer *grid;
|
||||
wxHtmlWindow* html_window;
|
||||
|
||||
int compatible_printers_width = { 100 };
|
||||
std::string empty_printers_label;
|
||||
bool first_paint = { false };
|
||||
static const std::string EMPTY;
|
||||
int last_hovered_item = { -1 } ;
|
||||
|
||||
BugPageMaterials(BugWizard *parent, BugMaterials *materials, wxString title, wxString shortname, wxString list1name);
|
||||
|
||||
void reload_presets();
|
||||
void update_lists(int sel_type, int sel_vendor, int last_selected_printer = -1);
|
||||
void on_material_highlighted(int sel_material);
|
||||
void on_material_hovered(int sel_material);
|
||||
void select_material(int i);
|
||||
void select_all(bool select);
|
||||
void clear();
|
||||
void set_compatible_printers_html_window(const std::vector<std::string>& printer_names, bool all_printers = false);
|
||||
void clear_compatible_printers_label();
|
||||
|
||||
void sort_list_data(BugStringList* list, bool add_All_item, bool material_type_ordering);
|
||||
void sort_list_data(BugPresetList* list, const std::vector<BugProfilePrintData>& data);
|
||||
|
||||
void on_paint();
|
||||
void on_mouse_move_on_profiles(wxMouseEvent& evt);
|
||||
void on_mouse_enter_profiles(wxMouseEvent& evt);
|
||||
void on_mouse_leave_profiles(wxMouseEvent& evt);
|
||||
virtual void on_activate() override;
|
||||
};
|
||||
|
||||
struct BugPageCustom: BugWizardPage
|
||||
{
|
||||
BugPageCustom(BugWizard *parent);
|
||||
|
||||
bool custom_wanted() const { return cb_custom->GetValue(); }
|
||||
std::string profile_name() const { return into_u8(tc_profile_name->GetValue()); }
|
||||
|
||||
private:
|
||||
static const char* default_profile_name;
|
||||
|
||||
wxCheckBox *cb_custom;
|
||||
wxTextCtrl *tc_profile_name;
|
||||
wxString profile_name_prev;
|
||||
|
||||
};
|
||||
|
||||
struct BugPageUpdate: BugWizardPage
|
||||
{
|
||||
bool version_check;
|
||||
bool preset_update;
|
||||
|
||||
BugPageUpdate(BugWizard *parent);
|
||||
};
|
||||
|
||||
struct BugPageReloadFromDisk : BugWizardPage
|
||||
{
|
||||
bool full_pathnames;
|
||||
|
||||
BugPageReloadFromDisk(BugWizard* parent);
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
struct BugPageFilesAssociation : BugWizardPage
|
||||
{
|
||||
private:
|
||||
wxCheckBox* cb_3mf{ nullptr };
|
||||
wxCheckBox* cb_stl{ nullptr };
|
||||
// wxCheckBox* cb_gcode;
|
||||
|
||||
public:
|
||||
BugPageFilesAssociation(BugWizard* parent);
|
||||
|
||||
bool associate_3mf() const { return cb_3mf->IsChecked(); }
|
||||
bool associate_stl() const { return cb_stl->IsChecked(); }
|
||||
// bool associate_gcode() const { return cb_gcode->IsChecked(); }
|
||||
};
|
||||
#endif // _WIN32
|
||||
|
||||
struct BugPageMode: BugWizardPage
|
||||
{
|
||||
wxRadioButton *radio_simple;
|
||||
wxRadioButton *radio_advanced;
|
||||
wxRadioButton *radio_expert;
|
||||
|
||||
wxCheckBox *check_inch;
|
||||
|
||||
BugPageMode(BugWizard *parent);
|
||||
|
||||
void serialize_mode(AppConfig *app_config) const;
|
||||
|
||||
virtual void on_activate();
|
||||
};
|
||||
|
||||
struct BugPageVendors: BugWizardPage
|
||||
{
|
||||
BugPageVendors(BugWizard *parent);
|
||||
};
|
||||
|
||||
struct BugPageFirmware: BugWizardPage
|
||||
{
|
||||
const ConfigOptionDef &gcode_opt;
|
||||
wxChoice *gcode_picker;
|
||||
|
||||
BugPageFirmware(BugWizard *parent);
|
||||
virtual void apply_custom_config(DynamicPrintConfig &config);
|
||||
};
|
||||
|
||||
struct BugPageBedShape: BugWizardPage
|
||||
{
|
||||
BedShapePanel *shape_panel;
|
||||
|
||||
BugPageBedShape(BugWizard *parent);
|
||||
virtual void apply_custom_config(DynamicPrintConfig &config);
|
||||
};
|
||||
|
||||
struct BugPageDiameters: BugWizardPage
|
||||
{
|
||||
wxTextCtrl *diam_nozzle;
|
||||
wxTextCtrl *diam_filam;
|
||||
|
||||
BugPageDiameters(BugWizard *parent);
|
||||
virtual void apply_custom_config(DynamicPrintConfig &config);
|
||||
};
|
||||
|
||||
struct BugPageTemperatures: BugWizardPage
|
||||
{
|
||||
wxSpinCtrlDouble *spin_extr;
|
||||
wxSpinCtrlDouble *spin_bed;
|
||||
|
||||
BugPageTemperatures(BugWizard *parent);
|
||||
virtual void apply_custom_config(DynamicPrintConfig &config);
|
||||
};
|
||||
|
||||
// hypothetically, each vendor can has printers both of technologies (FFF and SLA)
|
||||
typedef std::map<std::string /* = vendor ID */,
|
||||
std::pair<BugPagePrinters* /* = FFF page */,
|
||||
BugPagePrinters* /* = SLA page */>> BugPages3rdparty;
|
||||
|
||||
|
||||
class BugWizardIndex: public wxPanel
|
||||
{
|
||||
public:
|
||||
BugWizardIndex(wxWindow *parent);
|
||||
|
||||
void add_page(BugWizardPage *page);
|
||||
void add_label(wxString label, unsigned indent = 0);
|
||||
|
||||
size_t active_item() const { return item_active; }
|
||||
BugWizardPage* active_page() const;
|
||||
bool active_is_last() const { return item_active < items.size() && item_active == last_page; }
|
||||
|
||||
void go_prev();
|
||||
void go_next();
|
||||
void go_to(size_t i);
|
||||
void go_to(const BugWizardPage *page);
|
||||
|
||||
void clear();
|
||||
void msw_rescale();
|
||||
|
||||
int em() const { return em_w; }
|
||||
|
||||
static const size_t NO_ITEM = size_t(-1);
|
||||
private:
|
||||
struct Item
|
||||
{
|
||||
wxString label;
|
||||
unsigned indent;
|
||||
BugWizardPage *page; // nullptr page => label-only item
|
||||
|
||||
bool operator==(BugWizardPage *page) const { return this->page == page; }
|
||||
};
|
||||
|
||||
int em_w;
|
||||
int em_h;
|
||||
ScalableBitmap bg;
|
||||
ScalableBitmap bullet_black;
|
||||
ScalableBitmap bullet_blue;
|
||||
ScalableBitmap bullet_white;
|
||||
|
||||
std::vector<Item> items;
|
||||
size_t item_active;
|
||||
ssize_t item_hover;
|
||||
size_t last_page;
|
||||
|
||||
int item_height() const { return std::max(bullet_black.GetHeight(), em_w) + em_w; }
|
||||
//int item_height() const { return std::max(bullet_black.bmp().GetSize().GetHeight(), em_w) + em_w; }
|
||||
|
||||
void on_paint(wxPaintEvent &evt);
|
||||
void on_mouse_move(wxMouseEvent &evt);
|
||||
};
|
||||
|
||||
wxDEFINE_EVENT(EVT_INDEX_PAGE, wxCommandEvent);
|
||||
|
||||
|
||||
|
||||
// BugWizard private data
|
||||
|
||||
typedef std::map<std::string, std::set<std::string>> BugPresetAliases;
|
||||
|
||||
struct BugWizard::priv
|
||||
{
|
||||
BugWizard *q;
|
||||
BugWizard::BugRunReason run_reason = RR_USER;
|
||||
AppConfig appconfig_new; // Backing for vendor/model/variant and material selections in the GUI
|
||||
BugBundleMap bundles; // Holds all loaded config bundles, the key is the vendor names.
|
||||
// BugMaterials refers to Presets in those bundles by pointers.
|
||||
// Also we update the is_visible flag in printer Presets according to the
|
||||
// BugPrinterPickers state.
|
||||
BugMaterials filaments; // Holds available filament presets and their types & vendors
|
||||
BugMaterials sla_materials; // Ditto for SLA materials
|
||||
BugPresetAliases aliases_fff; // Map of aliase to preset names
|
||||
BugPresetAliases 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 BugMaterials page
|
||||
bool custom_printer_selected { false };
|
||||
// Set to true if there are none FFF printers on the main FFF page. If true, only SLA printers are shown (not even custum printers)
|
||||
bool only_sla_mode { false };
|
||||
|
||||
wxScrolledWindow *hscroll = nullptr;
|
||||
wxBoxSizer *hscroll_sizer = nullptr;
|
||||
wxBoxSizer *btnsizer = nullptr;
|
||||
BugWizardPage *page_current = nullptr;
|
||||
BugWizardIndex *index = nullptr;
|
||||
wxButton *btn_sel_all = nullptr;
|
||||
wxButton *btn_prev = nullptr;
|
||||
wxButton *btn_next = nullptr;
|
||||
wxButton *btn_finish = nullptr;
|
||||
wxButton *btn_cancel = nullptr;
|
||||
wxStaticText *head_label = nullptr;
|
||||
|
||||
BugPageWelcome *page_welcome = nullptr;
|
||||
BugPagePrinters *page_fff = nullptr;
|
||||
BugPagePrinters *page_msla = nullptr;
|
||||
BugPageMaterials *page_filaments = nullptr;
|
||||
BugPageMaterials *page_sla_materials = nullptr;
|
||||
BugPageCustom *page_custom = nullptr;
|
||||
BugPageUpdate *page_update = nullptr;
|
||||
BugPageReloadFromDisk *page_reload_from_disk = nullptr;
|
||||
#ifdef _WIN32
|
||||
BugPageFilesAssociation* page_files_association = nullptr;
|
||||
#endif // _WIN32
|
||||
BugPageMode *page_mode = nullptr;
|
||||
BugPageVendors *page_vendors = nullptr;
|
||||
BugPages3rdparty pages_3rdparty;
|
||||
|
||||
// Custom setup pages
|
||||
BugPageFirmware *page_firmware = nullptr;
|
||||
BugPageBedShape *page_bed = nullptr;
|
||||
BugPageDiameters *page_diams = nullptr;
|
||||
BugPageTemperatures *page_temps = nullptr;
|
||||
|
||||
// Pointers to all pages (regardless or whether currently part of the BugWizardIndex)
|
||||
std::vector<BugWizardPage*> all_pages;
|
||||
|
||||
priv(BugWizard *q)
|
||||
: q(q)
|
||||
, appconfig_new(AppConfig::EAppMode::Editor)
|
||||
, filaments(T_FFF)
|
||||
, sla_materials(T_SLA)
|
||||
{}
|
||||
|
||||
void load_pages();
|
||||
void init_dialog_size();
|
||||
|
||||
void load_vendors();
|
||||
void add_page(BugWizardPage *page);
|
||||
void enable_next(bool enable);
|
||||
void set_start_page(BugWizard::BugStartPage start_page);
|
||||
void create_3rdparty_pages();
|
||||
void set_run_reason(BugRunReason run_reason);
|
||||
void update_materials(BugTechnology technology);
|
||||
|
||||
void on_custom_setup(const bool custom_wanted);
|
||||
void on_printer_pick(BugPagePrinters *page, const BugPrinterPickerEvent &evt);
|
||||
void select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, BugTechnology technology);
|
||||
void select_default_materials_for_printer_models(BugTechnology technology, const std::set<const VendorProfile::PrinterModel*> &printer_models);
|
||||
void on_3rdparty_install(const VendorProfile *vendor, bool install);
|
||||
|
||||
bool on_bnt_finish();
|
||||
bool check_and_install_missing_materials(BugTechnology technology, const std::string &only_for_model_id = std::string());
|
||||
bool apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes);
|
||||
// #ys_FIXME_alise
|
||||
void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add);
|
||||
#ifdef __linux__
|
||||
void perform_desktop_integration() const;
|
||||
#endif
|
||||
bool check_fff_selected(); // Used to decide whether to display Filaments page
|
||||
bool check_sla_selected(); // Used to decide whether to display SLA BugMaterials page
|
||||
|
||||
int em() const { return index->em(); }
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
255
src/slic3r/GUI/ButtonsDescription.cpp
Normal file
255
src/slic3r/GUI/ButtonsDescription.cpp
Normal file
@@ -0,0 +1,255 @@
|
||||
#include "ButtonsDescription.hpp"
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/string.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/statbmp.h>
|
||||
#include <wx/clrpicker.h>
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "OptionsGroup.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "BitmapCache.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
//static ModePaletteComboBox::PalettesMap MODE_PALETTES =
|
||||
static std::vector<std::pair<std::string, std::vector<std::string>>> MODE_PALETTES =
|
||||
{
|
||||
{ L("Palette 1 (default)"), { "#00B000", "#FFDC00", "#E70000" } },
|
||||
{ L("Palette 2"), { "#FC766A", "#B0B8B4", "#184A45" } },
|
||||
{ L("Palette 3"), { "#567572", "#964F4C", "#696667" } },
|
||||
{ L("Palette 4"), { "#DA291C", "#56A8CB", "#53A567" } },
|
||||
{ L("Palette 5"), { "#F65058", "#FBDE44", "#28334A" } },
|
||||
{ L("Palette 6"), { "#FF3EA5", "#EDFF00", "#00A4CC" } },
|
||||
{ L("Palette 7"), { "#E95C20", "#006747", "#4F2C1D" } },
|
||||
{ L("Palette 8"), { "#D9514E", "#2A2B2D", "#2DA8D8" } }
|
||||
};
|
||||
|
||||
ModePaletteComboBox::ModePaletteComboBox(wxWindow* parent) :
|
||||
BitmapComboBox(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxCB_READONLY)
|
||||
{
|
||||
for (const auto& palette : MODE_PALETTES)
|
||||
Append(_(palette.first), *get_bmp(palette.second));
|
||||
}
|
||||
|
||||
void ModePaletteComboBox::UpdateSelection(const std::vector<wxColour> &palette_in)
|
||||
{
|
||||
for (size_t idx = 0; idx < MODE_PALETTES.size(); ++idx ) {
|
||||
const auto& palette = MODE_PALETTES[idx].second;
|
||||
|
||||
bool is_selected = true;
|
||||
for (size_t mode = 0; mode < palette_in.size(); mode++)
|
||||
if (wxColour(palette[mode]) != palette_in[mode]) {
|
||||
is_selected = false;
|
||||
break;
|
||||
}
|
||||
if (is_selected) {
|
||||
Select(int(idx));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Select(-1);
|
||||
}
|
||||
|
||||
BitmapCache& ModePaletteComboBox::bitmap_cache()
|
||||
{
|
||||
static BitmapCache bmps;
|
||||
return bmps;
|
||||
}
|
||||
|
||||
wxBitmapBundle * ModePaletteComboBox::get_bmp(const std::vector<std::string> &palette)
|
||||
{
|
||||
std::string bitmap_key;
|
||||
for (const auto& color : palette)
|
||||
bitmap_key += color + "+";
|
||||
|
||||
const int icon_height = wxOSX ? 10 : 12;
|
||||
|
||||
wxBitmapBundle* bmp_bndl = bitmap_cache().find_bndl(bitmap_key);
|
||||
if (bmp_bndl == nullptr) {
|
||||
// Create the bitmap with color bars.
|
||||
std::vector<wxBitmapBundle*> bmps;
|
||||
for (const auto& color : palette) {
|
||||
bmps.emplace_back(get_bmp_bundle("mode", icon_height, color));
|
||||
bmps.emplace_back(get_empty_bmp_bundle(wxOSX ? 5 : 6, icon_height));
|
||||
}
|
||||
bmp_bndl = bitmap_cache().insert_bndl(bitmap_key, bmps);
|
||||
}
|
||||
|
||||
return bmp_bndl;
|
||||
}
|
||||
|
||||
namespace GUI_Descriptions {
|
||||
|
||||
void FillSizerWithTextColorDescriptions(wxSizer* sizer, wxWindow* parent, wxColourPickerCtrl** sys_colour, wxColourPickerCtrl** mod_colour)
|
||||
{
|
||||
wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(3, 5, 5);
|
||||
sizer->Add(grid_sizer, 0, wxEXPAND);
|
||||
|
||||
auto add_color = [grid_sizer, parent](wxColourPickerCtrl** color_picker, const wxColour& color, const wxColour& def_color, wxString label_text) {
|
||||
// wrap the label_text to the max 80 characters
|
||||
if (label_text.Len() > 80) {
|
||||
size_t brack_pos = label_text.find_last_of(" ", 79);
|
||||
if (brack_pos > 0 && brack_pos < 80)
|
||||
label_text.insert(brack_pos + 1, "\n");
|
||||
}
|
||||
|
||||
auto sys_label = new wxStaticText(parent, wxID_ANY, label_text);
|
||||
sys_label->SetForegroundColour(color);
|
||||
|
||||
*color_picker = new wxColourPickerCtrl(parent, wxID_ANY, color);
|
||||
wxGetApp().UpdateDarkUI((*color_picker)->GetPickerCtrl(), true);
|
||||
(*color_picker)->Bind(wxEVT_COLOURPICKER_CHANGED, [color_picker, sys_label](wxCommandEvent&) {
|
||||
sys_label->SetForegroundColour((*color_picker)->GetColour());
|
||||
sys_label->Refresh();
|
||||
});
|
||||
|
||||
auto btn = new ScalableButton(parent, wxID_ANY, "undo");
|
||||
btn->SetToolTip(_L("Revert color to default"));
|
||||
btn->Bind(wxEVT_BUTTON, [sys_label, color_picker, def_color](wxEvent& event) {
|
||||
(*color_picker)->SetColour(def_color);
|
||||
sys_label->SetForegroundColour(def_color);
|
||||
sys_label->Refresh();
|
||||
});
|
||||
parent->Bind(wxEVT_UPDATE_UI, [color_picker, def_color](wxUpdateUIEvent& evt) {
|
||||
evt.Enable((*color_picker)->GetColour() != def_color);
|
||||
}, btn->GetId());
|
||||
|
||||
grid_sizer->Add(*color_picker, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
grid_sizer->Add(btn, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
grid_sizer->Add(sys_label, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
};
|
||||
|
||||
add_color(sys_colour, wxGetApp().get_label_clr_sys(), wxGetApp().get_label_default_clr_system(), _L("Value is the same as the system value"));
|
||||
add_color(mod_colour, wxGetApp().get_label_clr_modified(),wxGetApp().get_label_default_clr_modified(), _L("Value was changed and is not equal to the system value or the last saved preset"));
|
||||
}
|
||||
|
||||
void FillSizerWithModeColorDescriptions(
|
||||
wxSizer* sizer, wxWindow* parent,
|
||||
std::vector<wxColourPickerCtrl**> clr_pickers,
|
||||
std::vector<wxColour>& mode_palette)
|
||||
{
|
||||
const int margin = em_unit(parent);
|
||||
|
||||
auto palette_cb = new ModePaletteComboBox(parent);
|
||||
palette_cb->UpdateSelection(mode_palette);
|
||||
|
||||
palette_cb->Bind(wxEVT_COMBOBOX, [clr_pickers, &mode_palette](wxCommandEvent& evt) {
|
||||
const int selection = evt.GetSelection();
|
||||
if (selection < 0)
|
||||
return;
|
||||
const auto& palette = MODE_PALETTES[selection];
|
||||
for (int mode = 0; mode < 3; mode++)
|
||||
if (*clr_pickers[mode]) {
|
||||
wxColour clr = wxColour(palette.second[mode]);
|
||||
(*clr_pickers[mode])->SetColour(clr);
|
||||
mode_palette[mode] = clr;
|
||||
}
|
||||
});
|
||||
|
||||
wxBoxSizer* h_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
h_sizer->Add(new wxStaticText(parent, wxID_ANY, _L("Default palette for mode markers") + ": "), 0, wxALIGN_CENTER_VERTICAL);
|
||||
h_sizer->Add(palette_cb, 1, wxEXPAND);
|
||||
|
||||
sizer->Add(h_sizer, 0, wxEXPAND | wxBOTTOM, margin);
|
||||
|
||||
wxFlexGridSizer* grid_sizer = new wxFlexGridSizer(9, 5, 5);
|
||||
sizer->Add(grid_sizer, 0, wxEXPAND);
|
||||
|
||||
const std::vector<wxString> names = { _L("Simple"), _CTX("Advanced", "Mode"), _L("Expert") };
|
||||
|
||||
for (size_t mode = 0; mode < names.size(); ++mode) {
|
||||
wxColour& color = mode_palette[mode];
|
||||
|
||||
wxColourPickerCtrl** color_picker = clr_pickers[mode];
|
||||
*color_picker = new wxColourPickerCtrl(parent, wxID_ANY, color);
|
||||
wxGetApp().UpdateDarkUI((*color_picker)->GetPickerCtrl(), true);
|
||||
|
||||
(*color_picker)->Bind(wxEVT_COLOURPICKER_CHANGED, [color_picker, &color, palette_cb, &mode_palette](wxCommandEvent&) {
|
||||
const wxColour new_color = (*color_picker)->GetColour();
|
||||
if (new_color != color) {
|
||||
color = new_color;
|
||||
palette_cb->UpdateSelection(mode_palette);
|
||||
}
|
||||
});
|
||||
|
||||
wxColour def_color = color;
|
||||
auto btn = new ScalableButton(parent, wxID_ANY, "undo");
|
||||
btn->SetToolTip(_L("Revert color"));
|
||||
|
||||
btn->Bind(wxEVT_BUTTON, [color_picker, &color, def_color, palette_cb, &mode_palette](wxEvent& event) {
|
||||
color = def_color;
|
||||
(*color_picker)->SetColour(def_color);
|
||||
palette_cb->UpdateSelection(mode_palette);
|
||||
});
|
||||
parent->Bind(wxEVT_UPDATE_UI, [color_picker, def_color](wxUpdateUIEvent& evt) {
|
||||
evt.Enable((*color_picker)->GetColour() != def_color);
|
||||
}, btn->GetId());
|
||||
|
||||
grid_sizer->Add(*color_picker, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
grid_sizer->Add(btn, 0, wxALIGN_CENTRE_VERTICAL);
|
||||
grid_sizer->Add(new wxStaticText(parent, wxID_ANY, names[mode]), 0, wxALIGN_CENTRE_VERTICAL | wxRIGHT, 2*margin);
|
||||
}
|
||||
}
|
||||
|
||||
Dialog::Dialog(wxWindow* parent, const std::vector<ButtonEntry> &entries) :
|
||||
wxDialog(parent, wxID_ANY, _(L("Buttons And Text Colors Description")), wxDefaultPosition, wxDefaultSize),
|
||||
m_entries(entries)
|
||||
{
|
||||
wxGetApp().UpdateDarkUI(this);
|
||||
|
||||
auto grid_sizer = new wxFlexGridSizer(3, 20, 20);
|
||||
|
||||
auto main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
main_sizer->Add(grid_sizer, 0, wxEXPAND | wxALL, 20);
|
||||
|
||||
// Icon description
|
||||
for (const ButtonEntry &entry : m_entries)
|
||||
{
|
||||
auto icon = new wxStaticBitmap(this, wxID_ANY, entry.bitmap->bmp());
|
||||
grid_sizer->Add(icon, -1, wxALIGN_CENTRE_VERTICAL);
|
||||
auto description = new wxStaticText(this, wxID_ANY, _(entry.symbol));
|
||||
grid_sizer->Add(description, -1, wxALIGN_CENTRE_VERTICAL);
|
||||
description = new wxStaticText(this, wxID_ANY, _(entry.explanation));
|
||||
grid_sizer->Add(description, -1, wxALIGN_CENTRE_VERTICAL);
|
||||
}
|
||||
|
||||
// Text color description
|
||||
wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
GUI_Descriptions::FillSizerWithTextColorDescriptions(sizer, this, &sys_colour, &mod_colour);
|
||||
main_sizer->Add(sizer, 0, wxEXPAND | wxALL, 20);
|
||||
|
||||
// Mode color markers description
|
||||
mode_palette = wxGetApp().get_mode_palette();
|
||||
|
||||
wxSizer* mode_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
GUI_Descriptions::FillSizerWithModeColorDescriptions(mode_sizer, this, { &simple, &advanced, &expert }, mode_palette);
|
||||
main_sizer->Add(mode_sizer, 0, wxEXPAND | wxALL, 20);
|
||||
|
||||
auto buttons = CreateStdDialogButtonSizer(wxOK|wxCANCEL);
|
||||
main_sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
|
||||
|
||||
wxButton* btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
|
||||
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
|
||||
wxGetApp().set_label_clr_sys(sys_colour->GetColour());
|
||||
wxGetApp().set_label_clr_modified(mod_colour->GetColour());
|
||||
wxGetApp().set_mode_palette(mode_palette);
|
||||
|
||||
EndModal(wxID_OK);
|
||||
});
|
||||
|
||||
wxGetApp().UpdateDarkUI(btn);
|
||||
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(FindWindowById(wxID_CANCEL, this)));
|
||||
|
||||
SetSizer(main_sizer);
|
||||
main_sizer->SetSizeHints(this);
|
||||
}
|
||||
|
||||
} // GUI_Descriptions
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
77
src/slic3r/GUI/ButtonsDescription.hpp
Normal file
77
src/slic3r/GUI/ButtonsDescription.hpp
Normal file
@@ -0,0 +1,77 @@
|
||||
#ifndef slic3r_ButtonsDescription_hpp
|
||||
#define slic3r_ButtonsDescription_hpp
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <vector>
|
||||
|
||||
#include <wx/bmpbndl.h>
|
||||
|
||||
#include "BitmapComboBox.hpp"
|
||||
|
||||
class ScalableBitmap;
|
||||
class wxColourPickerCtrl;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class BitmapCache;
|
||||
|
||||
// ---------------------------------
|
||||
// *** PaletteComboBox ***
|
||||
// ---------------------------------
|
||||
|
||||
// BitmapComboBox used to palets list in GUI Preferences
|
||||
class ModePaletteComboBox : public BitmapComboBox
|
||||
{
|
||||
public:
|
||||
ModePaletteComboBox(wxWindow* parent);
|
||||
~ModePaletteComboBox() = default;
|
||||
|
||||
void UpdateSelection(const std::vector<wxColour>& palette_in);
|
||||
|
||||
protected:
|
||||
// Caching bitmaps for the all bitmaps, used in preset comboboxes
|
||||
static BitmapCache& bitmap_cache();
|
||||
wxBitmapBundle* get_bmp( const std::vector<std::string>& palette);
|
||||
};
|
||||
|
||||
namespace GUI_Descriptions {
|
||||
|
||||
struct ButtonEntry {
|
||||
ButtonEntry(ScalableBitmap *bitmap, const std::string &symbol, const std::string &explanation) : bitmap(bitmap), symbol(symbol), explanation(explanation) {}
|
||||
|
||||
ScalableBitmap *bitmap;
|
||||
std::string symbol;
|
||||
std::string explanation;
|
||||
};
|
||||
|
||||
class Dialog : public wxDialog
|
||||
{
|
||||
std::vector<ButtonEntry> m_entries;
|
||||
|
||||
wxColourPickerCtrl* sys_colour{ nullptr };
|
||||
wxColourPickerCtrl* mod_colour{ nullptr };
|
||||
|
||||
wxColourPickerCtrl* simple { nullptr };
|
||||
wxColourPickerCtrl* advanced { nullptr };
|
||||
wxColourPickerCtrl* expert { nullptr };
|
||||
|
||||
std::vector<wxColour> mode_palette;
|
||||
public:
|
||||
|
||||
Dialog(wxWindow* parent, const std::vector<ButtonEntry> &entries);
|
||||
~Dialog() {}
|
||||
};
|
||||
|
||||
extern void FillSizerWithTextColorDescriptions(wxSizer* sizer, wxWindow* parent, wxColourPickerCtrl** sys_colour, wxColourPickerCtrl** mod_colour);
|
||||
extern void FillSizerWithModeColorDescriptions(wxSizer* sizer, wxWindow* parent,
|
||||
std::vector<wxColourPickerCtrl**> clr_pickers,
|
||||
std::vector<wxColour>& mode_palette);
|
||||
} // GUI_Descriptions
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
603
src/slic3r/GUI/Camera.cpp
Normal file
603
src/slic3r/GUI/Camera.cpp
Normal file
@@ -0,0 +1,603 @@
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
|
||||
#include "Camera.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#if ENABLE_CAMERA_STATISTICS
|
||||
#include "Mouse3DController.hpp"
|
||||
#include "Plater.hpp"
|
||||
#endif // ENABLE_CAMERA_STATISTICS
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
const double Camera::DefaultDistance = 1000.0;
|
||||
const double Camera::DefaultZoomToBoxMarginFactor = 1.025;
|
||||
const double Camera::DefaultZoomToVolumesMarginFactor = 1.025;
|
||||
double Camera::FrustrumMinZRange = 50.0;
|
||||
double Camera::FrustrumMinNearZ = 100.0;
|
||||
double Camera::FrustrumZMargin = 10.0;
|
||||
double Camera::MaxFovDeg = 60.0;
|
||||
|
||||
std::string Camera::get_type_as_string() const
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
case EType::Unknown: return "unknown";
|
||||
case EType::Perspective: return "perspective";
|
||||
default:
|
||||
case EType::Ortho: return "orthographic";
|
||||
};
|
||||
}
|
||||
|
||||
void Camera::set_type(EType type)
|
||||
{
|
||||
if (m_type != type && (type == EType::Ortho || type == EType::Perspective)) {
|
||||
m_type = type;
|
||||
if (m_update_config_on_type_change_enabled)
|
||||
wxGetApp().app_config->set("use_perspective_camera", (m_type == EType::Perspective) ? "1" : "0");
|
||||
}
|
||||
}
|
||||
|
||||
void Camera::select_next_type()
|
||||
{
|
||||
unsigned char next = (unsigned char)m_type + 1;
|
||||
if (next == (unsigned char)EType::Num_types)
|
||||
next = 1;
|
||||
|
||||
set_type((EType)next);
|
||||
}
|
||||
|
||||
void Camera::set_target(const Vec3d& target)
|
||||
{
|
||||
const Vec3d new_target = validate_target(target);
|
||||
const Vec3d new_displacement = new_target - m_target;
|
||||
if (!new_displacement.isApprox(Vec3d::Zero())) {
|
||||
m_target = new_target;
|
||||
m_view_matrix.translate(-new_displacement);
|
||||
}
|
||||
}
|
||||
|
||||
void Camera::set_zoom(double zoom)
|
||||
{
|
||||
// Don't allow to zoom too far outside the scene.
|
||||
const double zoom_min = min_zoom();
|
||||
if (zoom_min > 0.0)
|
||||
zoom = std::max(zoom, zoom_min);
|
||||
|
||||
// Don't allow to zoom too close to the scene.
|
||||
m_zoom = std::min(zoom, max_zoom());
|
||||
}
|
||||
|
||||
void Camera::select_view(const std::string& direction)
|
||||
{
|
||||
if (direction == "iso")
|
||||
set_default_orientation();
|
||||
else if (direction == "left")
|
||||
look_at(m_target - m_distance * Vec3d::UnitX(), m_target, Vec3d::UnitZ());
|
||||
else if (direction == "right")
|
||||
look_at(m_target + m_distance * Vec3d::UnitX(), m_target, Vec3d::UnitZ());
|
||||
else if (direction == "top")
|
||||
look_at(m_target + m_distance * Vec3d::UnitZ(), m_target, Vec3d::UnitY());
|
||||
else if (direction == "bottom")
|
||||
look_at(m_target - m_distance * Vec3d::UnitZ(), m_target, -Vec3d::UnitY());
|
||||
else if (direction == "front")
|
||||
look_at(m_target - m_distance * Vec3d::UnitY(), m_target, Vec3d::UnitZ());
|
||||
else if (direction == "rear")
|
||||
look_at(m_target + m_distance * Vec3d::UnitY(), m_target, Vec3d::UnitZ());
|
||||
}
|
||||
|
||||
double Camera::get_near_left() const
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
case EType::Perspective:
|
||||
return m_frustrum_zs.first * (m_projection_matrix.matrix()(0, 2) - 1.0) / m_projection_matrix.matrix()(0, 0);
|
||||
default:
|
||||
case EType::Ortho:
|
||||
return -1.0 / m_projection_matrix.matrix()(0, 0) - 0.5 * m_projection_matrix.matrix()(0, 0) * m_projection_matrix.matrix()(0, 3);
|
||||
}
|
||||
}
|
||||
|
||||
double Camera::get_near_right() const
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
case EType::Perspective:
|
||||
return m_frustrum_zs.first * (m_projection_matrix.matrix()(0, 2) + 1.0) / m_projection_matrix.matrix()(0, 0);
|
||||
default:
|
||||
case EType::Ortho:
|
||||
return 1.0 / m_projection_matrix.matrix()(0, 0) - 0.5 * m_projection_matrix.matrix()(0, 0) * m_projection_matrix.matrix()(0, 3);
|
||||
}
|
||||
}
|
||||
|
||||
double Camera::get_near_top() const
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
case EType::Perspective:
|
||||
return m_frustrum_zs.first * (m_projection_matrix.matrix()(1, 2) + 1.0) / m_projection_matrix.matrix()(1, 1);
|
||||
default:
|
||||
case EType::Ortho:
|
||||
return 1.0 / m_projection_matrix.matrix()(1, 1) - 0.5 * m_projection_matrix.matrix()(1, 1) * m_projection_matrix.matrix()(1, 3);
|
||||
}
|
||||
}
|
||||
|
||||
double Camera::get_near_bottom() const
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
case EType::Perspective:
|
||||
return m_frustrum_zs.first * (m_projection_matrix.matrix()(1, 2) - 1.0) / m_projection_matrix.matrix()(1, 1);
|
||||
default:
|
||||
case EType::Ortho:
|
||||
return -1.0 / m_projection_matrix.matrix()(1, 1) - 0.5 * m_projection_matrix.matrix()(1, 1) * m_projection_matrix.matrix()(1, 3);
|
||||
}
|
||||
}
|
||||
|
||||
double Camera::get_near_width() const
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
case EType::Perspective:
|
||||
return 2.0 * m_frustrum_zs.first / m_projection_matrix.matrix()(0, 0);
|
||||
default:
|
||||
case EType::Ortho:
|
||||
return 2.0 / m_projection_matrix.matrix()(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
double Camera::get_near_height() const
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
case EType::Perspective:
|
||||
return 2.0 * m_frustrum_zs.first / m_projection_matrix.matrix()(1, 1);
|
||||
default:
|
||||
case EType::Ortho:
|
||||
return 2.0 / m_projection_matrix.matrix()(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
double Camera::get_fov() const
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
case EType::Perspective:
|
||||
return 2.0 * Geometry::rad2deg(std::atan(1.0 / m_projection_matrix.matrix()(1, 1)));
|
||||
default:
|
||||
case EType::Ortho:
|
||||
return 0.0;
|
||||
};
|
||||
}
|
||||
|
||||
void Camera::set_viewport(int x, int y, unsigned int w, unsigned int h)
|
||||
{
|
||||
m_viewport = { 0, 0, int(w), int(h) };
|
||||
}
|
||||
|
||||
void Camera::apply_viewport() const
|
||||
{
|
||||
glsafe(::glViewport(m_viewport[0], m_viewport[1], m_viewport[2], m_viewport[3]));
|
||||
}
|
||||
|
||||
void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double far_z)
|
||||
{
|
||||
double w = 0.0;
|
||||
double h = 0.0;
|
||||
|
||||
m_frustrum_zs = calc_tight_frustrum_zs_around(box);
|
||||
|
||||
if (near_z > 0.0)
|
||||
m_frustrum_zs.first = std::max(std::min(m_frustrum_zs.first, near_z), FrustrumMinNearZ);
|
||||
|
||||
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];
|
||||
|
||||
const double inv_zoom = get_inv_zoom();
|
||||
w *= inv_zoom;
|
||||
h *= inv_zoom;
|
||||
|
||||
switch (m_type)
|
||||
{
|
||||
default:
|
||||
case EType::Ortho:
|
||||
{
|
||||
m_gui_scale = 1.0;
|
||||
break;
|
||||
}
|
||||
case EType::Perspective:
|
||||
{
|
||||
// scale near plane to keep w and h constant on the plane at z = m_distance
|
||||
const double scale = m_frustrum_zs.first / m_distance;
|
||||
w *= scale;
|
||||
h *= scale;
|
||||
m_gui_scale = scale;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
apply_projection(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second);
|
||||
}
|
||||
|
||||
void Camera::apply_projection(double left, double right, double bottom, double top, double near_z, double far_z)
|
||||
{
|
||||
assert(left != right && bottom != top && near_z != far_z);
|
||||
const double inv_dx = 1.0 / (right - left);
|
||||
const double inv_dy = 1.0 / (top - bottom);
|
||||
const double inv_dz = 1.0 / (far_z - near_z);
|
||||
|
||||
switch (m_type)
|
||||
{
|
||||
default:
|
||||
case EType::Ortho:
|
||||
{
|
||||
m_projection_matrix.matrix() << 2.0 * inv_dx, 0.0, 0.0, -(left + right) * inv_dx,
|
||||
0.0, 2.0 * inv_dy, 0.0, -(bottom + top) * inv_dy,
|
||||
0.0, 0.0, -2.0 * inv_dz, -(near_z + far_z) * inv_dz,
|
||||
0.0, 0.0, 0.0, 1.0;
|
||||
break;
|
||||
}
|
||||
case EType::Perspective:
|
||||
{
|
||||
m_projection_matrix.matrix() << 2.0 * near_z * inv_dx, 0.0, (left + right) * inv_dx, 0.0,
|
||||
0.0, 2.0 * near_z * inv_dy, (bottom + top) * inv_dy, 0.0,
|
||||
0.0, 0.0, -(near_z + far_z) * inv_dz, -2.0 * near_z * far_z * inv_dz,
|
||||
0.0, 0.0, -1.0, 0.0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Camera::zoom_to_box(const BoundingBoxf3& box, double margin_factor)
|
||||
{
|
||||
// Calculate the zoom factor needed to adjust the view around the given box.
|
||||
const double zoom = calc_zoom_to_bounding_box_factor(box, margin_factor);
|
||||
if (zoom > 0.0) {
|
||||
m_zoom = zoom;
|
||||
// center view around box center
|
||||
set_target(box.center());
|
||||
}
|
||||
}
|
||||
|
||||
void Camera::zoom_to_volumes(const GLVolumePtrs& volumes, double margin_factor)
|
||||
{
|
||||
Vec3d center;
|
||||
const double zoom = calc_zoom_to_volumes_factor(volumes, center, margin_factor);
|
||||
if (zoom > 0.0) {
|
||||
m_zoom = zoom;
|
||||
// center view around the calculated center
|
||||
set_target(center);
|
||||
}
|
||||
}
|
||||
|
||||
#if ENABLE_CAMERA_STATISTICS
|
||||
void Camera::debug_render() const
|
||||
{
|
||||
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
||||
imgui.begin(std::string("Camera statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
std::string type = get_type_as_string();
|
||||
if (wxGetApp().plater()->get_mouse3d_controller().connected() || (wxGetApp().app_config->get_bool("use_free_camera")))
|
||||
type += "/free";
|
||||
else
|
||||
type += "/constrained";
|
||||
|
||||
Vec3f position = get_position().cast<float>();
|
||||
Vec3f target = m_target.cast<float>();
|
||||
float distance = (float)get_distance();
|
||||
float zenit = (float)m_zenit;
|
||||
Vec3f forward = get_dir_forward().cast<float>();
|
||||
Vec3f right = get_dir_right().cast<float>();
|
||||
Vec3f up = get_dir_up().cast<float>();
|
||||
float nearZ = (float)m_frustrum_zs.first;
|
||||
float farZ = (float)m_frustrum_zs.second;
|
||||
float deltaZ = farZ - nearZ;
|
||||
float zoom = (float)m_zoom;
|
||||
float fov = (float)get_fov();
|
||||
std::array<int, 4>viewport = get_viewport();
|
||||
float gui_scale = (float)get_gui_scale();
|
||||
|
||||
ImGui::InputText("Type", type.data(), type.length(), ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::Separator();
|
||||
ImGui::InputFloat3("Position", position.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::InputFloat3("Target", target.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::InputFloat("Distance", &distance, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::Separator();
|
||||
ImGui::InputFloat("Zenit", &zenit, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::Separator();
|
||||
ImGui::InputFloat3("Forward", forward.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::InputFloat3("Right", right.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::InputFloat3("Up", up.data(), "%.6f", ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::Separator();
|
||||
ImGui::InputFloat("Near Z", &nearZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::InputFloat("Far Z", &farZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::InputFloat("Delta Z", &deltaZ, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::Separator();
|
||||
ImGui::InputFloat("Zoom", &zoom, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::InputFloat("Fov", &fov, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::Separator();
|
||||
ImGui::InputInt4("Viewport", viewport.data(), ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::Separator();
|
||||
ImGui::InputFloat("GUI scale", &gui_scale, 0.0f, 0.0f, "%.6f", ImGuiInputTextFlags_ReadOnly);
|
||||
imgui.end();
|
||||
}
|
||||
#endif // ENABLE_CAMERA_STATISTICS
|
||||
|
||||
void Camera::rotate_on_sphere(double delta_azimut_rad, double delta_zenit_rad, bool apply_limits)
|
||||
{
|
||||
m_zenit += Geometry::rad2deg(delta_zenit_rad);
|
||||
if (apply_limits) {
|
||||
if (m_zenit > 90.0f) {
|
||||
delta_zenit_rad -= Geometry::deg2rad(m_zenit - 90.0f);
|
||||
m_zenit = 90.0f;
|
||||
}
|
||||
else if (m_zenit < -90.0f) {
|
||||
delta_zenit_rad -= Geometry::deg2rad(m_zenit + 90.0f);
|
||||
m_zenit = -90.0f;
|
||||
}
|
||||
}
|
||||
|
||||
const Vec3d translation = m_view_matrix.translation() + m_view_rotation * m_target;
|
||||
const auto rot_z = Eigen::AngleAxisd(delta_azimut_rad, Vec3d::UnitZ());
|
||||
m_view_rotation *= rot_z * Eigen::AngleAxisd(delta_zenit_rad, rot_z.inverse() * get_dir_right());
|
||||
m_view_rotation.normalize();
|
||||
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (- m_target) + translation, m_view_rotation, Vec3d(1., 1., 1.));
|
||||
}
|
||||
|
||||
// Virtual trackball, rotate around an axis, where the eucledian norm of the axis gives the rotation angle in radians.
|
||||
void Camera::rotate_local_around_target(const Vec3d& rotation_rad)
|
||||
{
|
||||
const double angle = rotation_rad.norm();
|
||||
if (std::abs(angle) > EPSILON) {
|
||||
const Vec3d translation = m_view_matrix.translation() + m_view_rotation * m_target;
|
||||
const Vec3d axis = m_view_rotation.conjugate() * rotation_rad.normalized();
|
||||
m_view_rotation *= Eigen::Quaterniond(Eigen::AngleAxisd(angle, axis));
|
||||
m_view_rotation.normalize();
|
||||
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-m_target) + translation, m_view_rotation, Vec3d(1., 1., 1.));
|
||||
update_zenit();
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<double, double> Camera::calc_tight_frustrum_zs_around(const BoundingBoxf3& box)
|
||||
{
|
||||
std::pair<double, double> ret;
|
||||
auto& [near_z, far_z] = ret;
|
||||
|
||||
// box in eye space
|
||||
const BoundingBoxf3 eye_box = box.transformed(m_view_matrix);
|
||||
near_z = -eye_box.max.z();
|
||||
far_z = -eye_box.min.z();
|
||||
|
||||
// apply margin
|
||||
near_z -= FrustrumZMargin;
|
||||
far_z += FrustrumZMargin;
|
||||
|
||||
// ensure min size
|
||||
if (far_z - near_z < FrustrumMinZRange) {
|
||||
const double mid_z = 0.5 * (near_z + far_z);
|
||||
const double half_size = 0.5 * FrustrumMinZRange;
|
||||
near_z = mid_z - half_size;
|
||||
far_z = mid_z + half_size;
|
||||
}
|
||||
|
||||
if (near_z < FrustrumMinNearZ) {
|
||||
const double delta = FrustrumMinNearZ - near_z;
|
||||
set_distance(m_distance + delta);
|
||||
near_z += delta;
|
||||
far_z += delta;
|
||||
}
|
||||
// The following is commented out because it causes flickering of the 3D scene GUI
|
||||
// when the bounding box of the scene gets large enough
|
||||
// We need to introduce some smarter code to move the camera back and forth in such case
|
||||
// else if (near_z > 2.0 * FrustrumMinNearZ && m_distance > DefaultDistance) {
|
||||
// float delta = m_distance - DefaultDistance;
|
||||
// set_distance(DefaultDistance);
|
||||
// near_z -= delta;
|
||||
// far_z -= delta;
|
||||
// }
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, double margin_factor) const
|
||||
{
|
||||
const double max_bb_size = box.max_size();
|
||||
if (max_bb_size == 0.0)
|
||||
return -1.0;
|
||||
|
||||
// project the box vertices on a plane perpendicular to the camera forward axis
|
||||
// then calculates the vertices coordinate on this plane along the camera xy axes
|
||||
|
||||
const Vec3d right = get_dir_right();
|
||||
const Vec3d up = get_dir_up();
|
||||
const Vec3d forward = get_dir_forward();
|
||||
const Vec3d bb_center = box.center();
|
||||
|
||||
// box vertices in world space
|
||||
const std::vector<Vec3d> vertices = {
|
||||
box.min,
|
||||
{ box.max(0), box.min(1), box.min(2) },
|
||||
{ box.max(0), box.max(1), box.min(2) },
|
||||
{ box.min(0), box.max(1), box.min(2) },
|
||||
{ box.min(0), box.min(1), box.max(2) },
|
||||
{ box.max(0), box.min(1), box.max(2) },
|
||||
box.max,
|
||||
{ box.min(0), box.max(1), box.max(2) }
|
||||
};
|
||||
|
||||
double min_x = DBL_MAX;
|
||||
double min_y = DBL_MAX;
|
||||
double max_x = -DBL_MAX;
|
||||
double max_y = -DBL_MAX;
|
||||
|
||||
for (const Vec3d& v : vertices) {
|
||||
// project vertex on the plane perpendicular to camera forward axis
|
||||
const Vec3d pos = v - bb_center;
|
||||
const Vec3d proj_on_plane = pos - pos.dot(forward) * forward;
|
||||
|
||||
// calculates vertex coordinate along camera xy axes
|
||||
const double x_on_plane = proj_on_plane.dot(right);
|
||||
const double y_on_plane = proj_on_plane.dot(up);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
double dx = max_x - min_x;
|
||||
double dy = max_y - min_y;
|
||||
if (dx <= 0.0 || dy <= 0.0)
|
||||
return -1.0f;
|
||||
|
||||
dx *= margin_factor;
|
||||
dy *= margin_factor;
|
||||
|
||||
return std::min((double)m_viewport[2] / dx, (double)m_viewport[3] / dy);
|
||||
}
|
||||
|
||||
double Camera::calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& center, double margin_factor) const
|
||||
{
|
||||
if (volumes.empty())
|
||||
return -1.0;
|
||||
|
||||
// project the volumes vertices on a plane perpendicular to the camera forward axis
|
||||
// then calculates the vertices coordinate on this plane along the camera xy axes
|
||||
|
||||
const Vec3d right = get_dir_right();
|
||||
const Vec3d up = get_dir_up();
|
||||
const Vec3d forward = get_dir_forward();
|
||||
|
||||
BoundingBoxf3 box;
|
||||
for (const GLVolume* volume : volumes) {
|
||||
box.merge(volume->transformed_bounding_box());
|
||||
}
|
||||
center = box.center();
|
||||
|
||||
double min_x = DBL_MAX;
|
||||
double min_y = DBL_MAX;
|
||||
double max_x = -DBL_MAX;
|
||||
double max_y = -DBL_MAX;
|
||||
|
||||
for (const GLVolume* volume : volumes) {
|
||||
const Transform3d& transform = volume->world_matrix();
|
||||
const TriangleMesh* hull = volume->convex_hull();
|
||||
if (hull == nullptr)
|
||||
continue;
|
||||
|
||||
for (const Vec3f& vertex : hull->its.vertices) {
|
||||
const Vec3d v = transform * vertex.cast<double>();
|
||||
|
||||
// project vertex on the plane perpendicular to camera forward axis
|
||||
const Vec3d pos = v - center;
|
||||
const Vec3d proj_on_plane = pos - pos.dot(forward) * forward;
|
||||
|
||||
// calculates vertex coordinate along camera xy axes
|
||||
const double x_on_plane = proj_on_plane.dot(right);
|
||||
const double y_on_plane = proj_on_plane.dot(up);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
center += 0.5 * (max_x + min_x) * right + 0.5 * (max_y + min_y) * up;
|
||||
|
||||
const double dx = margin_factor * (max_x - min_x);
|
||||
const double dy = margin_factor * (max_y - min_y);
|
||||
|
||||
if (dx <= 0.0 || dy <= 0.0)
|
||||
return -1.0f;
|
||||
|
||||
return std::min((double)m_viewport[2] / dx, (double)m_viewport[3] / dy);
|
||||
}
|
||||
|
||||
void Camera::set_distance(double distance)
|
||||
{
|
||||
if (m_distance != distance) {
|
||||
m_view_matrix.translate((distance - m_distance) * get_dir_forward());
|
||||
m_distance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
void Camera::look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up)
|
||||
{
|
||||
const Vec3d unit_z = (position - target).normalized();
|
||||
const Vec3d unit_x = up.cross(unit_z).normalized();
|
||||
const Vec3d unit_y = unit_z.cross(unit_x).normalized();
|
||||
|
||||
m_target = target;
|
||||
m_distance = (position - target).norm();
|
||||
const Vec3d new_position = m_target + m_distance * unit_z;
|
||||
|
||||
m_view_matrix(0, 0) = unit_x.x();
|
||||
m_view_matrix(0, 1) = unit_x.y();
|
||||
m_view_matrix(0, 2) = unit_x.z();
|
||||
m_view_matrix(0, 3) = -unit_x.dot(new_position);
|
||||
|
||||
m_view_matrix(1, 0) = unit_y.x();
|
||||
m_view_matrix(1, 1) = unit_y.y();
|
||||
m_view_matrix(1, 2) = unit_y.z();
|
||||
m_view_matrix(1, 3) = -unit_y.dot(new_position);
|
||||
|
||||
m_view_matrix(2, 0) = unit_z.x();
|
||||
m_view_matrix(2, 1) = unit_z.y();
|
||||
m_view_matrix(2, 2) = unit_z.z();
|
||||
m_view_matrix(2, 3) = -unit_z.dot(new_position);
|
||||
|
||||
m_view_matrix(3, 0) = 0.0;
|
||||
m_view_matrix(3, 1) = 0.0;
|
||||
m_view_matrix(3, 2) = 0.0;
|
||||
m_view_matrix(3, 3) = 1.0;
|
||||
|
||||
// Initialize the rotation quaternion from the rotation submatrix of of m_view_matrix.
|
||||
m_view_rotation = Eigen::Quaterniond(m_view_matrix.matrix().template block<3, 3>(0, 0));
|
||||
m_view_rotation.normalize();
|
||||
|
||||
update_zenit();
|
||||
}
|
||||
|
||||
void Camera::set_default_orientation()
|
||||
{
|
||||
m_zenit = 45.0f;
|
||||
const double theta_rad = Geometry::deg2rad(-(double)m_zenit);
|
||||
//B3
|
||||
const double phi_rad = Geometry::deg2rad(0.0);
|
||||
// const double phi_rad = Geometry::deg2rad(45.0);
|
||||
const double sin_theta = ::sin(theta_rad);
|
||||
const Vec3d camera_pos = m_target + m_distance * Vec3d(sin_theta * ::sin(phi_rad), sin_theta * ::cos(phi_rad), ::cos(theta_rad));
|
||||
m_view_rotation = Eigen::AngleAxisd(theta_rad, Vec3d::UnitX()) * Eigen::AngleAxisd(phi_rad, Vec3d::UnitZ());
|
||||
m_view_rotation.normalize();
|
||||
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-camera_pos), m_view_rotation, Vec3d::Ones());
|
||||
}
|
||||
|
||||
Vec3d Camera::validate_target(const Vec3d& target) const
|
||||
{
|
||||
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());
|
||||
|
||||
return { std::clamp(target(0), test_box.min(0), test_box.max(0)),
|
||||
std::clamp(target(1), test_box.min(1), test_box.max(1)),
|
||||
std::clamp(target(2), test_box.min(2), test_box.max(2)) };
|
||||
}
|
||||
|
||||
void Camera::update_zenit()
|
||||
{
|
||||
m_zenit = Geometry::rad2deg(0.5 * M_PI - std::acos(std::clamp(-get_dir_forward().dot(Vec3d::UnitZ()), -1.0, 1.0)));
|
||||
}
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
157
src/slic3r/GUI/Camera.hpp
Normal file
157
src/slic3r/GUI/Camera.hpp
Normal file
@@ -0,0 +1,157 @@
|
||||
#ifndef slic3r_Camera_hpp_
|
||||
#define slic3r_Camera_hpp_
|
||||
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "3DScene.hpp"
|
||||
#include <array>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
struct Camera
|
||||
{
|
||||
static const double DefaultDistance;
|
||||
static const double DefaultZoomToBoxMarginFactor;
|
||||
static const double DefaultZoomToVolumesMarginFactor;
|
||||
static double FrustrumMinZRange;
|
||||
static double FrustrumMinNearZ;
|
||||
static double FrustrumZMargin;
|
||||
static double MaxFovDeg;
|
||||
|
||||
enum class EType : unsigned char
|
||||
{
|
||||
Unknown,
|
||||
Ortho,
|
||||
Perspective,
|
||||
Num_types
|
||||
};
|
||||
|
||||
bool requires_zoom_to_bed{ false };
|
||||
|
||||
private:
|
||||
EType m_type{ EType::Perspective };
|
||||
bool m_update_config_on_type_change_enabled{ false };
|
||||
Vec3d m_target{ Vec3d::Zero() };
|
||||
float m_zenit{ 45.0f };
|
||||
double m_zoom{ 1.0 };
|
||||
// Distance between camera position and camera target measured along the camera Z axis
|
||||
double m_distance{ DefaultDistance };
|
||||
double m_gui_scale{ 1.0 };
|
||||
|
||||
std::array<int, 4> m_viewport;
|
||||
Transform3d m_view_matrix{ Transform3d::Identity() };
|
||||
// We are calculating the rotation part of the m_view_matrix from m_view_rotation.
|
||||
Eigen::Quaterniond m_view_rotation{ 1.0, 0.0, 0.0, 0.0 };
|
||||
Transform3d m_projection_matrix{ Transform3d::Identity() };
|
||||
std::pair<double, double> m_frustrum_zs;
|
||||
|
||||
BoundingBoxf3 m_scene_box;
|
||||
|
||||
public:
|
||||
Camera() { set_default_orientation(); }
|
||||
|
||||
EType get_type() const { return m_type; }
|
||||
std::string get_type_as_string() const;
|
||||
void set_type(EType type);
|
||||
// valid values for type: "0" -> ortho, "1" -> perspective
|
||||
void set_type(const std::string& type) { set_type((type == "1") ? EType::Perspective : EType::Ortho); }
|
||||
void select_next_type();
|
||||
|
||||
void enable_update_config_on_type_change(bool enable) { m_update_config_on_type_change_enabled = enable; }
|
||||
|
||||
const Vec3d& get_target() const { return m_target; }
|
||||
void set_target(const Vec3d& target);
|
||||
|
||||
double get_distance() const { return (get_position() - m_target).norm(); }
|
||||
double get_gui_scale() const { return m_gui_scale; }
|
||||
|
||||
double get_zoom() const { return m_zoom; }
|
||||
double get_inv_zoom() const { assert(m_zoom != 0.0); return 1.0 / m_zoom; }
|
||||
void update_zoom(double delta_zoom) { set_zoom(m_zoom / (1.0 - std::max(std::min(delta_zoom, 4.0), -4.0) * 0.1)); }
|
||||
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; }
|
||||
|
||||
void select_view(const std::string& direction);
|
||||
|
||||
const std::array<int, 4>& get_viewport() const { return m_viewport; }
|
||||
const Transform3d& get_view_matrix() const { return m_view_matrix; }
|
||||
const Transform3d& get_projection_matrix() const { return m_projection_matrix; }
|
||||
|
||||
Vec3d get_dir_right() const { return m_view_matrix.matrix().block(0, 0, 3, 3).row(0); }
|
||||
Vec3d get_dir_up() const { return m_view_matrix.matrix().block(0, 0, 3, 3).row(1); }
|
||||
Vec3d get_dir_forward() const { return -m_view_matrix.matrix().block(0, 0, 3, 3).row(2); }
|
||||
|
||||
Vec3d get_position() const { return m_view_matrix.matrix().inverse().block(0, 3, 3, 1); }
|
||||
|
||||
double get_near_z() const { return m_frustrum_zs.first; }
|
||||
double get_far_z() const { return m_frustrum_zs.second; }
|
||||
const std::pair<double, double>& get_z_range() const { return m_frustrum_zs; }
|
||||
|
||||
double get_near_left() const;
|
||||
double get_near_right() const;
|
||||
double get_near_top() const;
|
||||
double get_near_bottom() const;
|
||||
double get_near_width() const;
|
||||
double get_near_height() const;
|
||||
|
||||
double get_fov() const;
|
||||
|
||||
void set_viewport(int x, int y, unsigned int w, unsigned int h);
|
||||
void apply_viewport() 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);
|
||||
void apply_projection(double left, double right, double bottom, double top, double near_z, double far_z);
|
||||
|
||||
void zoom_to_box(const BoundingBoxf3& box, double margin_factor = DefaultZoomToBoxMarginFactor);
|
||||
void zoom_to_volumes(const GLVolumePtrs& volumes, double margin_factor = DefaultZoomToVolumesMarginFactor);
|
||||
|
||||
#if ENABLE_CAMERA_STATISTICS
|
||||
void debug_render() const;
|
||||
#endif // ENABLE_CAMERA_STATISTICS
|
||||
|
||||
// translate the camera in world space
|
||||
void translate_world(const Vec3d& displacement) { set_target(m_target + displacement); }
|
||||
|
||||
// rotate the camera on a sphere having center == m_target and radius == m_distance
|
||||
// using the given variations of spherical coordinates
|
||||
// if apply_limits == true the camera stops rotating when its forward vector is parallel to the world Z axis
|
||||
void rotate_on_sphere(double delta_azimut_rad, double delta_zenit_rad, bool apply_limits);
|
||||
|
||||
// rotate the camera around three axes parallel to the camera local axes and passing through m_target
|
||||
void rotate_local_around_target(const Vec3d& rotation_rad);
|
||||
|
||||
// returns true if the camera z axis (forward) is pointing in the negative direction of the world z axis
|
||||
bool is_looking_downward() const { return get_dir_forward().dot(Vec3d::UnitZ()) < 0.0; }
|
||||
|
||||
// forces camera right vector to be parallel to XY plane
|
||||
void recover_from_free_camera() {
|
||||
if (std::abs(get_dir_right()(2)) > EPSILON)
|
||||
look_at(get_position(), m_target, Vec3d::UnitZ());
|
||||
}
|
||||
|
||||
void look_at(const Vec3d& position, const Vec3d& target, const Vec3d& up);
|
||||
|
||||
double max_zoom() const { return 250.0; }
|
||||
double min_zoom() const { return 0.7 * calc_zoom_to_bounding_box_factor(m_scene_box); }
|
||||
|
||||
private:
|
||||
// returns tight values for nearZ and farZ plane around the given bounding box
|
||||
// the camera MUST be outside of the bounding box in eye coordinate of the given box
|
||||
std::pair<double, double> calc_tight_frustrum_zs_around(const BoundingBoxf3& box);
|
||||
double calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, double margin_factor = DefaultZoomToBoxMarginFactor) const;
|
||||
double calc_zoom_to_volumes_factor(const GLVolumePtrs& volumes, Vec3d& center, double margin_factor = DefaultZoomToVolumesMarginFactor) const;
|
||||
void set_distance(double distance);
|
||||
|
||||
void set_default_orientation();
|
||||
Vec3d validate_target(const Vec3d& target) const;
|
||||
void update_zenit();
|
||||
};
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
#endif // slic3r_Camera_hpp_
|
||||
|
||||
131
src/slic3r/GUI/CameraUtils.cpp
Normal file
131
src/slic3r/GUI/CameraUtils.cpp
Normal file
@@ -0,0 +1,131 @@
|
||||
#include "CameraUtils.hpp"
|
||||
#include <igl/project.h> // projecting points
|
||||
#include <igl/unproject.h>
|
||||
|
||||
#include "slic3r/GUI/3DScene.hpp" // GLVolume
|
||||
#include "libslic3r/Geometry/ConvexHull.hpp"
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace GUI;
|
||||
|
||||
Points CameraUtils::project(const Camera & camera,
|
||||
const std::vector<Vec3d> &points)
|
||||
{
|
||||
Vec4i viewport(camera.get_viewport().data());
|
||||
|
||||
// Convert our std::vector to Eigen dynamic matrix.
|
||||
Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::DontAlign>
|
||||
pts(points.size(), 3);
|
||||
for (size_t i = 0; i < points.size(); ++i)
|
||||
pts.block<1, 3>(i, 0) = points[i];
|
||||
|
||||
// Get the projections.
|
||||
Eigen::Matrix<double, Eigen::Dynamic, 3, Eigen::DontAlign> projections;
|
||||
igl::project(pts, camera.get_view_matrix().matrix(),
|
||||
camera.get_projection_matrix().matrix(), viewport, projections);
|
||||
|
||||
Points result;
|
||||
result.reserve(points.size());
|
||||
int window_height = viewport[3];
|
||||
|
||||
// convert to points --> loss precision
|
||||
for (int i = 0; i < projections.rows(); ++i) {
|
||||
double x = projections(i, 0);
|
||||
double y = projections(i, 1);
|
||||
// opposit direction o Y
|
||||
result.emplace_back(x, window_height - y);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Point CameraUtils::project(const Camera &camera, const Vec3d &point)
|
||||
{
|
||||
// IMPROVE: do it faster when you need it (inspire in project multi point)
|
||||
return project(camera, std::vector{point}).front();
|
||||
}
|
||||
|
||||
Slic3r::Polygon CameraUtils::create_hull2d(const Camera & camera,
|
||||
const GLVolume &volume)
|
||||
{
|
||||
std::vector<Vec3d> vertices;
|
||||
const TriangleMesh *hull = volume.convex_hull();
|
||||
if (hull != nullptr) {
|
||||
const indexed_triangle_set &its = hull->its;
|
||||
vertices.reserve(its.vertices.size());
|
||||
// cast vector
|
||||
for (const Vec3f &vertex : its.vertices)
|
||||
vertices.emplace_back(vertex.cast<double>());
|
||||
} else {
|
||||
// Negative volume doesn't have convex hull so use bounding box
|
||||
auto bb = volume.bounding_box();
|
||||
Vec3d &min = bb.min;
|
||||
Vec3d &max = bb.max;
|
||||
vertices = {min,
|
||||
Vec3d(min.x(), min.y(), max.z()),
|
||||
Vec3d(min.x(), max.y(), min.z()),
|
||||
Vec3d(min.x(), max.y(), max.z()),
|
||||
Vec3d(max.x(), min.y(), min.z()),
|
||||
Vec3d(max.x(), min.y(), max.z()),
|
||||
Vec3d(max.x(), max.y(), min.z()),
|
||||
max};
|
||||
}
|
||||
|
||||
const Transform3d &trafoMat =
|
||||
volume.get_instance_transformation().get_matrix() *
|
||||
volume.get_volume_transformation().get_matrix();
|
||||
for (Vec3d &vertex : vertices)
|
||||
vertex = trafoMat * vertex.cast<double>();
|
||||
|
||||
Points vertices_2d = project(camera, vertices);
|
||||
return Geometry::convex_hull(vertices_2d);
|
||||
}
|
||||
|
||||
void CameraUtils::ray_from_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction) {
|
||||
switch (camera.get_type()) {
|
||||
case Camera::EType::Ortho: return ray_from_ortho_screen_pos(camera, position, point, direction);
|
||||
case Camera::EType::Perspective: return ray_from_persp_screen_pos(camera, position, point, direction);
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
Vec3d CameraUtils::screen_point(const Camera &camera, const Vec2d &position)
|
||||
{
|
||||
double height = camera.get_viewport().data()[3];
|
||||
// Y coordinate has opposit direction
|
||||
return Vec3d(position.x(), height - position.y(), 0.);
|
||||
}
|
||||
|
||||
void CameraUtils::ray_from_ortho_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction)
|
||||
{
|
||||
assert(camera.get_type() == Camera::EType::Ortho);
|
||||
Matrix4d modelview = camera.get_view_matrix().matrix();
|
||||
Matrix4d projection = camera.get_projection_matrix().matrix();
|
||||
Vec4i viewport(camera.get_viewport().data());
|
||||
igl::unproject(screen_point(camera,position), modelview, projection, viewport, point);
|
||||
direction = camera.get_dir_forward();
|
||||
}
|
||||
void CameraUtils::ray_from_persp_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction)
|
||||
{
|
||||
assert(camera.get_type() == Camera::EType::Perspective);
|
||||
Matrix4d modelview = camera.get_view_matrix().matrix();
|
||||
Matrix4d projection = camera.get_projection_matrix().matrix();
|
||||
Vec4i viewport(camera.get_viewport().data());
|
||||
igl::unproject(screen_point(camera, position), modelview, projection, viewport, point);
|
||||
direction = point - camera.get_position();
|
||||
}
|
||||
|
||||
Vec2d CameraUtils::get_z0_position(const Camera &camera, const Vec2d & coor)
|
||||
{
|
||||
Vec3d p0, dir;
|
||||
ray_from_screen_pos(camera, coor, p0, dir);
|
||||
|
||||
// is approx zero
|
||||
if ((fabs(dir.z()) - 1e-4) < 0)
|
||||
return Vec2d(std::numeric_limits<double>::max(),
|
||||
std::numeric_limits<double>::max());
|
||||
|
||||
// find position of ray cross plane(z = 0)
|
||||
double t = p0.z() / dir.z();
|
||||
Vec3d p = p0 - t * dir;
|
||||
return Vec2d(p.x(), p.y());
|
||||
}
|
||||
69
src/slic3r/GUI/CameraUtils.hpp
Normal file
69
src/slic3r/GUI/CameraUtils.hpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#ifndef slic3r_CameraUtils_hpp_
|
||||
#define slic3r_CameraUtils_hpp_
|
||||
|
||||
#include "Camera.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
namespace Slic3r {
|
||||
class GLVolume;
|
||||
}
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
/// <summary>
|
||||
/// Help divide camera data and camera functions
|
||||
/// This utility work with camera data by static funtions
|
||||
/// </summary>
|
||||
class CameraUtils
|
||||
{
|
||||
public:
|
||||
CameraUtils() = delete; // only static functions
|
||||
|
||||
/// <summary>
|
||||
/// Project point throw camera to 2d coordinate into imgui window
|
||||
/// </summary>
|
||||
/// <param name="camera">Projection params</param>
|
||||
/// <param name="points">Point to project.</param>
|
||||
/// <returns>projected points by camera into coordinate of camera.
|
||||
/// x(from left to right), y(from top to bottom)</returns>
|
||||
static Points project(const Camera& camera, const std::vector<Vec3d> &points);
|
||||
static Point project(const Camera& camera, const Vec3d &point);
|
||||
|
||||
/// <summary>
|
||||
/// Create hull around GLVolume in 2d space of camera
|
||||
/// </summary>
|
||||
/// <param name="camera">Projection params</param>
|
||||
/// <param name="volume">Outline by 3d object</param>
|
||||
/// <returns>Polygon around object</returns>
|
||||
static Polygon create_hull2d(const Camera &camera, const GLVolume &volume);
|
||||
|
||||
/// <summary>
|
||||
/// Create ray(point and direction) for screen coordinate
|
||||
/// </summary>
|
||||
/// <param name="camera">Definition of camera</param>
|
||||
/// <param name="position">Position on screen(aka mouse position) </param>
|
||||
/// <param name="point">OUT start of ray</param>
|
||||
/// <param name="direction">OUT direction of ray</param>
|
||||
static void ray_from_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction);
|
||||
static void ray_from_ortho_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction);
|
||||
static void ray_from_persp_screen_pos(const Camera &camera, const Vec2d &position, Vec3d &point, Vec3d &direction);
|
||||
|
||||
/// <summary>
|
||||
/// Unproject mouse coordinate to get position in space where z coor is zero
|
||||
/// Platter surface should be in z == 0
|
||||
/// </summary>
|
||||
/// <param name="camera">Projection params</param>
|
||||
/// <param name="coor">Mouse position</param>
|
||||
/// <returns>Position on platter under mouse</returns>
|
||||
static Vec2d get_z0_position(const Camera &camera, const Vec2d &coor);
|
||||
|
||||
/// <summary>
|
||||
/// Create 3d screen point from 2d position
|
||||
/// </summary>
|
||||
/// <param name="camera">Define camera viewport</param>
|
||||
/// <param name="position">Position on screen(aka mouse position)</param>
|
||||
/// <returns>Point represented screen coor in 3d</returns>
|
||||
static Vec3d screen_point(const Camera &camera, const Vec2d &position);
|
||||
|
||||
};
|
||||
} // Slic3r::GUI
|
||||
|
||||
#endif /* slic3r_CameraUtils_hpp_ */
|
||||
15
src/slic3r/GUI/ConfigExceptions.hpp
Normal file
15
src/slic3r/GUI/ConfigExceptions.hpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#include <exception>
|
||||
namespace Slic3r {
|
||||
|
||||
class ConfigError : public Slic3r::RuntimeError {
|
||||
using Slic3r::RuntimeError::RuntimeError;
|
||||
};
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class ConfigGUITypeError : public ConfigError {
|
||||
using ConfigError::ConfigError;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
444
src/slic3r/GUI/ConfigManipulation.cpp
Normal file
444
src/slic3r/GUI/ConfigManipulation.cpp
Normal file
@@ -0,0 +1,444 @@
|
||||
// #include "libslic3r/GCodeSender.hpp"
|
||||
#include "ConfigManipulation.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "format.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
void ConfigManipulation::apply(DynamicPrintConfig* config, DynamicPrintConfig* new_config)
|
||||
{
|
||||
bool modified = false;
|
||||
for (auto opt_key : config->diff(*new_config)) {
|
||||
config->set_key_value(opt_key, new_config->option(opt_key)->clone());
|
||||
modified = true;
|
||||
}
|
||||
|
||||
if (modified && load_config != nullptr)
|
||||
load_config();
|
||||
}
|
||||
|
||||
void ConfigManipulation::toggle_field(const std::string& opt_key, const bool toggle, int opt_index/* = -1*/)
|
||||
{
|
||||
if (local_config) {
|
||||
if (local_config->option(opt_key) == nullptr)
|
||||
return;
|
||||
}
|
||||
cb_toggle_field(opt_key, toggle, opt_index);
|
||||
}
|
||||
|
||||
void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, const bool is_global_config)
|
||||
{
|
||||
// #ys_FIXME_to_delete
|
||||
//! Temporary workaround for the correct updates of the TextCtrl (like "layer_height"):
|
||||
// KillFocus() for the wxSpinCtrl use CallAfter function. So,
|
||||
// to except the duplicate call of the update() after dialog->ShowModal(),
|
||||
// let check if this process is already started.
|
||||
if (is_msg_dlg_already_exist)
|
||||
return;
|
||||
|
||||
// layer_height shouldn't be equal to zero
|
||||
if (config->opt_float("layer_height") < EPSILON)
|
||||
{
|
||||
const wxString msg_text = _(L("Layer height is not valid.\n\nThe layer height will be reset to 0.01."));
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, _(L("Layer height")), wxICON_WARNING | wxOK);
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
is_msg_dlg_already_exist = true;
|
||||
dialog.ShowModal();
|
||||
new_conf.set_key_value("layer_height", new ConfigOptionFloat(0.01));
|
||||
apply(config, &new_conf);
|
||||
is_msg_dlg_already_exist = false;
|
||||
}
|
||||
|
||||
if (config->option<ConfigOptionFloatOrPercent>("first_layer_height")->value < EPSILON)
|
||||
{
|
||||
const wxString msg_text = _(L("First layer height is not valid.\n\nThe first layer height will be reset to 0.01."));
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, _(L("First layer height")), wxICON_WARNING | wxOK);
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
is_msg_dlg_already_exist = true;
|
||||
dialog.ShowModal();
|
||||
new_conf.set_key_value("first_layer_height", new ConfigOptionFloatOrPercent(0.01, false));
|
||||
apply(config, &new_conf);
|
||||
is_msg_dlg_already_exist = false;
|
||||
}
|
||||
|
||||
double fill_density = config->option<ConfigOptionPercent>("fill_density")->value;
|
||||
|
||||
if (config->opt_bool("spiral_vase") &&
|
||||
! (config->opt_int("perimeters") == 1 &&
|
||||
config->opt_int("top_solid_layers") == 0 &&
|
||||
fill_density == 0 &&
|
||||
! config->opt_bool("support_material") &&
|
||||
config->opt_int("support_material_enforce_layers") == 0 &&
|
||||
! config->opt_bool("thin_walls")))
|
||||
{
|
||||
wxString msg_text = _(L("The Spiral Vase mode requires:\n"
|
||||
"- one perimeter\n"
|
||||
"- no top solid layers\n"
|
||||
"- 0% fill density\n"
|
||||
"- no support material\n"
|
||||
"- Detect thin walls disabled"));
|
||||
if (is_global_config)
|
||||
msg_text += "\n\n" + _(L("Shall I adjust those settings in order to enable Spiral Vase?"));
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, _(L("Spiral Vase")),
|
||||
wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK));
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
auto answer = dialog.ShowModal();
|
||||
bool support = true;
|
||||
if (!is_global_config || answer == wxID_YES) {
|
||||
new_conf.set_key_value("perimeters", new ConfigOptionInt(1));
|
||||
new_conf.set_key_value("top_solid_layers", new ConfigOptionInt(0));
|
||||
new_conf.set_key_value("fill_density", new ConfigOptionPercent(0));
|
||||
new_conf.set_key_value("support_material", new ConfigOptionBool(false));
|
||||
new_conf.set_key_value("support_material_enforce_layers", new ConfigOptionInt(0));
|
||||
new_conf.set_key_value("thin_walls", new ConfigOptionBool(false));
|
||||
fill_density = 0;
|
||||
support = false;
|
||||
}
|
||||
else {
|
||||
new_conf.set_key_value("spiral_vase", new ConfigOptionBool(false));
|
||||
}
|
||||
apply(config, &new_conf);
|
||||
if (cb_value_change) {
|
||||
cb_value_change("fill_density", fill_density);
|
||||
if (!support)
|
||||
cb_value_change("support_material", false);
|
||||
}
|
||||
}
|
||||
|
||||
if (config->opt_bool("wipe_tower") && config->opt_bool("support_material") &&
|
||||
// Organic supports are always synchronized with object layers as of now.
|
||||
config->opt_enum<SupportMaterialStyle>("support_material_style") != smsOrganic) {
|
||||
if (config->opt_float("support_material_contact_distance") == 0) {
|
||||
if (!config->opt_bool("support_material_synchronize_layers")) {
|
||||
wxString msg_text = _(L("For the Wipe Tower to work with the soluble supports, the support layers\n"
|
||||
"need to be synchronized with the object layers."));
|
||||
if (is_global_config)
|
||||
msg_text += "\n\n" + _(L("Shall I synchronize support layers in order to enable the Wipe Tower?"));
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, _(L("Wipe Tower")),
|
||||
wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK));
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
auto answer = dialog.ShowModal();
|
||||
if (!is_global_config || answer == wxID_YES) {
|
||||
new_conf.set_key_value("support_material_synchronize_layers", new ConfigOptionBool(true));
|
||||
}
|
||||
else
|
||||
new_conf.set_key_value("wipe_tower", new ConfigOptionBool(false));
|
||||
apply(config, &new_conf);
|
||||
}
|
||||
} else {
|
||||
if ((config->opt_int("support_material_extruder") != 0 || config->opt_int("support_material_interface_extruder") != 0)) {
|
||||
wxString msg_text = _(L("The Wipe Tower currently supports the non-soluble supports only "
|
||||
"if they are printed with the current extruder without triggering a tool change. "
|
||||
"(both support_material_extruder and support_material_interface_extruder need to be set to 0)."));
|
||||
if (is_global_config)
|
||||
msg_text += "\n\n" + _(L("Shall I adjust those settings in order to enable the Wipe Tower?"));
|
||||
MessageDialog dialog (m_msg_dlg_parent, msg_text, _(L("Wipe Tower")),
|
||||
wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK));
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
auto answer = dialog.ShowModal();
|
||||
if (!is_global_config || answer == wxID_YES) {
|
||||
new_conf.set_key_value("support_material_extruder", new ConfigOptionInt(0));
|
||||
new_conf.set_key_value("support_material_interface_extruder", new ConfigOptionInt(0));
|
||||
}
|
||||
else
|
||||
new_conf.set_key_value("wipe_tower", new ConfigOptionBool(false));
|
||||
apply(config, &new_conf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check "support_material" and "overhangs" relations only on global settings level
|
||||
if (is_global_config && config->opt_bool("support_material")) {
|
||||
// Ask only once.
|
||||
if (!m_support_material_overhangs_queried) {
|
||||
m_support_material_overhangs_queried = true;
|
||||
if (!config->opt_bool("overhangs")/* != 1*/) {
|
||||
wxString msg_text = _(L("Supports work better, if the following feature is enabled:\n"
|
||||
"- Detect bridging perimeters"));
|
||||
if (is_global_config)
|
||||
msg_text += "\n\n" + _(L("Shall I adjust those settings for supports?"));
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, _L("Support Generator"), wxICON_WARNING | wxYES | wxNO);
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
auto answer = dialog.ShowModal();
|
||||
if (answer == wxID_YES) {
|
||||
// Enable "detect bridging perimeters".
|
||||
new_conf.set_key_value("overhangs", new ConfigOptionBool(true));
|
||||
}
|
||||
//else Do nothing, leave supports on and "detect bridging perimeters" off.
|
||||
apply(config, &new_conf);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_support_material_overhangs_queried = false;
|
||||
}
|
||||
|
||||
if (config->option<ConfigOptionPercent>("fill_density")->value == 100) {
|
||||
const int fill_pattern = config->option<ConfigOptionEnum<InfillPattern>>("fill_pattern")->value;
|
||||
if (bool correct_100p_fill = config->option_def("top_fill_pattern")->enum_def->enum_to_index(fill_pattern).has_value();
|
||||
! correct_100p_fill) {
|
||||
// get fill_pattern name from enum_labels for using this one at dialog_msg
|
||||
const ConfigOptionDef *fill_pattern_def = config->option_def("fill_pattern");
|
||||
assert(fill_pattern_def != nullptr);
|
||||
if (auto label = fill_pattern_def->enum_def->enum_to_label(fill_pattern); label.has_value()) {
|
||||
wxString msg_text = GUI::format_wxstr(_L("The %1% infill pattern is not supposed to work at 100%% density."), _(*label));
|
||||
if (is_global_config)
|
||||
msg_text += "\n\n" + _L("Shall I switch to rectilinear fill pattern?");
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, _L("Infill"),
|
||||
wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK) );
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
auto answer = dialog.ShowModal();
|
||||
if (!is_global_config || answer == wxID_YES) {
|
||||
new_conf.set_key_value("fill_pattern", new ConfigOptionEnum<InfillPattern>(ipRectilinear));
|
||||
fill_density = 100;
|
||||
}
|
||||
else
|
||||
fill_density = wxGetApp().preset_bundle->prints.get_selected_preset().config.option<ConfigOptionPercent>("fill_density")->value;
|
||||
new_conf.set_key_value("fill_density", new ConfigOptionPercent(fill_density));
|
||||
apply(config, &new_conf);
|
||||
if (cb_value_change)
|
||||
cb_value_change("fill_density", fill_density);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
|
||||
{
|
||||
bool have_perimeters = config->opt_int("perimeters") > 0;
|
||||
for (auto el : { "extra_perimeters","extra_perimeters_on_overhangs", "thin_walls", "overhangs",
|
||||
"seam_position","staggered_inner_seams", "external_perimeters_first", "external_perimeter_extrusion_width",
|
||||
"perimeter_speed", "small_perimeter_speed", "external_perimeter_speed", "enable_dynamic_overhang_speeds"})
|
||||
toggle_field(el, have_perimeters);
|
||||
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
toggle_field("overhang_speed_" + std::to_string(i), config->opt_bool("enable_dynamic_overhang_speeds"));
|
||||
}
|
||||
|
||||
bool have_infill = config->option<ConfigOptionPercent>("fill_density")->value > 0;
|
||||
// infill_extruder uses the same logic as in Print::extruders()
|
||||
for (auto el : { "fill_pattern", "infill_every_layers", "infill_only_where_needed",
|
||||
"solid_infill_every_layers", "solid_infill_below_area", "infill_extruder", "infill_anchor_max" })
|
||||
toggle_field(el, have_infill);
|
||||
// Only allow configuration of open anchors if the anchoring is enabled.
|
||||
bool has_infill_anchors = have_infill && config->option<ConfigOptionFloatOrPercent>("infill_anchor_max")->value > 0;
|
||||
toggle_field("infill_anchor", has_infill_anchors);
|
||||
|
||||
bool has_spiral_vase = config->opt_bool("spiral_vase");
|
||||
bool has_top_solid_infill = config->opt_int("top_solid_layers") > 0;
|
||||
bool has_bottom_solid_infill = config->opt_int("bottom_solid_layers") > 0;
|
||||
bool has_solid_infill = has_top_solid_infill || has_bottom_solid_infill;
|
||||
// solid_infill_extruder uses the same logic as in Print::extruders()
|
||||
for (auto el : { "top_fill_pattern", "bottom_fill_pattern", "infill_first", "solid_infill_extruder",
|
||||
"solid_infill_extrusion_width", "solid_infill_speed" })
|
||||
toggle_field(el, has_solid_infill);
|
||||
|
||||
for (auto el : { "fill_angle", "bridge_angle", "infill_extrusion_width",
|
||||
"infill_speed", "bridge_speed" })
|
||||
toggle_field(el, have_infill || has_solid_infill);
|
||||
|
||||
toggle_field("top_solid_min_thickness", ! has_spiral_vase && has_top_solid_infill);
|
||||
toggle_field("bottom_solid_min_thickness", ! has_spiral_vase && has_bottom_solid_infill);
|
||||
|
||||
// Gap fill is newly allowed in between perimeter lines even for empty infill (see GH #1476).
|
||||
toggle_field("gap_fill_speed", have_perimeters);
|
||||
|
||||
for (auto el : { "top_infill_extrusion_width", "top_solid_infill_speed" })
|
||||
toggle_field(el, has_top_solid_infill || (has_spiral_vase && has_bottom_solid_infill));
|
||||
|
||||
bool have_default_acceleration = config->opt_float("default_acceleration") > 0;
|
||||
for (auto el : { "perimeter_acceleration", "infill_acceleration", "top_solid_infill_acceleration",
|
||||
"solid_infill_acceleration", "external_perimeter_acceleration",
|
||||
"bridge_acceleration", "first_layer_acceleration" })
|
||||
toggle_field(el, have_default_acceleration);
|
||||
|
||||
bool have_skirt = config->opt_int("skirts") > 0;
|
||||
toggle_field("skirt_height", have_skirt && config->opt_enum<DraftShield>("draft_shield") != dsEnabled);
|
||||
for (auto el : { "skirt_distance", "draft_shield", "min_skirt_length" })
|
||||
toggle_field(el, have_skirt);
|
||||
|
||||
bool have_brim = config->opt_enum<BrimType>("brim_type") != btNoBrim;
|
||||
for (auto el : { "brim_width", "brim_separation" })
|
||||
toggle_field(el, have_brim);
|
||||
// perimeter_extruder uses the same logic as in Print::extruders()
|
||||
toggle_field("perimeter_extruder", have_perimeters || have_brim);
|
||||
|
||||
bool have_raft = config->opt_int("raft_layers") > 0;
|
||||
bool have_support_material = config->opt_bool("support_material") || have_raft;
|
||||
bool have_support_material_auto = have_support_material && config->opt_bool("support_material_auto");
|
||||
bool have_support_interface = config->opt_int("support_material_interface_layers") > 0;
|
||||
bool have_support_soluble = have_support_material && config->opt_float("support_material_contact_distance") == 0;
|
||||
auto support_material_style = config->opt_enum<SupportMaterialStyle>("support_material_style");
|
||||
for (auto el : { "support_material_style", "support_material_pattern", "support_material_with_sheath",
|
||||
"support_material_spacing", "support_material_angle",
|
||||
"support_material_interface_pattern", "support_material_interface_layers",
|
||||
"dont_support_bridges", "support_material_extrusion_width", "support_material_contact_distance",
|
||||
"support_material_xy_spacing" })
|
||||
toggle_field(el, have_support_material);
|
||||
toggle_field("support_material_threshold", have_support_material_auto);
|
||||
toggle_field("support_material_bottom_contact_distance", have_support_material && ! have_support_soluble);
|
||||
toggle_field("support_material_closing_radius", have_support_material && support_material_style == smsSnug);
|
||||
|
||||
const bool has_organic_supports = support_material_style == smsOrganic &&
|
||||
(config->opt_bool("support_material") ||
|
||||
config->opt_int("support_material_enforce_layers") > 0);
|
||||
for (const std::string& key : { "support_tree_angle", "support_tree_angle_slow", "support_tree_branch_diameter",
|
||||
"support_tree_branch_diameter_angle", "support_tree_branch_diameter_double_wall",
|
||||
"support_tree_tip_diameter", "support_tree_branch_distance", "support_tree_top_rate" })
|
||||
toggle_field(key, has_organic_supports);
|
||||
|
||||
for (auto el : { "support_material_bottom_interface_layers", "support_material_interface_spacing", "support_material_interface_extruder",
|
||||
"support_material_interface_speed", "support_material_interface_contact_loops" })
|
||||
toggle_field(el, have_support_material && have_support_interface);
|
||||
toggle_field("support_material_synchronize_layers", have_support_soluble);
|
||||
|
||||
toggle_field("perimeter_extrusion_width", have_perimeters || have_skirt || have_brim);
|
||||
toggle_field("support_material_extruder", have_support_material || have_skirt);
|
||||
toggle_field("support_material_speed", have_support_material || have_brim || have_skirt);
|
||||
|
||||
toggle_field("raft_contact_distance", have_raft && !have_support_soluble);
|
||||
for (auto el : { "raft_expansion", "first_layer_acceleration_over_raft", "first_layer_speed_over_raft" })
|
||||
toggle_field(el, have_raft);
|
||||
|
||||
bool has_ironing = config->opt_bool("ironing");
|
||||
for (auto el : { "ironing_type", "ironing_flowrate", "ironing_spacing", "ironing_speed" })
|
||||
toggle_field(el, has_ironing);
|
||||
|
||||
bool have_sequential_printing = config->opt_bool("complete_objects");
|
||||
for (auto el : { "extruder_clearance_radius", "extruder_clearance_height" })
|
||||
toggle_field(el, have_sequential_printing);
|
||||
|
||||
bool have_ooze_prevention = config->opt_bool("ooze_prevention");
|
||||
toggle_field("standby_temperature_delta", have_ooze_prevention);
|
||||
|
||||
bool have_wipe_tower = config->opt_bool("wipe_tower");
|
||||
for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle",
|
||||
"wipe_tower_extra_spacing", "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" })
|
||||
toggle_field(el, have_wipe_tower);
|
||||
|
||||
toggle_field("avoid_crossing_curled_overhangs", !config->opt_bool("avoid_crossing_perimeters"));
|
||||
toggle_field("avoid_crossing_perimeters", !config->opt_bool("avoid_crossing_curled_overhangs"));
|
||||
|
||||
bool have_avoid_crossing_perimeters = config->opt_bool("avoid_crossing_perimeters");
|
||||
toggle_field("avoid_crossing_perimeters_max_detour", have_avoid_crossing_perimeters);
|
||||
|
||||
bool have_arachne = config->opt_enum<PerimeterGeneratorType>("perimeter_generator") == PerimeterGeneratorType::Arachne;
|
||||
toggle_field("wall_transition_length", have_arachne);
|
||||
toggle_field("wall_transition_filter_deviation", have_arachne);
|
||||
toggle_field("wall_transition_angle", have_arachne);
|
||||
toggle_field("wall_distribution_count", have_arachne);
|
||||
toggle_field("min_feature_size", have_arachne);
|
||||
toggle_field("min_bead_width", have_arachne);
|
||||
toggle_field("thin_walls", !have_arachne);
|
||||
}
|
||||
|
||||
void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config/* = false*/)
|
||||
{
|
||||
double head_penetration = config->opt_float("support_head_penetration");
|
||||
double head_width = config->opt_float("support_head_width");
|
||||
if (head_penetration > head_width) {
|
||||
wxString msg_text = _(L("Head penetration should not be greater than the head width."));
|
||||
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, _(L("Invalid Head penetration")), wxICON_WARNING | wxOK);
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
if (dialog.ShowModal() == wxID_OK) {
|
||||
new_conf.set_key_value("support_head_penetration", new ConfigOptionFloat(head_width));
|
||||
apply(config, &new_conf);
|
||||
}
|
||||
}
|
||||
|
||||
double pinhead_d = config->opt_float("support_head_front_diameter");
|
||||
double pillar_d = config->opt_float("support_pillar_diameter");
|
||||
if (pinhead_d > pillar_d) {
|
||||
wxString msg_text = _(L("Pinhead diameter should be smaller than the pillar diameter."));
|
||||
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, _(L("Invalid pinhead diameter")), wxICON_WARNING | wxOK);
|
||||
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
if (dialog.ShowModal() == wxID_OK) {
|
||||
new_conf.set_key_value("support_head_front_diameter", new ConfigOptionFloat(pillar_d / 2.0));
|
||||
apply(config, &new_conf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config)
|
||||
{
|
||||
bool supports_en = config->opt_bool("supports_enable");
|
||||
sla::SupportTreeType treetype = config->opt_enum<sla::SupportTreeType>("support_tree_type");
|
||||
bool is_default_tree = treetype == sla::SupportTreeType::Default;
|
||||
bool is_branching_tree = treetype == sla::SupportTreeType::Branching;
|
||||
|
||||
toggle_field("support_tree_type", supports_en);
|
||||
|
||||
toggle_field("support_head_front_diameter", supports_en && is_default_tree);
|
||||
toggle_field("support_head_penetration", supports_en && is_default_tree);
|
||||
toggle_field("support_head_width", supports_en && is_default_tree);
|
||||
toggle_field("support_pillar_diameter", supports_en && is_default_tree);
|
||||
toggle_field("support_small_pillar_diameter_percent", supports_en && is_default_tree);
|
||||
toggle_field("support_max_bridges_on_pillar", supports_en && is_default_tree);
|
||||
toggle_field("support_pillar_connection_mode", supports_en && is_default_tree);
|
||||
toggle_field("support_buildplate_only", supports_en && is_default_tree);
|
||||
toggle_field("support_base_diameter", supports_en && is_default_tree);
|
||||
toggle_field("support_base_height", supports_en && is_default_tree);
|
||||
toggle_field("support_base_safety_distance", supports_en && is_default_tree);
|
||||
toggle_field("support_critical_angle", supports_en && is_default_tree);
|
||||
toggle_field("support_max_bridge_length", supports_en && is_default_tree);
|
||||
toggle_field("support_enforcers_only", supports_en);
|
||||
toggle_field("support_max_pillar_link_distance", supports_en && is_default_tree);
|
||||
toggle_field("support_pillar_widening_factor", false);
|
||||
toggle_field("support_max_weight_on_model", false);
|
||||
|
||||
toggle_field("branchingsupport_head_front_diameter", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_head_penetration", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_head_width", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_pillar_diameter", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_small_pillar_diameter_percent", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_max_bridges_on_pillar", false);
|
||||
toggle_field("branchingsupport_pillar_connection_mode", false);
|
||||
toggle_field("branchingsupport_buildplate_only", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_base_diameter", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_base_height", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_base_safety_distance", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_critical_angle", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_max_bridge_length", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_max_pillar_link_distance", false);
|
||||
toggle_field("branchingsupport_pillar_widening_factor", supports_en && is_branching_tree);
|
||||
toggle_field("branchingsupport_max_weight_on_model", supports_en && is_branching_tree);
|
||||
|
||||
toggle_field("support_points_density_relative", supports_en);
|
||||
toggle_field("support_points_minimal_distance", supports_en);
|
||||
|
||||
bool pad_en = config->opt_bool("pad_enable");
|
||||
|
||||
toggle_field("pad_wall_thickness", pad_en);
|
||||
toggle_field("pad_wall_height", pad_en);
|
||||
toggle_field("pad_brim_size", pad_en);
|
||||
toggle_field("pad_max_merge_distance", pad_en);
|
||||
// toggle_field("pad_edge_radius", supports_en);
|
||||
toggle_field("pad_wall_slope", pad_en);
|
||||
toggle_field("pad_around_object", pad_en);
|
||||
toggle_field("pad_around_object_everywhere", pad_en);
|
||||
|
||||
bool zero_elev = config->opt_bool("pad_around_object") && pad_en;
|
||||
|
||||
toggle_field("support_object_elevation", supports_en && is_default_tree && !zero_elev);
|
||||
toggle_field("branchingsupport_object_elevation", supports_en && is_branching_tree && !zero_elev);
|
||||
toggle_field("pad_object_gap", zero_elev);
|
||||
toggle_field("pad_around_object_everywhere", zero_elev);
|
||||
toggle_field("pad_object_connector_stride", zero_elev);
|
||||
toggle_field("pad_object_connector_width", zero_elev);
|
||||
toggle_field("pad_object_connector_penetration", zero_elev);
|
||||
}
|
||||
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
76
src/slic3r/GUI/ConfigManipulation.hpp
Normal file
76
src/slic3r/GUI/ConfigManipulation.hpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#ifndef slic3r_ConfigManipulation_hpp_
|
||||
#define slic3r_ConfigManipulation_hpp_
|
||||
|
||||
/* Class for validation config options
|
||||
* and update (enable/disable) IU components
|
||||
*
|
||||
* Used for config validation for global config (Print Settings Tab)
|
||||
* and local config (overrides options on sidebar)
|
||||
* */
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "Field.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ModelConfig;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class ConfigManipulation
|
||||
{
|
||||
bool is_msg_dlg_already_exist{ false };
|
||||
|
||||
bool m_is_initialized_support_material_overhangs_queried{ false };
|
||||
bool m_support_material_overhangs_queried{ false };
|
||||
|
||||
// function to loading of changed configuration
|
||||
std::function<void()> load_config = nullptr;
|
||||
std::function<void (const std::string&, bool toggle, int opt_index)> cb_toggle_field = nullptr;
|
||||
// callback to propagation of changed value, if needed
|
||||
std::function<void(const std::string&, const boost::any&)> cb_value_change = nullptr;
|
||||
ModelConfig* local_config = nullptr;
|
||||
wxWindow* m_msg_dlg_parent {nullptr};
|
||||
|
||||
public:
|
||||
ConfigManipulation(std::function<void()> load_config,
|
||||
std::function<void(const std::string&, bool toggle, int opt_index)> cb_toggle_field,
|
||||
std::function<void(const std::string&, const boost::any&)> cb_value_change,
|
||||
ModelConfig* local_config = nullptr,
|
||||
wxWindow* msg_dlg_parent = nullptr) :
|
||||
load_config(load_config),
|
||||
cb_toggle_field(cb_toggle_field),
|
||||
cb_value_change(cb_value_change),
|
||||
m_msg_dlg_parent(msg_dlg_parent),
|
||||
local_config(local_config) {}
|
||||
ConfigManipulation() {}
|
||||
|
||||
~ConfigManipulation() {
|
||||
load_config = nullptr;
|
||||
cb_toggle_field = nullptr;
|
||||
cb_value_change = nullptr;
|
||||
}
|
||||
|
||||
void apply(DynamicPrintConfig* config, DynamicPrintConfig* new_config);
|
||||
void toggle_field(const std::string& field_key, const bool toggle, int opt_index = -1);
|
||||
|
||||
// FFF print
|
||||
void update_print_fff_config(DynamicPrintConfig* config, const bool is_global_config = false);
|
||||
void toggle_print_fff_options(DynamicPrintConfig* config);
|
||||
|
||||
// SLA print
|
||||
void update_print_sla_config(DynamicPrintConfig* config, const bool is_global_config = false);
|
||||
void toggle_print_sla_options(DynamicPrintConfig* config);
|
||||
|
||||
bool is_initialized_support_material_overhangs_queried() { return m_is_initialized_support_material_overhangs_queried; }
|
||||
void initialize_support_material_overhangs_queried(bool queried)
|
||||
{
|
||||
m_is_initialized_support_material_overhangs_queried = true;
|
||||
m_support_material_overhangs_queried = queried;
|
||||
}
|
||||
};
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
#endif /* slic3r_ConfigManipulation_hpp_ */
|
||||
199
src/slic3r/GUI/ConfigSnapshotDialog.cpp
Normal file
199
src/slic3r/GUI/ConfigSnapshotDialog.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
#include "ConfigSnapshotDialog.hpp"
|
||||
#include "I18N.hpp"
|
||||
|
||||
#include "../Config/Snapshot.hpp"
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/Time.hpp"
|
||||
#include "libslic3r/Color.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
static wxString format_reason(const Config::Snapshot::Reason reason)
|
||||
{
|
||||
switch (reason) {
|
||||
case Config::Snapshot::SNAPSHOT_UPGRADE:
|
||||
return wxString(_(L("Upgrade")));
|
||||
case Config::Snapshot::SNAPSHOT_DOWNGRADE:
|
||||
return wxString(_(L("Downgrade")));
|
||||
case Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK:
|
||||
return wxString(_(L("Before roll back")));
|
||||
case Config::Snapshot::SNAPSHOT_USER:
|
||||
return wxString(_(L("User")));
|
||||
case Config::Snapshot::SNAPSHOT_UNKNOWN:
|
||||
default:
|
||||
return wxString(_(L("Unknown")));
|
||||
}
|
||||
}
|
||||
|
||||
static std::string get_color(wxColour colour)
|
||||
{
|
||||
return encode_color(ColorRGB(colour.Red(), colour.Green(), colour.Blue()));
|
||||
};
|
||||
|
||||
static wxString generate_html_row(const Config::Snapshot &snapshot, bool row_even, bool snapshot_active, bool dark_mode)
|
||||
{
|
||||
// Start by declaring a row with an alternating background color.
|
||||
wxString text = "<tr bgcolor=\"";
|
||||
text += snapshot_active ?
|
||||
dark_mode ? "#208a20" : "#B3FFCB" :
|
||||
(row_even ? get_color(wxGetApp().get_window_default_clr()/*wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)*/) : dark_mode ? "#656565" : "#D5D5D5" );
|
||||
text += "\">";
|
||||
text += "<td>";
|
||||
|
||||
static const constexpr char *LOCALE_TIME_FMT = "%x %X";
|
||||
wxString datetime = wxDateTime(snapshot.time_captured).Format(LOCALE_TIME_FMT);
|
||||
|
||||
// Format the row header.
|
||||
text += wxString("<font size=\"5\"><b>") + (snapshot_active ? _(L("Active")) + ": " : "") +
|
||||
datetime + ": " + format_reason(snapshot.reason);
|
||||
|
||||
if (! snapshot.comment.empty())
|
||||
text += " (" + wxString::FromUTF8(snapshot.comment.data()) + ")";
|
||||
text += "</b></font><br>";
|
||||
// End of row header.
|
||||
text += _(L("QIDISlicer version")) + ": " + snapshot.slic3r_version_captured.to_string() + "<br>";
|
||||
bool has_fff = ! snapshot.print.empty() || ! snapshot.filaments.empty();
|
||||
bool has_sla = ! snapshot.sla_print.empty() || ! snapshot.sla_material.empty();
|
||||
if (has_fff || ! has_sla) {
|
||||
text += _(L("print")) + ": " + snapshot.print + "<br>";
|
||||
text += _(L("filaments")) + ": " + snapshot.filaments.front() + "<br>";
|
||||
}
|
||||
if (has_sla) {
|
||||
text += _(L("SLA print")) + ": " + snapshot.sla_print + "<br>";
|
||||
text += _(L("SLA material")) + ": " + snapshot.sla_material + "<br>";
|
||||
}
|
||||
text += _(L("printer")) + ": " + (snapshot.physical_printer.empty() ? snapshot.printer : snapshot.physical_printer) + "<br>";
|
||||
|
||||
bool compatible = true;
|
||||
for (const Config::Snapshot::VendorConfig &vc : snapshot.vendor_configs) {
|
||||
text += _(L("vendor")) + ": " + vc.name +", " + _(L("version")) + ": " + vc.version.config_version.to_string() +
|
||||
", " + _(L("min QIDISlicer version")) + ": " + vc.version.min_slic3r_version.to_string();
|
||||
if (vc.version.max_slic3r_version != Semver::inf())
|
||||
text += ", " + _(L("max QIDISlicer version")) + ": " + vc.version.max_slic3r_version.to_string();
|
||||
text += "<br>";
|
||||
for (const auto& model : vc.models_variants_installed) {
|
||||
text += _(L("model")) + ": " + model.first + ", " + _(L("variants")) + ": ";
|
||||
for (const std::string &variant : model.second) {
|
||||
if (&variant != &*model.second.begin())
|
||||
text += ", ";
|
||||
text += variant;
|
||||
}
|
||||
text += "<br>";
|
||||
}
|
||||
if (! vc.version.is_current_slic3r_supported()) { compatible = false; }
|
||||
}
|
||||
|
||||
if (! compatible) {
|
||||
text += "<p align=\"right\">" + format_wxstr(_L("Incompatible with this %s"), SLIC3R_APP_NAME) + "</p>";
|
||||
}
|
||||
else if (! snapshot_active)
|
||||
text += "<p align=\"right\"><a href=\"" + snapshot.id + "\">" + _L("Activate") + "</a></p>";
|
||||
text += "</td>";
|
||||
text += "</tr>";
|
||||
return text;
|
||||
}
|
||||
|
||||
static wxString generate_html_page(const Config::SnapshotDB &snapshot_db, const wxString &on_snapshot)
|
||||
{
|
||||
bool dark_mode = wxGetApp().dark_mode();
|
||||
wxString text =
|
||||
"<html>"
|
||||
"<body bgcolor=\"" + get_color(wxGetApp().get_window_default_clr()/*wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)*/) + "\" cellspacing=\"2\" cellpadding=\"0\" border=\"0\" link=\"#800000\">"
|
||||
"<font color=\"" + get_color(wxGetApp().get_label_clr_default()) + "\">";
|
||||
text += "<table style=\"width:100%\">";
|
||||
for (size_t i_row = 0; i_row < snapshot_db.snapshots().size(); ++ i_row) {
|
||||
const Config::Snapshot &snapshot = snapshot_db.snapshots()[snapshot_db.snapshots().size() - i_row - 1];
|
||||
text += generate_html_row(snapshot, i_row & 1, snapshot.id == on_snapshot, dark_mode);
|
||||
}
|
||||
text +=
|
||||
"</table>"
|
||||
"</font>"
|
||||
"</body>"
|
||||
"</html>";
|
||||
return text;
|
||||
}
|
||||
|
||||
ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const wxString &on_snapshot)
|
||||
: DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, _(L("Configuration Snapshots")), wxDefaultPosition,
|
||||
wxSize(45 * wxGetApp().em_unit(), 40 * wxGetApp().em_unit()),
|
||||
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX)
|
||||
{
|
||||
this->SetFont(wxGetApp().normal_font());
|
||||
#ifdef _WIN32
|
||||
wxGetApp().UpdateDarkUI(this);
|
||||
#else
|
||||
this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
#endif
|
||||
|
||||
wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
this->SetSizer(vsizer);
|
||||
|
||||
// text
|
||||
html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO);
|
||||
{
|
||||
wxFont font = get_default_font(this);
|
||||
#ifdef __WXMSW__
|
||||
const int fs = font.GetPointSize();
|
||||
const int fs1 = static_cast<int>(0.8f*fs);
|
||||
const int fs2 = static_cast<int>(1.1f*fs);
|
||||
int size[] = {fs1, fs1, fs1, fs1, fs2, fs2, fs2};
|
||||
// int size[] = {8,8,8,8,11,11,11};
|
||||
#else
|
||||
int size[] = {11,11,11,11,14,14,14};
|
||||
#endif
|
||||
html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
|
||||
html->SetBorders(2);
|
||||
wxString text = generate_html_page(snapshot_db, on_snapshot);
|
||||
html->SetPage(text);
|
||||
vsizer->Add(html, 1, wxEXPAND | wxALIGN_LEFT | wxRIGHT | wxBOTTOM, 0);
|
||||
html->Bind(wxEVT_HTML_LINK_CLICKED, &ConfigSnapshotDialog::onLinkClicked, this);
|
||||
}
|
||||
|
||||
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE);
|
||||
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_CLOSE, this)));
|
||||
this->SetEscapeId(wxID_CLOSE);
|
||||
this->Bind(wxEVT_BUTTON, &ConfigSnapshotDialog::onCloseDialog, this, wxID_CLOSE);
|
||||
vsizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
|
||||
}
|
||||
|
||||
void ConfigSnapshotDialog::on_dpi_changed(const wxRect &suggested_rect)
|
||||
{
|
||||
wxFont font = get_default_font(this);
|
||||
const int fs = font.GetPointSize();
|
||||
const int fs1 = static_cast<int>(0.8f*fs);
|
||||
const int fs2 = static_cast<int>(1.1f*fs);
|
||||
int font_size[] = { fs1, fs1, fs1, fs1, fs2, fs2, fs2 };
|
||||
|
||||
html->SetFonts(font.GetFaceName(), font.GetFaceName(), font_size);
|
||||
html->Refresh();
|
||||
|
||||
const int& em = em_unit();
|
||||
|
||||
msw_buttons_rescale(this, em, { wxID_CLOSE});
|
||||
|
||||
const wxSize& size = wxSize(45 * em, 40 * em);
|
||||
SetMinSize(size);
|
||||
Fit();
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void ConfigSnapshotDialog::onLinkClicked(wxHtmlLinkEvent &event)
|
||||
{
|
||||
m_snapshot_to_activate = event.GetLinkInfo().GetHref().ToUTF8();
|
||||
this->EndModal(wxID_CLOSE);
|
||||
}
|
||||
|
||||
void ConfigSnapshotDialog::onCloseDialog(wxEvent &)
|
||||
{
|
||||
this->EndModal(wxID_CLOSE);
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
40
src/slic3r/GUI/ConfigSnapshotDialog.hpp
Normal file
40
src/slic3r/GUI/ConfigSnapshotDialog.hpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef slic3r_GUI_ConfigSnapshotDialog_hpp_
|
||||
#define slic3r_GUI_ConfigSnapshotDialog_hpp_
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_Utils.hpp"
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/intl.h>
|
||||
#include <wx/html/htmlwin.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
namespace Config {
|
||||
class SnapshotDB;
|
||||
}
|
||||
|
||||
class ConfigSnapshotDialog : public DPIDialog
|
||||
{
|
||||
public:
|
||||
ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const wxString &id);
|
||||
const std::string& snapshot_to_activate() const { return m_snapshot_to_activate; }
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect &suggested_rect) override;
|
||||
|
||||
private:
|
||||
void onLinkClicked(wxHtmlLinkEvent &event);
|
||||
void onCloseDialog(wxEvent &);
|
||||
|
||||
// If set, it contains a snapshot ID to be restored after the dialog closes.
|
||||
std::string m_snapshot_to_activate;
|
||||
|
||||
wxHtmlWindow* html;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_GUI_ConfigSnapshotDialog_hpp_ */
|
||||
3579
src/slic3r/GUI/ConfigWizard.cpp
Normal file
3579
src/slic3r/GUI/ConfigWizard.cpp
Normal file
File diff suppressed because it is too large
Load Diff
96
src/slic3r/GUI/ConfigWizard.hpp
Normal file
96
src/slic3r/GUI/ConfigWizard.hpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#ifndef slic3r_ConfigWizard_hpp_
|
||||
#define slic3r_ConfigWizard_hpp_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/textctrl.h>
|
||||
|
||||
#include "GUI_Utils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class PresetBundle;
|
||||
class PresetUpdater;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
namespace DownloaderUtils {
|
||||
class Worker : public wxBoxSizer
|
||||
{
|
||||
wxWindow* m_parent{ nullptr };
|
||||
wxTextCtrl* m_input_path{ nullptr };
|
||||
bool downloader_checked{ false };
|
||||
#ifdef __linux__
|
||||
bool perform_registration_linux{ false };
|
||||
#endif // __linux__
|
||||
|
||||
void deregister();
|
||||
|
||||
public:
|
||||
Worker(wxWindow* parent);
|
||||
~Worker() {}
|
||||
|
||||
void allow(bool allow_) { downloader_checked = allow_; }
|
||||
bool is_checked() const { return downloader_checked; }
|
||||
wxString path_name() const { return m_input_path ? m_input_path->GetValue() : wxString(); }
|
||||
|
||||
void set_path_name(wxString name);
|
||||
void set_path_name(const std::string& name);
|
||||
|
||||
bool on_finish();
|
||||
bool perform_register(const std::string& path_override = {});
|
||||
#ifdef __linux__
|
||||
bool get_perform_registration_linux() { return perform_registration_linux; }
|
||||
#endif // __linux__
|
||||
};
|
||||
}
|
||||
|
||||
class ConfigWizard: public DPIDialog
|
||||
{
|
||||
public:
|
||||
// Why is the Wizard run
|
||||
enum RunReason {
|
||||
RR_DATA_EMPTY, // No or empty datadir
|
||||
RR_DATA_LEGACY, // Pre-updating datadir
|
||||
RR_DATA_INCOMPAT, // Incompatible datadir - Slic3r downgrade situation
|
||||
RR_USER, // User requested the Wizard from the menus
|
||||
};
|
||||
|
||||
// What page should wizard start on
|
||||
enum StartPage {
|
||||
SP_WELCOME,
|
||||
SP_PRINTERS,
|
||||
SP_FILAMENTS,
|
||||
SP_MATERIALS,
|
||||
};
|
||||
|
||||
ConfigWizard(wxWindow *parent);
|
||||
ConfigWizard(ConfigWizard &&) = delete;
|
||||
ConfigWizard(const ConfigWizard &) = delete;
|
||||
ConfigWizard &operator=(ConfigWizard &&) = delete;
|
||||
ConfigWizard &operator=(const ConfigWizard &) = delete;
|
||||
~ConfigWizard();
|
||||
|
||||
// Run the Wizard. Return whether it was completed.
|
||||
bool run(RunReason reason, StartPage start_page = SP_WELCOME);
|
||||
|
||||
static const wxString& name(const bool from_menu = false);
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect &suggested_rect) override ;
|
||||
void on_sys_color_changed() override;
|
||||
|
||||
private:
|
||||
struct priv;
|
||||
std::unique_ptr<priv> p;
|
||||
|
||||
friend struct ConfigWizardPage;
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
679
src/slic3r/GUI/ConfigWizard_private.hpp
Normal file
679
src/slic3r/GUI/ConfigWizard_private.hpp
Normal file
@@ -0,0 +1,679 @@
|
||||
#ifndef slic3r_ConfigWizard_private_hpp_
|
||||
#define slic3r_ConfigWizard_private_hpp_
|
||||
|
||||
#include "ConfigWizard.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <wx/panel.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/spinctrl.h>
|
||||
#include <wx/listbox.h>
|
||||
#include <wx/checklst.h>
|
||||
#include <wx/radiobut.h>
|
||||
#include <wx/html/htmlwin.h>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "slic3r/Utils/PresetUpdater.hpp"
|
||||
#include "BedShapeDialog.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "SavePresetDialog.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
enum {
|
||||
WRAP_WIDTH = 500,
|
||||
MODEL_MIN_WRAP = 150,
|
||||
|
||||
DIALOG_MARGIN = 15,
|
||||
INDEX_MARGIN = 40,
|
||||
BTN_SPACING = 10,
|
||||
INDENT_SPACING = 30,
|
||||
VERTICAL_SPACING = 10,
|
||||
|
||||
MAX_COLS = 4,
|
||||
ROW_SPACING = 75,
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Configuration data structures extensions needed for the wizard
|
||||
|
||||
enum Technology {
|
||||
// Bitflag equivalent of PrinterTechnology
|
||||
T_FFF = 0x1,
|
||||
T_SLA = 0x2,
|
||||
T_ANY = ~0,
|
||||
};
|
||||
|
||||
enum BundleLocation{
|
||||
IN_VENDOR,
|
||||
IN_ARCHIVE,
|
||||
IN_RESOURCES
|
||||
};
|
||||
|
||||
struct Bundle
|
||||
{
|
||||
std::unique_ptr<PresetBundle> preset_bundle;
|
||||
VendorProfile* vendor_profile{ nullptr };
|
||||
//bool is_in_resources{ false };
|
||||
BundleLocation location;
|
||||
bool is_qidi_bundle{ false };
|
||||
|
||||
Bundle() = default;
|
||||
Bundle(Bundle&& other);
|
||||
|
||||
// Returns false if not loaded. Reason for that is logged as boost::log error.
|
||||
bool load(fs::path source_path, BundleLocation location, bool is_qidi_bundle = false);
|
||||
|
||||
const std::string& vendor_id() const { return vendor_profile->id; }
|
||||
};
|
||||
|
||||
struct BundleMap : std::map<std::string /* = vendor ID */, Bundle>
|
||||
{
|
||||
static BundleMap load();
|
||||
|
||||
Bundle& qidi_bundle();
|
||||
const Bundle& qidi_bundle() const;
|
||||
};
|
||||
|
||||
struct Materials;
|
||||
|
||||
|
||||
|
||||
struct PrinterPickerEvent;
|
||||
|
||||
// GUI elements
|
||||
|
||||
typedef std::function<bool(const VendorProfile::PrinterModel&)> ModelFilter;
|
||||
|
||||
struct PrinterPicker: wxPanel
|
||||
{
|
||||
struct Checkbox : wxCheckBox
|
||||
{
|
||||
Checkbox(wxWindow *parent, const wxString &label, const std::string &model, const std::string &variant) :
|
||||
wxCheckBox(parent, wxID_ANY, label),
|
||||
model(model),
|
||||
variant(variant)
|
||||
{}
|
||||
|
||||
std::string model;
|
||||
std::string variant;
|
||||
};
|
||||
|
||||
const std::string vendor_id;
|
||||
std::vector<Checkbox*> cboxes;
|
||||
std::vector<Checkbox*> cboxes_alt;
|
||||
|
||||
PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig, const ModelFilter &filter);
|
||||
PrinterPicker(wxWindow *parent, const VendorProfile &vendor, wxString title, size_t max_cols, const AppConfig &appconfig);
|
||||
|
||||
void select_all(bool select, bool alternates = false);
|
||||
void select_one(size_t i, bool select);
|
||||
bool any_selected() const;
|
||||
std::set<std::string> get_selected_models() const ;
|
||||
|
||||
int get_width() const { return width; }
|
||||
const std::vector<int>& get_button_indexes() { return m_button_indexes; }
|
||||
|
||||
static const std::string PRINTER_PLACEHOLDER;
|
||||
private:
|
||||
int width;
|
||||
std::vector<int> m_button_indexes;
|
||||
|
||||
void on_checkbox(const Checkbox *cbox, bool checked);
|
||||
};
|
||||
|
||||
struct ConfigWizardPage: wxPanel
|
||||
{
|
||||
ConfigWizard *parent;
|
||||
const wxString shortname;
|
||||
wxBoxSizer *content;
|
||||
const unsigned indent;
|
||||
|
||||
ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname, unsigned indent = 0);
|
||||
virtual ~ConfigWizardPage();
|
||||
|
||||
template<class T>
|
||||
T* append(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10)
|
||||
{
|
||||
content->Add(thing, proportion, flag, border);
|
||||
return thing;
|
||||
}
|
||||
|
||||
wxStaticText* append_text(wxString text);
|
||||
void append_spacer(int space);
|
||||
|
||||
ConfigWizard::priv *wizard_p() const { return parent->p.get(); }
|
||||
|
||||
virtual void apply_custom_config(DynamicPrintConfig &config) {}
|
||||
virtual void set_run_reason(ConfigWizard::RunReason run_reason) {}
|
||||
virtual void on_activate() {}
|
||||
};
|
||||
|
||||
struct PageWelcome: ConfigWizardPage
|
||||
{
|
||||
wxStaticText *welcome_text;
|
||||
wxCheckBox *cbox_reset;
|
||||
wxCheckBox *cbox_integrate;
|
||||
|
||||
PageWelcome(ConfigWizard *parent);
|
||||
|
||||
bool reset_user_profile() const { return cbox_reset != nullptr ? cbox_reset->GetValue() : false; }
|
||||
bool integrate_desktop() const { return cbox_integrate != nullptr ? cbox_integrate->GetValue() : false; }
|
||||
|
||||
virtual void set_run_reason(ConfigWizard::RunReason run_reason) override;
|
||||
};
|
||||
|
||||
struct PagePrinters: ConfigWizardPage
|
||||
{
|
||||
std::vector<PrinterPicker *> printer_pickers;
|
||||
Technology technology;
|
||||
bool install;
|
||||
|
||||
PagePrinters(ConfigWizard *parent,
|
||||
wxString title,
|
||||
wxString shortname,
|
||||
const VendorProfile &vendor,
|
||||
unsigned indent, Technology technology);
|
||||
|
||||
void select_all(bool select, bool alternates = false);
|
||||
int get_width() const;
|
||||
bool any_selected() const;
|
||||
std::set<std::string> get_selected_models();
|
||||
|
||||
std::string get_vendor_id() const { return printer_pickers.empty() ? "" : printer_pickers[0]->vendor_id; }
|
||||
|
||||
virtual void set_run_reason(ConfigWizard::RunReason run_reason) override;
|
||||
|
||||
bool has_printers { false };
|
||||
bool is_primary_printer_page { false };
|
||||
};
|
||||
|
||||
// Here we extend wxListBox and wxCheckListBox
|
||||
// to make the client data API much easier to use.
|
||||
template<class T, class D> struct DataList : public T
|
||||
{
|
||||
DataList(wxWindow *parent) : T(parent, wxID_ANY) {}
|
||||
DataList(wxWindow* parent, int style) : T(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, style) {}
|
||||
|
||||
// Note: We're _not_ using wxLB_SORT here because it doesn't do the right thing,
|
||||
// eg. "ABS" is sorted before "(All)"
|
||||
|
||||
int append(const std::string &label, const D *data) {
|
||||
void *ptr = reinterpret_cast<void*>(const_cast<D*>(data));
|
||||
return this->Append(from_u8(label), ptr);
|
||||
}
|
||||
|
||||
int append(const wxString &label, const D *data) {
|
||||
void *ptr = reinterpret_cast<void*>(const_cast<D*>(data));
|
||||
return this->Append(label, ptr);
|
||||
}
|
||||
|
||||
const D& get_data(int n) {
|
||||
return *reinterpret_cast<const D*>(this->GetClientData(n));
|
||||
}
|
||||
|
||||
int find(const D &data) {
|
||||
for (unsigned i = 0; i < this->GetCount(); i++) {
|
||||
if (get_data(i) == data) { return i; }
|
||||
}
|
||||
|
||||
return wxNOT_FOUND;
|
||||
}
|
||||
|
||||
int size() { return this->GetCount(); }
|
||||
|
||||
void on_mouse_move(const wxPoint& position) {
|
||||
int item = T::HitTest(position);
|
||||
|
||||
if(item == wxHitTest::wxHT_WINDOW_INSIDE)
|
||||
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_INSIDE";
|
||||
else if (item == wxHitTest::wxHT_WINDOW_OUTSIDE)
|
||||
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_OUTSIDE";
|
||||
else if(item == wxHitTest::wxHT_WINDOW_CORNER)
|
||||
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_CORNER";
|
||||
else if (item == wxHitTest::wxHT_WINDOW_VERT_SCROLLBAR)
|
||||
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_VERT_SCROLLBAR";
|
||||
else if (item == wxHitTest::wxHT_NOWHERE)
|
||||
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_NOWHERE";
|
||||
else if (item == wxHitTest::wxHT_MAX)
|
||||
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_MAX";
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(error) << "hit test: " << item;
|
||||
}
|
||||
};
|
||||
|
||||
typedef DataList<wxListBox, std::string> StringList;
|
||||
typedef DataList<wxCheckListBox, std::string> PresetList;
|
||||
|
||||
struct ProfilePrintData
|
||||
{
|
||||
std::reference_wrapper<const std::string> name;
|
||||
bool omnipresent;
|
||||
bool checked;
|
||||
ProfilePrintData(const std::string& n, bool o, bool c) : name(n), omnipresent(o), checked(c) {}
|
||||
};
|
||||
|
||||
struct PageMaterials: ConfigWizardPage
|
||||
{
|
||||
Materials *materials;
|
||||
StringList *list_printer, *list_type, *list_vendor;
|
||||
PresetList *list_profile;
|
||||
wxArrayInt sel_printers_prev;
|
||||
int sel_type_prev, sel_vendor_prev;
|
||||
bool presets_loaded;
|
||||
|
||||
wxFlexGridSizer *grid;
|
||||
wxHtmlWindow* html_window;
|
||||
|
||||
int compatible_printers_width = { 100 };
|
||||
std::string empty_printers_label;
|
||||
bool first_paint = { false };
|
||||
static const std::string EMPTY;
|
||||
static const std::string TEMPLATES;
|
||||
// notify user first time they choose template profile
|
||||
bool template_shown = { false };
|
||||
bool notification_shown = { false };
|
||||
int last_hovered_item = { -1 } ;
|
||||
|
||||
PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name);
|
||||
|
||||
void reload_presets();
|
||||
void update_lists(int sel_type, int sel_vendor, int last_selected_printer = -1);
|
||||
void on_material_highlighted(int sel_material);
|
||||
void on_material_hovered(int sel_material);
|
||||
void select_material(int i);
|
||||
void select_all(bool select);
|
||||
void clear();
|
||||
void set_compatible_printers_html_window(const std::vector<std::string>& printer_names, bool all_printers = false);
|
||||
void clear_compatible_printers_label();
|
||||
|
||||
void sort_list_data(StringList* list, bool add_All_item, bool material_type_ordering);
|
||||
void sort_list_data(PresetList* list, const std::vector<ProfilePrintData>& data);
|
||||
|
||||
void on_paint();
|
||||
void on_mouse_move_on_profiles(wxMouseEvent& evt);
|
||||
void on_mouse_enter_profiles(wxMouseEvent& evt);
|
||||
void on_mouse_leave_profiles(wxMouseEvent& evt);
|
||||
virtual void on_activate() override;
|
||||
};
|
||||
|
||||
struct Materials
|
||||
{
|
||||
Technology technology;
|
||||
// use vector for the presets to purpose of save of presets sorting in the bundle
|
||||
std::vector<const Preset*> presets;
|
||||
// String is alias of material, size_t number of compatible counters
|
||||
std::vector<std::pair<std::string, size_t>> compatibility_counter;
|
||||
std::set<std::string> types;
|
||||
std::set<const Preset*> printers;
|
||||
|
||||
Materials(Technology technology) : technology(technology) {}
|
||||
|
||||
void push(const Preset* preset);
|
||||
void add_printer(const Preset* preset);
|
||||
void clear();
|
||||
bool containts(const Preset* preset) const {
|
||||
//return std::find(presets.begin(), presets.end(), preset) != presets.end();
|
||||
return std::find_if(presets.begin(), presets.end(),
|
||||
[preset](const Preset* element) { return element == preset; }) != presets.end();
|
||||
|
||||
}
|
||||
|
||||
bool get_omnipresent(const Preset* preset) {
|
||||
return get_printer_counter(preset) == printers.size();
|
||||
}
|
||||
|
||||
const std::vector<const Preset*> get_presets_by_alias(const std::string name) {
|
||||
std::vector<const Preset*> ret_vec;
|
||||
for (auto it = presets.begin(); it != presets.end(); ++it) {
|
||||
if ((*it)->alias == name)
|
||||
ret_vec.push_back((*it));
|
||||
}
|
||||
return ret_vec;
|
||||
}
|
||||
|
||||
|
||||
|
||||
size_t get_printer_counter(const Preset* preset) {
|
||||
for (auto it : compatibility_counter) {
|
||||
if (it.first == preset->alias)
|
||||
return it.second;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const std::string& appconfig_section() const;
|
||||
const std::string& get_type(const Preset* preset) const;
|
||||
const std::string& get_vendor(const Preset* preset) const;
|
||||
|
||||
template<class F> void filter_presets(const Preset* printer, const std::string& printer_name, const std::string& type, const std::string& vendor, F cb) {
|
||||
for (auto preset : presets) {
|
||||
const Preset& prst = *(preset);
|
||||
const Preset& prntr = *printer;
|
||||
if (((printer == nullptr && printer_name == PageMaterials::EMPTY) || (printer != nullptr && is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor)))) &&
|
||||
(type.empty() || get_type(preset) == type) &&
|
||||
(vendor.empty() || get_vendor(preset) == vendor) &&
|
||||
prst.vendor && !prst.vendor->templates_profile) {
|
||||
|
||||
cb(preset);
|
||||
}
|
||||
else if ((printer == nullptr && printer_name == PageMaterials::TEMPLATES) && prst.vendor && prst.vendor->templates_profile &&
|
||||
(type.empty() || get_type(preset) == type) &&
|
||||
(vendor.empty() || get_vendor(preset) == vendor)) {
|
||||
cb(preset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
static const std::string& get_material_type(const Preset* preset);
|
||||
static const std::string& get_material_vendor(const Preset* preset);
|
||||
};
|
||||
|
||||
|
||||
struct PageCustom: ConfigWizardPage
|
||||
{
|
||||
PageCustom(ConfigWizard *parent);
|
||||
~PageCustom() {
|
||||
if (profile_name_editor)
|
||||
delete profile_name_editor;
|
||||
}
|
||||
|
||||
bool custom_wanted() const { return cb_custom->GetValue(); }
|
||||
bool is_valid_profile_name() const { return profile_name_editor->is_valid();}
|
||||
std::string profile_name() const { return profile_name_editor->preset_name(); }
|
||||
|
||||
private:
|
||||
static const char* default_profile_name;
|
||||
|
||||
wxCheckBox *cb_custom {nullptr};
|
||||
SavePresetDialog::Item *profile_name_editor {nullptr};
|
||||
|
||||
};
|
||||
|
||||
struct PageUpdate: ConfigWizardPage
|
||||
{
|
||||
bool version_check;
|
||||
bool preset_update;
|
||||
wxTextCtrl* path_text_ctrl;
|
||||
|
||||
PageUpdate(ConfigWizard *parent);
|
||||
};
|
||||
|
||||
|
||||
struct PageDownloader : ConfigWizardPage
|
||||
{
|
||||
DownloaderUtils::Worker* m_downloader { nullptr };
|
||||
|
||||
PageDownloader(ConfigWizard* parent);
|
||||
|
||||
bool on_finish_downloader() const ;
|
||||
};
|
||||
|
||||
struct PageReloadFromDisk : ConfigWizardPage
|
||||
{
|
||||
bool full_pathnames;
|
||||
|
||||
PageReloadFromDisk(ConfigWizard* parent);
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
struct PageFilesAssociation : ConfigWizardPage
|
||||
{
|
||||
private:
|
||||
wxCheckBox* cb_3mf{ nullptr };
|
||||
wxCheckBox* cb_stl{ nullptr };
|
||||
// wxCheckBox* cb_gcode;
|
||||
|
||||
public:
|
||||
PageFilesAssociation(ConfigWizard* parent);
|
||||
|
||||
bool associate_3mf() const { return cb_3mf->IsChecked(); }
|
||||
bool associate_stl() const { return cb_stl->IsChecked(); }
|
||||
// bool associate_gcode() const { return cb_gcode->IsChecked(); }
|
||||
};
|
||||
#endif // _WIN32
|
||||
|
||||
struct PageMode: ConfigWizardPage
|
||||
{
|
||||
wxRadioButton *radio_simple;
|
||||
wxRadioButton *radio_advanced;
|
||||
wxRadioButton *radio_expert;
|
||||
|
||||
wxCheckBox *check_inch;
|
||||
|
||||
PageMode(ConfigWizard *parent);
|
||||
|
||||
void serialize_mode(AppConfig *app_config) const;
|
||||
};
|
||||
|
||||
struct PageVendors: ConfigWizardPage
|
||||
{
|
||||
PageVendors(ConfigWizard *parent);
|
||||
};
|
||||
|
||||
struct PageFirmware: ConfigWizardPage
|
||||
{
|
||||
const ConfigOptionDef &gcode_opt;
|
||||
wxChoice *gcode_picker;
|
||||
|
||||
PageFirmware(ConfigWizard *parent);
|
||||
virtual void apply_custom_config(DynamicPrintConfig &config);
|
||||
};
|
||||
|
||||
struct PageBedShape: ConfigWizardPage
|
||||
{
|
||||
BedShapePanel *shape_panel;
|
||||
|
||||
PageBedShape(ConfigWizard *parent);
|
||||
virtual void apply_custom_config(DynamicPrintConfig &config);
|
||||
};
|
||||
|
||||
struct PageBuildVolume : ConfigWizardPage
|
||||
{
|
||||
wxTextCtrl* build_volume;
|
||||
|
||||
PageBuildVolume(ConfigWizard* parent);
|
||||
virtual void apply_custom_config(DynamicPrintConfig& config);
|
||||
};
|
||||
|
||||
struct PageDiameters: ConfigWizardPage
|
||||
{
|
||||
wxTextCtrl *diam_nozzle;
|
||||
wxTextCtrl *diam_filam;
|
||||
|
||||
PageDiameters(ConfigWizard *parent);
|
||||
virtual void apply_custom_config(DynamicPrintConfig &config);
|
||||
};
|
||||
|
||||
struct PageTemperatures: ConfigWizardPage
|
||||
{
|
||||
wxSpinCtrlDouble *spin_extr;
|
||||
wxSpinCtrlDouble *spin_bed;
|
||||
|
||||
PageTemperatures(ConfigWizard *parent);
|
||||
virtual void apply_custom_config(DynamicPrintConfig &config);
|
||||
};
|
||||
|
||||
// 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
|
||||
{
|
||||
public:
|
||||
ConfigWizardIndex(wxWindow *parent);
|
||||
|
||||
void add_page(ConfigWizardPage *page);
|
||||
void add_label(wxString label, unsigned indent = 0);
|
||||
|
||||
size_t active_item() const { return item_active; }
|
||||
ConfigWizardPage* active_page() const;
|
||||
bool active_is_last() const { return item_active < items.size() && item_active == last_page; }
|
||||
|
||||
void go_prev();
|
||||
void go_next();
|
||||
void go_to(size_t i);
|
||||
void go_to(const ConfigWizardPage *page);
|
||||
|
||||
void clear();
|
||||
void msw_rescale();
|
||||
|
||||
int em() const { return em_w; }
|
||||
|
||||
static const size_t NO_ITEM = size_t(-1);
|
||||
private:
|
||||
struct Item
|
||||
{
|
||||
wxString label;
|
||||
unsigned indent;
|
||||
ConfigWizardPage *page; // nullptr page => label-only item
|
||||
|
||||
bool operator==(ConfigWizardPage *page) const { return this->page == page; }
|
||||
};
|
||||
|
||||
int em_w;
|
||||
int em_h;
|
||||
ScalableBitmap bg;
|
||||
ScalableBitmap bullet_black;
|
||||
ScalableBitmap bullet_blue;
|
||||
ScalableBitmap bullet_white;
|
||||
|
||||
std::vector<Item> items;
|
||||
size_t item_active;
|
||||
ssize_t item_hover;
|
||||
size_t last_page;
|
||||
|
||||
int item_height() const { return std::max(bullet_black.GetHeight(), em_w) + em_w; }
|
||||
|
||||
void on_paint(wxPaintEvent &evt);
|
||||
void on_mouse_move(wxMouseEvent &evt);
|
||||
};
|
||||
|
||||
wxDEFINE_EVENT(EVT_INDEX_PAGE, wxCommandEvent);
|
||||
|
||||
|
||||
|
||||
// ConfigWizard private data
|
||||
|
||||
typedef std::map<std::string, std::set<std::string>> PresetAliases;
|
||||
|
||||
struct ConfigWizard::priv
|
||||
{
|
||||
ConfigWizard *q;
|
||||
ConfigWizard::RunReason run_reason = RR_USER;
|
||||
AppConfig appconfig_new; // Backing for vendor/model/variant and material selections in the GUI
|
||||
BundleMap bundles; // Holds all loaded config bundles, the key is the vendor names.
|
||||
// Materials refers to Presets in those bundles by pointers.
|
||||
// Also we update the is_visible flag in printer Presets according to the
|
||||
// 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
|
||||
bool custom_printer_selected { false }; // New custom printer is requested
|
||||
bool custom_printer_in_bundle { false }; // Older custom printer already exists when wizard starts
|
||||
// Set to true if there are none FFF printers on the main FFF page. If true, only SLA printers are shown (not even custum printers)
|
||||
bool only_sla_mode { false };
|
||||
bool template_profile_selected { false }; // This bool has one purpose - to tell that template profile should be installed if its not (because it cannot be added to appconfig)
|
||||
|
||||
wxScrolledWindow *hscroll = nullptr;
|
||||
wxBoxSizer *hscroll_sizer = nullptr;
|
||||
wxBoxSizer *btnsizer = nullptr;
|
||||
ConfigWizardPage *page_current = nullptr;
|
||||
ConfigWizardIndex *index = nullptr;
|
||||
wxButton *btn_sel_all = nullptr;
|
||||
wxButton *btn_prev = nullptr;
|
||||
wxButton *btn_next = nullptr;
|
||||
wxButton *btn_finish = nullptr;
|
||||
wxButton *btn_cancel = nullptr;
|
||||
|
||||
PageWelcome *page_welcome = nullptr;
|
||||
PagePrinters *page_fff = nullptr;
|
||||
PagePrinters *page_msla = nullptr;
|
||||
PageMaterials *page_filaments = nullptr;
|
||||
PageMaterials *page_sla_materials = nullptr;
|
||||
PageCustom *page_custom = nullptr;
|
||||
PageUpdate* page_update = nullptr;
|
||||
PageDownloader* page_downloader = nullptr;
|
||||
PageReloadFromDisk *page_reload_from_disk = nullptr;
|
||||
#ifdef _WIN32
|
||||
PageFilesAssociation* page_files_association = nullptr;
|
||||
#endif // _WIN32
|
||||
PageMode *page_mode = nullptr;
|
||||
PageVendors *page_vendors = nullptr;
|
||||
Pages3rdparty pages_3rdparty;
|
||||
|
||||
// Custom setup pages
|
||||
PageFirmware *page_firmware = nullptr;
|
||||
PageBedShape *page_bed = nullptr;
|
||||
PageDiameters *page_diams = nullptr;
|
||||
PageTemperatures *page_temps = nullptr;
|
||||
PageBuildVolume* page_bvolume = nullptr;
|
||||
|
||||
// Pointers to all pages (regardless or whether currently part of the ConfigWizardIndex)
|
||||
std::vector<ConfigWizardPage*> all_pages;
|
||||
|
||||
priv(ConfigWizard *q)
|
||||
: q(q)
|
||||
, appconfig_new(AppConfig::EAppMode::Editor)
|
||||
, filaments(T_FFF)
|
||||
, sla_materials(T_SLA)
|
||||
{}
|
||||
|
||||
void load_pages();
|
||||
void init_dialog_size();
|
||||
|
||||
void load_vendors();
|
||||
void add_page(ConfigWizardPage *page);
|
||||
void enable_next(bool enable);
|
||||
void set_start_page(ConfigWizard::StartPage start_page);
|
||||
void create_3rdparty_pages();
|
||||
void set_run_reason(RunReason run_reason);
|
||||
void update_materials(Technology technology);
|
||||
|
||||
void on_custom_setup(const bool custom_wanted);
|
||||
void on_printer_pick(PagePrinters *page, const PrinterPickerEvent &evt);
|
||||
void select_default_materials_for_printer_model(const VendorProfile::PrinterModel &printer_model, Technology technology);
|
||||
void select_default_materials_for_printer_models(Technology technology, const std::set<const VendorProfile::PrinterModel*> &printer_models);
|
||||
void on_3rdparty_install(const VendorProfile *vendor, bool install);
|
||||
|
||||
bool on_bnt_finish();
|
||||
bool check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id = std::string());
|
||||
bool apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes);
|
||||
// #ys_FIXME_alise
|
||||
void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add);
|
||||
//#ifdef __linux__
|
||||
// void perform_desktop_integration() const;
|
||||
//#endif
|
||||
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(); }
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
69
src/slic3r/GUI/CoordAxes.cpp
Normal file
69
src/slic3r/GUI/CoordAxes.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include "CoordAxes.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "3DScene.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "Camera.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
const float CoordAxes::DefaultStemRadius = 0.5f;
|
||||
const float CoordAxes::DefaultStemLength = 25.0f;
|
||||
const float CoordAxes::DefaultTipRadius = 2.5f * CoordAxes::DefaultStemRadius;
|
||||
const float CoordAxes::DefaultTipLength = 5.0f;
|
||||
|
||||
void CoordAxes::render(const Transform3d& trafo, float emission_factor)
|
||||
{
|
||||
auto render_axis = [this](GLShaderProgram& shader, const Transform3d& transform) {
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
const Transform3d& view_matrix = camera.get_view_matrix();
|
||||
const Transform3d matrix = view_matrix * transform;
|
||||
shader.set_uniform("view_model_matrix", matrix);
|
||||
shader.set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
shader.set_uniform("view_normal_matrix", (Matrix3d)(view_matrix.matrix().block(0, 0, 3, 3) * transform.matrix().block(0, 0, 3, 3).inverse().transpose()));
|
||||
m_arrow.render();
|
||||
};
|
||||
|
||||
if (!m_arrow.is_initialized())
|
||||
m_arrow.init_from(stilized_arrow(16, m_tip_radius, m_tip_length, m_stem_radius, m_stem_length));
|
||||
|
||||
GLShaderProgram* curr_shader = wxGetApp().get_current_shader();
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
|
||||
if (shader == nullptr)
|
||||
return;
|
||||
|
||||
if (curr_shader != nullptr)
|
||||
curr_shader->stop_using();
|
||||
|
||||
shader->start_using();
|
||||
shader->set_uniform("emission_factor", emission_factor);
|
||||
|
||||
// Scale the axes if the camera is close to them to avoid issues
|
||||
// such as https://github.com/qidi3d/QIDISlicer/issues/9483
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
Transform3d scale_tr = Transform3d::Identity();
|
||||
scale_tr.scale(std::min(1., camera.get_inv_zoom() * 10.));
|
||||
|
||||
// x axis
|
||||
m_arrow.set_color(ColorRGBA::X());
|
||||
render_axis(*shader, trafo * Geometry::translation_transform(m_origin) * Geometry::rotation_transform({ 0.0, 0.5 * M_PI, 0.0 }) * scale_tr);
|
||||
|
||||
// y axis
|
||||
m_arrow.set_color(ColorRGBA::Y());
|
||||
render_axis(*shader, trafo * Geometry::translation_transform(m_origin) * Geometry::rotation_transform({ -0.5 * M_PI, 0.0, 0.0 }) * scale_tr);
|
||||
|
||||
// z axis
|
||||
m_arrow.set_color(ColorRGBA::Z());
|
||||
render_axis(*shader, trafo * Geometry::translation_transform(m_origin) * scale_tr);
|
||||
|
||||
shader->stop_using();
|
||||
if (curr_shader != nullptr)
|
||||
curr_shader->start_using();
|
||||
}
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
57
src/slic3r/GUI/CoordAxes.hpp
Normal file
57
src/slic3r/GUI/CoordAxes.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
#ifndef slic3r_CoordAxes_hpp_
|
||||
#define slic3r_CoordAxes_hpp_
|
||||
|
||||
#include "GLModel.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class CoordAxes
|
||||
{
|
||||
public:
|
||||
static const float DefaultStemRadius;
|
||||
static const float DefaultStemLength;
|
||||
static const float DefaultTipRadius;
|
||||
static const float DefaultTipLength;
|
||||
|
||||
private:
|
||||
Vec3d m_origin{ Vec3d::Zero() };
|
||||
float m_stem_radius{ DefaultStemRadius };
|
||||
float m_stem_length{ DefaultStemLength };
|
||||
float m_tip_radius{ DefaultTipRadius };
|
||||
float m_tip_length{ DefaultTipLength };
|
||||
GLModel m_arrow;
|
||||
|
||||
public:
|
||||
const Vec3d& get_origin() const { return m_origin; }
|
||||
void set_origin(const Vec3d& origin) { m_origin = origin; }
|
||||
void set_stem_radius(float radius) {
|
||||
m_stem_radius = radius;
|
||||
m_arrow.reset();
|
||||
}
|
||||
void set_stem_length(float length) {
|
||||
m_stem_length = length;
|
||||
m_arrow.reset();
|
||||
}
|
||||
void set_tip_radius(float radius) {
|
||||
m_tip_radius = radius;
|
||||
m_arrow.reset();
|
||||
}
|
||||
void set_tip_length(float length) {
|
||||
m_tip_length = length;
|
||||
m_arrow.reset();
|
||||
}
|
||||
|
||||
float get_stem_radius() const { return m_stem_radius; }
|
||||
float get_stem_length() const { return m_stem_length; }
|
||||
float get_tip_radius() const { return m_tip_radius; }
|
||||
float get_tip_length() const { return m_tip_length; }
|
||||
float get_total_length() const { return m_stem_length + m_tip_length; }
|
||||
|
||||
void render(const Transform3d& trafo, float emission_factor = 0.0f);
|
||||
};
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
#endif // slic3r_CoordAxes_hpp_
|
||||
673
src/slic3r/GUI/DesktopIntegrationDialog.cpp
Normal file
673
src/slic3r/GUI/DesktopIntegrationDialog.cpp
Normal file
@@ -0,0 +1,673 @@
|
||||
#ifdef __linux__
|
||||
#include "DesktopIntegrationDialog.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "format.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "NotificationManager.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/Platform.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/dll/runtime_symbol_info.hpp>
|
||||
#include <boost/algorithm/string/replace.hpp>
|
||||
|
||||
#include <wx/filename.h>
|
||||
#include <wx/stattext.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
namespace {
|
||||
|
||||
// escaping of path string according to
|
||||
// https://cgit.freedesktop.org/xdg/xdg-specs/tree/desktop-entry/desktop-entry-spec.xml
|
||||
std::string escape_string(const std::string& str)
|
||||
{
|
||||
// The buffer needs to be bigger if escaping <,>,&
|
||||
std::vector<char> out(str.size() * 4, 0);
|
||||
char *outptr = out.data();
|
||||
for (size_t i = 0; i < str.size(); ++ i) {
|
||||
char c = str[i];
|
||||
// must be escaped
|
||||
if (c == '\"') { //double quote
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = '\"';
|
||||
} else if (c == '`') { // backtick character
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = '`';
|
||||
} else if (c == '$') { // dollar sign
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = '$';
|
||||
} else if (c == '\\') { // backslash character
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = '\\';
|
||||
// Reserved characters
|
||||
// At Ubuntu, all these characters must NOT be escaped for desktop integration to work
|
||||
/*
|
||||
} else if (c == ' ') { // space
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = ' ';
|
||||
} else if (c == '\t') { // tab
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = '\t';
|
||||
} else if (c == '\n') { // newline
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = '\n';
|
||||
} else if (c == '\'') { // single quote
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = '\'';
|
||||
} else if (c == '>') { // greater-than sign
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = '&';
|
||||
(*outptr ++) = 'g';
|
||||
(*outptr ++) = 't';
|
||||
(*outptr ++) = ';';
|
||||
} else if (c == '<') { //less-than sign
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = '&';
|
||||
(*outptr ++) = 'l';
|
||||
(*outptr ++) = 't';
|
||||
(*outptr ++) = ';';
|
||||
} else if (c == '~') { // tilde
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = '~';
|
||||
} else if (c == '|') { // vertical bar
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = '|';
|
||||
} else if (c == '&') { // ampersand
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = '&';
|
||||
(*outptr ++) = 'a';
|
||||
(*outptr ++) = 'm';
|
||||
(*outptr ++) = 'p';
|
||||
(*outptr ++) = ';';
|
||||
} else if (c == ';') { // semicolon
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = ';';
|
||||
} else if (c == '*') { //asterisk
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = '*';
|
||||
} else if (c == '?') { // question mark
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = '?';
|
||||
} else if (c == '#') { // hash mark
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = '#';
|
||||
} else if (c == '(') { // parenthesis
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = '(';
|
||||
} else if (c == ')') {
|
||||
(*outptr ++) = '\\';
|
||||
(*outptr ++) = ')';
|
||||
*/
|
||||
} else
|
||||
(*outptr ++) = c;
|
||||
}
|
||||
return std::string(out.data(), outptr - out.data());
|
||||
}
|
||||
// Disects path strings stored in system variable divided by ':' and adds into vector
|
||||
void resolve_path_from_var(const std::string& var, std::vector<std::string>& paths)
|
||||
{
|
||||
wxString wxdirs;
|
||||
if (! wxGetEnv(boost::nowide::widen(var), &wxdirs) || wxdirs.empty() )
|
||||
return;
|
||||
std::string dirs = boost::nowide::narrow(wxdirs);
|
||||
for (size_t i = dirs.find(':'); i != std::string::npos; i = dirs.find(':'))
|
||||
{
|
||||
paths.push_back(dirs.substr(0, i));
|
||||
if (dirs.size() > i+1)
|
||||
dirs = dirs.substr(i+1);
|
||||
}
|
||||
paths.push_back(dirs);
|
||||
}
|
||||
// Return true if directory in path p+dir_name exists
|
||||
bool contains_path_dir(const std::string& p, const std::string& dir_name)
|
||||
{
|
||||
if (p.empty() || dir_name.empty())
|
||||
return false;
|
||||
boost::filesystem::path path(p + (p[p.size()-1] == '/' ? "" : "/") + dir_name);
|
||||
if (boost::filesystem::exists(path) && boost::filesystem::is_directory(path)) {
|
||||
//BOOST_LOG_TRIVIAL(debug) << path.string() << " " << std::oct << boost::filesystem::status(path).permissions();
|
||||
return true; //boost::filesystem::status(path).permissions() & boost::filesystem::owner_write;
|
||||
} else
|
||||
BOOST_LOG_TRIVIAL(debug) << path.string() << " doesnt exists";
|
||||
return false;
|
||||
}
|
||||
// Creates directory in path if not exists yet
|
||||
void create_dir(const boost::filesystem::path& path)
|
||||
{
|
||||
if (boost::filesystem::exists(path))
|
||||
return;
|
||||
BOOST_LOG_TRIVIAL(debug)<< "creating " << path.string();
|
||||
boost::system::error_code ec;
|
||||
boost::filesystem::create_directory(path, ec);
|
||||
if (ec)
|
||||
BOOST_LOG_TRIVIAL(error)<< "create directory failed: " << ec.message();
|
||||
}
|
||||
// Starts at basic_path (excluded) and creates all directories in dir_path
|
||||
void create_path(const std::string& basic_path, const std::string& dir_path)
|
||||
{
|
||||
if (basic_path.empty() || dir_path.empty())
|
||||
return;
|
||||
|
||||
boost::filesystem::path path(basic_path);
|
||||
std::string dirs = dir_path;
|
||||
for (size_t i = dirs.find('/'); i != std::string::npos; i = dirs.find('/'))
|
||||
{
|
||||
std::string dir = dirs.substr(0, i);
|
||||
path = boost::filesystem::path(path.string() +"/"+ dir);
|
||||
create_dir(path);
|
||||
dirs = dirs.substr(i+1);
|
||||
}
|
||||
path = boost::filesystem::path(path.string() +"/"+ dirs);
|
||||
create_dir(path);
|
||||
}
|
||||
// Calls our internal copy_file function to copy file at icon_path to dest_path
|
||||
bool copy_icon(const std::string& icon_path, const std::string& dest_path)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) <<"icon from "<< icon_path;
|
||||
BOOST_LOG_TRIVIAL(debug) <<"icon to "<< dest_path;
|
||||
std::string error_message;
|
||||
auto cfr = copy_file(icon_path, dest_path, error_message, false);
|
||||
if (cfr) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "Copy icon fail(" << cfr << "): " << error_message;
|
||||
return false;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(debug) << "Copy icon success.";
|
||||
return true;
|
||||
}
|
||||
// Creates new file filled with data.
|
||||
bool create_desktop_file(const std::string& path, const std::string& data)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) <<".desktop to "<< path;
|
||||
std::ofstream output(path);
|
||||
output << data;
|
||||
struct stat buffer;
|
||||
if (stat(path.c_str(), &buffer) == 0)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << "Desktop file created.";
|
||||
return true;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(debug) << "Desktop file NOT created.";
|
||||
return false;
|
||||
}
|
||||
} // namespace integratec_desktop_internal
|
||||
|
||||
// methods that actually do / undo desktop integration. Static to be accesible from anywhere.
|
||||
bool DesktopIntegrationDialog::is_integrated()
|
||||
{
|
||||
const AppConfig *app_config = wxGetApp().app_config;
|
||||
std::string path(app_config->get("desktop_integration_app_path"));
|
||||
BOOST_LOG_TRIVIAL(debug) << "Desktop integration desktop file path: " << path;
|
||||
|
||||
if (path.empty())
|
||||
return false;
|
||||
|
||||
// confirmation that QIDISlicer.desktop exists
|
||||
struct stat buffer;
|
||||
return (stat (path.c_str(), &buffer) == 0);
|
||||
}
|
||||
bool DesktopIntegrationDialog::integration_possible()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
void DesktopIntegrationDialog::perform_desktop_integration()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << "performing desktop integration.";
|
||||
// Path to appimage
|
||||
const char *appimage_env = std::getenv("APPIMAGE");
|
||||
std::string excutable_path;
|
||||
if (appimage_env) {
|
||||
try {
|
||||
excutable_path = boost::filesystem::canonical(boost::filesystem::path(appimage_env)).string();
|
||||
} catch (std::exception &) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - boost::filesystem::canonical did not return appimage path.";
|
||||
show_error(nullptr, _L("Performing desktop integration failed - boost::filesystem::canonical did not return appimage path."));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// not appimage - find executable
|
||||
excutable_path = boost::dll::program_location().string();
|
||||
//excutable_path = wxStandardPaths::Get().GetExecutablePath().string();
|
||||
BOOST_LOG_TRIVIAL(debug) << "non-appimage path to executable: " << excutable_path;
|
||||
if (excutable_path.empty())
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - no executable found.";
|
||||
show_error(nullptr, _L("Performing desktop integration failed - Could not find executable."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Escape ' characters in appimage, other special symbols will be esacaped in desktop file by 'excutable_path'
|
||||
//boost::replace_all(excutable_path, "'", "'\\''");
|
||||
excutable_path = escape_string(excutable_path);
|
||||
|
||||
// Find directories icons and applications
|
||||
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
|
||||
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
|
||||
// $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data files in addition to the $XDG_DATA_HOME base directory.
|
||||
// The directories in $XDG_DATA_DIRS should be seperated with a colon ':'.
|
||||
// If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used.
|
||||
std::vector<std::string>target_candidates;
|
||||
resolve_path_from_var("XDG_DATA_HOME", target_candidates);
|
||||
resolve_path_from_var("XDG_DATA_DIRS", target_candidates);
|
||||
|
||||
AppConfig *app_config = wxGetApp().app_config;
|
||||
// suffix string to create different desktop file for alpha, beta.
|
||||
|
||||
std::string version_suffix;
|
||||
std::string name_suffix;
|
||||
std::string version(SLIC3R_VERSION);
|
||||
if (version.find("alpha") != std::string::npos)
|
||||
{
|
||||
version_suffix = "-alpha";
|
||||
name_suffix = " - alpha";
|
||||
}else if (version.find("beta") != std::string::npos)
|
||||
{
|
||||
version_suffix = "-beta";
|
||||
name_suffix = " - beta";
|
||||
}
|
||||
|
||||
// theme path to icon destination
|
||||
std::string icon_theme_path;
|
||||
std::string icon_theme_dirs;
|
||||
|
||||
if (platform_flavor() == PlatformFlavor::LinuxOnChromium) {
|
||||
icon_theme_path = "hicolor/96x96/apps/";
|
||||
icon_theme_dirs = "/hicolor/96x96/apps";
|
||||
}
|
||||
|
||||
std::string target_dir_icons;
|
||||
std::string target_dir_desktop;
|
||||
|
||||
// slicer icon
|
||||
// iterate thru target_candidates to find icons folder
|
||||
for (size_t i = 0; i < target_candidates.size(); ++i) {
|
||||
// Copy icon QIDISlicer.png from resources_dir()/icons to target_dir_icons/icons/
|
||||
if (contains_path_dir(target_candidates[i], "icons")) {
|
||||
target_dir_icons = target_candidates[i];
|
||||
std::string icon_path = GUI::format("%1%/icons/QIDISlicer.png",resources_dir());
|
||||
std::string dest_path = GUI::format("%1%/icons/%2%QIDISlicer%3%.png", target_dir_icons, icon_theme_path, version_suffix);
|
||||
if (copy_icon(icon_path, dest_path))
|
||||
break; // success
|
||||
else
|
||||
target_dir_icons.clear(); // copying failed
|
||||
}
|
||||
// if all failed - try creating default home folder
|
||||
if (i == target_candidates.size() - 1) {
|
||||
// create $HOME/.local/share
|
||||
create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/icons" + icon_theme_dirs);
|
||||
// copy icon
|
||||
target_dir_icons = GUI::format("%1%/.local/share",wxFileName::GetHomeDir());
|
||||
std::string icon_path = GUI::format("%1%/icons/QIDISlicer.png",resources_dir());
|
||||
std::string dest_path = GUI::format("%1%/icons/%2%QIDISlicer%3%.png", target_dir_icons, icon_theme_path, version_suffix);
|
||||
if (!contains_path_dir(target_dir_icons, "icons")
|
||||
|| !copy_icon(icon_path, dest_path)) {
|
||||
// every attempt failed - icon wont be present
|
||||
target_dir_icons.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
if(target_dir_icons.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Copying QIDISlicer icon to icons directory failed.";
|
||||
} else
|
||||
// save path to icon
|
||||
app_config->set("desktop_integration_icon_slicer_path", GUI::format("%1%/icons/%2%QIDISlicer%3%.png", target_dir_icons, icon_theme_path, version_suffix));
|
||||
|
||||
// desktop file
|
||||
// iterate thru target_candidates to find applications folder
|
||||
|
||||
std::string desktop_file = GUI::format(
|
||||
"[Desktop Entry]\n"
|
||||
"Name=QIDISlicer%1%\n"
|
||||
"GenericName=3D Printing Software\n"
|
||||
"Icon=QIDISlicer%2%\n"
|
||||
"Exec=\"%3%\" %%F\n"
|
||||
"Terminal=false\n"
|
||||
"Type=Application\n"
|
||||
"MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;application/x-amf;\n"
|
||||
"Categories=Graphics;3DGraphics;Engineering;\n"
|
||||
"Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA\n"
|
||||
"StartupNotify=false\n"
|
||||
"StartupWMClass=qidi-slicer\n", name_suffix, version_suffix, excutable_path);
|
||||
|
||||
bool candidate_found = false;
|
||||
for (size_t i = 0; i < target_candidates.size(); ++i) {
|
||||
if (contains_path_dir(target_candidates[i], "applications")) {
|
||||
target_dir_desktop = target_candidates[i];
|
||||
// Write slicer desktop file
|
||||
std::string path = GUI::format("%1%/applications/QIDISlicer%2%.desktop", target_dir_desktop, version_suffix);
|
||||
if (create_desktop_file(path, desktop_file)) {
|
||||
candidate_found = true;
|
||||
BOOST_LOG_TRIVIAL(debug) << "QIDISlicer.desktop file installation success.";
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// write failed - try another path
|
||||
BOOST_LOG_TRIVIAL(debug) << "Attempt to QIDISlicer.desktop file installation failed. failed path: " << target_candidates[i];
|
||||
target_dir_desktop.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
// if all failed - try creating default home folder
|
||||
if (!candidate_found) {
|
||||
// create $HOME/.local/share
|
||||
create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications");
|
||||
// create desktop file
|
||||
target_dir_desktop = GUI::format("%1%/.local/share", wxFileName::GetHomeDir());
|
||||
std::string path = GUI::format("%1%/applications/QIDISlicer%2%.desktop", target_dir_desktop, version_suffix);
|
||||
if (contains_path_dir(target_dir_desktop, "applications")) {
|
||||
if (!create_desktop_file(path, desktop_file)) {
|
||||
// Desktop file not written - end desktop integration
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create desktop file";
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Desktop file not written - end desktop integration
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed because the application directory was not found.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
assert(!target_dir_desktop.empty());
|
||||
if (target_dir_desktop.empty()) {
|
||||
// Desktop file not written - end desktop integration
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed because the application directory was not found.";
|
||||
show_error(nullptr, _L("Performing desktop integration failed because the application directory was not found."));
|
||||
return;
|
||||
}
|
||||
// save path to desktop file
|
||||
app_config->set("desktop_integration_app_path", GUI::format("%1%/applications/QIDISlicer%2%.desktop", target_dir_desktop, version_suffix));
|
||||
|
||||
// Repeat for Gcode viewer - use same paths as for slicer files
|
||||
// Do NOT add gcode viewer desktop file on ChromeOS
|
||||
if (platform_flavor() != PlatformFlavor::LinuxOnChromium) {
|
||||
// Icon
|
||||
if (!target_dir_icons.empty())
|
||||
{
|
||||
std::string icon_path = GUI::format("%1%/icons/QIDISlicer-gcodeviewer_192px.png",resources_dir());
|
||||
std::string dest_path = GUI::format("%1%/icons/%2%QIDISlicer-gcodeviewer%3%.png", target_dir_icons, icon_theme_path, version_suffix);
|
||||
if (copy_icon(icon_path, dest_path))
|
||||
// save path to icon
|
||||
app_config->set("desktop_integration_icon_viewer_path", dest_path);
|
||||
else
|
||||
BOOST_LOG_TRIVIAL(error) << "Copying Gcode Viewer icon to icons directory failed.";
|
||||
}
|
||||
|
||||
// Desktop file
|
||||
std::string desktop_file_viewer = GUI::format(
|
||||
"[Desktop Entry]\n"
|
||||
"Name=QIDI Gcode Viewer%1%\n"
|
||||
"GenericName=3D Printing Software\n"
|
||||
"Icon=QIDISlicer-gcodeviewer%2%\n"
|
||||
"Exec=\"%3%\" --gcodeviewer %%F\n"
|
||||
"Terminal=false\n"
|
||||
"Type=Application\n"
|
||||
"MimeType=text/x.gcode;\n"
|
||||
"Categories=Graphics;3DGraphics;\n"
|
||||
"Keywords=3D;Printing;Slicer;\n"
|
||||
"StartupNotify=false\n", name_suffix, version_suffix, excutable_path);
|
||||
std::string desktop_path = GUI::format("%1%/applications/QIDISlicerGcodeViewer%2%.desktop", target_dir_desktop, version_suffix);
|
||||
if (create_desktop_file(desktop_path, desktop_file_viewer))
|
||||
// save path to desktop file
|
||||
app_config->set("desktop_integration_app_viewer_path", desktop_path);
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing desktop integration failed - could not create Gcodeviewer desktop file";
|
||||
show_error(nullptr, _L("Performing desktop integration failed - could not create Gcodeviewer desktop file. QIDISlicer desktop file was probably created successfully."));
|
||||
}
|
||||
}
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess);
|
||||
}
|
||||
void DesktopIntegrationDialog::undo_desktop_intgration()
|
||||
{
|
||||
const AppConfig *app_config = wxGetApp().app_config;
|
||||
// slicer .desktop
|
||||
std::string path = std::string(app_config->get("desktop_integration_app_path"));
|
||||
if (!path.empty()) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
|
||||
std::remove(path.c_str());
|
||||
}
|
||||
// slicer icon
|
||||
path = std::string(app_config->get("desktop_integration_icon_slicer_path"));
|
||||
if (!path.empty()) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
|
||||
std::remove(path.c_str());
|
||||
}
|
||||
// No gcode viewer at ChromeOS
|
||||
if (platform_flavor() != PlatformFlavor::LinuxOnChromium) {
|
||||
// gcode viewer .desktop
|
||||
path = std::string(app_config->get("desktop_integration_app_viewer_path"));
|
||||
if (!path.empty()) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
|
||||
std::remove(path.c_str());
|
||||
}
|
||||
// gcode viewer icon
|
||||
path = std::string(app_config->get("desktop_integration_icon_viewer_path"));
|
||||
if (!path.empty()) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
|
||||
std::remove(path.c_str());
|
||||
}
|
||||
}
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::UndoDesktopIntegrationSuccess);
|
||||
}
|
||||
void DesktopIntegrationDialog::perform_downloader_desktop_integration()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << "performing downloader desktop integration.";
|
||||
// Path to appimage
|
||||
const char* appimage_env = std::getenv("APPIMAGE");
|
||||
std::string excutable_path;
|
||||
if (appimage_env) {
|
||||
try {
|
||||
excutable_path = boost::filesystem::canonical(boost::filesystem::path(appimage_env)).string();
|
||||
}
|
||||
catch (std::exception&) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed - boost::filesystem::canonical did not return appimage path.";
|
||||
show_error(nullptr, _L("Performing downloader desktop integration failed - boost::filesystem::canonical did not return appimage path."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// not appimage - find executable
|
||||
excutable_path = boost::dll::program_location().string();
|
||||
//excutable_path = wxStandardPaths::Get().GetExecutablePath().string();
|
||||
BOOST_LOG_TRIVIAL(debug) << "non-appimage path to executable: " << excutable_path;
|
||||
if (excutable_path.empty())
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed - no executable found.";
|
||||
show_error(nullptr, _L("Performing downloader desktop integration failed - Could not find executable."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Escape ' characters in appimage, other special symbols will be esacaped in desktop file by 'excutable_path'
|
||||
//boost::replace_all(excutable_path, "'", "'\\''");
|
||||
excutable_path = escape_string(excutable_path);
|
||||
|
||||
// Find directories icons and applications
|
||||
// $XDG_DATA_HOME defines the base directory relative to which user specific data files should be stored.
|
||||
// If $XDG_DATA_HOME is either not set or empty, a default equal to $HOME/.local/share should be used.
|
||||
// $XDG_DATA_DIRS defines the preference-ordered set of base directories to search for data files in addition to the $XDG_DATA_HOME base directory.
|
||||
// The directories in $XDG_DATA_DIRS should be seperated with a colon ':'.
|
||||
// If $XDG_DATA_DIRS is either not set or empty, a value equal to /usr/local/share/:/usr/share/ should be used.
|
||||
std::vector<std::string>target_candidates;
|
||||
resolve_path_from_var("XDG_DATA_HOME", target_candidates);
|
||||
resolve_path_from_var("XDG_DATA_DIRS", target_candidates);
|
||||
|
||||
AppConfig* app_config = wxGetApp().app_config;
|
||||
// suffix string to create different desktop file for alpha, beta.
|
||||
|
||||
std::string version_suffix;
|
||||
std::string name_suffix;
|
||||
std::string version(SLIC3R_VERSION);
|
||||
if (version.find("alpha") != std::string::npos)
|
||||
{
|
||||
version_suffix = "-alpha";
|
||||
name_suffix = " - alpha";
|
||||
}
|
||||
else if (version.find("beta") != std::string::npos)
|
||||
{
|
||||
version_suffix = "-beta";
|
||||
name_suffix = " - beta";
|
||||
}
|
||||
|
||||
// theme path to icon destination
|
||||
std::string icon_theme_path;
|
||||
std::string icon_theme_dirs;
|
||||
|
||||
if (platform_flavor() == PlatformFlavor::LinuxOnChromium) {
|
||||
icon_theme_path = "hicolor/96x96/apps/";
|
||||
icon_theme_dirs = "/hicolor/96x96/apps";
|
||||
}
|
||||
|
||||
std::string target_dir_desktop;
|
||||
|
||||
// desktop file
|
||||
// iterate thru target_candidates to find applications folder
|
||||
|
||||
std::string desktop_file_downloader = GUI::format(
|
||||
"[Desktop Entry]\n"
|
||||
"Name=QIDISlicer URL Protocol%1%\n"
|
||||
"Exec=\"%2%\" --single-instance %%u\n"
|
||||
"Terminal=false\n"
|
||||
"Type=Application\n"
|
||||
"MimeType=x-scheme-handler/qidislicer;\n"
|
||||
"StartupNotify=false\n"
|
||||
"NoDisplay=true\n"
|
||||
, name_suffix, excutable_path);
|
||||
|
||||
// desktop file for downloader as part of main app
|
||||
std::string desktop_path = GUI::format("%1%/applications/QIDISlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
|
||||
if (create_desktop_file(desktop_path, desktop_file_downloader)) {
|
||||
// save path to desktop file
|
||||
app_config->set("desktop_integration_URL_path", desktop_path);
|
||||
// finish registration on mime type
|
||||
std::string command = GUI::format("xdg-mime default QIDISlicerURLProtocol%1%.desktop x-scheme-handler/qidislicer", version_suffix);
|
||||
BOOST_LOG_TRIVIAL(debug) << "system command: " << command;
|
||||
int r = system(command.c_str());
|
||||
BOOST_LOG_TRIVIAL(debug) << "system result: " << r;
|
||||
|
||||
}
|
||||
|
||||
bool candidate_found = false;
|
||||
for (size_t i = 0; i < target_candidates.size(); ++i) {
|
||||
if (contains_path_dir(target_candidates[i], "applications")) {
|
||||
target_dir_desktop = target_candidates[i];
|
||||
// Write slicer desktop file
|
||||
std::string path = GUI::format("%1%/applications/QIDISlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
|
||||
if (create_desktop_file(path, desktop_file_downloader)) {
|
||||
app_config->set("desktop_integration_URL_path", path);
|
||||
candidate_found = true;
|
||||
BOOST_LOG_TRIVIAL(debug) << "QIDISlicerURLProtocol.desktop file installation success.";
|
||||
break;
|
||||
}
|
||||
else {
|
||||
// write failed - try another path
|
||||
BOOST_LOG_TRIVIAL(debug) << "Attempt to QIDISlicerURLProtocol.desktop file installation failed. failed path: " << target_candidates[i];
|
||||
target_dir_desktop.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
// if all failed - try creating default home folder
|
||||
if (!candidate_found) {
|
||||
// create $HOME/.local/share
|
||||
create_path(boost::nowide::narrow(wxFileName::GetHomeDir()), ".local/share/applications");
|
||||
// create desktop file
|
||||
target_dir_desktop = GUI::format("%1%/.local/share", wxFileName::GetHomeDir());
|
||||
std::string path = GUI::format("%1%/applications/QIDISlicerURLProtocol%2%.desktop", target_dir_desktop, version_suffix);
|
||||
if (contains_path_dir(target_dir_desktop, "applications")) {
|
||||
if (!create_desktop_file(path, desktop_file_downloader)) {
|
||||
// Desktop file not written - end desktop integration
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed - could not create desktop file.";
|
||||
return;
|
||||
}
|
||||
app_config->set("desktop_integration_URL_path", path);
|
||||
}
|
||||
else {
|
||||
// Desktop file not written - end desktop integration
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed because the application directory was not found.";
|
||||
return;
|
||||
}
|
||||
}
|
||||
assert(!target_dir_desktop.empty());
|
||||
if (target_dir_desktop.empty()) {
|
||||
// Desktop file not written - end desktop integration
|
||||
BOOST_LOG_TRIVIAL(error) << "Performing downloader desktop integration failed because the application directory was not found.";
|
||||
show_error(nullptr, _L("Performing downloader desktop integration failed because the application directory was not found."));
|
||||
return;
|
||||
}
|
||||
|
||||
// finish registration on mime type
|
||||
std::string command = GUI::format("xdg-mime default QIDISlicerURLProtocol%1%.desktop x-scheme-handler/qidislicer", version_suffix);
|
||||
BOOST_LOG_TRIVIAL(debug) << "system command: " << command;
|
||||
int r = system(command.c_str());
|
||||
BOOST_LOG_TRIVIAL(debug) << "system result: " << r;
|
||||
|
||||
wxGetApp().plater()->get_notification_manager()->push_notification(NotificationType::DesktopIntegrationSuccess);
|
||||
}
|
||||
void DesktopIntegrationDialog::undo_downloader_registration()
|
||||
{
|
||||
const AppConfig *app_config = wxGetApp().app_config;
|
||||
std::string path = std::string(app_config->get("desktop_integration_URL_path"));
|
||||
if (!path.empty()) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "removing " << path;
|
||||
std::remove(path.c_str());
|
||||
}
|
||||
// There is no need to undo xdg-mime default command. It is done automatically when desktop file is deleted.
|
||||
}
|
||||
|
||||
DesktopIntegrationDialog::DesktopIntegrationDialog(wxWindow *parent)
|
||||
: wxDialog(parent, wxID_ANY, _(L("Desktop Integration")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
|
||||
{
|
||||
bool can_undo = DesktopIntegrationDialog::is_integrated();
|
||||
|
||||
wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
|
||||
wxString text = _L("Desktop Integration sets this binary to be searchable by the system.\n\nPress \"Perform\" to proceed.");
|
||||
if (can_undo)
|
||||
text += "\nPress \"Undo\" to remove previous integration.";
|
||||
|
||||
vbox->Add(
|
||||
new wxStaticText( this, wxID_ANY, text),
|
||||
// , wxDefaultPosition, wxSize(100,50), wxTE_MULTILINE),
|
||||
1, // make vertically stretchable
|
||||
wxEXPAND | // make horizontally stretchable
|
||||
wxALL, // and make border all around
|
||||
10 ); // set border width to 10
|
||||
|
||||
|
||||
wxBoxSizer *btn_szr = new wxBoxSizer(wxHORIZONTAL);
|
||||
wxButton *btn_perform = new wxButton(this, wxID_ANY, _L("Perform"));
|
||||
btn_szr->Add(btn_perform, 0, wxALL, 10);
|
||||
|
||||
btn_perform->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { DesktopIntegrationDialog::perform_desktop_integration(); EndModal(wxID_ANY); });
|
||||
|
||||
if (can_undo){
|
||||
wxButton *btn_undo = new wxButton(this, wxID_ANY, _L("Undo"));
|
||||
btn_szr->Add(btn_undo, 0, wxALL, 10);
|
||||
btn_undo->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { DesktopIntegrationDialog::undo_desktop_intgration(); EndModal(wxID_ANY); });
|
||||
}
|
||||
wxButton *btn_cancel = new wxButton(this, wxID_ANY, _L("Cancel"));
|
||||
btn_szr->Add(btn_cancel, 0, wxALL, 10);
|
||||
btn_cancel->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { EndModal(wxID_ANY); });
|
||||
|
||||
vbox->Add(btn_szr, 0, wxALIGN_CENTER);
|
||||
|
||||
SetSizerAndFit(vbox);
|
||||
}
|
||||
|
||||
DesktopIntegrationDialog::~DesktopIntegrationDialog()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
#endif // __linux__
|
||||
45
src/slic3r/GUI/DesktopIntegrationDialog.hpp
Normal file
45
src/slic3r/GUI/DesktopIntegrationDialog.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifdef __linux__
|
||||
#ifndef slic3r_DesktopIntegrationDialog_hpp_
|
||||
#define slic3r_DesktopIntegrationDialog_hpp_
|
||||
|
||||
#include <wx/dialog.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
class DesktopIntegrationDialog : public wxDialog
|
||||
{
|
||||
public:
|
||||
DesktopIntegrationDialog(wxWindow *parent);
|
||||
DesktopIntegrationDialog(DesktopIntegrationDialog &&) = delete;
|
||||
DesktopIntegrationDialog(const DesktopIntegrationDialog &) = delete;
|
||||
DesktopIntegrationDialog &operator=(DesktopIntegrationDialog &&) = delete;
|
||||
DesktopIntegrationDialog &operator=(const DesktopIntegrationDialog &) = delete;
|
||||
~DesktopIntegrationDialog();
|
||||
|
||||
// methods that actually do / undo desktop integration. Static to be accesible from anywhere.
|
||||
|
||||
// returns true if path to QIDISlicer.desktop is stored in App Config and existence of desktop file.
|
||||
// Does not check if desktop file leads to this binary or existence of icons and viewer desktop file.
|
||||
static bool is_integrated();
|
||||
// true if appimage
|
||||
static bool integration_possible();
|
||||
// Creates Desktop files and icons for both QIDISlicer and GcodeViewer.
|
||||
// Stores paths into App Config.
|
||||
// Rewrites if files already existed.
|
||||
// if perform_downloader:
|
||||
// Creates Destktop files for QIDISlicer downloader feature
|
||||
// Regiters QIDISlicer to start on qidislicer:// URL
|
||||
static void perform_desktop_integration();
|
||||
// Deletes Desktop files and icons for both QIDISlicer and GcodeViewer at paths stored in App Config.
|
||||
static void undo_desktop_intgration();
|
||||
|
||||
static void perform_downloader_desktop_integration();
|
||||
static void undo_downloader_registration();
|
||||
private:
|
||||
|
||||
};
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_DesktopIntegrationDialog_hpp_
|
||||
#endif // __linux__
|
||||
2909
src/slic3r/GUI/DoubleSlider.cpp
Normal file
2909
src/slic3r/GUI/DoubleSlider.cpp
Normal file
File diff suppressed because it is too large
Load Diff
484
src/slic3r/GUI/DoubleSlider.hpp
Normal file
484
src/slic3r/GUI/DoubleSlider.hpp
Normal file
@@ -0,0 +1,484 @@
|
||||
#ifndef slic3r_GUI_DoubleSlider_hpp_
|
||||
#define slic3r_GUI_DoubleSlider_hpp_
|
||||
|
||||
#include "libslic3r/CustomGCode.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
#include <wx/window.h>
|
||||
#include <wx/control.h>
|
||||
#include <wx/dc.h>
|
||||
#include <wx/slider.h>
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
class wxMenu;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
using namespace CustomGCode;
|
||||
class PrintObject;
|
||||
class Layer;
|
||||
|
||||
namespace DoubleSlider {
|
||||
|
||||
/* For exporting GCode in GCodeWriter is used XYZF_NUM(val) = PRECISION(val, 3) for XYZ values.
|
||||
* So, let use same value as a permissible error for layer height.
|
||||
*/
|
||||
constexpr double epsilon() { return 0.0011; }
|
||||
|
||||
// return true when areas are mostly equivalent
|
||||
bool equivalent_areas(const double& bottom_area, const double& top_area);
|
||||
|
||||
// return true if color change was detected
|
||||
bool check_color_change(const PrintObject* object, size_t frst_layer_id, size_t layers_cnt, bool check_overhangs,
|
||||
// what to do with detected color change
|
||||
// and return true when detection have to be desturbed
|
||||
std::function<bool(const Layer*)> break_condition);
|
||||
|
||||
// custom message the slider sends to its parent to notify a tick-change:
|
||||
wxDECLARE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent);
|
||||
|
||||
enum SelectedSlider {
|
||||
ssUndef,
|
||||
ssLower,
|
||||
ssHigher
|
||||
};
|
||||
|
||||
enum FocusedItem {
|
||||
fiNone,
|
||||
fiRevertIcon,
|
||||
fiOneLayerIcon,
|
||||
fiCogIcon,
|
||||
fiColorBand,
|
||||
fiActionIcon,
|
||||
fiLowerThumb,
|
||||
fiHigherThumb,
|
||||
fiSmartWipeTower,
|
||||
fiTick
|
||||
};
|
||||
|
||||
enum ConflictType
|
||||
{
|
||||
ctNone,
|
||||
ctModeConflict,
|
||||
ctMeaninglessColorChange,
|
||||
ctMeaninglessToolChange,
|
||||
ctRedundant
|
||||
};
|
||||
|
||||
enum MouseAction
|
||||
{
|
||||
maNone,
|
||||
maAddMenu, // show "Add" context menu for NOTexist active tick
|
||||
maEditMenu, // show "Edit" context menu for exist active tick
|
||||
maCogIconMenu, // show context for "cog" icon
|
||||
maForceColorEdit, // force color editing from colored band
|
||||
maAddTick, // force tick adding
|
||||
maDeleteTick, // force tick deleting
|
||||
maCogIconClick, // LeftMouseClick on "cog" icon
|
||||
maOneLayerIconClick, // LeftMouseClick on "one_layer" icon
|
||||
maRevertIconClick, // LeftMouseClick on "revert" icon
|
||||
};
|
||||
|
||||
enum DrawMode
|
||||
{
|
||||
dmRegular,
|
||||
dmSlaPrint,
|
||||
dmSequentialFffPrint,
|
||||
dmSequentialGCodeView,
|
||||
};
|
||||
|
||||
enum LabelType
|
||||
{
|
||||
ltHeightWithLayer,
|
||||
ltHeight,
|
||||
ltEstimatedTime,
|
||||
};
|
||||
|
||||
struct TickCode
|
||||
{
|
||||
bool operator<(const TickCode& other) const { return other.tick > this->tick; }
|
||||
bool operator>(const TickCode& other) const { return other.tick < this->tick; }
|
||||
|
||||
int tick = 0;
|
||||
Type type = ColorChange;
|
||||
int extruder = 0;
|
||||
std::string color;
|
||||
std::string extra;
|
||||
};
|
||||
|
||||
class TickCodeInfo
|
||||
{
|
||||
std::string custom_gcode;
|
||||
std::string pause_print_msg;
|
||||
bool m_suppress_plus = false;
|
||||
bool m_suppress_minus = false;
|
||||
bool m_use_default_colors= false;
|
||||
// int m_default_color_idx = 0;
|
||||
|
||||
std::vector<std::string>* m_colors {nullptr};
|
||||
|
||||
std::string get_color_for_tick(TickCode tick, Type type, const int extruder);
|
||||
|
||||
public:
|
||||
std::set<TickCode> ticks {};
|
||||
Mode mode = Undef;
|
||||
|
||||
bool empty() const { return ticks.empty(); }
|
||||
void set_pause_print_msg(const std::string& message) { pause_print_msg = message; }
|
||||
|
||||
bool add_tick(const int tick, Type type, int extruder, double print_z);
|
||||
bool edit_tick(std::set<TickCode>::iterator it, double print_z);
|
||||
void switch_code(Type type_from, Type type_to);
|
||||
bool switch_code_for_tick(std::set<TickCode>::iterator it, Type type_to, const int extruder);
|
||||
void erase_all_ticks_with_code(Type type);
|
||||
|
||||
bool has_tick_with_code(Type type);
|
||||
bool has_tick(int tick);
|
||||
ConflictType is_conflict_tick(const TickCode& tick, Mode out_mode, int only_extruder, double print_z);
|
||||
|
||||
// Get used extruders for tick.
|
||||
// Means all extruders(tools) which will be used during printing from current tick to the end
|
||||
std::set<int> get_used_extruders_for_tick(int tick, int only_extruder, double print_z, Mode force_mode = Undef) const;
|
||||
|
||||
void suppress_plus (bool suppress) { m_suppress_plus = suppress; }
|
||||
void suppress_minus(bool suppress) { m_suppress_minus = suppress; }
|
||||
bool suppressed_plus () { return m_suppress_plus; }
|
||||
bool suppressed_minus() { return m_suppress_minus; }
|
||||
void set_default_colors(bool default_colors_on) { m_use_default_colors = default_colors_on; }
|
||||
|
||||
void set_extruder_colors(std::vector<std::string>* extruder_colors) { m_colors = extruder_colors; }
|
||||
};
|
||||
|
||||
|
||||
struct ExtrudersSequence
|
||||
{
|
||||
bool is_mm_intervals = true;
|
||||
double interval_by_mm = 3.0;
|
||||
int interval_by_layers = 10;
|
||||
bool random_sequence { false };
|
||||
bool color_repetition { false };
|
||||
std::vector<size_t> extruders = { 0 };
|
||||
|
||||
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.random_sequence == this->random_sequence ) &&
|
||||
(other.color_repetition == this->color_repetition ) &&
|
||||
(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.random_sequence != this->random_sequence ) ||
|
||||
(other.color_repetition != this->color_repetition ) ||
|
||||
(other.extruders != this->extruders ) ;
|
||||
}
|
||||
|
||||
void add_extruder(size_t pos, size_t extruder_id = size_t(0))
|
||||
{
|
||||
extruders.insert(extruders.begin() + pos+1, extruder_id);
|
||||
}
|
||||
|
||||
void delete_extruder(size_t pos)
|
||||
{
|
||||
if (extruders.size() == 1)
|
||||
return;// last item can't be deleted
|
||||
extruders.erase(extruders.begin() + pos);
|
||||
}
|
||||
|
||||
void init(size_t extruders_count)
|
||||
{
|
||||
extruders.clear();
|
||||
for (size_t extruder = 0; extruder < extruders_count; extruder++)
|
||||
extruders.push_back(extruder);
|
||||
}
|
||||
};
|
||||
|
||||
class Control : public wxControl
|
||||
{
|
||||
public:
|
||||
Control(
|
||||
wxWindow *parent,
|
||||
wxWindowID id,
|
||||
int lowerValue,
|
||||
int higherValue,
|
||||
int minValue,
|
||||
int maxValue,
|
||||
const wxPoint& pos = wxDefaultPosition,
|
||||
const wxSize& size = wxDefaultSize,
|
||||
long style = wxSL_VERTICAL,
|
||||
const wxValidator& val = wxDefaultValidator,
|
||||
const wxString& name = wxEmptyString);
|
||||
~Control() {}
|
||||
|
||||
void msw_rescale();
|
||||
void sys_color_changed();
|
||||
|
||||
int GetMinValue() const { return m_min_value; }
|
||||
int GetMaxValue() const { return m_max_value; }
|
||||
double GetMinValueD() { return m_values.empty() ? 0. : m_values[m_min_value]; }
|
||||
double GetMaxValueD() { return m_values.empty() ? 0. : m_values[m_max_value]; }
|
||||
int GetLowerValue() const { return m_lower_value; }
|
||||
int GetHigherValue() const { return m_higher_value; }
|
||||
int GetActiveValue() const;
|
||||
double GetLowerValueD() { return get_double_value(ssLower); }
|
||||
double GetHigherValueD() { return get_double_value(ssHigher); }
|
||||
wxSize DoGetBestSize() const override;
|
||||
wxSize get_min_size() const ;
|
||||
|
||||
// Set low and high slider position. If the span is non-empty, disable the "one layer" mode.
|
||||
void SetLowerValue (const int lower_val);
|
||||
void SetHigherValue(const int higher_val);
|
||||
void SetSelectionSpan(const int lower_val, const int higher_val);
|
||||
|
||||
void SetMaxValue(const int max_value);
|
||||
void SetKoefForLabels(const double koef) { m_label_koef = koef; }
|
||||
void SetSliderValues(const std::vector<double>& values);
|
||||
void ChangeOneLayerLock();
|
||||
void SetSliderAlternateValues(const std::vector<double>& values) { m_alternate_values = values; }
|
||||
|
||||
Info GetTicksValues() const;
|
||||
void SetTicksValues(const Info &custom_gcode_per_print_z);
|
||||
void SetLayersTimes(const std::vector<float>& layers_times, float total_time);
|
||||
void SetLayersTimes(const std::vector<double>& layers_times);
|
||||
|
||||
void SetDrawMode(bool is_sla_print, bool is_sequential_print);
|
||||
void SetDrawMode(DrawMode mode) { m_draw_mode = mode; }
|
||||
|
||||
void SetManipulationMode(Mode mode) { m_mode = mode; }
|
||||
Mode GetManipulationMode() const { return m_mode; }
|
||||
void SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder);
|
||||
void SetExtruderColors(const std::vector<std::string>& extruder_colors);
|
||||
|
||||
bool IsNewPrint();
|
||||
|
||||
void set_render_as_disabled(bool value) { m_render_as_disabled = value; }
|
||||
bool is_rendering_as_disabled() const { return m_render_as_disabled; }
|
||||
|
||||
bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; }
|
||||
bool is_one_layer() const { return m_is_one_layer; }
|
||||
bool is_lower_at_min() const { return m_lower_value == m_min_value; }
|
||||
bool is_higher_at_max() const { return m_higher_value == m_max_value; }
|
||||
bool is_full_span() const { return this->is_lower_at_min() && this->is_higher_at_max(); }
|
||||
|
||||
void OnPaint(wxPaintEvent& ) { render(); }
|
||||
void OnLeftDown(wxMouseEvent& event);
|
||||
void OnMotion(wxMouseEvent& event);
|
||||
void OnLeftUp(wxMouseEvent& event);
|
||||
void OnEnterWin(wxMouseEvent& event) { enter_window(event, true); }
|
||||
void OnLeaveWin(wxMouseEvent& event) { enter_window(event, false); }
|
||||
void UseDefaultColors(bool def_colors_on) { m_ticks.set_default_colors(def_colors_on); }
|
||||
void OnWheel(wxMouseEvent& event);
|
||||
void OnKeyDown(wxKeyEvent &event);
|
||||
void OnKeyUp(wxKeyEvent &event);
|
||||
void OnChar(wxKeyEvent &event);
|
||||
void OnRightDown(wxMouseEvent& event);
|
||||
void OnRightUp(wxMouseEvent& event);
|
||||
|
||||
void add_code_as_tick(Type type, int selected_extruder = -1);
|
||||
// add default action for tick, when press "+"
|
||||
void add_current_tick(bool call_from_keyboard = false);
|
||||
// delete current tick, when press "-"
|
||||
void delete_current_tick();
|
||||
void edit_tick(int tick = -1);
|
||||
void switch_one_layer_mode();
|
||||
void discard_all_thicks();
|
||||
void move_current_thumb_to_pos(wxPoint pos);
|
||||
void edit_extruder_sequence();
|
||||
void jump_to_value();
|
||||
void enable_action_icon(bool enable) { m_enable_action_icon = enable; }
|
||||
void show_add_context_menu();
|
||||
void show_edit_context_menu();
|
||||
void show_cog_icon_context_menu();
|
||||
void auto_color_change();
|
||||
|
||||
ExtrudersSequence m_extruders_sequence;
|
||||
|
||||
protected:
|
||||
|
||||
void render();
|
||||
void draw_focus_rect(wxDC& dc);
|
||||
void draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_end);
|
||||
void draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos);
|
||||
void draw_thumb(wxDC& dc, const wxCoord& pos_coord, const SelectedSlider& selection);
|
||||
void draw_thumbs(wxDC& dc, const wxCoord& lower_pos, const wxCoord& higher_pos);
|
||||
void draw_ticks_pair(wxDC& dc, wxCoord pos, wxCoord mid, int tick_len);
|
||||
void draw_ticks(wxDC& dc);
|
||||
void draw_colored_band(wxDC& dc);
|
||||
void draw_ruler(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_tick_on_mouse_position(wxDC &dc);
|
||||
void draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, LabelType label_type = ltHeight, bool right_side = true) const;
|
||||
void draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const;
|
||||
|
||||
void update_thumb_rect(const wxCoord begin_x, const wxCoord begin_y, const SelectedSlider& selection);
|
||||
bool is_lower_thumb_editable();
|
||||
bool detect_selected_slider(const wxPoint& pt);
|
||||
void correct_lower_value();
|
||||
void correct_higher_value();
|
||||
void move_current_thumb(const bool condition);
|
||||
void enter_window(wxMouseEvent& event, const bool enter);
|
||||
bool is_wipe_tower_layer(int tick) const;
|
||||
|
||||
private:
|
||||
|
||||
bool is_point_in_rect(const wxPoint& pt, const wxRect& rect);
|
||||
int get_tick_near_point(const wxPoint& pt);
|
||||
|
||||
double get_scroll_step();
|
||||
wxString get_label(int tick, LabelType label_type = ltHeightWithLayer) const;
|
||||
void get_lower_and_higher_position(int& lower_pos, int& higher_pos);
|
||||
int get_value_from_position(const wxCoord x, const wxCoord y);
|
||||
int get_value_from_position(const wxPoint pos) { return get_value_from_position(pos.x, pos.y); }
|
||||
wxCoord get_position_from_value(const int value);
|
||||
wxSize get_size() const;
|
||||
void get_size(int* w, int* h) const;
|
||||
double get_double_value(const SelectedSlider& selection);
|
||||
int get_tick_from_value(double value, bool force_lower_bound = false);
|
||||
wxString get_tooltip(int tick = -1);
|
||||
int get_edited_tick_for_position(wxPoint pos, Type type = ColorChange);
|
||||
|
||||
std::string get_color_for_tool_change_tick(std::set<TickCode>::const_iterator it) const;
|
||||
std::string get_color_for_color_change_tick(std::set<TickCode>::const_iterator it) const;
|
||||
wxRect get_colored_band_rect();
|
||||
|
||||
// Get active extruders for tick.
|
||||
// Means one current extruder for not existing tick OR
|
||||
// 2 extruders - for existing tick (extruder before ToolChangeCode and extruder of current existing tick)
|
||||
// Use those values to disable selection of active extruders
|
||||
std::array<int, 2> get_active_extruders_for_tick(int tick) const;
|
||||
|
||||
void post_ticks_changed_event(Type type = Custom);
|
||||
bool check_ticks_changed_event(Type type);
|
||||
|
||||
void append_change_extruder_menu_item (wxMenu*, bool switch_current_code = false);
|
||||
void append_add_color_change_menu_item(wxMenu*, bool switch_current_code = false);
|
||||
|
||||
bool is_osx { false };
|
||||
wxFont m_font;
|
||||
int m_min_value;
|
||||
int m_max_value;
|
||||
int m_lower_value;
|
||||
int m_higher_value;
|
||||
|
||||
bool m_render_as_disabled{ false };
|
||||
|
||||
ScalableBitmap m_bmp_thumb_higher;
|
||||
ScalableBitmap m_bmp_thumb_lower;
|
||||
ScalableBitmap m_bmp_add_tick_on;
|
||||
ScalableBitmap m_bmp_add_tick_off;
|
||||
ScalableBitmap m_bmp_del_tick_on;
|
||||
ScalableBitmap m_bmp_del_tick_off;
|
||||
ScalableBitmap m_bmp_one_layer_lock_on;
|
||||
ScalableBitmap m_bmp_one_layer_lock_off;
|
||||
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;
|
||||
bool m_is_one_layer = false;
|
||||
bool m_is_focused = false;
|
||||
bool m_force_mode_apply = true;
|
||||
bool m_enable_action_icon = true;
|
||||
bool m_is_wipe_tower = false; //This flag indicates that there is multiple extruder print with wipe tower
|
||||
|
||||
DrawMode m_draw_mode = dmRegular;
|
||||
|
||||
Mode m_mode = SingleExtruder;
|
||||
int m_only_extruder = -1;
|
||||
|
||||
MouseAction m_mouse = maNone;
|
||||
FocusedItem m_focus = fiNone;
|
||||
wxPoint m_moving_pos = wxDefaultPosition;
|
||||
|
||||
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;
|
||||
long m_extra_style;
|
||||
float m_label_koef{ 1.0 };
|
||||
|
||||
std::vector<double> m_values;
|
||||
TickCodeInfo m_ticks;
|
||||
std::vector<double> m_layers_times;
|
||||
std::vector<double> m_layers_values;
|
||||
std::vector<std::string> m_extruder_colors;
|
||||
std::string m_print_obj_idxs;
|
||||
|
||||
std::vector<double> m_alternate_values;
|
||||
|
||||
// control's view variables
|
||||
wxCoord SLIDER_MARGIN; // margin around slider
|
||||
|
||||
wxPen DARK_ORANGE_PEN;
|
||||
wxPen ORANGE_PEN;
|
||||
wxPen LIGHT_ORANGE_PEN;
|
||||
|
||||
//B18
|
||||
wxPen LIGHT_BLUE_PEN;
|
||||
|
||||
|
||||
wxPen DARK_GREY_PEN;
|
||||
wxPen GREY_PEN;
|
||||
wxPen LIGHT_GREY_PEN;
|
||||
|
||||
wxPen FOCUS_RECT_PEN;
|
||||
wxBrush FOCUS_RECT_BRUSH;
|
||||
|
||||
std::vector<wxPen*> m_line_pens;
|
||||
std::vector<wxPen*> m_segm_pens;
|
||||
|
||||
class Ruler {
|
||||
wxWindow* m_parent{nullptr}; // m_parent is nullptr for Unused ruler
|
||||
// in this case we will not init/update/render it
|
||||
// values to check if ruler has to be updated
|
||||
double m_min_val;
|
||||
double m_max_val;
|
||||
double m_scroll_step;
|
||||
size_t m_max_values_cnt;
|
||||
int m_DPI;
|
||||
|
||||
public:
|
||||
|
||||
double long_step;
|
||||
double short_step;
|
||||
std::vector<double> max_values;// max value for each object/instance in sequence print
|
||||
// > 1 for sequential print
|
||||
|
||||
void set_parent(wxWindow* parent);
|
||||
void update_dpi();
|
||||
void init(const std::vector<double>& values, double scroll_step);
|
||||
void update(const std::vector<double>& values, double scroll_step);
|
||||
bool is_ok() { return long_step > 0 && short_step > 0; }
|
||||
size_t count() { return max_values.size(); }
|
||||
bool can_draw() { return m_parent != nullptr; }
|
||||
} m_ruler;
|
||||
};
|
||||
|
||||
} // DoubleSlider;
|
||||
|
||||
} // Slic3r
|
||||
|
||||
|
||||
|
||||
#endif // slic3r_GUI_DoubleSlider_hpp_
|
||||
247
src/slic3r/GUI/Downloader.cpp
Normal file
247
src/slic3r/GUI/Downloader.cpp
Normal file
@@ -0,0 +1,247 @@
|
||||
#include "Downloader.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "NotificationManager.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
namespace {
|
||||
void open_folder(const std::string& path)
|
||||
{
|
||||
// Code taken from NotificationManager.cpp
|
||||
|
||||
// Execute command to open a file explorer, platform dependent.
|
||||
// FIXME: The const_casts aren't needed in wxWidgets 3.1, remove them when we upgrade.
|
||||
|
||||
#ifdef _WIN32
|
||||
const wxString widepath = from_u8(path);
|
||||
const wchar_t* argv[] = { L"explorer", widepath.GetData(), nullptr };
|
||||
::wxExecute(const_cast<wchar_t**>(argv), wxEXEC_ASYNC, nullptr);
|
||||
#elif __APPLE__
|
||||
const char* argv[] = { "open", path.data(), nullptr };
|
||||
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr);
|
||||
#else
|
||||
const char* argv[] = { "xdg-open", path.data(), nullptr };
|
||||
|
||||
// Check if we're running in an AppImage container, if so, we need to remove AppImage's env vars,
|
||||
// because they may mess up the environment expected by the file manager.
|
||||
// Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure.
|
||||
if (wxGetEnv("APPIMAGE", nullptr)) {
|
||||
// We're running from AppImage
|
||||
wxEnvVariableHashMap env_vars;
|
||||
wxGetEnvMap(&env_vars);
|
||||
|
||||
env_vars.erase("APPIMAGE");
|
||||
env_vars.erase("APPDIR");
|
||||
env_vars.erase("LD_LIBRARY_PATH");
|
||||
env_vars.erase("LD_PRELOAD");
|
||||
env_vars.erase("UNION_PRELOAD");
|
||||
|
||||
wxExecuteEnv exec_env;
|
||||
exec_env.env = std::move(env_vars);
|
||||
|
||||
wxString owd;
|
||||
if (wxGetEnv("OWD", &owd)) {
|
||||
// This is the original work directory from which the AppImage image was run,
|
||||
// set it as CWD for the child process:
|
||||
exec_env.cwd = std::move(owd);
|
||||
}
|
||||
|
||||
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, &exec_env);
|
||||
}
|
||||
else {
|
||||
// Looks like we're NOT running from AppImage, we'll make no changes to the environment.
|
||||
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, nullptr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string filename_from_url(const std::string& url)
|
||||
{
|
||||
// TODO: can it be done with curl?
|
||||
size_t slash = url.find_last_of("/");
|
||||
if (slash == std::string::npos && slash != url.size() - 1)
|
||||
return std::string();
|
||||
return url.substr(slash + 1, url.size() - slash + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Download::Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
|
||||
: m_id(ID)
|
||||
, m_filename(filename_from_url(url))
|
||||
, m_dest_folder(dest_folder)
|
||||
{
|
||||
assert(boost::filesystem::is_directory(dest_folder));
|
||||
m_final_path = dest_folder / m_filename;
|
||||
m_file_get = std::make_shared<FileGet>(ID, std::move(url), m_filename, evt_handler, dest_folder);
|
||||
}
|
||||
|
||||
void Download::start()
|
||||
{
|
||||
m_state = DownloadState::DownloadOngoing;
|
||||
m_file_get->get();
|
||||
}
|
||||
void Download::cancel()
|
||||
{
|
||||
m_state = DownloadState::DownloadStopped;
|
||||
m_file_get->cancel();
|
||||
}
|
||||
void Download::pause()
|
||||
{
|
||||
//assert(m_state == DownloadState::DownloadOngoing);
|
||||
// if instead of assert - it can happen that user clicks on pause several times before the pause happens
|
||||
if (m_state != DownloadState::DownloadOngoing)
|
||||
return;
|
||||
m_state = DownloadState::DownloadPaused;
|
||||
m_file_get->pause();
|
||||
}
|
||||
void Download::resume()
|
||||
{
|
||||
//assert(m_state == DownloadState::DownloadPaused);
|
||||
if (m_state != DownloadState::DownloadPaused)
|
||||
return;
|
||||
m_state = DownloadState::DownloadOngoing;
|
||||
m_file_get->resume();
|
||||
}
|
||||
|
||||
|
||||
Downloader::Downloader()
|
||||
: wxEvtHandler()
|
||||
{
|
||||
//Bind(EVT_DWNLDR_FILE_COMPLETE, [](const wxCommandEvent& evt) {});
|
||||
//Bind(EVT_DWNLDR_FILE_PROGRESS, [](const wxCommandEvent& evt) {});
|
||||
//Bind(EVT_DWNLDR_FILE_ERROR, [](const wxCommandEvent& evt) {});
|
||||
//Bind(EVT_DWNLDR_FILE_NAME_CHANGE, [](const wxCommandEvent& evt) {});
|
||||
|
||||
Bind(EVT_DWNLDR_FILE_COMPLETE, &Downloader::on_complete, this);
|
||||
Bind(EVT_DWNLDR_FILE_PROGRESS, &Downloader::on_progress, this);
|
||||
Bind(EVT_DWNLDR_FILE_ERROR, &Downloader::on_error, this);
|
||||
Bind(EVT_DWNLDR_FILE_NAME_CHANGE, &Downloader::on_name_change, this);
|
||||
Bind(EVT_DWNLDR_FILE_PAUSED, &Downloader::on_paused, this);
|
||||
Bind(EVT_DWNLDR_FILE_CANCELED, &Downloader::on_canceled, this);
|
||||
}
|
||||
|
||||
void Downloader::start_download(const std::string& full_url)
|
||||
{
|
||||
assert(m_initialized);
|
||||
|
||||
// TODO: There is a misterious slash appearing in recieved msg on windows
|
||||
#ifdef _WIN32
|
||||
if (!boost::starts_with(full_url, "qidislicer://open/?file=")) {
|
||||
#else
|
||||
if (!boost::starts_with(full_url, "qidislicer://open?file=")) {
|
||||
#endif
|
||||
BOOST_LOG_TRIVIAL(error) << "Could not start download due to wrong URL: " << full_url;
|
||||
// TODO: show error?
|
||||
return;
|
||||
}
|
||||
size_t id = get_next_id();
|
||||
// TODO: still same mistery
|
||||
#ifdef _WIN32
|
||||
std::string escaped_url = FileGet::escape_url(full_url.substr(25));
|
||||
#else
|
||||
std::string escaped_url = FileGet::escape_url(full_url.substr(24));
|
||||
#endif
|
||||
if (!boost::starts_with(escaped_url, "https://") || !FileGet::is_subdomain(escaped_url, "printables.com")) {
|
||||
std::string msg = format(_L("Download won't start. Download URL doesn't point to https://printables.com : %1%"), escaped_url);
|
||||
BOOST_LOG_TRIVIAL(error) << msg;
|
||||
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
|
||||
ntf_mngr->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::RegularNotificationLevel, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string text(escaped_url);
|
||||
m_downloads.emplace_back(std::make_unique<Download>(id, std::move(escaped_url), this, m_dest_folder));
|
||||
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
|
||||
ntf_mngr->push_download_URL_progress_notification(id, m_downloads.back()->get_filename(), std::bind(&Downloader::user_action_callback, this, std::placeholders::_1, std::placeholders::_2));
|
||||
m_downloads.back()->start();
|
||||
BOOST_LOG_TRIVIAL(debug) << "started download";
|
||||
}
|
||||
|
||||
void Downloader::on_progress(wxCommandEvent& event)
|
||||
{
|
||||
size_t id = event.GetInt();
|
||||
float percent = (float)std::stoi(boost::nowide::narrow(event.GetString())) / 100.f;
|
||||
//BOOST_LOG_TRIVIAL(error) << "progress " << id << ": " << percent;
|
||||
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
|
||||
BOOST_LOG_TRIVIAL(trace) << "Download "<< id << ": " << percent;
|
||||
ntf_mngr->set_download_URL_progress(id, percent);
|
||||
}
|
||||
void Downloader::on_error(wxCommandEvent& event)
|
||||
{
|
||||
size_t id = event.GetInt();
|
||||
set_download_state(event.GetInt(), DownloadState::DownloadError);
|
||||
BOOST_LOG_TRIVIAL(error) << "Download error: " << event.GetString();
|
||||
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
|
||||
ntf_mngr->set_download_URL_error(id, boost::nowide::narrow(event.GetString()));
|
||||
show_error(nullptr, format_wxstr(L"%1%\n%2%", _L("The download has failed") + ":", event.GetString()));
|
||||
}
|
||||
void Downloader::on_complete(wxCommandEvent& event)
|
||||
{
|
||||
// TODO: is this always true? :
|
||||
// here we open the file itself, notification should get 1.f progress from on progress.
|
||||
set_download_state(event.GetInt(), DownloadState::DownloadDone);
|
||||
wxArrayString paths;
|
||||
paths.Add(event.GetString());
|
||||
wxGetApp().plater()->load_files(paths);
|
||||
}
|
||||
bool Downloader::user_action_callback(DownloaderUserAction action, int id)
|
||||
{
|
||||
for (size_t i = 0; i < m_downloads.size(); ++i) {
|
||||
if (m_downloads[i]->get_id() == id) {
|
||||
switch (action) {
|
||||
case DownloadUserCanceled:
|
||||
m_downloads[i]->cancel();
|
||||
return true;
|
||||
case DownloadUserPaused:
|
||||
m_downloads[i]->pause();
|
||||
return true;
|
||||
case DownloadUserContinued:
|
||||
m_downloads[i]->resume();
|
||||
return true;
|
||||
case DownloadUserOpenedFolder:
|
||||
open_folder(m_downloads[i]->get_dest_folder());
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Downloader::on_name_change(wxCommandEvent& event)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Downloader::on_paused(wxCommandEvent& event)
|
||||
{
|
||||
size_t id = event.GetInt();
|
||||
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
|
||||
ntf_mngr->set_download_URL_paused(id);
|
||||
}
|
||||
|
||||
void Downloader::on_canceled(wxCommandEvent& event)
|
||||
{
|
||||
size_t id = event.GetInt();
|
||||
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
|
||||
ntf_mngr->set_download_URL_canceled(id);
|
||||
}
|
||||
|
||||
void Downloader::set_download_state(int id, DownloadState state)
|
||||
{
|
||||
for (size_t i = 0; i < m_downloads.size(); ++i) {
|
||||
if (m_downloads[i]->get_id() == id) {
|
||||
m_downloads[i]->set_state(state);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
99
src/slic3r/GUI/Downloader.hpp
Normal file
99
src/slic3r/GUI/Downloader.hpp
Normal file
@@ -0,0 +1,99 @@
|
||||
#ifndef slic3r_Downloader_hpp_
|
||||
#define slic3r_Downloader_hpp_
|
||||
|
||||
#include "DownloaderFileGet.hpp"
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <wx/wx.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class NotificationManager;
|
||||
|
||||
enum DownloadState
|
||||
{
|
||||
DownloadPending = 0,
|
||||
DownloadOngoing,
|
||||
DownloadStopped,
|
||||
DownloadDone,
|
||||
DownloadError,
|
||||
DownloadPaused,
|
||||
DownloadStateUnknown
|
||||
};
|
||||
|
||||
enum DownloaderUserAction
|
||||
{
|
||||
DownloadUserCanceled,
|
||||
DownloadUserPaused,
|
||||
DownloadUserContinued,
|
||||
DownloadUserOpenedFolder
|
||||
};
|
||||
|
||||
class Download {
|
||||
public:
|
||||
Download(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder);
|
||||
void start();
|
||||
void cancel();
|
||||
void pause();
|
||||
void resume();
|
||||
|
||||
int get_id() const { return m_id; }
|
||||
boost::filesystem::path get_final_path() const { return m_final_path; }
|
||||
std::string get_filename() const { return m_filename; }
|
||||
DownloadState get_state() const { return m_state; }
|
||||
void set_state(DownloadState state) { m_state = state; }
|
||||
std::string get_dest_folder() { return m_dest_folder.string(); }
|
||||
private:
|
||||
const int m_id;
|
||||
std::string m_filename;
|
||||
boost::filesystem::path m_final_path;
|
||||
boost::filesystem::path m_dest_folder;
|
||||
std::shared_ptr<FileGet> m_file_get;
|
||||
DownloadState m_state { DownloadState::DownloadPending };
|
||||
};
|
||||
|
||||
class Downloader : public wxEvtHandler {
|
||||
public:
|
||||
Downloader();
|
||||
|
||||
bool get_initialized() { return m_initialized; }
|
||||
void init(const boost::filesystem::path& dest_folder)
|
||||
{
|
||||
m_dest_folder = dest_folder;
|
||||
m_initialized = true;
|
||||
}
|
||||
void start_download(const std::string& full_url);
|
||||
// cancel = false -> just pause
|
||||
bool user_action_callback(DownloaderUserAction action, int id);
|
||||
private:
|
||||
bool m_initialized { false };
|
||||
|
||||
std::vector<std::unique_ptr<Download>> m_downloads;
|
||||
boost::filesystem::path m_dest_folder;
|
||||
|
||||
size_t m_next_id { 0 };
|
||||
size_t get_next_id() { return ++m_next_id; }
|
||||
|
||||
void on_progress(wxCommandEvent& event);
|
||||
void on_error(wxCommandEvent& event);
|
||||
void on_complete(wxCommandEvent& event);
|
||||
void on_name_change(wxCommandEvent& event);
|
||||
void on_paused(wxCommandEvent& event);
|
||||
void on_canceled(wxCommandEvent& event);
|
||||
|
||||
void set_download_state(int id, DownloadState state);
|
||||
/*
|
||||
bool is_in_state(int id, DownloadState state) const;
|
||||
DownloadState get_download_state(int id) const;
|
||||
bool cancel_download(int id);
|
||||
bool pause_download(int id);
|
||||
bool resume_download(int id);
|
||||
bool delete_download(int id);
|
||||
wxString get_path_of(int id) const;
|
||||
wxString get_folder_path_of(int id) const;
|
||||
*/
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
391
src/slic3r/GUI/DownloaderFileGet.cpp
Normal file
391
src/slic3r/GUI/DownloaderFileGet.cpp
Normal file
@@ -0,0 +1,391 @@
|
||||
#include "DownloaderFileGet.hpp"
|
||||
|
||||
#include <thread>
|
||||
#include <curl/curl.h>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#include "format.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "I18N.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
const size_t DOWNLOAD_MAX_CHUNK_SIZE = 10 * 1024 * 1024;
|
||||
const size_t DOWNLOAD_SIZE_LIMIT = 1024 * 1024 * 1024;
|
||||
|
||||
std::string FileGet::escape_url(const std::string& unescaped)
|
||||
{
|
||||
std::string ret_val;
|
||||
CURL* curl = curl_easy_init();
|
||||
if (curl) {
|
||||
int decodelen;
|
||||
char* decoded = curl_easy_unescape(curl, unescaped.c_str(), unescaped.size(), &decodelen);
|
||||
if (decoded) {
|
||||
ret_val = std::string(decoded);
|
||||
curl_free(decoded);
|
||||
}
|
||||
curl_easy_cleanup(curl);
|
||||
}
|
||||
return ret_val;
|
||||
}
|
||||
bool FileGet::is_subdomain(const std::string& url, const std::string& domain)
|
||||
{
|
||||
// domain should be f.e. printables.com (.com including)
|
||||
char* host;
|
||||
std::string host_string;
|
||||
CURLUcode rc;
|
||||
CURLU* curl = curl_url();
|
||||
if (!curl) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to init Curl library in function is_domain.";
|
||||
return false;
|
||||
}
|
||||
rc = curl_url_set(curl, CURLUPART_URL, url.c_str(), 0);
|
||||
if (rc != CURLUE_OK) {
|
||||
curl_url_cleanup(curl);
|
||||
return false;
|
||||
}
|
||||
rc = curl_url_get(curl, CURLUPART_HOST, &host, 0);
|
||||
if (rc != CURLUE_OK || !host) {
|
||||
curl_url_cleanup(curl);
|
||||
return false;
|
||||
}
|
||||
host_string = std::string(host);
|
||||
curl_free(host);
|
||||
// now host should be subdomain.domain or just domain
|
||||
if (domain == host_string) {
|
||||
curl_url_cleanup(curl);
|
||||
return true;
|
||||
}
|
||||
if(boost::ends_with(host_string, "." + domain)) {
|
||||
curl_url_cleanup(curl);
|
||||
return true;
|
||||
}
|
||||
curl_url_cleanup(curl);
|
||||
return false;
|
||||
}
|
||||
|
||||
namespace {
|
||||
unsigned get_current_pid()
|
||||
{
|
||||
#ifdef WIN32
|
||||
return GetCurrentProcessId();
|
||||
#else
|
||||
return ::getpid();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// int = DOWNLOAD ID; string = file path
|
||||
wxDEFINE_EVENT(EVT_DWNLDR_FILE_COMPLETE, wxCommandEvent);
|
||||
// int = DOWNLOAD ID; string = error msg
|
||||
wxDEFINE_EVENT(EVT_DWNLDR_FILE_ERROR, wxCommandEvent);
|
||||
// int = DOWNLOAD ID; string = progress percent
|
||||
wxDEFINE_EVENT(EVT_DWNLDR_FILE_PROGRESS, wxCommandEvent);
|
||||
// int = DOWNLOAD ID; string = name
|
||||
wxDEFINE_EVENT(EVT_DWNLDR_FILE_NAME_CHANGE, wxCommandEvent);
|
||||
// int = DOWNLOAD ID;
|
||||
wxDEFINE_EVENT(EVT_DWNLDR_FILE_PAUSED, wxCommandEvent);
|
||||
// int = DOWNLOAD ID;
|
||||
wxDEFINE_EVENT(EVT_DWNLDR_FILE_CANCELED, wxCommandEvent);
|
||||
|
||||
struct FileGet::priv
|
||||
{
|
||||
const int m_id;
|
||||
std::string m_url;
|
||||
std::string m_filename;
|
||||
std::thread m_io_thread;
|
||||
wxEvtHandler* m_evt_handler;
|
||||
boost::filesystem::path m_dest_folder;
|
||||
boost::filesystem::path m_tmp_path; // path when ongoing download
|
||||
std::atomic_bool m_cancel { false };
|
||||
std::atomic_bool m_pause { false };
|
||||
std::atomic_bool m_stopped { false }; // either canceled or paused - download is not running
|
||||
size_t m_written { 0 };
|
||||
size_t m_absolute_size { 0 };
|
||||
priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder);
|
||||
|
||||
void get_perform();
|
||||
};
|
||||
|
||||
FileGet::priv::priv(int ID, std::string&& url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
|
||||
: m_id(ID)
|
||||
, m_url(std::move(url))
|
||||
, m_filename(filename)
|
||||
, m_evt_handler(evt_handler)
|
||||
, m_dest_folder(dest_folder)
|
||||
{
|
||||
}
|
||||
|
||||
void FileGet::priv::get_perform()
|
||||
{
|
||||
assert(m_evt_handler);
|
||||
assert(!m_url.empty());
|
||||
assert(!m_filename.empty());
|
||||
assert(boost::filesystem::is_directory(m_dest_folder));
|
||||
|
||||
m_stopped = false;
|
||||
|
||||
// open dest file
|
||||
if (m_written == 0)
|
||||
{
|
||||
boost::filesystem::path dest_path = m_dest_folder / m_filename;
|
||||
std::string extension = boost::filesystem::extension(dest_path);
|
||||
std::string just_filename = m_filename.substr(0, m_filename.size() - extension.size());
|
||||
std::string final_filename = just_filename;
|
||||
// Find unsed filename
|
||||
try {
|
||||
size_t version = 0;
|
||||
while (boost::filesystem::exists(m_dest_folder / (final_filename + extension)) || boost::filesystem::exists(m_dest_folder / (final_filename + extension + "." + std::to_string(get_current_pid()) + ".download")))
|
||||
{
|
||||
++version;
|
||||
if (version > 999) {
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
|
||||
evt->SetString(GUI::format_wxstr(L"Failed to find suitable filename. Last name: %1%." , (m_dest_folder / (final_filename + extension)).string()));
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
return;
|
||||
}
|
||||
final_filename = GUI::format("%1%(%2%)", just_filename, std::to_string(version));
|
||||
}
|
||||
} catch (const boost::filesystem::filesystem_error& e)
|
||||
{
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
|
||||
evt->SetString(e.what());
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
return;
|
||||
}
|
||||
|
||||
m_filename = final_filename + extension;
|
||||
|
||||
m_tmp_path = m_dest_folder / (m_filename + "." + std::to_string(get_current_pid()) + ".download");
|
||||
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_NAME_CHANGE);
|
||||
evt->SetString(boost::nowide::widen(m_filename));
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
}
|
||||
|
||||
boost::filesystem::path dest_path = m_dest_folder / m_filename;
|
||||
|
||||
wxString temp_path_wstring(m_tmp_path.wstring());
|
||||
|
||||
//std::cout << "dest_path: " << dest_path.string() << std::endl;
|
||||
//std::cout << "m_tmp_path: " << m_tmp_path.string() << std::endl;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << GUI::format("Starting download from %1% to %2%. Temp path is %3%",m_url, dest_path, m_tmp_path);
|
||||
|
||||
FILE* file;
|
||||
// open file for writting
|
||||
if (m_written == 0)
|
||||
file = fopen(temp_path_wstring.c_str(), "wb");
|
||||
else
|
||||
file = fopen(temp_path_wstring.c_str(), "ab");
|
||||
|
||||
//assert(file != NULL);
|
||||
if (file == NULL) {
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
|
||||
// TRN %1% = file path
|
||||
evt->SetString(GUI::format_wxstr(_L("Can't create file at %1%"), temp_path_wstring));
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
return;
|
||||
}
|
||||
|
||||
std:: string range_string = std::to_string(m_written) + "-";
|
||||
|
||||
size_t written_previously = m_written;
|
||||
size_t written_this_session = 0;
|
||||
Http::get(m_url)
|
||||
.size_limit(DOWNLOAD_SIZE_LIMIT) //more?
|
||||
.set_range(range_string)
|
||||
.on_progress([&](Http::Progress progress, bool& cancel) {
|
||||
// to prevent multiple calls into following ifs (m_cancel / m_pause)
|
||||
if (m_stopped){
|
||||
cancel = true;
|
||||
return;
|
||||
}
|
||||
if (m_cancel) {
|
||||
m_stopped = true;
|
||||
fclose(file);
|
||||
// remove canceled file
|
||||
std::remove(m_tmp_path.string().c_str());
|
||||
m_written = 0;
|
||||
cancel = true;
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_CANCELED);
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
return;
|
||||
// TODO: send canceled event?
|
||||
}
|
||||
if (m_pause) {
|
||||
m_stopped = true;
|
||||
fclose(file);
|
||||
cancel = true;
|
||||
if (m_written == 0)
|
||||
std::remove(m_tmp_path.string().c_str());
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PAUSED);
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_absolute_size < progress.dltotal) {
|
||||
m_absolute_size = progress.dltotal;
|
||||
}
|
||||
|
||||
if (progress.dlnow != 0) {
|
||||
if (progress.dlnow - written_this_session > DOWNLOAD_MAX_CHUNK_SIZE || progress.dlnow == progress.dltotal) {
|
||||
try
|
||||
{
|
||||
std::string part_for_write = progress.buffer.substr(written_this_session, progress.dlnow);
|
||||
fwrite(part_for_write.c_str(), 1, part_for_write.size(), file);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
// fclose(file); do it?
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
|
||||
evt->SetString(e.what());
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
cancel = true;
|
||||
return;
|
||||
}
|
||||
written_this_session = progress.dlnow;
|
||||
m_written = written_previously + written_this_session;
|
||||
}
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_PROGRESS);
|
||||
int percent_total = (written_previously + progress.dlnow) * 100 / m_absolute_size;
|
||||
evt->SetString(std::to_string(percent_total));
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
}
|
||||
|
||||
})
|
||||
.on_error([&](std::string body, std::string error, unsigned http_status) {
|
||||
if (file != NULL)
|
||||
fclose(file);
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
|
||||
if (!error.empty())
|
||||
evt->SetString(GUI::from_u8(error));
|
||||
else
|
||||
evt->SetString(GUI::from_u8(body));
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned /* http_status */) {
|
||||
|
||||
// TODO: perform a body size check
|
||||
//
|
||||
//size_t body_size = body.size();
|
||||
//if (body_size != expected_size) {
|
||||
// return;
|
||||
//}
|
||||
try
|
||||
{
|
||||
/*
|
||||
if (m_written < body.size())
|
||||
{
|
||||
// this code should never be entered. As there should be on_progress call after last bit downloaded.
|
||||
std::string part_for_write = body.substr(m_written);
|
||||
fwrite(part_for_write.c_str(), 1, part_for_write.size(), file);
|
||||
}
|
||||
*/
|
||||
fclose(file);
|
||||
boost::filesystem::rename(m_tmp_path, dest_path);
|
||||
}
|
||||
catch (const std::exception& /*e*/)
|
||||
{
|
||||
//TODO: report?
|
||||
//error_message = GUI::format("Failed to write and move %1% to %2%", tmp_path, dest_path);
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
|
||||
evt->SetString("Failed to write and move.");
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
return;
|
||||
}
|
||||
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_COMPLETE);
|
||||
evt->SetString(dest_path.wstring());
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
}
|
||||
|
||||
FileGet::FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
|
||||
: p(new priv(ID, std::move(url), filename, evt_handler, dest_folder))
|
||||
{}
|
||||
|
||||
FileGet::FileGet(FileGet&& other) : p(std::move(other.p)) {}
|
||||
|
||||
FileGet::~FileGet()
|
||||
{
|
||||
if (p && p->m_io_thread.joinable()) {
|
||||
p->m_cancel = true;
|
||||
p->m_io_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void FileGet::get()
|
||||
{
|
||||
assert(p);
|
||||
if (p->m_io_thread.joinable()) {
|
||||
// This will stop transfers being done by the thread, if any.
|
||||
// Cancelling takes some time, but should complete soon enough.
|
||||
p->m_cancel = true;
|
||||
p->m_io_thread.join();
|
||||
}
|
||||
p->m_cancel = false;
|
||||
p->m_pause = false;
|
||||
p->m_io_thread = std::thread([this]() {
|
||||
p->get_perform();
|
||||
});
|
||||
}
|
||||
|
||||
void FileGet::cancel()
|
||||
{
|
||||
if(p && p->m_stopped) {
|
||||
if (p->m_io_thread.joinable()) {
|
||||
p->m_cancel = true;
|
||||
p->m_io_thread.join();
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_CANCELED);
|
||||
evt->SetInt(p->m_id);
|
||||
p->m_evt_handler->QueueEvent(evt);
|
||||
}
|
||||
}
|
||||
|
||||
if (p)
|
||||
p->m_cancel = true;
|
||||
|
||||
}
|
||||
|
||||
void FileGet::pause()
|
||||
{
|
||||
if (p) {
|
||||
p->m_pause = true;
|
||||
}
|
||||
}
|
||||
void FileGet::resume()
|
||||
{
|
||||
assert(p);
|
||||
if (p->m_io_thread.joinable()) {
|
||||
// This will stop transfers being done by the thread, if any.
|
||||
// Cancelling takes some time, but should complete soon enough.
|
||||
p->m_cancel = true;
|
||||
p->m_io_thread.join();
|
||||
}
|
||||
p->m_cancel = false;
|
||||
p->m_pause = false;
|
||||
p->m_io_thread = std::thread([this]() {
|
||||
p->get_perform();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
45
src/slic3r/GUI/DownloaderFileGet.hpp
Normal file
45
src/slic3r/GUI/DownloaderFileGet.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#ifndef slic3r_DownloaderFileGet_hpp_
|
||||
#define slic3r_DownloaderFileGet_hpp_
|
||||
|
||||
#include "../Utils/Http.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <wx/event.h>
|
||||
#include <wx/frame.h>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
class FileGet : public std::enable_shared_from_this<FileGet> {
|
||||
private:
|
||||
struct priv;
|
||||
public:
|
||||
FileGet(int ID, std::string url, const std::string& filename, wxEvtHandler* evt_handler,const boost::filesystem::path& dest_folder);
|
||||
FileGet(FileGet&& other);
|
||||
~FileGet();
|
||||
|
||||
void get();
|
||||
void cancel();
|
||||
void pause();
|
||||
void resume();
|
||||
static std::string escape_url(const std::string& url);
|
||||
static bool is_subdomain(const std::string& url, const std::string& domain);
|
||||
private:
|
||||
std::unique_ptr<priv> p;
|
||||
};
|
||||
// int = DOWNLOAD ID; string = file path
|
||||
wxDECLARE_EVENT(EVT_DWNLDR_FILE_COMPLETE, wxCommandEvent);
|
||||
// int = DOWNLOAD ID; string = error msg
|
||||
wxDECLARE_EVENT(EVT_DWNLDR_FILE_PROGRESS, wxCommandEvent);
|
||||
// int = DOWNLOAD ID; string = progress percent
|
||||
wxDECLARE_EVENT(EVT_DWNLDR_FILE_ERROR, wxCommandEvent);
|
||||
// int = DOWNLOAD ID; string = name
|
||||
wxDECLARE_EVENT(EVT_DWNLDR_FILE_NAME_CHANGE, wxCommandEvent);
|
||||
// int = DOWNLOAD ID;
|
||||
wxDECLARE_EVENT(EVT_DWNLDR_FILE_PAUSED, wxCommandEvent);
|
||||
// int = DOWNLOAD ID;
|
||||
wxDECLARE_EVENT(EVT_DWNLDR_FILE_CANCELED, wxCommandEvent);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
71
src/slic3r/GUI/Event.hpp
Normal file
71
src/slic3r/GUI/Event.hpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#ifndef slic3r_Events_hpp_
|
||||
#define slic3r_Events_hpp_
|
||||
|
||||
#include <array>
|
||||
#include <wx/event.h>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace GUI {
|
||||
|
||||
|
||||
struct SimpleEvent : public wxEvent
|
||||
{
|
||||
SimpleEvent(wxEventType type, wxObject* origin = nullptr) : wxEvent(0, type)
|
||||
{
|
||||
m_propagationLevel = wxEVENT_PROPAGATE_MAX;
|
||||
SetEventObject(origin);
|
||||
}
|
||||
|
||||
virtual wxEvent* Clone() const
|
||||
{
|
||||
return new SimpleEvent(GetEventType(), GetEventObject());
|
||||
}
|
||||
};
|
||||
|
||||
template<class T, size_t N> struct ArrayEvent : public wxEvent
|
||||
{
|
||||
std::array<T, N> data;
|
||||
|
||||
ArrayEvent(wxEventType type, std::array<T, N> data, wxObject* origin = nullptr)
|
||||
: wxEvent(0, type), data(std::move(data))
|
||||
{
|
||||
m_propagationLevel = wxEVENT_PROPAGATE_MAX;
|
||||
SetEventObject(origin);
|
||||
}
|
||||
|
||||
virtual wxEvent* Clone() const
|
||||
{
|
||||
return new ArrayEvent<T, N>(GetEventType(), data, GetEventObject());
|
||||
}
|
||||
};
|
||||
|
||||
template<class T> struct Event : public wxEvent
|
||||
{
|
||||
T data;
|
||||
|
||||
Event(wxEventType type, const T &data, wxObject* origin = nullptr)
|
||||
: wxEvent(0, type), data(std::move(data))
|
||||
{
|
||||
m_propagationLevel = wxEVENT_PROPAGATE_MAX;
|
||||
SetEventObject(origin);
|
||||
}
|
||||
|
||||
Event(wxEventType type, T&& data, wxObject* origin = nullptr)
|
||||
: wxEvent(0, type), data(std::move(data))
|
||||
{
|
||||
m_propagationLevel = wxEVENT_PROPAGATE_MAX;
|
||||
SetEventObject(origin);
|
||||
}
|
||||
|
||||
virtual wxEvent* Clone() const
|
||||
{
|
||||
return new Event<T>(GetEventType(), data, GetEventObject());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // slic3r_Events_hpp_
|
||||
395
src/slic3r/GUI/ExtraRenderers.cpp
Normal file
395
src/slic3r/GUI/ExtraRenderers.cpp
Normal file
@@ -0,0 +1,395 @@
|
||||
#include "ExtraRenderers.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "BitmapComboBox.hpp"
|
||||
#include "Plater.hpp"
|
||||
|
||||
#include <wx/dc.h>
|
||||
#ifdef wxHAS_GENERIC_DATAVIEWCTRL
|
||||
#include "wx/generic/private/markuptext.h"
|
||||
#include "wx/generic/private/rowheightcache.h"
|
||||
#include "wx/generic/private/widthcalc.h"
|
||||
#endif
|
||||
/*
|
||||
#ifdef __WXGTK__
|
||||
#include "wx/gtk/private.h"
|
||||
#include "wx/gtk/private/value.h"
|
||||
#endif
|
||||
*/
|
||||
#if wxUSE_ACCESSIBILITY
|
||||
#include "wx/private/markupparser.h"
|
||||
#endif // wxUSE_ACCESSIBILITY
|
||||
|
||||
using Slic3r::GUI::from_u8;
|
||||
using Slic3r::GUI::into_u8;
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// DataViewBitmapText
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
wxIMPLEMENT_DYNAMIC_CLASS(DataViewBitmapText, wxObject)
|
||||
|
||||
IMPLEMENT_VARIANT_OBJECT(DataViewBitmapText)
|
||||
|
||||
static wxSize get_size(const wxBitmap& icon)
|
||||
{
|
||||
#ifdef __WIN32__
|
||||
return icon.GetSize();
|
||||
#else
|
||||
return icon.GetScaledSize();
|
||||
#endif
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// BitmapTextRenderer
|
||||
// ---------------------------------------------------------
|
||||
|
||||
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
|
||||
BitmapTextRenderer::BitmapTextRenderer(wxDataViewCellMode mode /*= wxDATAVIEW_CELL_EDITABLE*/,
|
||||
int align /*= wxDVR_DEFAULT_ALIGNMENT*/):
|
||||
wxDataViewRenderer(wxT("QIDIDataViewBitmapText"), mode, align)
|
||||
{
|
||||
SetMode(mode);
|
||||
SetAlignment(align);
|
||||
}
|
||||
#endif // ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
|
||||
|
||||
BitmapTextRenderer::~BitmapTextRenderer()
|
||||
{
|
||||
#ifdef SUPPORTS_MARKUP
|
||||
#ifdef wxHAS_GENERIC_DATAVIEWCTRL
|
||||
delete m_markupText;
|
||||
#endif //wxHAS_GENERIC_DATAVIEWCTRL
|
||||
#endif // SUPPORTS_MARKUP
|
||||
}
|
||||
|
||||
void BitmapTextRenderer::EnableMarkup(bool enable)
|
||||
{
|
||||
#ifdef SUPPORTS_MARKUP
|
||||
#ifdef wxHAS_GENERIC_DATAVIEWCTRL
|
||||
if (enable) {
|
||||
if (!m_markupText)
|
||||
m_markupText = new wxItemMarkupText(wxString());
|
||||
}
|
||||
else {
|
||||
if (m_markupText) {
|
||||
delete m_markupText;
|
||||
m_markupText = nullptr;
|
||||
}
|
||||
}
|
||||
#else
|
||||
is_markupText = enable;
|
||||
#endif //wxHAS_GENERIC_DATAVIEWCTRL
|
||||
#endif // SUPPORTS_MARKUP
|
||||
}
|
||||
|
||||
bool BitmapTextRenderer::SetValue(const wxVariant &value)
|
||||
{
|
||||
m_value << value;
|
||||
|
||||
#ifdef SUPPORTS_MARKUP
|
||||
#ifdef wxHAS_GENERIC_DATAVIEWCTRL
|
||||
if (m_markupText)
|
||||
m_markupText->SetMarkup(m_value.GetText());
|
||||
/*
|
||||
#else
|
||||
#if defined(__WXGTK__)
|
||||
GValue gvalue = G_VALUE_INIT;
|
||||
g_value_init(&gvalue, G_TYPE_STRING);
|
||||
g_value_set_string(&gvalue, wxGTK_CONV_FONT(str.GetText(), GetOwner()->GetOwner()->GetFont()));
|
||||
g_object_set_property(G_OBJECT(m_renderer/ *.GetText()* /), is_markupText ? "markup" : "text", &gvalue);
|
||||
g_value_unset(&gvalue);
|
||||
#endif // __WXGTK__
|
||||
*/
|
||||
#endif // wxHAS_GENERIC_DATAVIEWCTRL
|
||||
#endif // SUPPORTS_MARKUP
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BitmapTextRenderer::GetValue(wxVariant& WXUNUSED(value)) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY
|
||||
wxString BitmapTextRenderer::GetAccessibleDescription() const
|
||||
{
|
||||
#ifdef SUPPORTS_MARKUP
|
||||
if (m_markupText)
|
||||
return wxMarkupParser::Strip(m_text);
|
||||
#endif // SUPPORTS_MARKUP
|
||||
|
||||
return m_value.GetText();
|
||||
}
|
||||
#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
|
||||
|
||||
bool BitmapTextRenderer::Render(wxRect rect, wxDC *dc, int state)
|
||||
{
|
||||
int xoffset = 0;
|
||||
|
||||
const wxBitmap& icon = m_value.GetBitmap();
|
||||
if (icon.IsOk())
|
||||
{
|
||||
wxSize icon_sz = get_size(icon);
|
||||
dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon_sz.y) / 2);
|
||||
xoffset = icon_sz.x + 4;
|
||||
}
|
||||
|
||||
#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL)
|
||||
if (m_markupText)
|
||||
{
|
||||
rect.x += xoffset;
|
||||
m_markupText->Render(GetView(), *dc, rect, 0, GetEllipsizeMode());
|
||||
}
|
||||
else
|
||||
#endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL
|
||||
#ifdef _WIN32
|
||||
// workaround for Windows DarkMode : Don't respect to the state & wxDATAVIEW_CELL_SELECTED to avoid update of the text color
|
||||
RenderText(m_value.GetText(), xoffset, rect, dc, state & wxDATAVIEW_CELL_SELECTED ? 0 :state);
|
||||
#else
|
||||
RenderText(m_value.GetText(), xoffset, rect, dc, state);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
wxSize BitmapTextRenderer::GetSize() const
|
||||
{
|
||||
if (!m_value.GetText().empty())
|
||||
{
|
||||
wxSize size;
|
||||
#if defined(SUPPORTS_MARKUP) && defined(wxHAS_GENERIC_DATAVIEWCTRL)
|
||||
if (m_markupText)
|
||||
{
|
||||
wxDataViewCtrl* const view = GetView();
|
||||
wxClientDC dc(view);
|
||||
if (GetAttr().HasFont())
|
||||
dc.SetFont(GetAttr().GetEffectiveFont(view->GetFont()));
|
||||
|
||||
size = m_markupText->Measure(dc);
|
||||
|
||||
int lines = m_value.GetText().Freq('\n') + 1;
|
||||
size.SetHeight(size.GetHeight() * lines);
|
||||
}
|
||||
else
|
||||
#endif // SUPPORTS_MARKUP && wxHAS_GENERIC_DATAVIEWCTRL
|
||||
size = GetTextExtent(m_value.GetText());
|
||||
|
||||
if (m_value.GetBitmap().IsOk())
|
||||
size.x += m_value.GetBitmap().GetWidth() + 4;
|
||||
return size;
|
||||
}
|
||||
return wxSize(80, 20);
|
||||
}
|
||||
|
||||
|
||||
wxWindow* BitmapTextRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value)
|
||||
{
|
||||
if (can_create_editor_ctrl && !can_create_editor_ctrl())
|
||||
return nullptr;
|
||||
|
||||
DataViewBitmapText data;
|
||||
data << value;
|
||||
|
||||
m_was_unusable_symbol = false;
|
||||
|
||||
wxPoint position = labelRect.GetPosition();
|
||||
if (data.GetBitmap().IsOk()) {
|
||||
const int bmp_width = data.GetBitmap().GetWidth();
|
||||
position.x += bmp_width;
|
||||
labelRect.SetWidth(labelRect.GetWidth() - bmp_width);
|
||||
}
|
||||
|
||||
#ifdef __WXMSW__
|
||||
// Case when from some reason we try to create next EditorCtrl till old one was not deleted
|
||||
if (auto children = parent->GetChildren(); children.GetCount() > 0)
|
||||
for (auto child : children)
|
||||
if (dynamic_cast<wxTextCtrl*>(child)) {
|
||||
parent->RemoveChild(child);
|
||||
child->Destroy();
|
||||
break;
|
||||
}
|
||||
#endif // __WXMSW__
|
||||
|
||||
wxTextCtrl* text_editor = new wxTextCtrl(parent, wxID_ANY, data.GetText(),
|
||||
position, labelRect.GetSize(), wxTE_PROCESS_ENTER);
|
||||
text_editor->SetInsertionPointEnd();
|
||||
text_editor->SelectAll();
|
||||
|
||||
return text_editor;
|
||||
}
|
||||
|
||||
bool BitmapTextRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value)
|
||||
{
|
||||
wxTextCtrl* text_editor = wxDynamicCast(ctrl, wxTextCtrl);
|
||||
if (!text_editor || text_editor->GetValue().IsEmpty())
|
||||
return false;
|
||||
|
||||
m_was_unusable_symbol = Slic3r::GUI::Plater::has_illegal_filename_characters(text_editor->GetValue());
|
||||
if (m_was_unusable_symbol)
|
||||
return false;
|
||||
|
||||
// The icon can't be edited so get its old value and reuse it.
|
||||
wxVariant valueOld;
|
||||
GetView()->GetModel()->GetValue(valueOld, m_item, /*colName*/0);
|
||||
|
||||
DataViewBitmapText bmpText;
|
||||
bmpText << valueOld;
|
||||
|
||||
// But replace the text with the value entered by user.
|
||||
bmpText.SetText(text_editor->GetValue());
|
||||
|
||||
value << bmpText;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// BitmapChoiceRenderer
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
bool BitmapChoiceRenderer::SetValue(const wxVariant& value)
|
||||
{
|
||||
m_value << value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BitmapChoiceRenderer::GetValue(wxVariant& value) const
|
||||
{
|
||||
value << m_value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BitmapChoiceRenderer::Render(wxRect rect, wxDC* dc, int state)
|
||||
{
|
||||
int xoffset = 0;
|
||||
|
||||
const wxBitmap& icon = m_value.GetBitmap();
|
||||
if (icon.IsOk())
|
||||
{
|
||||
wxSize icon_sz = get_size(icon);
|
||||
|
||||
dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon_sz.GetHeight()) / 2);
|
||||
xoffset = icon_sz.GetWidth() + 4;
|
||||
|
||||
if (rect.height==0)
|
||||
rect.height= icon_sz.GetHeight();
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
// workaround for Windows DarkMode : Don't respect to the state & wxDATAVIEW_CELL_SELECTED to avoid update of the text color
|
||||
RenderText(m_value.GetText(), xoffset, rect, dc, state & wxDATAVIEW_CELL_SELECTED ? 0 : state);
|
||||
#else
|
||||
RenderText(m_value.GetText(), xoffset, rect, dc, state);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
wxSize BitmapChoiceRenderer::GetSize() const
|
||||
{
|
||||
wxSize sz = GetTextExtent(m_value.GetText());
|
||||
|
||||
if (m_value.GetBitmap().IsOk())
|
||||
sz.x += m_value.GetBitmap().GetWidth() + 4;
|
||||
|
||||
return sz;
|
||||
}
|
||||
|
||||
|
||||
wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value)
|
||||
{
|
||||
if (can_create_editor_ctrl && !can_create_editor_ctrl())
|
||||
return nullptr;
|
||||
|
||||
std::vector<wxBitmapBundle*> icons = get_extruder_color_icons();
|
||||
if (icons.empty())
|
||||
return nullptr;
|
||||
|
||||
DataViewBitmapText data;
|
||||
data << value;
|
||||
|
||||
#ifdef _WIN32
|
||||
Slic3r::GUI::BitmapComboBox* c_editor = new Slic3r::GUI::BitmapComboBox(parent, wxID_ANY, wxEmptyString,
|
||||
#else
|
||||
auto c_editor = new wxBitmapComboBox(parent, wxID_ANY, wxEmptyString,
|
||||
#endif
|
||||
labelRect.GetTopLeft(), wxSize(labelRect.GetWidth(), -1),
|
||||
0, nullptr , wxCB_READONLY);
|
||||
|
||||
int def_id = get_default_extruder_idx ? get_default_extruder_idx() : 0;
|
||||
c_editor->Append(_L("default"), def_id < 0 ? wxNullBitmap : *icons[def_id]);
|
||||
for (size_t i = 0; i < icons.size(); i++)
|
||||
c_editor->Append(wxString::Format("%d", i+1), *icons[i]);
|
||||
|
||||
c_editor->SetSelection(atoi(data.GetText().c_str()));
|
||||
|
||||
|
||||
#ifdef __linux__
|
||||
c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) {
|
||||
// to avoid event propagation to other sidebar items
|
||||
evt.StopPropagation();
|
||||
// FinishEditing grabs new selection and triggers config update. We better call
|
||||
// it explicitly, automatic update on KILL_FOCUS didn't work on Linux.
|
||||
this->FinishEditing();
|
||||
});
|
||||
#else
|
||||
// to avoid event propagation to other sidebar items
|
||||
c_editor->Bind(wxEVT_COMBOBOX, [](wxCommandEvent& evt) { evt.StopPropagation(); });
|
||||
#endif
|
||||
|
||||
return c_editor;
|
||||
}
|
||||
|
||||
bool BitmapChoiceRenderer::GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value)
|
||||
{
|
||||
wxBitmapComboBox* c = static_cast<wxBitmapComboBox*>(ctrl);
|
||||
int selection = c->GetSelection();
|
||||
if (selection < 0)
|
||||
return false;
|
||||
|
||||
DataViewBitmapText bmpText;
|
||||
|
||||
bmpText.SetText(c->GetString(selection));
|
||||
bmpText.SetBitmap(c->GetItemBitmap(selection));
|
||||
|
||||
value << bmpText;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// TextRenderer
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
bool TextRenderer::SetValue(const wxVariant& value)
|
||||
{
|
||||
m_value = value.GetString();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TextRenderer::GetValue(wxVariant& value) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TextRenderer::Render(wxRect rect, wxDC* dc, int state)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// workaround for Windows DarkMode : Don't respect to the state & wxDATAVIEW_CELL_SELECTED to avoid update of the text color
|
||||
RenderText(m_value, 0, rect, dc, state & wxDATAVIEW_CELL_SELECTED ? 0 : state);
|
||||
#else
|
||||
RenderText(m_value, 0, rect, dc, state);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
wxSize TextRenderer::GetSize() const
|
||||
{
|
||||
return GetTextExtent(m_value);
|
||||
}
|
||||
|
||||
|
||||
189
src/slic3r/GUI/ExtraRenderers.hpp
Normal file
189
src/slic3r/GUI/ExtraRenderers.hpp
Normal file
@@ -0,0 +1,189 @@
|
||||
#ifndef slic3r_GUI_ExtraRenderers_hpp_
|
||||
#define slic3r_GUI_ExtraRenderers_hpp_
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <wx/dataview.h>
|
||||
|
||||
#if wxUSE_MARKUP && wxCHECK_VERSION(3, 1, 1)
|
||||
#define SUPPORTS_MARKUP
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// DataViewBitmapText: helper class used by BitmapTextRenderer
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class DataViewBitmapText : public wxObject
|
||||
{
|
||||
public:
|
||||
DataViewBitmapText( const wxString &text = wxEmptyString,
|
||||
const wxBitmap& bmp = wxNullBitmap) :
|
||||
m_text(text),
|
||||
m_bmp(bmp)
|
||||
{ }
|
||||
|
||||
DataViewBitmapText(const DataViewBitmapText &other)
|
||||
: wxObject(),
|
||||
m_text(other.m_text),
|
||||
m_bmp(other.m_bmp)
|
||||
{ }
|
||||
|
||||
void SetText(const wxString &text) { m_text = text; }
|
||||
wxString GetText() const { return m_text; }
|
||||
void SetBitmap(const wxBitmap &bmp) { m_bmp = bmp; }
|
||||
const wxBitmap &GetBitmap() const { return m_bmp; }
|
||||
|
||||
bool IsSameAs(const DataViewBitmapText& other) const {
|
||||
return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp);
|
||||
}
|
||||
|
||||
bool operator==(const DataViewBitmapText& other) const {
|
||||
return IsSameAs(other);
|
||||
}
|
||||
|
||||
bool operator!=(const DataViewBitmapText& other) const {
|
||||
return !IsSameAs(other);
|
||||
}
|
||||
|
||||
private:
|
||||
wxString m_text;
|
||||
wxBitmap m_bmp;
|
||||
|
||||
wxDECLARE_DYNAMIC_CLASS(DataViewBitmapText);
|
||||
};
|
||||
DECLARE_VARIANT_OBJECT(DataViewBitmapText)
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// BitmapTextRenderer
|
||||
// ----------------------------------------------------------------------------
|
||||
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
|
||||
class BitmapTextRenderer : public wxDataViewRenderer
|
||||
#else
|
||||
class BitmapTextRenderer : public wxDataViewCustomRenderer
|
||||
#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
|
||||
{
|
||||
public:
|
||||
BitmapTextRenderer(bool use_markup = false,
|
||||
wxDataViewCellMode mode =
|
||||
#ifdef __WXOSX__
|
||||
wxDATAVIEW_CELL_INERT
|
||||
#else
|
||||
wxDATAVIEW_CELL_EDITABLE
|
||||
#endif
|
||||
|
||||
, int align = wxDVR_DEFAULT_ALIGNMENT
|
||||
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
|
||||
);
|
||||
#else
|
||||
) :
|
||||
wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align)
|
||||
{
|
||||
EnableMarkup(use_markup);
|
||||
}
|
||||
#endif //ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
|
||||
|
||||
~BitmapTextRenderer();
|
||||
|
||||
void EnableMarkup(bool enable = true);
|
||||
|
||||
bool SetValue(const wxVariant& value) override;
|
||||
bool GetValue(wxVariant& value) const override;
|
||||
#if ENABLE_NONCUSTOM_DATA_VIEW_RENDERING && wxUSE_ACCESSIBILITY
|
||||
virtual wxString GetAccessibleDescription() const override;
|
||||
#endif // wxUSE_ACCESSIBILITY && ENABLE_NONCUSTOM_DATA_VIEW_RENDERING
|
||||
|
||||
virtual bool Render(wxRect cell, wxDC* dc, int state) override;
|
||||
virtual wxSize GetSize() const override;
|
||||
|
||||
bool HasEditorCtrl() const override
|
||||
{
|
||||
#ifdef __WXOSX__
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
wxWindow* CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) override;
|
||||
bool GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override;
|
||||
bool WasCanceled() const { return m_was_unusable_symbol; }
|
||||
|
||||
void set_can_create_editor_ctrl_function(std::function<bool()> can_create_fn) { can_create_editor_ctrl = can_create_fn; }
|
||||
|
||||
private:
|
||||
DataViewBitmapText m_value;
|
||||
bool m_was_unusable_symbol{ false };
|
||||
|
||||
std::function<bool()> can_create_editor_ctrl { nullptr };
|
||||
|
||||
#ifdef SUPPORTS_MARKUP
|
||||
#ifdef wxHAS_GENERIC_DATAVIEWCTRL
|
||||
class wxItemMarkupText* m_markupText { nullptr };;
|
||||
#else
|
||||
bool is_markupText {false};
|
||||
#endif
|
||||
#endif // SUPPORTS_MARKUP
|
||||
};
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// BitmapChoiceRenderer
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class BitmapChoiceRenderer : public wxDataViewCustomRenderer
|
||||
{
|
||||
public:
|
||||
BitmapChoiceRenderer(wxDataViewCellMode mode =
|
||||
#ifdef __WXOSX__
|
||||
wxDATAVIEW_CELL_INERT
|
||||
#else
|
||||
wxDATAVIEW_CELL_EDITABLE
|
||||
#endif
|
||||
, int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL
|
||||
) : wxDataViewCustomRenderer(wxT("DataViewBitmapText"), mode, align) {}
|
||||
|
||||
bool SetValue(const wxVariant& value) override;
|
||||
bool GetValue(wxVariant& value) const override;
|
||||
|
||||
virtual bool Render(wxRect cell, wxDC* dc, int state) override;
|
||||
virtual wxSize GetSize() const override;
|
||||
|
||||
bool HasEditorCtrl() const override { return true; }
|
||||
wxWindow* CreateEditorCtrl(wxWindow* parent, wxRect labelRect, const wxVariant& value) override;
|
||||
bool GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override;
|
||||
|
||||
void set_can_create_editor_ctrl_function(std::function<bool()> can_create_fn) { can_create_editor_ctrl = can_create_fn; }
|
||||
void set_default_extruder_idx(std::function<int()> default_extruder_idx_fn) { get_default_extruder_idx = default_extruder_idx_fn; }
|
||||
|
||||
private:
|
||||
DataViewBitmapText m_value;
|
||||
std::function<bool()> can_create_editor_ctrl { nullptr };
|
||||
std::function<int()> get_default_extruder_idx{ nullptr };
|
||||
};
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// TextRenderer
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class TextRenderer : public wxDataViewCustomRenderer
|
||||
{
|
||||
public:
|
||||
TextRenderer(wxDataViewCellMode mode = wxDATAVIEW_CELL_INERT
|
||||
, int align = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL
|
||||
) : wxDataViewCustomRenderer(wxT("string"), mode, align) {}
|
||||
|
||||
bool SetValue(const wxVariant& value) override;
|
||||
bool GetValue(wxVariant& value) const override;
|
||||
|
||||
virtual bool Render(wxRect cell, wxDC* dc, int state) override;
|
||||
virtual wxSize GetSize() const override;
|
||||
|
||||
bool HasEditorCtrl() const override { return false; }
|
||||
|
||||
private:
|
||||
wxString m_value;
|
||||
};
|
||||
|
||||
|
||||
#endif // slic3r_GUI_ExtraRenderers_hpp_
|
||||
286
src/slic3r/GUI/ExtruderSequenceDialog.cpp
Normal file
286
src/slic3r/GUI/ExtruderSequenceDialog.cpp
Normal file
@@ -0,0 +1,286 @@
|
||||
#include "ExtruderSequenceDialog.hpp"
|
||||
|
||||
#include <wx/wx.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/bmpcbox.h>
|
||||
#include <wx/checkbox.h>
|
||||
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <functional>
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "OptionsGroup.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "BitmapComboBox.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
ExtruderSequenceDialog::ExtruderSequenceDialog(const DoubleSlider::ExtrudersSequence& sequence)
|
||||
: DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("Set extruder sequence")),
|
||||
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
|
||||
m_sequence(sequence)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
wxGetApp().UpdateDarkUI(this);
|
||||
#else
|
||||
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
#endif
|
||||
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"))+ ": ");
|
||||
wxGetApp().UpdateDarkUI(intervals_box);
|
||||
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 = wxID_ANY;// 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
|
||||
#ifdef _WIN32
|
||||
, wxBORDER_SIMPLE
|
||||
#endif
|
||||
);
|
||||
wxGetApp().UpdateDarkUI(m_interval_by_layers);
|
||||
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
|
||||
#ifdef _WIN32
|
||||
| wxBORDER_SIMPLE
|
||||
#endif
|
||||
);
|
||||
wxGetApp().UpdateDarkUI(m_interval_by_mm);
|
||||
|
||||
double min_layer_height = wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_float("layer_height");
|
||||
auto change_value = [this, min_layer_height]()
|
||||
{
|
||||
wxString str = m_interval_by_mm->GetValue();
|
||||
if (str.IsEmpty()) {
|
||||
m_interval_by_mm->SetValue(wxString::Format("%d", m_sequence.interval_by_mm));
|
||||
return;
|
||||
}
|
||||
|
||||
char dec_sep = '.';
|
||||
if (! is_decimal_separator_point()) {
|
||||
str.Replace(".", ",", false);
|
||||
dec_sep = ',';
|
||||
}
|
||||
|
||||
double val;
|
||||
if (str == dec_sep || !str.ToDouble(&val) || val <= 0.0)
|
||||
val = 3.0; // default value
|
||||
|
||||
if (fabs(m_sequence.interval_by_layers - val) < 0.001)
|
||||
return;
|
||||
|
||||
if (val < min_layer_height) {
|
||||
val = min_layer_height;
|
||||
m_interval_by_mm->SetValue(double_to_string(val, 2));
|
||||
}
|
||||
|
||||
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, [change_value](wxFocusEvent& event)
|
||||
{
|
||||
change_value();
|
||||
event.Skip();
|
||||
});
|
||||
|
||||
m_interval_by_mm->Bind(wxEVT_TEXT_ENTER, [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);
|
||||
|
||||
m_random_sequence = new wxCheckBox(this, wxID_ANY, _L("Random sequence"));
|
||||
m_random_sequence->SetValue(m_sequence.random_sequence);
|
||||
m_random_sequence->SetToolTip(_L("If enabled, random sequence of the selected extruders will be used."));
|
||||
m_random_sequence->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& e) {
|
||||
m_sequence.random_sequence = e.IsChecked();
|
||||
m_color_repetition->Enable(m_sequence.random_sequence);
|
||||
});
|
||||
|
||||
m_color_repetition = new wxCheckBox(this, wxID_ANY, _L("Allow next color repetition"));
|
||||
m_color_repetition->SetValue(m_sequence.color_repetition);
|
||||
m_color_repetition->SetToolTip(_L("If enabled, a repetition of the next random color will be allowed."));
|
||||
m_color_repetition->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& e) {m_sequence.color_repetition = e.IsChecked(); });
|
||||
|
||||
auto extruders_box = new wxStaticBox(this, wxID_ANY, _(L("Set extruder(tool) sequence"))+ ": ");
|
||||
wxGetApp().UpdateDarkUI(extruders_box);
|
||||
|
||||
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);
|
||||
extruders_box_sizer->Add(m_random_sequence, 0, wxLEFT | wxBOTTOM, em);
|
||||
extruders_box_sizer->Add(m_color_repetition, 0, wxLEFT | wxBOTTOM, 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);
|
||||
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_OK, this)));
|
||||
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_CANCEL, this)));
|
||||
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)
|
||||
{
|
||||
BitmapComboBox* 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, extruder_selector](wxEvent&) {
|
||||
size_t extr_cnt = (size_t)extruder_selector->GetCount();
|
||||
size_t seq_extr_cnt = m_sequence.extruders.size();
|
||||
size_t extr_id = seq_extr_cnt - size_t(seq_extr_cnt / extr_cnt) * extr_cnt;
|
||||
m_sequence.add_extruder(extruder, std::min(extr_id, extr_cnt-1));
|
||||
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()
|
||||
|
||||
bool show_checkboxes = m_sequence.extruders.size() > 1;
|
||||
m_random_sequence->Enable(show_checkboxes);
|
||||
m_color_repetition->Enable(show_checkboxes && m_sequence.random_sequence);
|
||||
|
||||
Fit();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void ExtruderSequenceDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
{
|
||||
SetFont(wxGetApp().normal_font());
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
48
src/slic3r/GUI/ExtruderSequenceDialog.hpp
Normal file
48
src/slic3r/GUI/ExtruderSequenceDialog.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#ifndef slic3r_GUI_ExtruderSequenceDialog_hpp_
|
||||
#define slic3r_GUI_ExtruderSequenceDialog_hpp_
|
||||
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "DoubleSlider.hpp"
|
||||
|
||||
class wxTextCtrl;
|
||||
class wxFlexGridSizer;
|
||||
class wxCheckBox;
|
||||
|
||||
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};
|
||||
wxCheckBox* m_random_sequence {nullptr};
|
||||
wxCheckBox* m_color_repetition{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_
|
||||
1707
src/slic3r/GUI/Field.cpp
Normal file
1707
src/slic3r/GUI/Field.cpp
Normal file
File diff suppressed because it is too large
Load Diff
515
src/slic3r/GUI/Field.hpp
Normal file
515
src/slic3r/GUI/Field.hpp
Normal file
@@ -0,0 +1,515 @@
|
||||
#ifndef SLIC3R_GUI_FIELD_HPP
|
||||
#define SLIC3R_GUI_FIELD_HPP
|
||||
|
||||
#include <wx/wxprec.h>
|
||||
#ifndef WX_PRECOMP
|
||||
#include <wx/wx.h>
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <boost/any.hpp>
|
||||
|
||||
#include <wx/spinctrl.h>
|
||||
#include <wx/bmpcbox.h>
|
||||
#include <wx/clrpicker.h>
|
||||
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
#ifdef __WXMSW__
|
||||
#define wxMSW true
|
||||
#else
|
||||
#define wxMSW false
|
||||
#endif
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
class Field;
|
||||
using t_field = std::unique_ptr<Field>;
|
||||
using t_kill_focus = std::function<void(const std::string&)>;
|
||||
using t_change = std::function<void(const t_config_option_key&, const boost::any&)>;
|
||||
using t_back_to_init = std::function<void(const std::string&)>;
|
||||
|
||||
wxString double_to_string(double const value, const int max_precision = 4);
|
||||
wxString get_thumbnails_string(const std::vector<Vec2d>& values);
|
||||
|
||||
class UndoValueUIManager
|
||||
{
|
||||
struct UndoValueUI {
|
||||
// Bitmap and Tooltip text for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
|
||||
const ScalableBitmap* undo_bitmap{ nullptr };
|
||||
const wxString* undo_tooltip{ nullptr };
|
||||
// Bitmap and Tooltip text for m_Undo_to_sys_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
|
||||
const ScalableBitmap* undo_to_sys_bitmap{ nullptr };
|
||||
const wxString* undo_to_sys_tooltip{ nullptr };
|
||||
// Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one.
|
||||
const wxColour* label_color{ nullptr };
|
||||
// State of the blinker icon
|
||||
bool blink{ false };
|
||||
|
||||
bool set_undo_bitmap(const ScalableBitmap* bmp) {
|
||||
if (undo_bitmap != bmp) {
|
||||
undo_bitmap = bmp;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool set_undo_to_sys_bitmap(const ScalableBitmap* bmp) {
|
||||
if (undo_to_sys_bitmap != bmp) {
|
||||
undo_to_sys_bitmap = bmp;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool set_label_colour(const wxColour* clr) {
|
||||
if (label_color != clr) {
|
||||
label_color = clr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool set_undo_tooltip(const wxString* tip) {
|
||||
if (undo_tooltip != tip) {
|
||||
undo_tooltip = tip;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool set_undo_to_sys_tooltip(const wxString* tip) {
|
||||
if (undo_to_sys_tooltip != tip) {
|
||||
undo_to_sys_tooltip = tip;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
UndoValueUI m_undo_ui;
|
||||
|
||||
public:
|
||||
UndoValueUIManager() {}
|
||||
~UndoValueUIManager() {}
|
||||
|
||||
bool set_undo_bitmap(const ScalableBitmap* bmp) { return m_undo_ui.set_undo_bitmap(bmp); }
|
||||
bool set_undo_to_sys_bitmap(const ScalableBitmap* bmp) { return m_undo_ui.set_undo_to_sys_bitmap(bmp); }
|
||||
bool set_label_colour(const wxColour* clr) { return m_undo_ui.set_label_colour(clr); }
|
||||
bool set_undo_tooltip(const wxString* tip) { return m_undo_ui.set_undo_tooltip(tip); }
|
||||
bool set_undo_to_sys_tooltip(const wxString* tip) { return m_undo_ui.set_undo_to_sys_tooltip(tip); }
|
||||
|
||||
// ui items used for revert line value
|
||||
bool has_undo_ui() const { return m_undo_ui.undo_bitmap != nullptr; }
|
||||
const wxBitmapBundle& undo_bitmap() const { return m_undo_ui.undo_bitmap->bmp(); }
|
||||
const wxString* undo_tooltip() const { return m_undo_ui.undo_tooltip; }
|
||||
const wxBitmapBundle& undo_to_sys_bitmap() const { return m_undo_ui.undo_to_sys_bitmap->bmp(); }
|
||||
const wxString* undo_to_sys_tooltip() const { return m_undo_ui.undo_to_sys_tooltip; }
|
||||
const wxColour* label_color() const { return m_undo_ui.label_color; }
|
||||
const bool blink() const { return m_undo_ui.blink; }
|
||||
bool* get_blink_ptr() { return &m_undo_ui.blink; }
|
||||
};
|
||||
|
||||
|
||||
class Field : public UndoValueUIManager
|
||||
{
|
||||
protected:
|
||||
// factory function to defer and enforce creation of derived type.
|
||||
virtual void PostInitialize();
|
||||
|
||||
/// Finish constructing the Field's wxWidget-related properties, including setting its own sizer, etc.
|
||||
virtual void BUILD() = 0;
|
||||
|
||||
/// Call the attached on_kill_focus method.
|
||||
//! It's important to use wxEvent instead of wxFocusEvent,
|
||||
//! in another case we can't unfocused control at all
|
||||
void on_kill_focus();
|
||||
/// Call the attached on_change method.
|
||||
void on_change_field();
|
||||
|
||||
class EnterPressed {
|
||||
public:
|
||||
EnterPressed(Field* field) :
|
||||
m_parent(field){ m_parent->set_enter_pressed(true); }
|
||||
~EnterPressed() { m_parent->set_enter_pressed(false); }
|
||||
private:
|
||||
Field* m_parent;
|
||||
};
|
||||
|
||||
public:
|
||||
/// Call the attached m_back_to_initial_value method.
|
||||
void on_back_to_initial_value();
|
||||
/// Call the attached m_back_to_sys_value method.
|
||||
void on_back_to_sys_value();
|
||||
|
||||
public:
|
||||
/// parent wx item, opportunity to refactor (probably not necessary - data duplication)
|
||||
wxWindow* m_parent {nullptr};
|
||||
|
||||
/// Function object to store callback passed in from owning object.
|
||||
t_kill_focus m_on_kill_focus {nullptr};
|
||||
|
||||
/// Function object to store callback passed in from owning object.
|
||||
t_change m_on_change {nullptr};
|
||||
|
||||
/// Function object to store callback passed in from owning object.
|
||||
t_back_to_init m_back_to_initial_value{ nullptr };
|
||||
t_back_to_init m_back_to_sys_value{ nullptr };
|
||||
|
||||
// This is used to avoid recursive invocation of the field change/update by wxWidgets.
|
||||
bool m_disable_change_event {false};
|
||||
bool m_is_modified_value {false};
|
||||
bool m_is_nonsys_value {true};
|
||||
|
||||
/// Copy of ConfigOption for deduction purposes
|
||||
const ConfigOptionDef m_opt {ConfigOptionDef()};
|
||||
const t_config_option_key m_opt_id;//! {""};
|
||||
int m_opt_idx = 0;
|
||||
|
||||
double opt_height{ 0.0 };
|
||||
bool parent_is_custom_ctrl{ false };
|
||||
|
||||
/// Sets a value for this control.
|
||||
/// subclasses should overload with a specific version
|
||||
/// Postcondition: Method does not fire the on_change event.
|
||||
virtual void set_value(const boost::any& value, bool change_event) = 0;
|
||||
virtual void set_last_meaningful_value() {}
|
||||
virtual void set_na_value() {}
|
||||
|
||||
/// Gets a boost::any representing this control.
|
||||
/// subclasses should overload with a specific version
|
||||
virtual boost::any& get_value() = 0;
|
||||
|
||||
virtual void enable() = 0;
|
||||
virtual void disable() = 0;
|
||||
|
||||
/// Fires the enable or disable function, based on the input.
|
||||
inline void toggle(bool en) { en ? enable() : disable(); }
|
||||
|
||||
virtual wxString get_tooltip_text(const wxString& default_string);
|
||||
|
||||
void field_changed() { on_change_field(); }
|
||||
|
||||
Field(const ConfigOptionDef& opt, const t_config_option_key& id) : m_opt(opt), m_opt_id(id) {}
|
||||
Field(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : m_parent(parent), m_opt(opt), m_opt_id(id) {}
|
||||
virtual ~Field();
|
||||
|
||||
/// If you don't know what you are getting back, check both methods for nullptr.
|
||||
virtual wxSizer* getSizer() { return nullptr; }
|
||||
virtual wxWindow* getWindow() { return nullptr; }
|
||||
|
||||
bool is_matched(const std::string& string, const std::string& pattern);
|
||||
void get_value_by_opt_type(wxString& str, const bool check_value = true);
|
||||
|
||||
/// Factory method for generating new derived classes.
|
||||
template<class T>
|
||||
static t_field Create(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id)// interface for creating shared objects
|
||||
{
|
||||
auto p = Slic3r::make_unique<T>(parent, opt, id);
|
||||
p->PostInitialize();
|
||||
return std::move(p); //!p;
|
||||
}
|
||||
|
||||
virtual void msw_rescale();
|
||||
virtual void sys_color_changed();
|
||||
|
||||
bool get_enter_pressed() const { return bEnterPressed; }
|
||||
void set_enter_pressed(bool pressed) { bEnterPressed = pressed; }
|
||||
|
||||
// Values of width to alignments of fields
|
||||
static int def_width() ;
|
||||
static int def_width_wider() ;
|
||||
static int def_width_thinner() ;
|
||||
|
||||
protected:
|
||||
// current value
|
||||
boost::any m_value;
|
||||
// last maeningful value
|
||||
boost::any m_last_meaningful_value;
|
||||
|
||||
int m_em_unit;
|
||||
|
||||
bool bEnterPressed = false;
|
||||
|
||||
friend class OptionsGroup;
|
||||
};
|
||||
|
||||
/// Convenience function, accepts a const reference to t_field and checks to see whether
|
||||
/// or not both wx pointers are null.
|
||||
inline bool is_bad_field(const t_field& obj) { return obj->getSizer() == nullptr && obj->getWindow() == nullptr; }
|
||||
|
||||
/// Covenience function to determine whether this field is a valid window field.
|
||||
inline bool is_window_field(const t_field& obj) { return !is_bad_field(obj) && obj->getWindow() != nullptr && obj->getSizer() == nullptr; }
|
||||
|
||||
/// Covenience function to determine whether this field is a valid sizer field.
|
||||
inline bool is_sizer_field(const t_field& obj) { return !is_bad_field(obj) && obj->getSizer() != nullptr; }
|
||||
|
||||
class TextCtrl : public Field {
|
||||
using Field::Field;
|
||||
#ifdef __WXGTK__
|
||||
bool bChangedValueEvent = true;
|
||||
void change_field_value(wxEvent& event);
|
||||
#endif //__WXGTK__
|
||||
|
||||
public:
|
||||
TextCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
TextCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
~TextCtrl() {}
|
||||
|
||||
void BUILD() override;
|
||||
bool value_was_changed();
|
||||
// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
|
||||
void propagate_value();
|
||||
wxWindow* window {nullptr};
|
||||
|
||||
void set_value(const std::string& value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxTextCtrl*>(window)->SetValue(wxString(value));
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
void set_value(const boost::any& value, bool change_event = false) override;
|
||||
void set_last_meaningful_value() override;
|
||||
void set_na_value() override;
|
||||
|
||||
boost::any& get_value() override;
|
||||
|
||||
void msw_rescale() override;
|
||||
|
||||
void enable() override;
|
||||
void disable() override;
|
||||
wxWindow* getWindow() override { return window; }
|
||||
};
|
||||
|
||||
class CheckBox : public Field {
|
||||
using Field::Field;
|
||||
bool m_is_na_val {false};
|
||||
public:
|
||||
CheckBox(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
CheckBox(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
~CheckBox() {}
|
||||
|
||||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
|
||||
void set_value(const bool value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxCheckBox*>(window)->SetValue(value);
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
void set_value(const boost::any& value, bool change_event = false) override;
|
||||
void set_last_meaningful_value() override;
|
||||
void set_na_value() override;
|
||||
boost::any& get_value() override;
|
||||
|
||||
void msw_rescale() override;
|
||||
|
||||
void enable() override { dynamic_cast<wxCheckBox*>(window)->Enable(); }
|
||||
void disable() override { dynamic_cast<wxCheckBox*>(window)->Disable(); }
|
||||
wxWindow* getWindow() override { return window; }
|
||||
};
|
||||
|
||||
class SpinCtrl : public Field {
|
||||
using Field::Field;
|
||||
private:
|
||||
static const int UNDEF_VALUE = INT_MIN;
|
||||
|
||||
public:
|
||||
SpinCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id), tmp_value(UNDEF_VALUE) {}
|
||||
SpinCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id), tmp_value(UNDEF_VALUE) {}
|
||||
~SpinCtrl() {}
|
||||
|
||||
int tmp_value;
|
||||
|
||||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
/// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
|
||||
void propagate_value() ;
|
||||
/*
|
||||
void set_value(const std::string& value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxSpinCtrl*>(window)->SetValue(value);
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
*/
|
||||
void set_value(const boost::any& value, bool change_event = false) override;
|
||||
void set_last_meaningful_value() override;
|
||||
void set_na_value() override;
|
||||
|
||||
boost::any& get_value() override;
|
||||
|
||||
void msw_rescale() override;
|
||||
|
||||
void enable() override { dynamic_cast<wxSpinCtrl*>(window)->Enable(); }
|
||||
void disable() override { dynamic_cast<wxSpinCtrl*>(window)->Disable(); }
|
||||
wxWindow* getWindow() override { return window; }
|
||||
};
|
||||
|
||||
class Choice : public Field {
|
||||
using Field::Field;
|
||||
|
||||
public:
|
||||
Choice(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
Choice(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
~Choice() {}
|
||||
|
||||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
|
||||
void propagate_value();
|
||||
|
||||
/* Under OSX: wxBitmapComboBox->GetWindowStyle() returns some weard value,
|
||||
* so let use a flag, which has TRUE value for a control without wxCB_READONLY style
|
||||
*/
|
||||
bool m_is_editable { false };
|
||||
bool m_is_dropped { false };
|
||||
bool m_suppress_scroll { false };
|
||||
int m_last_selected { wxNOT_FOUND };
|
||||
|
||||
void set_selection();
|
||||
void set_value(const std::string& value, bool change_event = false);
|
||||
void set_value(const boost::any& value, bool change_event = false) override;
|
||||
void set_values(const std::vector<std::string> &values);
|
||||
void set_values(const wxArrayString &values);
|
||||
boost::any& get_value() override;
|
||||
|
||||
void msw_rescale() override;
|
||||
|
||||
void enable() override ;//{ dynamic_cast<wxBitmapComboBox*>(window)->Enable(); };
|
||||
void disable() override;//{ dynamic_cast<wxBitmapComboBox*>(window)->Disable(); };
|
||||
wxWindow* getWindow() override { return window; }
|
||||
|
||||
void suppress_scroll();
|
||||
};
|
||||
|
||||
class ColourPicker : public Field {
|
||||
using Field::Field;
|
||||
|
||||
void set_undef_value(wxColourPickerCtrl* field);
|
||||
public:
|
||||
ColourPicker(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
ColourPicker(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
~ColourPicker() {}
|
||||
|
||||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
|
||||
void set_value(const std::string& value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxColourPickerCtrl*>(window)->SetColour(value);
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
void set_value(const boost::any& value, bool change_event = false) override;
|
||||
boost::any& get_value() override;
|
||||
void msw_rescale() override;
|
||||
void sys_color_changed() override;
|
||||
|
||||
void enable() override { dynamic_cast<wxColourPickerCtrl*>(window)->Enable(); }
|
||||
void disable() override{ dynamic_cast<wxColourPickerCtrl*>(window)->Disable(); }
|
||||
wxWindow* getWindow() override { return window; }
|
||||
};
|
||||
|
||||
class PointCtrl : public Field {
|
||||
using Field::Field;
|
||||
public:
|
||||
PointCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
PointCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
~PointCtrl();
|
||||
|
||||
wxSizer* sizer{ nullptr };
|
||||
wxTextCtrl* x_textctrl{ nullptr };
|
||||
wxTextCtrl* y_textctrl{ nullptr };
|
||||
|
||||
void BUILD() override;
|
||||
bool value_was_changed(wxTextCtrl* win);
|
||||
// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
|
||||
void propagate_value(wxTextCtrl* win);
|
||||
void set_value(const Vec2d& value, bool change_event = false);
|
||||
void set_value(const boost::any& value, bool change_event = false) override;
|
||||
boost::any& get_value() override;
|
||||
|
||||
void msw_rescale() override;
|
||||
void sys_color_changed() override;
|
||||
|
||||
void enable() override {
|
||||
x_textctrl->Enable();
|
||||
y_textctrl->Enable(); }
|
||||
void disable() override{
|
||||
x_textctrl->Disable();
|
||||
y_textctrl->Disable(); }
|
||||
wxSizer* getSizer() override { return sizer; }
|
||||
wxWindow* getWindow() override { return dynamic_cast<wxWindow*>(x_textctrl); }
|
||||
};
|
||||
|
||||
class StaticText : public Field {
|
||||
using Field::Field;
|
||||
public:
|
||||
StaticText(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
StaticText(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
~StaticText() {}
|
||||
|
||||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
|
||||
void set_value(const std::string& value, bool change_event = false) {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxStaticText*>(window)->SetLabel(wxString::FromUTF8(value.data()));
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
void set_value(const boost::any& value, bool change_event = false) override {
|
||||
m_disable_change_event = !change_event;
|
||||
dynamic_cast<wxStaticText*>(window)->SetLabel(boost::any_cast<wxString>(value));
|
||||
m_disable_change_event = false;
|
||||
}
|
||||
|
||||
boost::any& get_value()override { return m_value; }
|
||||
|
||||
void msw_rescale() override;
|
||||
|
||||
void enable() override { dynamic_cast<wxStaticText*>(window)->Enable(); }
|
||||
void disable() override{ dynamic_cast<wxStaticText*>(window)->Disable(); }
|
||||
wxWindow* getWindow() override { return window; }
|
||||
};
|
||||
|
||||
class SliderCtrl : public Field {
|
||||
using Field::Field;
|
||||
public:
|
||||
SliderCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
SliderCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
~SliderCtrl() {}
|
||||
|
||||
wxSizer* m_sizer{ nullptr };
|
||||
wxTextCtrl* m_textctrl{ nullptr };
|
||||
wxSlider* m_slider{ nullptr };
|
||||
|
||||
int m_scale = 10;
|
||||
|
||||
void BUILD() override;
|
||||
|
||||
void set_value(const int value, bool change_event = false);
|
||||
void set_value(const boost::any& value, bool change_event = false) override;
|
||||
boost::any& get_value() override;
|
||||
|
||||
void enable() override {
|
||||
m_slider->Enable();
|
||||
m_textctrl->Enable();
|
||||
m_textctrl->SetEditable(true);
|
||||
}
|
||||
void disable() override{
|
||||
m_slider->Disable();
|
||||
m_textctrl->Disable();
|
||||
m_textctrl->SetEditable(false);
|
||||
}
|
||||
wxSizer* getSizer() override { return m_sizer; }
|
||||
wxWindow* getWindow() override { return dynamic_cast<wxWindow*>(m_slider); }
|
||||
};
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
#endif /* SLIC3R_GUI_FIELD_HPP */
|
||||
387
src/slic3r/GUI/FileArchiveDialog.cpp
Normal file
387
src/slic3r/GUI/FileArchiveDialog.cpp
Normal file
@@ -0,0 +1,387 @@
|
||||
#include "FileArchiveDialog.hpp"
|
||||
|
||||
#include "I18N.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "ExtraRenderers.hpp"
|
||||
#include "format.hpp"
|
||||
#include <regex>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
ArchiveViewModel::ArchiveViewModel(wxWindow* parent)
|
||||
:m_parent(parent)
|
||||
{}
|
||||
ArchiveViewModel::~ArchiveViewModel()
|
||||
{}
|
||||
|
||||
std::shared_ptr<ArchiveViewNode> ArchiveViewModel::AddFile(std::shared_ptr<ArchiveViewNode> parent, const wxString& name, bool container)
|
||||
{
|
||||
std::shared_ptr<ArchiveViewNode> node = std::make_shared<ArchiveViewNode>(ArchiveViewNode(name));
|
||||
node->set_container(container);
|
||||
|
||||
if (parent.get() != nullptr) {
|
||||
parent->get_children().push_back(node);
|
||||
node->set_parent(parent);
|
||||
parent->set_is_folder(true);
|
||||
} else {
|
||||
m_top_children.emplace_back(node);
|
||||
}
|
||||
|
||||
wxDataViewItem child = wxDataViewItem((void*)node.get());
|
||||
wxDataViewItem parent_item= wxDataViewItem((void*)parent.get());
|
||||
ItemAdded(parent_item, child);
|
||||
|
||||
if (parent)
|
||||
m_ctrl->Expand(parent_item);
|
||||
return node;
|
||||
}
|
||||
|
||||
wxString ArchiveViewModel::GetColumnType(unsigned int col) const
|
||||
{
|
||||
if (col == 0)
|
||||
return "bool";
|
||||
return "string";//"DataViewBitmapText";
|
||||
}
|
||||
|
||||
void ArchiveViewModel::Rescale()
|
||||
{
|
||||
// There should be no pictures rendered
|
||||
}
|
||||
|
||||
void ArchiveViewModel::Delete(const wxDataViewItem& item)
|
||||
{
|
||||
assert(item.IsOk());
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
||||
assert(node->get_parent() != nullptr);
|
||||
for (std::shared_ptr<ArchiveViewNode> child : node->get_children())
|
||||
{
|
||||
Delete(wxDataViewItem((void*)child.get()));
|
||||
}
|
||||
delete [] node;
|
||||
}
|
||||
void ArchiveViewModel::Clear()
|
||||
{
|
||||
}
|
||||
|
||||
wxDataViewItem ArchiveViewModel::GetParent(const wxDataViewItem& item) const
|
||||
{
|
||||
assert(item.IsOk());
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
||||
return wxDataViewItem((void*)node->get_parent().get());
|
||||
}
|
||||
unsigned int ArchiveViewModel::GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const
|
||||
{
|
||||
if (!parent.IsOk()) {
|
||||
for (std::shared_ptr<ArchiveViewNode>child : m_top_children) {
|
||||
array.push_back(wxDataViewItem((void*)child.get()));
|
||||
}
|
||||
return m_top_children.size();
|
||||
}
|
||||
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(parent.GetID());
|
||||
for (std::shared_ptr<ArchiveViewNode> child : node->get_children()) {
|
||||
array.push_back(wxDataViewItem((void*)child.get()));
|
||||
}
|
||||
return node->get_children().size();
|
||||
}
|
||||
|
||||
void ArchiveViewModel::GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const
|
||||
{
|
||||
assert(item.IsOk());
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
||||
if (col == 0) {
|
||||
variant = node->get_toggle();
|
||||
} else {
|
||||
variant = node->get_name();
|
||||
}
|
||||
}
|
||||
|
||||
void ArchiveViewModel::untoggle_folders(const wxDataViewItem& item)
|
||||
{
|
||||
assert(item.IsOk());
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
||||
node->set_toggle(false);
|
||||
if (node->get_parent().get() != nullptr)
|
||||
untoggle_folders(wxDataViewItem((void*)node->get_parent().get()));
|
||||
}
|
||||
|
||||
bool ArchiveViewModel::SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col)
|
||||
{
|
||||
assert(item.IsOk());
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
||||
if (col == 0) {
|
||||
node->set_toggle(variant.GetBool());
|
||||
// if folder recursivelly check all children
|
||||
for (std::shared_ptr<ArchiveViewNode> child : node->get_children()) {
|
||||
SetValue(variant, wxDataViewItem((void*)child.get()), col);
|
||||
}
|
||||
if(!variant.GetBool() && node->get_parent())
|
||||
untoggle_folders(wxDataViewItem((void*)node->get_parent().get()));
|
||||
} else {
|
||||
node->set_name(variant.GetString());
|
||||
}
|
||||
m_parent->Refresh();
|
||||
return true;
|
||||
}
|
||||
bool ArchiveViewModel::IsEnabled(const wxDataViewItem& item, unsigned int col) const
|
||||
{
|
||||
// As of now, all items are always enabled.
|
||||
// Returning false for col 1 would gray out text.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ArchiveViewModel::IsContainer(const wxDataViewItem& item) const
|
||||
{
|
||||
if(!item.IsOk())
|
||||
return true;
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
||||
return node->is_container();
|
||||
}
|
||||
|
||||
ArchiveViewCtrl::ArchiveViewCtrl(wxWindow* parent, wxSize size)
|
||||
: wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, size, wxDV_VARIABLE_LINE_HEIGHT | wxDV_ROW_LINES
|
||||
#ifdef _WIN32
|
||||
| wxBORDER_SIMPLE
|
||||
#endif
|
||||
)
|
||||
//, m_em_unit(em_unit(parent))
|
||||
{
|
||||
wxGetApp().UpdateDVCDarkUI(this);
|
||||
|
||||
m_model = new ArchiveViewModel(parent);
|
||||
this->AssociateModel(m_model);
|
||||
m_model->SetAssociatedControl(this);
|
||||
}
|
||||
|
||||
ArchiveViewCtrl::~ArchiveViewCtrl()
|
||||
{
|
||||
if (m_model) {
|
||||
m_model->Clear();
|
||||
m_model->DecRef();
|
||||
}
|
||||
}
|
||||
|
||||
FileArchiveDialog::FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* archive, std::vector<std::pair<boost::filesystem::path, size_t>>& selected_paths_w_size)
|
||||
: DPIDialog(parent_window, wxID_ANY, _(L("Archive preview")), wxDefaultPosition,
|
||||
wxSize(45 * wxGetApp().em_unit(), 40 * wxGetApp().em_unit()),
|
||||
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX)
|
||||
, m_selected_paths_w_size (selected_paths_w_size)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
wxGetApp().UpdateDarkUI(this);
|
||||
#else
|
||||
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
#endif
|
||||
|
||||
int em = em_unit();
|
||||
|
||||
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
m_avc = new ArchiveViewCtrl(this, wxSize(45 * em, 30 * em));
|
||||
wxDataViewColumn* toggle_column = m_avc->AppendToggleColumn(L"\u2714", 0, wxDATAVIEW_CELL_ACTIVATABLE, 6 * em);
|
||||
m_avc->AppendTextColumn("filename", 1);
|
||||
|
||||
std::vector<std::shared_ptr<ArchiveViewNode>> stack;
|
||||
|
||||
std::function<void(std::vector<std::shared_ptr<ArchiveViewNode> >&, size_t)> reduce_stack = [] (std::vector<std::shared_ptr<ArchiveViewNode>>& stack, size_t size) {
|
||||
if (size == 0) {
|
||||
stack.clear();
|
||||
return;
|
||||
}
|
||||
while (stack.size() > size)
|
||||
stack.pop_back();
|
||||
};
|
||||
// recursively stores whole structure of file onto function stack and synchoronize with stack object.
|
||||
std::function<size_t(const boost::filesystem::path&, std::vector<std::shared_ptr<ArchiveViewNode>>&)> adjust_stack = [&adjust_stack, &reduce_stack, &avc = m_avc](const boost::filesystem::path& const_file, std::vector<std::shared_ptr<ArchiveViewNode>>& stack)->size_t {
|
||||
boost::filesystem::path file(const_file);
|
||||
size_t struct_size = file.has_parent_path() ? adjust_stack(file.parent_path(), stack) : 0;
|
||||
|
||||
if (stack.size() > struct_size && (file.has_extension() || file.filename().string() != stack[struct_size]->get_name()))
|
||||
{
|
||||
reduce_stack(stack, struct_size);
|
||||
}
|
||||
if (!file.has_extension() && stack.size() == struct_size)
|
||||
stack.push_back(avc->get_model()->AddFile((stack.empty() ? std::shared_ptr<ArchiveViewNode>(nullptr) : stack.back()), boost::nowide::widen(file.filename().string()), true)); // filename string to wstring?
|
||||
return struct_size + 1;
|
||||
};
|
||||
|
||||
const std::regex pattern_drop(".*[.](stl|obj|amf|3mf|qidi|step|stp)", std::regex::icase);
|
||||
mz_uint num_entries = mz_zip_reader_get_num_files(archive);
|
||||
mz_zip_archive_file_stat stat;
|
||||
std::vector<std::pair<boost::filesystem::path, size_t>> filtered_entries; // second is unzipped size
|
||||
for (mz_uint i = 0; i < num_entries; ++i) {
|
||||
if (mz_zip_reader_file_stat(archive, i, &stat)) {
|
||||
std::string extra(1024, 0);
|
||||
boost::filesystem::path path;
|
||||
size_t extra_size = mz_zip_reader_get_filename_from_extra(archive, i, extra.data(), extra.size());
|
||||
if (extra_size > 0) {
|
||||
path = boost::filesystem::path(extra.substr(0, extra_size));
|
||||
} else {
|
||||
wxString wname = boost::nowide::widen(stat.m_filename);
|
||||
std::string name = boost::nowide::narrow(wname);
|
||||
path = boost::filesystem::path(name);
|
||||
}
|
||||
assert(!path.empty());
|
||||
if (!path.has_extension())
|
||||
continue;
|
||||
// filter out MACOS specific hidden files
|
||||
if (boost::algorithm::starts_with(path.string(), "__MACOSX"))
|
||||
continue;
|
||||
filtered_entries.emplace_back(std::move(path), stat.m_uncomp_size);
|
||||
}
|
||||
}
|
||||
// sorting files will help adjust_stack function to not create multiple same folders
|
||||
std::sort(filtered_entries.begin(), filtered_entries.end(), [](const std::pair<boost::filesystem::path, size_t>& p1, const std::pair<boost::filesystem::path, size_t>& p2){ return p1.first.string() < p2.first.string(); });
|
||||
size_t entry_count = 0;
|
||||
size_t depth = 1;
|
||||
for (const auto& entry : filtered_entries)
|
||||
{
|
||||
const boost::filesystem::path& path = entry.first;
|
||||
std::shared_ptr<ArchiveViewNode> parent(nullptr);
|
||||
|
||||
depth = std::max(depth, adjust_stack(path, stack));
|
||||
if (!stack.empty())
|
||||
parent = stack.back();
|
||||
if (std::regex_match(path.extension().string(), pattern_drop)) { // this leaves out non-compatible files
|
||||
std::shared_ptr<ArchiveViewNode> new_node = m_avc->get_model()->AddFile(parent, boost::nowide::widen(path.filename().string()), false);
|
||||
new_node->set_fullpath(/*std::move(path)*/path); // filename string to wstring?
|
||||
new_node->set_size(entry.second);
|
||||
entry_count++;
|
||||
}
|
||||
}
|
||||
if (entry_count == 1)
|
||||
on_all_button();
|
||||
|
||||
toggle_column->SetWidth((4 + depth) * em);
|
||||
|
||||
wxBoxSizer* btn_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
|
||||
wxButton* btn_all = new wxButton(this, wxID_ANY, _L("All"));
|
||||
btn_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_all_button(); });
|
||||
btn_sizer->Add(btn_all, 0);
|
||||
|
||||
wxButton* btn_none = new wxButton(this, wxID_ANY, _L("None"));
|
||||
btn_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_none_button(); });
|
||||
btn_sizer->Add(btn_none, 0, wxLEFT, em);
|
||||
|
||||
btn_sizer->AddStretchSpacer();
|
||||
wxButton* btn_run = new wxButton(this, wxID_OK, _L("Open"));
|
||||
btn_run->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { on_open_button(); });
|
||||
btn_sizer->Add(btn_run, 0, wxRIGHT, em);
|
||||
|
||||
wxButton* cancel_btn = new wxButton(this, wxID_CANCEL, _L("Cancel"));
|
||||
cancel_btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent& evt) { this->EndModal(wxID_CANCEL); });
|
||||
btn_sizer->Add(cancel_btn, 0, wxRIGHT, em);
|
||||
|
||||
topSizer->Add(m_avc, 1, wxEXPAND | wxALL, 10);
|
||||
topSizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 10);
|
||||
this->SetSizer(topSizer);
|
||||
SetMinSize(wxSize(40 * em, 30 * em));
|
||||
|
||||
for (const wxString& id : {_L("All"), _L("None"), _L("Open"), _L("Cancel") })
|
||||
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(FindWindowByLabel(id, this)));
|
||||
}
|
||||
|
||||
void FileArchiveDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
{
|
||||
int em = em_unit();
|
||||
BOOST_LOG_TRIVIAL(error) << "on_dpi_changed";
|
||||
//msw_buttons_rescale(this, em, { wxID_CANCEL, m_save_btn_id, m_move_btn_id, m_continue_btn_id });
|
||||
//for (auto btn : { m_save_btn, m_transfer_btn, m_discard_btn })
|
||||
// if (btn) btn->msw_rescale();
|
||||
|
||||
const wxSize& size = wxSize(45 * em, 40 * em);
|
||||
SetSize(size);
|
||||
//m_tree->Rescale(em);
|
||||
|
||||
Fit();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void FileArchiveDialog::on_open_button()
|
||||
{
|
||||
wxDataViewItemArray top_items;
|
||||
m_avc->get_model()->GetChildren(wxDataViewItem(nullptr), top_items);
|
||||
|
||||
std::function<void(ArchiveViewNode*)> deep_fill = [&paths = m_selected_paths_w_size, &deep_fill](ArchiveViewNode* node){
|
||||
if (node == nullptr)
|
||||
return;
|
||||
if (node->get_children().empty()) {
|
||||
if (node->get_toggle())
|
||||
paths.emplace_back(node->get_fullpath(), node->get_size());
|
||||
} else {
|
||||
for (std::shared_ptr<ArchiveViewNode> child : node->get_children())
|
||||
deep_fill(child.get());
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& item : top_items)
|
||||
{
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
||||
deep_fill(node);
|
||||
}
|
||||
this->EndModal(wxID_OK);
|
||||
}
|
||||
|
||||
void FileArchiveDialog::on_all_button()
|
||||
{
|
||||
|
||||
wxDataViewItemArray top_items;
|
||||
m_avc->get_model()->GetChildren(wxDataViewItem(nullptr), top_items);
|
||||
|
||||
std::function<void(ArchiveViewNode*)> deep_fill = [&deep_fill](ArchiveViewNode* node) {
|
||||
if (node == nullptr)
|
||||
return;
|
||||
node->set_toggle(true);
|
||||
if (!node->get_children().empty()) {
|
||||
for (std::shared_ptr<ArchiveViewNode> child : node->get_children())
|
||||
deep_fill(child.get());
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& item : top_items)
|
||||
{
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
||||
deep_fill(node);
|
||||
// Fix for linux, where Refresh or Update wont help to redraw toggle checkboxes.
|
||||
// It should be enough to call ValueChanged for top items.
|
||||
m_avc->get_model()->ValueChanged(item, 0);
|
||||
}
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void FileArchiveDialog::on_none_button()
|
||||
{
|
||||
wxDataViewItemArray top_items;
|
||||
m_avc->get_model()->GetChildren(wxDataViewItem(nullptr), top_items);
|
||||
|
||||
std::function<void(ArchiveViewNode*)> deep_fill = [&deep_fill](ArchiveViewNode* node) {
|
||||
if (node == nullptr)
|
||||
return;
|
||||
node->set_toggle(false);
|
||||
if (!node->get_children().empty()) {
|
||||
for (std::shared_ptr<ArchiveViewNode> child : node->get_children())
|
||||
deep_fill(child.get());
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& item : top_items)
|
||||
{
|
||||
ArchiveViewNode* node = static_cast<ArchiveViewNode*>(item.GetID());
|
||||
deep_fill(node);
|
||||
// Fix for linux, where Refresh or Update wont help to redraw toggle checkboxes.
|
||||
// It should be enough to call ValueChanged for top items.
|
||||
m_avc->get_model()->ValueChanged(item, 0);
|
||||
}
|
||||
|
||||
this->Refresh();
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
123
src/slic3r/GUI/FileArchiveDialog.hpp
Normal file
123
src/slic3r/GUI/FileArchiveDialog.hpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#ifndef slic3r_GUI_FileArchiveDialog_hpp_
|
||||
#define slic3r_GUI_FileArchiveDialog_hpp_
|
||||
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "libslic3r/miniz_extension.hpp"
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <wx/wx.h>
|
||||
#include <wx/dataview.h>
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class ArchiveViewCtrl;
|
||||
|
||||
class ArchiveViewNode
|
||||
{
|
||||
public:
|
||||
ArchiveViewNode(const wxString& name) : m_name(name) {}
|
||||
|
||||
std::vector<std::shared_ptr<ArchiveViewNode>>& get_children() { return m_children; }
|
||||
void set_parent(std::shared_ptr<ArchiveViewNode> parent) { m_parent = parent; }
|
||||
// On Linux, get_parent cannot just return size of m_children. ItemAdded would than crash.
|
||||
std::shared_ptr<ArchiveViewNode> get_parent() const { return m_parent; }
|
||||
bool is_container() const { return m_container; }
|
||||
void set_container(bool is_container) { m_container = is_container; }
|
||||
wxString get_name() const { return m_name; }
|
||||
void set_name(const wxString& name) { m_name = name; }
|
||||
bool get_toggle() const { return m_toggle; }
|
||||
void set_toggle(bool toggle) { m_toggle = toggle; }
|
||||
bool get_is_folder() const { return m_folder; }
|
||||
void set_is_folder(bool is_folder) { m_folder = is_folder; }
|
||||
void set_fullpath(boost::filesystem::path path) { m_fullpath = path; }
|
||||
boost::filesystem::path get_fullpath() const { return m_fullpath; }
|
||||
void set_size(size_t size) { m_size = size; }
|
||||
size_t get_size() const { return m_size; }
|
||||
|
||||
private:
|
||||
wxString m_name;
|
||||
std::shared_ptr<ArchiveViewNode> m_parent { nullptr };
|
||||
std::vector<std::shared_ptr<ArchiveViewNode>> m_children;
|
||||
|
||||
bool m_toggle { false };
|
||||
bool m_folder { false };
|
||||
boost::filesystem::path m_fullpath;
|
||||
bool m_container { false };
|
||||
size_t m_size { 0 };
|
||||
};
|
||||
|
||||
class ArchiveViewModel : public wxDataViewModel
|
||||
{
|
||||
public:
|
||||
ArchiveViewModel(wxWindow* parent);
|
||||
~ArchiveViewModel();
|
||||
|
||||
/* wxDataViewItem AddFolder(wxDataViewItem& parent, wxString name);
|
||||
wxDataViewItem AddFile(wxDataViewItem& parent, wxString name);*/
|
||||
|
||||
std::shared_ptr<ArchiveViewNode> AddFile(std::shared_ptr<ArchiveViewNode> parent,const wxString& name, bool container);
|
||||
|
||||
wxString GetColumnType(unsigned int col) const override;
|
||||
unsigned int GetColumnCount() const override { return 2; }
|
||||
|
||||
void Rescale();
|
||||
void Delete(const wxDataViewItem& item);
|
||||
void Clear();
|
||||
|
||||
wxDataViewItem GetParent(const wxDataViewItem& item) const override;
|
||||
unsigned int GetChildren(const wxDataViewItem& parent, wxDataViewItemArray& array) const override;
|
||||
|
||||
void SetAssociatedControl(ArchiveViewCtrl* ctrl) { m_ctrl = ctrl; }
|
||||
|
||||
void GetValue(wxVariant& variant, const wxDataViewItem& item, unsigned int col) const override;
|
||||
bool SetValue(const wxVariant& variant, const wxDataViewItem& item, unsigned int col) override;
|
||||
|
||||
void untoggle_folders(const wxDataViewItem& item);
|
||||
|
||||
bool IsEnabled(const wxDataViewItem& item, unsigned int col) const override;
|
||||
bool IsContainer(const wxDataViewItem& item) const override;
|
||||
// Is the container just a header or an item with all columns
|
||||
// In our case it is an item with all columns
|
||||
bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; }
|
||||
|
||||
protected:
|
||||
wxWindow* m_parent { nullptr };
|
||||
ArchiveViewCtrl* m_ctrl { nullptr };
|
||||
std::vector<std::shared_ptr<ArchiveViewNode>> m_top_children;
|
||||
};
|
||||
|
||||
class ArchiveViewCtrl : public wxDataViewCtrl
|
||||
{
|
||||
public:
|
||||
ArchiveViewCtrl(wxWindow* parent, wxSize size);
|
||||
~ArchiveViewCtrl();
|
||||
|
||||
ArchiveViewModel* get_model() const {return m_model; }
|
||||
protected:
|
||||
ArchiveViewModel* m_model;
|
||||
};
|
||||
|
||||
|
||||
class FileArchiveDialog : public DPIDialog
|
||||
{
|
||||
public:
|
||||
FileArchiveDialog(wxWindow* parent_window, mz_zip_archive* archive, std::vector<std::pair<boost::filesystem::path, size_t>>& selected_paths_w_size);
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
|
||||
void on_open_button();
|
||||
void on_all_button();
|
||||
void on_none_button();
|
||||
|
||||
// chosen files are written into this vector and returned to caller via reference.
|
||||
// path in archive and decompressed size. The size can be used to distinguish between files with same path.
|
||||
std::vector<std::pair<boost::filesystem::path,size_t>>& m_selected_paths_w_size;
|
||||
ArchiveViewCtrl* m_avc;
|
||||
};
|
||||
|
||||
} // namespace GU
|
||||
} // namespace Slic3r
|
||||
#endif // slic3r_GUI_FileArchiveDialog_hpp_
|
||||
977
src/slic3r/GUI/FirmwareDialog.cpp
Normal file
977
src/slic3r/GUI/FirmwareDialog.cpp
Normal file
@@ -0,0 +1,977 @@
|
||||
#include <numeric>
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
#include <stdexcept>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#if _WIN32
|
||||
#include <regex>
|
||||
#endif
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "avrdude/avrdude-slic3r.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "../Utils/HexFile.hpp"
|
||||
#include "../Utils/Serial.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
// wx includes need to come after asio because of the WinSock.h problem
|
||||
#include "FirmwareDialog.hpp"
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/settings.h>
|
||||
#include <wx/timer.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/filepicker.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/combobox.h>
|
||||
#include <wx/gauge.h>
|
||||
#include <wx/collpane.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/filefn.h>
|
||||
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace asio = boost::asio;
|
||||
using boost::system::error_code;
|
||||
using boost::optional;
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
using Utils::HexFile;
|
||||
using Utils::SerialPortInfo;
|
||||
using Utils::Serial;
|
||||
|
||||
|
||||
// USB IDs used to perform device lookup
|
||||
enum {
|
||||
USB_VID_PRUSA = 0x2c99,
|
||||
USB_PID_MK2 = 1,
|
||||
USB_PID_MK3 = 2,
|
||||
USB_PID_MMU_BOOT = 3,
|
||||
USB_PID_MMU_APP = 4,
|
||||
USB_PID_CW1_BOOT = 7,
|
||||
USB_PID_CW1_APP = 8,
|
||||
USB_PID_CW1S_BOOT = 14,
|
||||
USB_PID_CW1S_APP = 15,
|
||||
};
|
||||
|
||||
// This enum discriminates the kind of information in EVT_AVRDUDE,
|
||||
// it's stored in the ExtraLong field of wxCommandEvent.
|
||||
enum AvrdudeEvent
|
||||
{
|
||||
AE_MESSAGE,
|
||||
AE_PROGRESS,
|
||||
AE_STATUS,
|
||||
AE_EXIT,
|
||||
};
|
||||
|
||||
wxDECLARE_EVENT(EVT_AVRDUDE, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_AVRDUDE, wxCommandEvent);
|
||||
|
||||
wxDECLARE_EVENT(EVT_ASYNC_DIALOG, wxCommandEvent);
|
||||
wxDEFINE_EVENT(EVT_ASYNC_DIALOG, wxCommandEvent);
|
||||
|
||||
struct Avr109Pid
|
||||
{
|
||||
unsigned boot;
|
||||
unsigned app;
|
||||
|
||||
Avr109Pid(unsigned boot, unsigned app) : boot(boot), app(app) {}
|
||||
};
|
||||
|
||||
// Private
|
||||
|
||||
struct FirmwareDialog::priv
|
||||
{
|
||||
enum AvrDudeComplete
|
||||
{
|
||||
AC_NONE,
|
||||
AC_SUCCESS,
|
||||
AC_FAILURE,
|
||||
AC_USER_CANCELLED,
|
||||
};
|
||||
|
||||
FirmwareDialog *q; // PIMPL back pointer ("Q-Pointer")
|
||||
|
||||
// GUI elements
|
||||
wxComboBox *port_picker;
|
||||
wxStaticText *txt_port_autodetect;
|
||||
wxFilePickerCtrl *hex_picker;
|
||||
wxStaticText *txt_status;
|
||||
wxGauge *progressbar;
|
||||
wxCollapsiblePane *spoiler;
|
||||
wxTextCtrl *txt_stdout;
|
||||
wxButton *btn_rescan;
|
||||
wxButton *btn_close;
|
||||
wxButton *btn_flash;
|
||||
wxString btn_flash_label_ready;
|
||||
wxString btn_flash_label_flashing;
|
||||
wxString label_status_flashing;
|
||||
|
||||
wxTimer timer_pulse;
|
||||
|
||||
int min_width;
|
||||
int min_height;
|
||||
int min_height_expanded;
|
||||
|
||||
// Async modal dialog during flashing
|
||||
std::mutex mutex;
|
||||
int modal_response;
|
||||
std::condition_variable response_cv;
|
||||
|
||||
// Data
|
||||
std::vector<SerialPortInfo> ports;
|
||||
optional<SerialPortInfo> port;
|
||||
bool port_autodetect;
|
||||
HexFile hex_file;
|
||||
|
||||
// This is a shared pointer holding the background AvrDude task
|
||||
// also serves as a status indication (it is set _iff_ the background task is running, otherwise it is reset).
|
||||
AvrDude::Ptr avrdude;
|
||||
unsigned progress_tasks_done;
|
||||
unsigned progress_tasks_bar;
|
||||
bool user_cancelled;
|
||||
const bool extra_verbose; // For debugging
|
||||
|
||||
priv(FirmwareDialog *q) :
|
||||
q(q),
|
||||
btn_flash_label_ready(_(L("Flash!"))),
|
||||
btn_flash_label_flashing(_(L("Cancel"))),
|
||||
label_status_flashing(_(L("Flashing in progress. Please do not disconnect the printer!"))),
|
||||
timer_pulse(q),
|
||||
port_autodetect(false),
|
||||
progress_tasks_done(0),
|
||||
progress_tasks_bar(0),
|
||||
user_cancelled(false),
|
||||
extra_verbose(false)
|
||||
{}
|
||||
|
||||
void find_serial_ports();
|
||||
void fit_no_shrink();
|
||||
void set_txt_status(const wxString &label);
|
||||
void flashing_start(unsigned tasks);
|
||||
void flashing_done(AvrDudeComplete complete);
|
||||
void set_autodetect(bool autodetect);
|
||||
void update_flash_enabled();
|
||||
void load_hex_file(const wxString &path);
|
||||
void queue_event(AvrdudeEvent aevt, wxString message);
|
||||
|
||||
bool ask_model_id_mismatch(const std::string &printer_model);
|
||||
bool check_model_id();
|
||||
void avr109_wait_for_bootloader(Avr109Pid usb_pid, unsigned retries);
|
||||
void avr109_reboot(const SerialPortInfo &port);
|
||||
void avr109_lookup_port(Avr109Pid usb_pid);
|
||||
void prepare_common();
|
||||
void prepare_mk2();
|
||||
void prepare_mk3();
|
||||
void prepare_avr109(Avr109Pid usb_pid);
|
||||
bool get_serial_port();
|
||||
void perform_upload();
|
||||
|
||||
void user_cancel();
|
||||
void on_avrdude(const wxCommandEvent &evt);
|
||||
void on_async_dialog(const wxCommandEvent &evt);
|
||||
void ensure_joined();
|
||||
|
||||
void queue_status(wxString message) { queue_event(AE_STATUS, std::move(message)); }
|
||||
|
||||
template<class ...Args> void queue_message(const wxString &format, Args... args) {
|
||||
auto message = wxString::Format(format, args...);
|
||||
BOOST_LOG_TRIVIAL(info) << message;
|
||||
message.Append('\n');
|
||||
queue_event(AE_MESSAGE, std::move(message));
|
||||
}
|
||||
|
||||
template<class ...Args> void queue_error(const wxString &format, Args... args) {
|
||||
queue_message(format, args...);
|
||||
queue_event(AE_STATUS, _(L("Flashing failed")) +": "+ wxString::Format(format, args...));
|
||||
avrdude->cancel();
|
||||
}
|
||||
|
||||
static const char* avr109_dev_name(Avr109Pid usb_pid);
|
||||
};
|
||||
|
||||
void FirmwareDialog::priv::find_serial_ports()
|
||||
{
|
||||
auto new_ports = Utils::scan_serial_ports_extended();
|
||||
if (new_ports != this->ports) {
|
||||
this->ports = new_ports;
|
||||
port_picker->Clear();
|
||||
for (const auto &port : this->ports)
|
||||
port_picker->Append(wxString::FromUTF8(port.friendly_name.data()));
|
||||
if (ports.size() > 0) {
|
||||
int idx = port_picker->GetValue().IsEmpty() ? 0 : -1;
|
||||
for (int i = 0; i < (int)this->ports.size(); ++ i)
|
||||
if (this->ports[i].is_printer) {
|
||||
idx = i;
|
||||
break;
|
||||
}
|
||||
if (idx != -1) {
|
||||
port_picker->SetSelection(idx);
|
||||
update_flash_enabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::fit_no_shrink()
|
||||
{
|
||||
// Ensure content fits into window and window is not shrinked
|
||||
const auto old_size = q->GetSize();
|
||||
q->Layout();
|
||||
q->Fit();
|
||||
const auto new_size = q->GetSize();
|
||||
const auto new_width = std::max(old_size.GetWidth(), new_size.GetWidth());
|
||||
const auto new_height = std::max(old_size.GetHeight(), new_size.GetHeight());
|
||||
q->SetSize(new_width, new_height);
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::set_txt_status(const wxString &label)
|
||||
{
|
||||
const auto width = txt_status->GetSize().GetWidth();
|
||||
txt_status->SetLabel(label);
|
||||
txt_status->Wrap(width);
|
||||
|
||||
fit_no_shrink();
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::flashing_start(unsigned tasks)
|
||||
{
|
||||
modal_response = wxID_NONE;
|
||||
txt_stdout->Clear();
|
||||
set_txt_status(label_status_flashing);
|
||||
txt_status->SetForegroundColour(GUI::wxGetApp().get_label_clr_modified());
|
||||
port_picker->Disable();
|
||||
btn_rescan->Disable();
|
||||
hex_picker->Disable();
|
||||
btn_close->Disable();
|
||||
btn_flash->SetLabel(btn_flash_label_flashing);
|
||||
progressbar->SetRange(200 * tasks); // See progress callback below
|
||||
progressbar->SetValue(0);
|
||||
progress_tasks_done = 0;
|
||||
progress_tasks_bar = 0;
|
||||
user_cancelled = false;
|
||||
timer_pulse.Start(50);
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::flashing_done(AvrDudeComplete complete)
|
||||
{
|
||||
port_picker->Enable();
|
||||
btn_rescan->Enable();
|
||||
hex_picker->Enable();
|
||||
btn_close->Enable();
|
||||
btn_flash->SetLabel(btn_flash_label_ready);
|
||||
txt_status->SetForegroundColour(GUI::wxGetApp().get_label_clr_default());
|
||||
timer_pulse.Stop();
|
||||
progressbar->SetValue(progressbar->GetRange());
|
||||
|
||||
switch (complete) {
|
||||
case AC_SUCCESS: set_txt_status(_(L("Flashing succeeded!"))); break;
|
||||
case AC_FAILURE: set_txt_status(_(L("Flashing failed. Please see the avrdude log below."))); break;
|
||||
case AC_USER_CANCELLED: set_txt_status(_(L("Flashing cancelled."))); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::set_autodetect(bool autodetect)
|
||||
{
|
||||
port_autodetect = autodetect;
|
||||
|
||||
port_picker->Show(!autodetect);
|
||||
btn_rescan->Show(!autodetect);
|
||||
txt_port_autodetect->Show(autodetect);
|
||||
q->Layout();
|
||||
fit_no_shrink();
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::update_flash_enabled()
|
||||
{
|
||||
const bool hex_exists = wxFileExists(hex_picker->GetPath());
|
||||
const bool port_valid = port_autodetect || get_serial_port();
|
||||
|
||||
btn_flash->Enable(hex_exists && port_valid);
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::load_hex_file(const wxString &path)
|
||||
{
|
||||
hex_file = HexFile(path.wx_str());
|
||||
const bool autodetect = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1 || hex_file.device == HexFile::DEV_CW1S;
|
||||
set_autodetect(autodetect);
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::queue_event(AvrdudeEvent aevt, wxString message)
|
||||
{
|
||||
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
|
||||
evt->SetExtraLong(aevt);
|
||||
evt->SetString(std::move(message));
|
||||
wxQueueEvent(this->q, evt);
|
||||
}
|
||||
|
||||
bool FirmwareDialog::priv::ask_model_id_mismatch(const std::string &printer_model)
|
||||
{
|
||||
// model_id in the hex file doesn't match what the printer repoted.
|
||||
// Ask the user if it should be flashed anyway.
|
||||
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
|
||||
auto evt = new wxCommandEvent(EVT_ASYNC_DIALOG, this->q->GetId());
|
||||
evt->SetString(wxString::Format(_(L(
|
||||
"This firmware hex file does not match the printer model.\n"
|
||||
"The hex file is intended for: %s\n"
|
||||
"Printer reported: %s\n\n"
|
||||
"Do you want to continue and flash this hex file anyway?\n"
|
||||
"Please only continue if you are sure this is the right thing to do.")),
|
||||
hex_file.model_id, printer_model
|
||||
));
|
||||
wxQueueEvent(this->q, evt);
|
||||
|
||||
response_cv.wait(lock, [this]() { return this->modal_response != wxID_NONE; });
|
||||
|
||||
if (modal_response == wxID_YES) {
|
||||
return true;
|
||||
} else {
|
||||
user_cancel();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool FirmwareDialog::priv::check_model_id()
|
||||
{
|
||||
// XXX: The implementation in Serial doesn't currently work reliably enough to be used.
|
||||
// Therefore, regretably, so far the check cannot be used and we just return true here.
|
||||
// TODO: Rewrite Serial using more platform-native code.
|
||||
return true;
|
||||
|
||||
// if (hex_file.model_id.empty()) {
|
||||
// // No data to check against, assume it's ok
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// asio::io_service io;
|
||||
// Serial serial(io, port->port, 115200);
|
||||
// serial.printer_setup();
|
||||
|
||||
// enum {
|
||||
// TIMEOUT = 2000,
|
||||
// RETREIES = 5,
|
||||
// };
|
||||
|
||||
// if (! serial.printer_ready_wait(RETREIES, TIMEOUT)) {
|
||||
// queue_error(wxString::Format(_(L("Could not connect to the printer at %s")), port->port));
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// std::string line;
|
||||
// error_code ec;
|
||||
// serial.printer_write_line("PRUSA Rev");
|
||||
// while (serial.read_line(TIMEOUT, line, ec)) {
|
||||
// if (ec) {
|
||||
// queue_error(wxString::Format(_(L("Could not connect to the printer at %s")), port->port));
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// if (line == "ok") { continue; }
|
||||
|
||||
// if (line == hex_file.model_id) {
|
||||
// return true;
|
||||
// } else {
|
||||
// return ask_model_id_mismatch(line);
|
||||
// }
|
||||
|
||||
// line.clear();
|
||||
// }
|
||||
|
||||
// return false;
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::avr109_wait_for_bootloader(Avr109Pid usb_pid, unsigned retries)
|
||||
{
|
||||
enum {
|
||||
SLEEP_MS = 500,
|
||||
};
|
||||
|
||||
for (unsigned i = 0; i < retries && !user_cancelled; i++) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(SLEEP_MS));
|
||||
|
||||
auto ports = Utils::scan_serial_ports_extended();
|
||||
ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) {
|
||||
return port.id_vendor != USB_VID_PRUSA || port.id_product != usb_pid.boot;
|
||||
}), ports.end());
|
||||
|
||||
if (ports.size() == 1) {
|
||||
port = ports[0];
|
||||
return;
|
||||
} else if (ports.size() > 1) {
|
||||
queue_message("Several VID/PID 0x2c99/%u devices found", usb_pid.boot);
|
||||
queue_error(_(L("Multiple %s devices found. Please only connect one at a time for flashing.")), avr109_dev_name(usb_pid));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::avr109_reboot(const SerialPortInfo &port)
|
||||
{
|
||||
asio::io_service io;
|
||||
Serial serial(io, port.port, 1200);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(50));
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::avr109_lookup_port(Avr109Pid usb_pid)
|
||||
{
|
||||
const char *dev_name = avr109_dev_name(usb_pid);
|
||||
const wxString msg_not_found = wxString::Format(
|
||||
_(L("The %s device was not found.\n"
|
||||
"If the device is connected, please press the Reset button next to the USB connector ...")),
|
||||
dev_name);
|
||||
|
||||
queue_message("Flashing %s, looking for VID/PID 0x2c99/%u or 0x2c99/%u ...", dev_name, usb_pid.boot, usb_pid.app);
|
||||
|
||||
auto ports = Utils::scan_serial_ports_extended();
|
||||
ports.erase(std::remove_if(ports.begin(), ports.end(), [=](const SerialPortInfo &port ) {
|
||||
return port.id_vendor != USB_VID_PRUSA ||
|
||||
(port.id_product != usb_pid.boot && port.id_product != usb_pid.app);
|
||||
}), ports.end());
|
||||
|
||||
if (ports.size() == 0) {
|
||||
queue_message("The %s device was not found.", dev_name);
|
||||
queue_status(msg_not_found);
|
||||
avr109_wait_for_bootloader(usb_pid, 30);
|
||||
} else if (ports.size() > 1) {
|
||||
queue_message("Several VID/PID 0x2c99/%u devices found", usb_pid.boot);
|
||||
queue_error(_(L("Multiple %s devices found. Please only connect one at a time for flashing.")), dev_name);
|
||||
} else {
|
||||
if (ports[0].id_product == usb_pid.app) {
|
||||
// The device needs to be rebooted into the bootloader mode
|
||||
queue_message("Found VID/PID 0x2c99/%u at `%s`, rebooting the device ...", usb_pid.app, ports[0].port);
|
||||
avr109_reboot(ports[0]);
|
||||
avr109_wait_for_bootloader(usb_pid, 10);
|
||||
|
||||
if (! port) {
|
||||
// The device in bootloader mode was not found, inform the user and wait some more...
|
||||
queue_message("%s device not found after reboot", dev_name);
|
||||
queue_status(msg_not_found);
|
||||
avr109_wait_for_bootloader(usb_pid, 30);
|
||||
}
|
||||
} else {
|
||||
port = ports[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::prepare_common()
|
||||
{
|
||||
std::vector<std::string> args {{
|
||||
extra_verbose ? "-vvvvv" : "-v",
|
||||
"-p", "atmega2560",
|
||||
// Using the "Wiring" mode to program Rambo or Einsy, using the STK500v2 protocol (not the STK500).
|
||||
// The QIDI's avrdude is patched to never send semicolons inside the data packets, as the USB to serial chip
|
||||
// is flashed with a buggy firmware.
|
||||
"-c", "wiring",
|
||||
"-P", port->port,
|
||||
"-b", "115200", // TODO: Allow other rates? Ditto elsewhere.
|
||||
"-D",
|
||||
"-U", (boost::format("flash:w:0:%1%:i") % hex_file.path.string()).str(),
|
||||
}};
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Preparing arguments avrdude: "
|
||||
<< std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) {
|
||||
return a + ' ' + b;
|
||||
});
|
||||
|
||||
avrdude->push_args(std::move(args));
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::prepare_mk2()
|
||||
{
|
||||
if (! port) { return; }
|
||||
|
||||
if (! check_model_id()) {
|
||||
avrdude->cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
prepare_common();
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::prepare_mk3()
|
||||
{
|
||||
if (! port) { return; }
|
||||
|
||||
if (! check_model_id()) {
|
||||
avrdude->cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
prepare_common();
|
||||
|
||||
// The hex file also contains another section with l10n data to be flashed into the external flash on MK3 (Einsy)
|
||||
// This is done via another avrdude invocation, here we build arg list for that:
|
||||
std::vector<std::string> args {{
|
||||
extra_verbose ? "-vvvvv" : "-v",
|
||||
"-p", "atmega2560",
|
||||
// Using the "Arduino" mode to program Einsy's external flash with languages, using the STK500 protocol (not the STK500v2).
|
||||
// The QIDI's avrdude is patched again to never send semicolons inside the data packets.
|
||||
"-c", "arduino",
|
||||
"-P", port->port,
|
||||
"-b", "115200",
|
||||
"-D",
|
||||
"-u", // disable safe mode
|
||||
"-U", (boost::format("flash:w:1:%1%:i") % hex_file.path.string()).str(),
|
||||
}};
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Preparing avrdude arguments for external flash flashing: "
|
||||
<< std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) {
|
||||
return a + ' ' + b;
|
||||
});
|
||||
|
||||
avrdude->push_args(std::move(args));
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::prepare_avr109(Avr109Pid usb_pid)
|
||||
{
|
||||
port = boost::none;
|
||||
avr109_lookup_port(usb_pid);
|
||||
if (! port) {
|
||||
queue_error(_(L("The %s device could not have been found")), avr109_dev_name(usb_pid));
|
||||
return;
|
||||
}
|
||||
|
||||
queue_message("Found VID/PID 0x2c99/%u at `%s`, flashing ...", usb_pid.boot, port->port);
|
||||
queue_status(label_status_flashing);
|
||||
|
||||
std::vector<std::string> args {{
|
||||
extra_verbose ? "-vvvvv" : "-v",
|
||||
"-p", "atmega32u4",
|
||||
"-c", "avr109",
|
||||
"-P", port->port,
|
||||
"-b", "57600",
|
||||
"-D",
|
||||
"-U", (boost::format("flash:w:0:%1%:i") % hex_file.path.string()).str(),
|
||||
}};
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Preparing avrdude arguments: "
|
||||
<< std::accumulate(std::next(args.begin()), args.end(), args[0], [](std::string a, const std::string &b) {
|
||||
return a + ' ' + b;
|
||||
});
|
||||
|
||||
avrdude->push_args(std::move(args));
|
||||
}
|
||||
|
||||
|
||||
bool FirmwareDialog::priv::get_serial_port()
|
||||
{
|
||||
const int selection = port_picker->GetSelection();
|
||||
if (selection != wxNOT_FOUND) {
|
||||
port = this->ports[selection];
|
||||
} else {
|
||||
// User has supplied a custom filename
|
||||
|
||||
std::string path_u8 = GUI::into_u8(port_picker->GetValue());
|
||||
#ifdef _WIN32
|
||||
static const std::regex com_pattern("COM[0-9]+", std::regex::icase);
|
||||
std::smatch matches;
|
||||
if (std::regex_match(path_u8, matches, com_pattern)) {
|
||||
#else
|
||||
if (fs::is_other(fs::path(path_u8))) {
|
||||
#endif
|
||||
port = SerialPortInfo(std::move(path_u8));
|
||||
} else {
|
||||
port = boost::none;
|
||||
}
|
||||
}
|
||||
|
||||
return !!port;
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::perform_upload()
|
||||
{
|
||||
auto filename = hex_picker->GetPath();
|
||||
if (filename.IsEmpty()) { return; }
|
||||
|
||||
load_hex_file(filename); // Might already be loaded, but we want to make sure it's fresh
|
||||
|
||||
if (!port_autodetect && !get_serial_port()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool extra_verbose = false; // For debugging
|
||||
|
||||
flashing_start(hex_file.device == HexFile::DEV_MK3 ? 2 : 1);
|
||||
|
||||
// Init the avrdude object
|
||||
AvrDude avrdude;
|
||||
|
||||
// It is ok here to use the q-pointer to the FirmwareDialog
|
||||
// because the dialog ensures it doesn't exit before the background thread is done.
|
||||
auto q = this->q;
|
||||
|
||||
avrdude
|
||||
.on_run([this](AvrDude::Ptr avrdude) {
|
||||
this->avrdude = std::move(avrdude);
|
||||
|
||||
try {
|
||||
switch (this->hex_file.device) {
|
||||
case HexFile::DEV_MK3:
|
||||
this->prepare_mk3();
|
||||
break;
|
||||
|
||||
case HexFile::DEV_MM_CONTROL:
|
||||
this->prepare_avr109(Avr109Pid(USB_PID_MMU_BOOT, USB_PID_MMU_APP));
|
||||
break;
|
||||
|
||||
case HexFile::DEV_CW1:
|
||||
this->prepare_avr109(Avr109Pid(USB_PID_CW1_BOOT, USB_PID_CW1_APP));
|
||||
break;
|
||||
|
||||
case HexFile::DEV_CW1S:
|
||||
this->prepare_avr109(Avr109Pid(USB_PID_CW1S_BOOT, USB_PID_CW1S_APP));
|
||||
break;
|
||||
|
||||
default:
|
||||
this->prepare_mk2();
|
||||
break;
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
if (port) {
|
||||
queue_error(_(L("Error accessing port at %s: %s")), port->port, ex.what());
|
||||
} else {
|
||||
queue_error(_(L("Error: %s")), ex.what());
|
||||
}
|
||||
}
|
||||
})
|
||||
.on_message([
|
||||
#if !defined(__APPLE__) && !defined(__clang__)
|
||||
// clang complains when capturing constants.
|
||||
extra_verbose,
|
||||
#endif // __APPLE__
|
||||
q](const char* msg, unsigned /* size */) {
|
||||
if (extra_verbose) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "avrdude: " << msg;
|
||||
}
|
||||
|
||||
auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId());
|
||||
auto wxmsg = wxString::FromUTF8(msg);
|
||||
#ifdef WIN32
|
||||
// The string might be in local encoding
|
||||
if (wxmsg.IsEmpty() && *msg != '\0') {
|
||||
wxmsg = wxString(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
evt->SetExtraLong(AE_MESSAGE);
|
||||
evt->SetString(std::move(wxmsg));
|
||||
wxQueueEvent(q, evt);
|
||||
})
|
||||
.on_progress([q](const char * /* task */, unsigned progress) {
|
||||
auto evt = new wxCommandEvent(EVT_AVRDUDE, q->GetId());
|
||||
evt->SetExtraLong(AE_PROGRESS);
|
||||
evt->SetInt(progress);
|
||||
wxQueueEvent(q, evt);
|
||||
})
|
||||
.on_complete([this]() {
|
||||
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
|
||||
evt->SetExtraLong(AE_EXIT);
|
||||
evt->SetInt(this->avrdude->exit_code());
|
||||
wxQueueEvent(this->q, evt);
|
||||
})
|
||||
.run();
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::user_cancel()
|
||||
{
|
||||
if (avrdude) {
|
||||
user_cancelled = true;
|
||||
avrdude->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::on_avrdude(const wxCommandEvent &evt)
|
||||
{
|
||||
AvrDudeComplete complete_kind;
|
||||
|
||||
switch (evt.GetExtraLong()) {
|
||||
case AE_MESSAGE:
|
||||
txt_stdout->AppendText(evt.GetString());
|
||||
break;
|
||||
|
||||
case AE_PROGRESS:
|
||||
// We try to track overall progress here.
|
||||
// Avrdude performs 3 tasks per one memory operation ("-U" arg),
|
||||
// first of which is reading of status data (very short).
|
||||
// We use the timer_pulse during the very first task to indicate intialization
|
||||
// and then display overall progress during the latter tasks.
|
||||
|
||||
if (progress_tasks_done > 0) {
|
||||
progressbar->SetValue(progress_tasks_bar + evt.GetInt());
|
||||
}
|
||||
|
||||
if (evt.GetInt() == 100) {
|
||||
timer_pulse.Stop();
|
||||
if (progress_tasks_done % 3 != 0) {
|
||||
progress_tasks_bar += 100;
|
||||
}
|
||||
progress_tasks_done++;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case AE_EXIT:
|
||||
BOOST_LOG_TRIVIAL(info) << "avrdude exit code: " << evt.GetInt();
|
||||
|
||||
// Figure out the exit state
|
||||
if (user_cancelled) { complete_kind = AC_USER_CANCELLED; }
|
||||
else if (avrdude->cancelled()) { complete_kind = AC_NONE; } // Ie. cancelled programatically
|
||||
else { complete_kind = evt.GetInt() == 0 ? AC_SUCCESS : AC_FAILURE; }
|
||||
|
||||
flashing_done(complete_kind);
|
||||
ensure_joined();
|
||||
break;
|
||||
|
||||
case AE_STATUS:
|
||||
set_txt_status(evt.GetString());
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::on_async_dialog(const wxCommandEvent &evt)
|
||||
{
|
||||
//wxMessageDialog dlg(this->q, evt.GetString(), wxMessageBoxCaptionStr, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
|
||||
GUI::MessageDialog dlg(this->q, evt.GetString(), wxMessageBoxCaptionStr, wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
modal_response = dlg.ShowModal();
|
||||
}
|
||||
response_cv.notify_all();
|
||||
}
|
||||
|
||||
void FirmwareDialog::priv::ensure_joined()
|
||||
{
|
||||
// Make sure the background thread is collected and the AvrDude object reset
|
||||
if (avrdude) { avrdude->join(); }
|
||||
avrdude.reset();
|
||||
}
|
||||
|
||||
const char* FirmwareDialog::priv::avr109_dev_name(Avr109Pid usb_pid) {
|
||||
switch (usb_pid.boot) {
|
||||
case USB_PID_MMU_BOOT:
|
||||
return "Original QIDI MMU 2.0 Control";
|
||||
case USB_PID_CW1_BOOT:
|
||||
return "Original QIDI CW1";
|
||||
case USB_PID_CW1S_BOOT:
|
||||
return "Original QIDI CW1S";
|
||||
default: throw Slic3r::RuntimeError((boost::format("Invalid avr109 device USB PID: %1%") % usb_pid.boot).str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Public
|
||||
|
||||
FirmwareDialog::FirmwareDialog(wxWindow *parent) :
|
||||
GUI::DPIDialog(parent, wxID_ANY, _(L("Firmware flasher")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
|
||||
p(new priv(this))
|
||||
{
|
||||
const int em = GUI::wxGetApp().em_unit();
|
||||
p->min_width = MIN_WIDTH * em;
|
||||
p->min_height = MIN_HEIGHT * em;
|
||||
p->min_height_expanded = MIN_HEIGHT_EXPANDED * em;
|
||||
|
||||
/* get current font from application,
|
||||
* because of wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) function
|
||||
* returns font for primary Display
|
||||
*/
|
||||
const wxFont& font = GUI::wxGetApp().normal_font();
|
||||
SetFont(font);
|
||||
wxFont status_font = font;//wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
status_font.MakeBold();
|
||||
wxFont mono_font = GUI::wxGetApp().code_font();
|
||||
mono_font.MakeSmaller();
|
||||
|
||||
// Create GUI components and layout
|
||||
|
||||
auto *panel = new wxPanel(this);
|
||||
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
|
||||
panel->SetSizer(vsizer);
|
||||
|
||||
auto *label_hex_picker = new wxStaticText(panel, wxID_ANY, _(L("Firmware image:")));
|
||||
p->hex_picker = new wxFilePickerCtrl(panel, wxID_ANY, wxEmptyString, /*wxFileSelectorPromptStr*/_L("Select a file"),
|
||||
"Hex files (*.hex)|*.hex|All files|*.*");
|
||||
p->hex_picker->GetPickerCtrl()->SetLabelText(_(L("Browse")));
|
||||
|
||||
auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:")));
|
||||
p->port_picker = new wxComboBox(panel, wxID_ANY);
|
||||
p->txt_port_autodetect = new wxStaticText(panel, wxID_ANY, _(L("Autodetected")));
|
||||
p->btn_rescan = new wxButton(panel, wxID_ANY, _(L("Rescan")));
|
||||
auto *port_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
port_sizer->Add(p->port_picker, 1, wxEXPAND | wxRIGHT, SPACING);
|
||||
port_sizer->Add(p->btn_rescan, 0);
|
||||
port_sizer->Add(p->txt_port_autodetect, 1, wxEXPAND);
|
||||
p->set_autodetect(false);
|
||||
|
||||
auto *label_progress = new wxStaticText(panel, wxID_ANY, _(L("Progress:")));
|
||||
p->progressbar = new wxGauge(panel, wxID_ANY, 1, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL | wxGA_SMOOTH);
|
||||
|
||||
auto *label_status = new wxStaticText(panel, wxID_ANY, _(L("Status:")));
|
||||
p->txt_status = new wxStaticText(panel, wxID_ANY, _(L("Ready")));
|
||||
p->txt_status->SetFont(status_font);
|
||||
|
||||
auto *grid = new wxFlexGridSizer(2, SPACING, SPACING);
|
||||
grid->AddGrowableCol(1);
|
||||
|
||||
grid->Add(label_hex_picker, 0, wxALIGN_CENTER_VERTICAL);
|
||||
grid->Add(p->hex_picker, 0, wxEXPAND);
|
||||
|
||||
grid->Add(label_port_picker, 0, wxALIGN_CENTER_VERTICAL);
|
||||
grid->Add(port_sizer, 0, wxEXPAND);
|
||||
|
||||
grid->Add(label_progress, 0, wxALIGN_CENTER_VERTICAL);
|
||||
grid->Add(p->progressbar, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
grid->Add(label_status, 0, wxALIGN_CENTER_VERTICAL);
|
||||
grid->Add(p->txt_status, 0, wxEXPAND);
|
||||
|
||||
vsizer->Add(grid, 0, wxEXPAND | wxTOP | wxBOTTOM, SPACING);
|
||||
|
||||
p->spoiler = new wxCollapsiblePane(panel, wxID_ANY, _(L("Advanced: Output log")), wxDefaultPosition, wxDefaultSize, wxCP_DEFAULT_STYLE | wxCP_NO_TLW_RESIZE);
|
||||
auto *spoiler_pane = p->spoiler->GetPane();
|
||||
auto *spoiler_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
p->txt_stdout = new wxTextCtrl(spoiler_pane, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY);
|
||||
p->txt_stdout->SetFont(mono_font);
|
||||
spoiler_sizer->Add(p->txt_stdout, 1, wxEXPAND);
|
||||
spoiler_pane->SetSizer(spoiler_sizer);
|
||||
// The doc says proportion need to be 0 for wxCollapsiblePane.
|
||||
// Experience says it needs to be 1, otherwise things won't get sized properly.
|
||||
vsizer->Add(p->spoiler, 1, wxEXPAND | wxBOTTOM, SPACING);
|
||||
|
||||
p->btn_close = new wxButton(panel, wxID_CLOSE, _(L("Close"))); // Note: The label needs to be present, otherwise we get accelerator bugs on Mac
|
||||
p->btn_flash = new wxButton(panel, wxID_ANY, p->btn_flash_label_ready);
|
||||
p->btn_flash->Disable();
|
||||
auto *bsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
bsizer->Add(p->btn_close);
|
||||
bsizer->AddStretchSpacer();
|
||||
bsizer->Add(p->btn_flash);
|
||||
vsizer->Add(bsizer, 0, wxEXPAND);
|
||||
|
||||
GUI::wxGetApp().UpdateDlgDarkUI(this);
|
||||
|
||||
auto *topsizer = new wxBoxSizer(wxVERTICAL);
|
||||
topsizer->Add(panel, 1, wxEXPAND | wxALL, DIALOG_MARGIN);
|
||||
SetMinSize(wxSize(p->min_width, p->min_height));
|
||||
SetSizerAndFit(topsizer);
|
||||
const auto size = GetSize();
|
||||
SetSize(std::max(size.GetWidth(), static_cast<int>(p->min_width)), std::max(size.GetHeight(), static_cast<int>(p->min_height)));
|
||||
Layout();
|
||||
|
||||
SetEscapeId(wxID_CLOSE); // To close the dialog using "Esc" button
|
||||
|
||||
// Bind events
|
||||
|
||||
p->hex_picker->Bind(wxEVT_FILEPICKER_CHANGED, [this](wxFileDirPickerEvent& evt) {
|
||||
if (wxFileExists(evt.GetPath())) {
|
||||
this->p->load_hex_file(evt.GetPath());
|
||||
}
|
||||
p->update_flash_enabled();
|
||||
});
|
||||
|
||||
p->port_picker->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) { p->update_flash_enabled(); });
|
||||
p->port_picker->Bind(wxEVT_TEXT, [this](wxCommandEvent &) { p->update_flash_enabled(); });
|
||||
|
||||
p->spoiler->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, [=](wxCollapsiblePaneEvent &evt) {
|
||||
if (evt.GetCollapsed()) {
|
||||
this->SetMinSize(wxSize(p->min_width, p->min_height));
|
||||
const auto new_height = this->GetSize().GetHeight() - this->p->txt_stdout->GetSize().GetHeight();
|
||||
this->SetSize(this->GetSize().GetWidth(), new_height);
|
||||
} else {
|
||||
this->SetMinSize(wxSize(p->min_width, p->min_height_expanded));
|
||||
}
|
||||
|
||||
this->Layout();
|
||||
this->p->fit_no_shrink();
|
||||
});
|
||||
|
||||
p->btn_rescan->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { this->p->find_serial_ports(); });
|
||||
|
||||
p->btn_flash->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) {
|
||||
if (this->p->avrdude) {
|
||||
// Flashing is in progress, ask the user if they're really sure about canceling it
|
||||
//wxMessageDialog dlg(this,
|
||||
GUI::MessageDialog dlg(this,
|
||||
_(L("Are you sure you want to cancel firmware flashing?\nThis could leave your printer in an unusable state!")),
|
||||
_(L("Confirmation")),
|
||||
wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION);
|
||||
if (dlg.ShowModal() == wxID_YES) {
|
||||
this->p->set_txt_status(_(L("Cancelling...")));
|
||||
this->p->user_cancel();
|
||||
}
|
||||
} else {
|
||||
// Start a flashing task
|
||||
this->p->perform_upload();
|
||||
}
|
||||
});
|
||||
|
||||
Bind(wxEVT_TIMER, [this](wxTimerEvent &evt) { this->p->progressbar->Pulse(); });
|
||||
|
||||
Bind(EVT_AVRDUDE, [this](wxCommandEvent &evt) { this->p->on_avrdude(evt); });
|
||||
Bind(EVT_ASYNC_DIALOG, [this](wxCommandEvent &evt) { this->p->on_async_dialog(evt); });
|
||||
|
||||
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt) {
|
||||
if (this->p->avrdude) {
|
||||
evt.Veto();
|
||||
} else {
|
||||
this->EndModal(wxID_CLOSE);
|
||||
evt.Skip();
|
||||
}
|
||||
});
|
||||
|
||||
p->find_serial_ports();
|
||||
}
|
||||
|
||||
FirmwareDialog::~FirmwareDialog()
|
||||
{
|
||||
// Needed bacuse of forward defs
|
||||
}
|
||||
|
||||
void FirmwareDialog::run(wxWindow *parent)
|
||||
{
|
||||
FirmwareDialog dialog(parent);
|
||||
dialog.ShowModal();
|
||||
}
|
||||
|
||||
void FirmwareDialog::on_dpi_changed(const wxRect &suggested_rect)
|
||||
{
|
||||
const int& em = em_unit();
|
||||
|
||||
msw_buttons_rescale(this, em, { p->btn_close->GetId(),
|
||||
p->btn_rescan->GetId(),
|
||||
p->btn_flash->GetId(),
|
||||
p->hex_picker->GetPickerCtrl()->GetId()
|
||||
});
|
||||
|
||||
p->min_width = MIN_WIDTH * em;
|
||||
p->min_height = MIN_HEIGHT * em;
|
||||
p->min_height_expanded = MIN_HEIGHT_EXPANDED * em;
|
||||
|
||||
const int min_height = p->spoiler->IsExpanded() ? p->min_height_expanded : p->min_height;
|
||||
SetMinSize(wxSize(p->min_width, min_height));
|
||||
Fit();
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
}
|
||||
43
src/slic3r/GUI/FirmwareDialog.hpp
Normal file
43
src/slic3r/GUI/FirmwareDialog.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef slic3r_FirmwareDialog_hpp_
|
||||
#define slic3r_FirmwareDialog_hpp_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include "GUI_Utils.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
class FirmwareDialog: public GUI::DPIDialog
|
||||
{
|
||||
enum {
|
||||
DIALOG_MARGIN = 15,
|
||||
SPACING = 10,
|
||||
MIN_WIDTH = 50,
|
||||
MIN_HEIGHT = /*18*/25,
|
||||
MIN_HEIGHT_EXPANDED = 40,
|
||||
};
|
||||
|
||||
public:
|
||||
FirmwareDialog(wxWindow *parent);
|
||||
FirmwareDialog(FirmwareDialog &&) = delete;
|
||||
FirmwareDialog(const FirmwareDialog &) = delete;
|
||||
FirmwareDialog &operator=(FirmwareDialog &&) = delete;
|
||||
FirmwareDialog &operator=(const FirmwareDialog &) = delete;
|
||||
~FirmwareDialog();
|
||||
|
||||
static void run(wxWindow *parent);
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect &suggested_rect) override;
|
||||
private:
|
||||
struct priv;
|
||||
std::unique_ptr<priv> p;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
4286
src/slic3r/GUI/GCodeViewer.cpp
Normal file
4286
src/slic3r/GUI/GCodeViewer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
864
src/slic3r/GUI/GCodeViewer.hpp
Normal file
864
src/slic3r/GUI/GCodeViewer.hpp
Normal file
@@ -0,0 +1,864 @@
|
||||
#ifndef slic3r_GCodeViewer_hpp_
|
||||
#define slic3r_GCodeViewer_hpp_
|
||||
|
||||
#include "3DScene.hpp"
|
||||
#include "libslic3r/ExtrusionRole.hpp"
|
||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||
#include "GLModel.hpp"
|
||||
|
||||
#include <boost/iostreams/device/mapped_file.hpp>
|
||||
|
||||
#include <cstdint>
|
||||
#include <float.h>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Print;
|
||||
class TriangleMesh;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class GCodeViewer
|
||||
{
|
||||
using IBufferType = unsigned short;
|
||||
using VertexBuffer = std::vector<float>;
|
||||
using MultiVertexBuffer = std::vector<VertexBuffer>;
|
||||
using IndexBuffer = std::vector<IBufferType>;
|
||||
using MultiIndexBuffer = std::vector<IndexBuffer>;
|
||||
using InstanceBuffer = std::vector<float>;
|
||||
using InstanceIdBuffer = std::vector<size_t>;
|
||||
using InstancesOffsets = std::vector<Vec3f>;
|
||||
|
||||
static const std::array<ColorRGBA, static_cast<size_t>(GCodeExtrusionRole::Count)> Extrusion_Role_Colors;
|
||||
static const std::vector<ColorRGBA> Options_Colors;
|
||||
static const std::vector<ColorRGBA> Travel_Colors;
|
||||
static const std::vector<ColorRGBA> Range_Colors;
|
||||
static const ColorRGBA Wipe_Color;
|
||||
static const ColorRGBA Neutral_Color;
|
||||
|
||||
enum class EOptionsColors : unsigned char
|
||||
{
|
||||
Retractions,
|
||||
Unretractions,
|
||||
Seams,
|
||||
ToolChanges,
|
||||
ColorChanges,
|
||||
PausePrints,
|
||||
CustomGCodes
|
||||
};
|
||||
|
||||
// vbo buffer containing vertices data used to render a specific toolpath type
|
||||
struct VBuffer
|
||||
{
|
||||
enum class EFormat : unsigned char
|
||||
{
|
||||
// vertex format: 3 floats -> position.x|position.y|position.z
|
||||
Position,
|
||||
// vertex format: 4 floats -> position.x|position.y|position.z|normal.x
|
||||
PositionNormal1,
|
||||
// vertex format: 6 floats -> position.x|position.y|position.z|normal.x|normal.y|normal.z
|
||||
PositionNormal3
|
||||
};
|
||||
|
||||
EFormat format{ EFormat::Position };
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
// vaos id
|
||||
std::vector<unsigned int> vaos;
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
// vbos id
|
||||
std::vector<unsigned int> vbos;
|
||||
// sizes of the buffers, in bytes, used in export to obj
|
||||
std::vector<size_t> sizes;
|
||||
// count of vertices, updated after data are sent to gpu
|
||||
size_t count{ 0 };
|
||||
|
||||
size_t data_size_bytes() const { return count * vertex_size_bytes(); }
|
||||
// We set 65536 as max count of vertices inside a vertex buffer to allow
|
||||
// to use unsigned short in place of unsigned int for indices in the index buffer, to save memory
|
||||
size_t max_size_bytes() const { return 65536 * vertex_size_bytes(); }
|
||||
|
||||
size_t vertex_size_floats() const { return position_size_floats() + normal_size_floats(); }
|
||||
size_t vertex_size_bytes() const { return vertex_size_floats() * sizeof(float); }
|
||||
|
||||
size_t position_offset_floats() const { return 0; }
|
||||
size_t position_offset_bytes() const { return position_offset_floats() * sizeof(float); }
|
||||
|
||||
size_t position_size_floats() const { return 3; }
|
||||
size_t position_size_bytes() const { return position_size_floats() * sizeof(float); }
|
||||
|
||||
size_t normal_offset_floats() const {
|
||||
assert(format == EFormat::PositionNormal1 || format == EFormat::PositionNormal3);
|
||||
return position_size_floats();
|
||||
}
|
||||
size_t normal_offset_bytes() const { return normal_offset_floats() * sizeof(float); }
|
||||
|
||||
size_t normal_size_floats() const {
|
||||
switch (format)
|
||||
{
|
||||
case EFormat::PositionNormal1: { return 1; }
|
||||
case EFormat::PositionNormal3: { return 3; }
|
||||
default: { return 0; }
|
||||
}
|
||||
}
|
||||
size_t normal_size_bytes() const { return normal_size_floats() * sizeof(float); }
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
// buffer containing instances data used to render a toolpaths using instanced or batched models
|
||||
// instance record format:
|
||||
// instanced models: 5 floats -> position.x|position.y|position.z|width|height (which are sent to the shader as -> vec3 (offset) + vec2 (scales) in GLModel::render_instanced())
|
||||
// batched models: 3 floats -> position.x|position.y|position.z
|
||||
struct InstanceVBuffer
|
||||
{
|
||||
// ranges used to render only subparts of the intances
|
||||
struct Ranges
|
||||
{
|
||||
struct Range
|
||||
{
|
||||
// offset in bytes of the 1st instance to render
|
||||
unsigned int offset;
|
||||
// count of instances to render
|
||||
unsigned int count;
|
||||
// vbo id
|
||||
unsigned int vbo{ 0 };
|
||||
// Color to apply to the instances
|
||||
ColorRGBA color;
|
||||
};
|
||||
|
||||
std::vector<Range> ranges;
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
enum class EFormat : unsigned char
|
||||
{
|
||||
InstancedModel,
|
||||
BatchedModel
|
||||
};
|
||||
|
||||
EFormat format;
|
||||
|
||||
// cpu-side buffer containing all instances data
|
||||
InstanceBuffer buffer;
|
||||
// indices of the moves for all instances
|
||||
std::vector<size_t> s_ids;
|
||||
// position offsets, used to show the correct value of the tool position
|
||||
InstancesOffsets offsets;
|
||||
Ranges render_ranges;
|
||||
|
||||
size_t data_size_bytes() const { return s_ids.size() * instance_size_bytes(); }
|
||||
|
||||
size_t instance_size_floats() const {
|
||||
switch (format)
|
||||
{
|
||||
case EFormat::InstancedModel: { return 5; }
|
||||
case EFormat::BatchedModel: { return 3; }
|
||||
default: { return 0; }
|
||||
}
|
||||
}
|
||||
size_t instance_size_bytes() const { return instance_size_floats() * sizeof(float); }
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
// ibo buffer containing indices data (for lines/triangles) used to render a specific toolpath type
|
||||
struct IBuffer
|
||||
{
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
// id of the associated vertex array buffer
|
||||
unsigned int vao{ 0 };
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
// id of the associated vertex buffer
|
||||
unsigned int vbo{ 0 };
|
||||
// ibo id
|
||||
unsigned int ibo{ 0 };
|
||||
// count of indices, updated after data are sent to gpu
|
||||
size_t count{ 0 };
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
// Used to identify different toolpath sub-types inside a IBuffer
|
||||
struct Path
|
||||
{
|
||||
struct Endpoint
|
||||
{
|
||||
// index of the buffer in the multibuffer vector
|
||||
// the buffer type may change:
|
||||
// it is the vertex buffer while extracting vertices data,
|
||||
// the index buffer while extracting indices data
|
||||
unsigned int b_id{ 0 };
|
||||
// index into the buffer
|
||||
size_t i_id{ 0 };
|
||||
// move id
|
||||
size_t s_id{ 0 };
|
||||
Vec3f position{ Vec3f::Zero() };
|
||||
};
|
||||
|
||||
struct Sub_Path
|
||||
{
|
||||
Endpoint first;
|
||||
Endpoint last;
|
||||
|
||||
bool contains(size_t s_id) const {
|
||||
return first.s_id <= s_id && s_id <= last.s_id;
|
||||
}
|
||||
};
|
||||
|
||||
EMoveType type{ EMoveType::Noop };
|
||||
GCodeExtrusionRole role{ GCodeExtrusionRole::None };
|
||||
float delta_extruder{ 0.0f };
|
||||
float height{ 0.0f };
|
||||
float width{ 0.0f };
|
||||
float feedrate{ 0.0f };
|
||||
float fan_speed{ 0.0f };
|
||||
float temperature{ 0.0f };
|
||||
float volumetric_rate{ 0.0f };
|
||||
unsigned char extruder_id{ 0 };
|
||||
unsigned char cp_color_id{ 0 };
|
||||
std::vector<Sub_Path> sub_paths;
|
||||
|
||||
bool matches(const GCodeProcessorResult::MoveVertex& move, bool account_for_volumetric_rate) const;
|
||||
size_t vertices_count() const {
|
||||
return sub_paths.empty() ? 0 : sub_paths.back().last.s_id - sub_paths.front().first.s_id + 1;
|
||||
}
|
||||
bool contains(size_t s_id) const {
|
||||
return sub_paths.empty() ? false : sub_paths.front().first.s_id <= s_id && s_id <= sub_paths.back().last.s_id;
|
||||
}
|
||||
int get_id_of_sub_path_containing(size_t s_id) const {
|
||||
if (sub_paths.empty())
|
||||
return -1;
|
||||
else {
|
||||
for (int i = 0; i < static_cast<int>(sub_paths.size()); ++i) {
|
||||
if (sub_paths[i].contains(s_id))
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
void add_sub_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id) {
|
||||
Endpoint endpoint = { b_id, i_id, s_id, move.position };
|
||||
sub_paths.push_back({ endpoint , endpoint });
|
||||
}
|
||||
};
|
||||
|
||||
// Used to batch the indices needed to render the paths
|
||||
struct RenderPath
|
||||
{
|
||||
// Index of the parent tbuffer
|
||||
unsigned char tbuffer_id;
|
||||
// Render path property
|
||||
ColorRGBA color;
|
||||
// Index of the buffer in TBuffer::indices
|
||||
unsigned int ibuffer_id;
|
||||
// Render path content
|
||||
// Index of the path in TBuffer::paths
|
||||
unsigned int path_id;
|
||||
std::vector<unsigned int> sizes;
|
||||
std::vector<size_t> offsets; // use size_t because we need an unsigned integer whose size matches pointer's size (used in the call glMultiDrawElements())
|
||||
bool contains(size_t offset) const {
|
||||
for (size_t i = 0; i < offsets.size(); ++i) {
|
||||
if (offsets[i] <= offset && offset <= offsets[i] + static_cast<size_t>(sizes[i] * sizeof(IBufferType)))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
struct RenderPathPropertyLower {
|
||||
bool operator() (const RenderPath &l, const RenderPath &r) const {
|
||||
if (l.tbuffer_id < r.tbuffer_id)
|
||||
return true;
|
||||
if (l.color < r.color)
|
||||
return true;
|
||||
else if (l.color > r.color)
|
||||
return false;
|
||||
return l.ibuffer_id < r.ibuffer_id;
|
||||
}
|
||||
};
|
||||
struct RenderPathPropertyEqual {
|
||||
bool operator() (const RenderPath &l, const RenderPath &r) const {
|
||||
return l.tbuffer_id == r.tbuffer_id && l.ibuffer_id == r.ibuffer_id && l.color == r.color;
|
||||
}
|
||||
};
|
||||
|
||||
// buffer containing data for rendering a specific toolpath type
|
||||
struct TBuffer
|
||||
{
|
||||
enum class ERenderPrimitiveType : unsigned char
|
||||
{
|
||||
Line,
|
||||
Triangle,
|
||||
InstancedModel,
|
||||
BatchedModel
|
||||
};
|
||||
|
||||
ERenderPrimitiveType render_primitive_type;
|
||||
|
||||
// buffers for point, line and triangle primitive types
|
||||
VBuffer vertices;
|
||||
std::vector<IBuffer> indices;
|
||||
|
||||
struct Model
|
||||
{
|
||||
GLModel model;
|
||||
ColorRGBA color;
|
||||
InstanceVBuffer instances;
|
||||
GLModel::Geometry data;
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
// contain the buffer for model primitive types
|
||||
Model model;
|
||||
|
||||
std::string shader;
|
||||
std::vector<Path> paths;
|
||||
std::vector<RenderPath> render_paths;
|
||||
bool visible{ false };
|
||||
|
||||
void reset();
|
||||
|
||||
// b_id index of buffer contained in this->indices
|
||||
// i_id index of first index contained in this->indices[b_id]
|
||||
// s_id index of first vertex contained in this->vertices
|
||||
void add_path(const GCodeProcessorResult::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id);
|
||||
|
||||
unsigned int max_vertices_per_segment() const {
|
||||
switch (render_primitive_type)
|
||||
{
|
||||
case ERenderPrimitiveType::Line: { return 2; }
|
||||
case ERenderPrimitiveType::Triangle: { return 8; }
|
||||
default: { return 0; }
|
||||
}
|
||||
}
|
||||
|
||||
size_t max_vertices_per_segment_size_floats() const { return vertices.vertex_size_floats() * static_cast<size_t>(max_vertices_per_segment()); }
|
||||
size_t max_vertices_per_segment_size_bytes() const { return max_vertices_per_segment_size_floats() * sizeof(float); }
|
||||
unsigned int indices_per_segment() const {
|
||||
switch (render_primitive_type)
|
||||
{
|
||||
case ERenderPrimitiveType::Line: { return 2; }
|
||||
case ERenderPrimitiveType::Triangle: { return 30; } // 3 indices x 10 triangles
|
||||
default: { return 0; }
|
||||
}
|
||||
}
|
||||
size_t indices_per_segment_size_bytes() const { return static_cast<size_t>(indices_per_segment() * sizeof(IBufferType)); }
|
||||
unsigned int max_indices_per_segment() const {
|
||||
switch (render_primitive_type)
|
||||
{
|
||||
case ERenderPrimitiveType::Line: { return 2; }
|
||||
case ERenderPrimitiveType::Triangle: { return 36; } // 3 indices x 12 triangles
|
||||
default: { return 0; }
|
||||
}
|
||||
}
|
||||
size_t max_indices_per_segment_size_bytes() const { return max_indices_per_segment() * sizeof(IBufferType); }
|
||||
|
||||
bool has_data() const {
|
||||
switch (render_primitive_type)
|
||||
{
|
||||
case ERenderPrimitiveType::Line:
|
||||
case ERenderPrimitiveType::Triangle: {
|
||||
return !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0;
|
||||
}
|
||||
case ERenderPrimitiveType::InstancedModel: { return model.model.is_initialized() && !model.instances.buffer.empty(); }
|
||||
case ERenderPrimitiveType::BatchedModel: {
|
||||
return !model.data.vertices.empty() && !model.data.indices.empty() &&
|
||||
!vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0;
|
||||
}
|
||||
default: { return false; }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// helper to render shells
|
||||
struct Shells
|
||||
{
|
||||
GLVolumeCollection volumes;
|
||||
bool visible{ false };
|
||||
};
|
||||
|
||||
// helper to render center of gravity
|
||||
class COG
|
||||
{
|
||||
GLModel m_model;
|
||||
bool m_visible{ false };
|
||||
// whether or not to render the model with fixed screen size
|
||||
bool m_fixed_size{ true };
|
||||
double m_total_mass{ 0.0 };
|
||||
Vec3d m_position{ Vec3d::Zero() };
|
||||
|
||||
public:
|
||||
void render();
|
||||
|
||||
void reset() {
|
||||
m_position = Vec3d::Zero();
|
||||
m_total_mass = 0.0;
|
||||
}
|
||||
|
||||
bool is_visible() const { return m_visible; }
|
||||
void set_visible(bool visible) { m_visible = visible; }
|
||||
|
||||
void add_segment(const Vec3d& v1, const Vec3d& v2, double mass) {
|
||||
assert(mass > 0.0);
|
||||
m_position += mass * 0.5 * (v1 + v2);
|
||||
m_total_mass += mass;
|
||||
}
|
||||
|
||||
Vec3d cog() const { return (m_total_mass > 0.0) ? (Vec3d)(m_position / m_total_mass) : Vec3d::Zero(); }
|
||||
|
||||
private:
|
||||
void init() {
|
||||
if (m_model.is_initialized())
|
||||
return;
|
||||
|
||||
const float radius = m_fixed_size ? 10.0f : 1.0f;
|
||||
m_model.init_from(smooth_sphere(32, radius));
|
||||
}
|
||||
};
|
||||
|
||||
// helper to render extrusion paths
|
||||
struct Extrusions
|
||||
{
|
||||
struct Range
|
||||
{
|
||||
enum class EType : unsigned char
|
||||
{
|
||||
Linear,
|
||||
Logarithmic
|
||||
};
|
||||
|
||||
float min;
|
||||
float max;
|
||||
unsigned int count;
|
||||
|
||||
Range() { reset(); }
|
||||
|
||||
void update_from(const float value) {
|
||||
if (value != max && value != min)
|
||||
++count;
|
||||
min = std::min(min, value);
|
||||
max = std::max(max, value);
|
||||
}
|
||||
void reset() { min = FLT_MAX; max = -FLT_MAX; count = 0; }
|
||||
|
||||
float step_size(EType type = EType::Linear) const;
|
||||
ColorRGBA get_color_at(float value, EType type = EType::Linear) const;
|
||||
};
|
||||
|
||||
struct Ranges
|
||||
{
|
||||
// Color mapping by layer height.
|
||||
Range height;
|
||||
// Color mapping by extrusion width.
|
||||
Range width;
|
||||
// Color mapping by feedrate.
|
||||
Range feedrate;
|
||||
// Color mapping by fan speed.
|
||||
Range fan_speed;
|
||||
// Color mapping by volumetric extrusion rate.
|
||||
Range volumetric_rate;
|
||||
// Color mapping by extrusion temperature.
|
||||
Range temperature;
|
||||
// Color mapping by layer time.
|
||||
std::array<Range, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)> layer_time;
|
||||
|
||||
void reset() {
|
||||
height.reset();
|
||||
width.reset();
|
||||
feedrate.reset();
|
||||
fan_speed.reset();
|
||||
volumetric_rate.reset();
|
||||
temperature.reset();
|
||||
for (auto& range : layer_time) {
|
||||
range.reset();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
unsigned int role_visibility_flags{ 0 };
|
||||
Ranges ranges;
|
||||
|
||||
void reset_role_visibility_flags() {
|
||||
role_visibility_flags = 0;
|
||||
for (uint32_t i = 0; i < uint32_t(GCodeExtrusionRole::Count); ++i) {
|
||||
role_visibility_flags |= 1 << i;
|
||||
}
|
||||
}
|
||||
|
||||
void reset_ranges() { ranges.reset(); }
|
||||
};
|
||||
|
||||
class Layers
|
||||
{
|
||||
public:
|
||||
struct Range
|
||||
{
|
||||
size_t first{ 0 };
|
||||
size_t last{ 0 };
|
||||
|
||||
bool operator == (const Range& other) const { return first == other.first && last == other.last; }
|
||||
bool operator != (const Range& other) const { return !operator==(other); }
|
||||
bool contains(size_t id) const { return first <= id && id <= last; }
|
||||
};
|
||||
|
||||
private:
|
||||
std::vector<double> m_zs;
|
||||
std::vector<Range> m_ranges;
|
||||
|
||||
public:
|
||||
void append(double z, const Range& range) {
|
||||
m_zs.emplace_back(z);
|
||||
m_ranges.emplace_back(range);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
m_zs = std::vector<double>();
|
||||
m_ranges = std::vector<Range>();
|
||||
}
|
||||
|
||||
size_t size() const { return m_zs.size(); }
|
||||
bool empty() const { return m_zs.empty(); }
|
||||
const std::vector<double>& get_zs() const { return m_zs; }
|
||||
const std::vector<Range>& get_ranges() const { return m_ranges; }
|
||||
std::vector<Range>& get_ranges() { return m_ranges; }
|
||||
double get_z_at(unsigned int id) const { return (id < m_zs.size()) ? m_zs[id] : 0.0; }
|
||||
Range get_range_at(unsigned int id) const { return (id < m_ranges.size()) ? m_ranges[id] : Range(); }
|
||||
|
||||
bool operator != (const Layers& other) const {
|
||||
if (m_zs != other.m_zs)
|
||||
return true;
|
||||
if (m_ranges != other.m_ranges)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// used to render the toolpath caps of the current sequential range
|
||||
// (i.e. when sliding on the horizontal slider)
|
||||
struct SequentialRangeCap
|
||||
{
|
||||
TBuffer* buffer{ nullptr };
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
unsigned int vao{ 0 };
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
unsigned int vbo{ 0 };
|
||||
unsigned int ibo{ 0 };
|
||||
ColorRGBA color;
|
||||
|
||||
~SequentialRangeCap();
|
||||
bool is_renderable() const { return buffer != nullptr; }
|
||||
void reset();
|
||||
size_t indices_count() const { return 6; }
|
||||
};
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
struct Statistics
|
||||
{
|
||||
// time
|
||||
int64_t results_time{ 0 };
|
||||
int64_t load_time{ 0 };
|
||||
int64_t load_vertices{ 0 };
|
||||
int64_t smooth_vertices{ 0 };
|
||||
int64_t load_indices{ 0 };
|
||||
int64_t refresh_time{ 0 };
|
||||
int64_t refresh_paths_time{ 0 };
|
||||
// opengl calls
|
||||
int64_t gl_multi_lines_calls_count{ 0 };
|
||||
int64_t gl_multi_triangles_calls_count{ 0 };
|
||||
int64_t gl_triangles_calls_count{ 0 };
|
||||
int64_t gl_instanced_models_calls_count{ 0 };
|
||||
int64_t gl_batched_models_calls_count{ 0 };
|
||||
// memory
|
||||
int64_t results_size{ 0 };
|
||||
int64_t total_vertices_gpu_size{ 0 };
|
||||
int64_t total_indices_gpu_size{ 0 };
|
||||
int64_t total_instances_gpu_size{ 0 };
|
||||
int64_t max_vbuffer_gpu_size{ 0 };
|
||||
int64_t max_ibuffer_gpu_size{ 0 };
|
||||
int64_t paths_size{ 0 };
|
||||
int64_t render_paths_size{ 0 };
|
||||
int64_t models_instances_size{ 0 };
|
||||
// other
|
||||
int64_t travel_segments_count{ 0 };
|
||||
int64_t wipe_segments_count{ 0 };
|
||||
int64_t extrude_segments_count{ 0 };
|
||||
int64_t instances_count{ 0 };
|
||||
int64_t batched_count{ 0 };
|
||||
int64_t vbuffers_count{ 0 };
|
||||
int64_t ibuffers_count{ 0 };
|
||||
|
||||
void reset_all() {
|
||||
reset_times();
|
||||
reset_opengl();
|
||||
reset_sizes();
|
||||
reset_others();
|
||||
}
|
||||
|
||||
void reset_times() {
|
||||
results_time = 0;
|
||||
load_time = 0;
|
||||
load_vertices = 0;
|
||||
smooth_vertices = 0;
|
||||
load_indices = 0;
|
||||
refresh_time = 0;
|
||||
refresh_paths_time = 0;
|
||||
}
|
||||
|
||||
void reset_opengl() {
|
||||
gl_multi_lines_calls_count = 0;
|
||||
gl_multi_triangles_calls_count = 0;
|
||||
gl_triangles_calls_count = 0;
|
||||
gl_instanced_models_calls_count = 0;
|
||||
gl_batched_models_calls_count = 0;
|
||||
}
|
||||
|
||||
void reset_sizes() {
|
||||
results_size = 0;
|
||||
total_vertices_gpu_size = 0;
|
||||
total_indices_gpu_size = 0;
|
||||
total_instances_gpu_size = 0;
|
||||
max_vbuffer_gpu_size = 0;
|
||||
max_ibuffer_gpu_size = 0;
|
||||
paths_size = 0;
|
||||
render_paths_size = 0;
|
||||
models_instances_size = 0;
|
||||
}
|
||||
|
||||
void reset_others() {
|
||||
travel_segments_count = 0;
|
||||
wipe_segments_count = 0;
|
||||
extrude_segments_count = 0;
|
||||
instances_count = 0;
|
||||
batched_count = 0;
|
||||
vbuffers_count = 0;
|
||||
ibuffers_count = 0;
|
||||
}
|
||||
};
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
|
||||
public:
|
||||
struct SequentialView
|
||||
{
|
||||
class Marker
|
||||
{
|
||||
GLModel m_model;
|
||||
Vec3f m_world_position;
|
||||
Transform3f m_world_transform;
|
||||
// for seams, the position of the marker is on the last endpoint of the toolpath containing it
|
||||
// the offset is used to show the correct value of tool position in the "ToolPosition" window
|
||||
// see implementation of render() method
|
||||
Vec3f m_world_offset;
|
||||
float m_z_offset{ 0.5f };
|
||||
bool m_visible{ true };
|
||||
|
||||
public:
|
||||
void init();
|
||||
|
||||
const BoundingBoxf3& get_bounding_box() const { return m_model.get_bounding_box(); }
|
||||
|
||||
void set_world_position(const Vec3f& position);
|
||||
void set_world_offset(const Vec3f& offset) { m_world_offset = offset; }
|
||||
|
||||
bool is_visible() const { return m_visible; }
|
||||
void set_visible(bool visible) { m_visible = visible; }
|
||||
|
||||
void render();
|
||||
};
|
||||
|
||||
class GCodeWindow
|
||||
{
|
||||
struct Line
|
||||
{
|
||||
std::string command;
|
||||
std::string parameters;
|
||||
std::string comment;
|
||||
};
|
||||
bool m_visible{ true };
|
||||
uint64_t m_selected_line_id{ 0 };
|
||||
size_t m_last_lines_size{ 0 };
|
||||
std::string m_filename;
|
||||
boost::iostreams::mapped_file_source m_file;
|
||||
// map for accessing data in file by line number
|
||||
std::vector<size_t> m_lines_ends;
|
||||
// current visible lines
|
||||
std::vector<Line> m_lines;
|
||||
|
||||
public:
|
||||
GCodeWindow() = default;
|
||||
~GCodeWindow() { stop_mapping_file(); }
|
||||
void load_gcode(const std::string& filename, const std::vector<size_t>& lines_ends);
|
||||
void reset() {
|
||||
stop_mapping_file();
|
||||
m_lines_ends.clear();
|
||||
m_lines.clear();
|
||||
m_filename.clear();
|
||||
}
|
||||
|
||||
void toggle_visibility() { m_visible = !m_visible; }
|
||||
|
||||
void render(float top, float bottom, uint64_t curr_line_id) const;
|
||||
|
||||
void stop_mapping_file();
|
||||
};
|
||||
|
||||
struct Endpoints
|
||||
{
|
||||
size_t first{ 0 };
|
||||
size_t last{ 0 };
|
||||
};
|
||||
|
||||
bool skip_invisible_moves{ false };
|
||||
Endpoints endpoints;
|
||||
Endpoints current;
|
||||
Endpoints last_current;
|
||||
Endpoints global;
|
||||
Vec3f current_position{ Vec3f::Zero() };
|
||||
Vec3f current_offset{ Vec3f::Zero() };
|
||||
Marker marker;
|
||||
GCodeWindow gcode_window;
|
||||
std::vector<unsigned int> gcode_ids;
|
||||
|
||||
void render(float legend_height);
|
||||
};
|
||||
|
||||
enum class EViewType : unsigned char
|
||||
{
|
||||
FeatureType,
|
||||
Height,
|
||||
Width,
|
||||
Feedrate,
|
||||
FanSpeed,
|
||||
Temperature,
|
||||
VolumetricRate,
|
||||
LayerTimeLinear,
|
||||
LayerTimeLogarithmic,
|
||||
Tool,
|
||||
ColorPrint,
|
||||
Count
|
||||
};
|
||||
|
||||
private:
|
||||
bool m_gl_data_initialized{ false };
|
||||
unsigned int m_last_result_id{ 0 };
|
||||
EViewType m_last_view_type{ EViewType::Count };
|
||||
size_t m_moves_count{ 0 };
|
||||
std::vector<TBuffer> m_buffers{ static_cast<size_t>(EMoveType::Extrude) };
|
||||
// bounding box of toolpaths
|
||||
BoundingBoxf3 m_paths_bounding_box;
|
||||
// bounding box of toolpaths + marker tools
|
||||
BoundingBoxf3 m_max_bounding_box;
|
||||
float m_max_print_height{ 0.0f };
|
||||
std::vector<ColorRGBA> m_tool_colors;
|
||||
Layers m_layers;
|
||||
std::array<unsigned int, 2> m_layers_z_range;
|
||||
std::vector<GCodeExtrusionRole> m_roles;
|
||||
size_t m_extruders_count;
|
||||
std::vector<unsigned char> m_extruder_ids;
|
||||
std::vector<float> m_filament_diameters;
|
||||
std::vector<float> m_filament_densities;
|
||||
Extrusions m_extrusions;
|
||||
SequentialView m_sequential_view;
|
||||
Shells m_shells;
|
||||
COG m_cog;
|
||||
EViewType m_view_type{ EViewType::FeatureType };
|
||||
bool m_legend_enabled{ true };
|
||||
struct LegendResizer
|
||||
{
|
||||
bool dirty{ true };
|
||||
void reset() { dirty = true; }
|
||||
};
|
||||
LegendResizer m_legend_resizer;
|
||||
PrintEstimatedStatistics m_print_statistics;
|
||||
PrintEstimatedStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedStatistics::ETimeMode::Normal };
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
Statistics m_statistics;
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
GCodeProcessorResult::SettingsIds m_settings_ids;
|
||||
std::array<SequentialRangeCap, 2> m_sequential_range_caps;
|
||||
std::array<std::vector<float>, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)> m_layers_times;
|
||||
|
||||
std::vector<CustomGCode::Item> m_custom_gcode_per_print_z;
|
||||
|
||||
bool m_contained_in_bed{ true };
|
||||
|
||||
public:
|
||||
GCodeViewer();
|
||||
~GCodeViewer() { reset(); }
|
||||
|
||||
void init();
|
||||
|
||||
// extract rendering data from the given parameters
|
||||
void load(const GCodeProcessorResult& gcode_result, const Print& print);
|
||||
// recalculate ranges in dependence of what is visible and sets tool/print colors
|
||||
void refresh(const GCodeProcessorResult& gcode_result, const std::vector<std::string>& str_tool_colors);
|
||||
void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const;
|
||||
void update_shells_color_by_extruder(const DynamicPrintConfig* config);
|
||||
|
||||
void reset();
|
||||
void render();
|
||||
void render_cog() { m_cog.render(); }
|
||||
|
||||
bool has_data() const { return !m_roles.empty(); }
|
||||
bool can_export_toolpaths() const;
|
||||
|
||||
const BoundingBoxf3& get_paths_bounding_box() const { return m_paths_bounding_box; }
|
||||
const BoundingBoxf3& get_max_bounding_box() const { return m_max_bounding_box; }
|
||||
const std::vector<double>& get_layers_zs() const { return m_layers.get_zs(); }
|
||||
|
||||
const SequentialView& get_sequential_view() const { return m_sequential_view; }
|
||||
void update_sequential_view_current(unsigned int first, unsigned int last);
|
||||
|
||||
bool is_contained_in_bed() const { return m_contained_in_bed; }
|
||||
|
||||
EViewType get_view_type() const { return m_view_type; }
|
||||
void set_view_type(EViewType type) {
|
||||
if (type == EViewType::Count)
|
||||
type = EViewType::FeatureType;
|
||||
|
||||
m_view_type = type;
|
||||
}
|
||||
|
||||
bool is_toolpath_move_type_visible(EMoveType type) const;
|
||||
void set_toolpath_move_type_visible(EMoveType type, bool visible);
|
||||
unsigned int get_toolpath_role_visibility_flags() const { return m_extrusions.role_visibility_flags; }
|
||||
void set_toolpath_role_visibility_flags(unsigned int flags) { m_extrusions.role_visibility_flags = flags; }
|
||||
unsigned int get_options_visibility_flags() const;
|
||||
void set_options_visibility_from_flags(unsigned int flags);
|
||||
void set_layers_z_range(const std::array<unsigned int, 2>& layers_z_range);
|
||||
|
||||
bool is_legend_enabled() const { return m_legend_enabled; }
|
||||
void enable_legend(bool enable) { m_legend_enabled = enable; }
|
||||
|
||||
void export_toolpaths_to_obj(const char* filename) const;
|
||||
|
||||
void toggle_gcode_window_visibility() { m_sequential_view.gcode_window.toggle_visibility(); }
|
||||
|
||||
std::vector<CustomGCode::Item>& get_custom_gcode_per_print_z() { return m_custom_gcode_per_print_z; }
|
||||
size_t get_extruders_count() { return m_extruders_count; }
|
||||
|
||||
void invalidate_legend() { m_legend_resizer.reset(); }
|
||||
|
||||
private:
|
||||
void load_toolpaths(const GCodeProcessorResult& gcode_result);
|
||||
void load_shells(const Print& print);
|
||||
void render_toolpaths();
|
||||
void render_shells();
|
||||
void render_legend(float& legend_height);
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
void render_statistics();
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
bool is_visible(GCodeExtrusionRole role) const {
|
||||
return role < GCodeExtrusionRole::Count && (m_extrusions.role_visibility_flags & (1 << int(role))) != 0;
|
||||
}
|
||||
bool is_visible(const Path& path) const { return is_visible(path.role); }
|
||||
void log_memory_used(const std::string& label, int64_t additional = 0) const;
|
||||
ColorRGBA option_color(EMoveType move_type) const;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GCodeViewer_hpp_
|
||||
7931
src/slic3r/GUI/GLCanvas3D.cpp
Normal file
7931
src/slic3r/GUI/GLCanvas3D.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1134
src/slic3r/GUI/GLCanvas3D.hpp
Normal file
1134
src/slic3r/GUI/GLCanvas3D.hpp
Normal file
File diff suppressed because it is too large
Load Diff
1723
src/slic3r/GUI/GLModel.cpp
Normal file
1723
src/slic3r/GUI/GLModel.cpp
Normal file
File diff suppressed because it is too large
Load Diff
322
src/slic3r/GUI/GLModel.hpp
Normal file
322
src/slic3r/GUI/GLModel.hpp
Normal file
@@ -0,0 +1,322 @@
|
||||
#ifndef slic3r_GLModel_hpp_
|
||||
#define slic3r_GLModel_hpp_
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/Color.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
struct indexed_triangle_set;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
class Polygon;
|
||||
using Polygons = std::vector<Polygon, PointsAllocator<Polygon>>;
|
||||
class BuildVolume;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class GLModel
|
||||
{
|
||||
public:
|
||||
struct Geometry
|
||||
{
|
||||
enum class EPrimitiveType : unsigned char
|
||||
{
|
||||
Points,
|
||||
Triangles,
|
||||
TriangleStrip,
|
||||
TriangleFan,
|
||||
Lines,
|
||||
LineStrip,
|
||||
LineLoop
|
||||
};
|
||||
|
||||
enum class EVertexLayout : unsigned char
|
||||
{
|
||||
P2, // position 2 floats
|
||||
P2T2, // position 2 floats + texture coords 2 floats
|
||||
P3, // position 3 floats
|
||||
P3T2, // position 3 floats + texture coords 2 floats
|
||||
P3N3, // position 3 floats + normal 3 floats
|
||||
P3N3T2, // position 3 floats + normal 3 floats + texture coords 2 floats
|
||||
#if ENABLE_OPENGL_ES
|
||||
P3N3E3, // position 3 floats + normal 3 floats + extra 3 floats
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
P4, // position 4 floats
|
||||
};
|
||||
|
||||
enum class EIndexType : unsigned char
|
||||
{
|
||||
UINT, // unsigned int
|
||||
USHORT, // unsigned short
|
||||
UBYTE // unsigned byte
|
||||
};
|
||||
|
||||
struct Format
|
||||
{
|
||||
EPrimitiveType type{ EPrimitiveType::Triangles };
|
||||
EVertexLayout vertex_layout{ EVertexLayout::P3N3 };
|
||||
};
|
||||
|
||||
Format format;
|
||||
std::vector<float> vertices;
|
||||
std::vector<unsigned int> indices;
|
||||
EIndexType index_type{ EIndexType::UINT };
|
||||
ColorRGBA color{ ColorRGBA::BLACK() };
|
||||
|
||||
void reserve_vertices(size_t vertices_count) { vertices.reserve(vertices_count * vertex_stride_floats(format)); }
|
||||
void reserve_more_vertices(size_t vertices_count) { vertices.reserve(next_highest_power_of_2(vertices.size() + vertices_count * vertex_stride_floats(format))); }
|
||||
void reserve_indices(size_t indices_count) { indices.reserve(indices_count); }
|
||||
void reserve_more_indices(size_t indices_count) { indices.reserve(next_highest_power_of_2(indices.size() + indices_count)); }
|
||||
|
||||
void add_vertex(const Vec2f& position); // EVertexLayout::P2
|
||||
void add_vertex(const Vec2f& position, const Vec2f& tex_coord); // EVertexLayout::P2T2
|
||||
void add_vertex(const Vec3f& position); // EVertexLayout::P3
|
||||
void add_vertex(const Vec3f& position, const Vec2f& tex_coord); // EVertexLayout::P3T2
|
||||
void add_vertex(const Vec3f& position, const Vec3f& normal) { // EVertexLayout::P3N3
|
||||
assert(format.vertex_layout == EVertexLayout::P3N3);
|
||||
vertices.insert(vertices.end(), position.data(), position.data() + 3);
|
||||
vertices.insert(vertices.end(), normal.data(), normal.data() + 3);
|
||||
}
|
||||
void add_vertex(const Vec3f& position, const Vec3f& normal, const Vec2f& tex_coord); // EVertexLayout::P3N3T2
|
||||
#if ENABLE_OPENGL_ES
|
||||
void add_vertex(const Vec3f& position, const Vec3f& normal, const Vec3f& extra); // EVertexLayout::P3N3E3
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
void add_vertex(const Vec4f& position); // EVertexLayout::P4
|
||||
|
||||
void set_vertex(size_t id, const Vec3f& position, const Vec3f& normal); // EVertexLayout::P3N3
|
||||
|
||||
void set_index(size_t id, unsigned int index);
|
||||
|
||||
void add_index(unsigned int id);
|
||||
void add_line(unsigned int id1, unsigned int id2);
|
||||
void add_triangle(unsigned int id1, unsigned int id2, unsigned int id3){
|
||||
indices.emplace_back(id1);
|
||||
indices.emplace_back(id2);
|
||||
indices.emplace_back(id3);
|
||||
}
|
||||
|
||||
Vec2f extract_position_2(size_t id) const;
|
||||
Vec3f extract_position_3(size_t id) const;
|
||||
Vec3f extract_normal_3(size_t id) const;
|
||||
Vec2f extract_tex_coord_2(size_t id) const;
|
||||
|
||||
unsigned int extract_index(size_t id) const;
|
||||
|
||||
void remove_vertex(size_t id);
|
||||
|
||||
bool is_empty() const { return vertices_count() == 0 || indices_count() == 0; }
|
||||
|
||||
size_t vertices_count() const { return vertices.size() / vertex_stride_floats(format); }
|
||||
size_t indices_count() const { return indices.size(); }
|
||||
|
||||
size_t vertices_size_floats() const { return vertices.size(); }
|
||||
size_t vertices_size_bytes() const { return vertices_size_floats() * sizeof(float); }
|
||||
size_t indices_size_bytes() const { return indices.size() * index_stride_bytes(*this); }
|
||||
|
||||
indexed_triangle_set get_as_indexed_triangle_set() const;
|
||||
|
||||
static size_t vertex_stride_floats(const Format& format);
|
||||
static size_t vertex_stride_bytes(const Format& format) { return vertex_stride_floats(format) * sizeof(float); }
|
||||
|
||||
static size_t position_stride_floats(const Format& format);
|
||||
static size_t position_stride_bytes(const Format& format) { return position_stride_floats(format) * sizeof(float); }
|
||||
static size_t position_offset_floats(const Format& format);
|
||||
static size_t position_offset_bytes(const Format& format) { return position_offset_floats(format) * sizeof(float); }
|
||||
|
||||
static size_t normal_stride_floats(const Format& format);
|
||||
static size_t normal_stride_bytes(const Format& format) { return normal_stride_floats(format) * sizeof(float); }
|
||||
static size_t normal_offset_floats(const Format& format);
|
||||
static size_t normal_offset_bytes(const Format& format) { return normal_offset_floats(format) * sizeof(float); }
|
||||
|
||||
static size_t tex_coord_stride_floats(const Format& format);
|
||||
static size_t tex_coord_stride_bytes(const Format& format) { return tex_coord_stride_floats(format) * sizeof(float); }
|
||||
static size_t tex_coord_offset_floats(const Format& format);
|
||||
static size_t tex_coord_offset_bytes(const Format& format) { return tex_coord_offset_floats(format) * sizeof(float); }
|
||||
|
||||
#if ENABLE_OPENGL_ES
|
||||
static size_t extra_stride_floats(const Format& format);
|
||||
static size_t extra_stride_bytes(const Format& format) { return extra_stride_floats(format) * sizeof(float); }
|
||||
static size_t extra_offset_floats(const Format& format);
|
||||
static size_t extra_offset_bytes(const Format& format) { return extra_offset_floats(format) * sizeof(float); }
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
|
||||
static size_t index_stride_bytes(const Geometry& data);
|
||||
|
||||
static bool has_position(const Format& format);
|
||||
static bool has_normal(const Format& format);
|
||||
static bool has_tex_coord(const Format& format);
|
||||
#if ENABLE_OPENGL_ES
|
||||
static bool has_extra(const Format& format);
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
};
|
||||
|
||||
struct RenderData
|
||||
{
|
||||
Geometry geometry;
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
unsigned int vao_id{ 0 };
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
unsigned int vbo_id{ 0 };
|
||||
unsigned int ibo_id{ 0 };
|
||||
size_t vertices_count{ 0 };
|
||||
size_t indices_count{ 0 };
|
||||
};
|
||||
|
||||
private:
|
||||
#if ENABLE_GLMODEL_STATISTICS
|
||||
struct Statistics
|
||||
{
|
||||
struct Buffers
|
||||
{
|
||||
struct Data
|
||||
{
|
||||
size_t current{ 0 };
|
||||
size_t max{ 0 };
|
||||
};
|
||||
Data indices;
|
||||
Data vertices;
|
||||
};
|
||||
|
||||
Buffers gpu_memory;
|
||||
|
||||
int64_t render_calls{ 0 };
|
||||
int64_t render_instanced_calls{ 0 };
|
||||
};
|
||||
|
||||
static Statistics s_statistics;
|
||||
#endif // ENABLE_GLMODEL_STATISTICS
|
||||
|
||||
RenderData m_render_data;
|
||||
|
||||
// By default the vertex and index buffers data are sent to gpu at the first call to render() method.
|
||||
// If you need to initialize a model from outside the main thread, so that a call to render() may happen
|
||||
// before the initialization is complete, use the methods:
|
||||
// disable_render()
|
||||
// ... do your initialization ...
|
||||
// enable_render()
|
||||
// to keep the data on cpu side until needed.
|
||||
bool m_render_disabled{ false };
|
||||
BoundingBoxf3 m_bounding_box;
|
||||
std::string m_filename;
|
||||
|
||||
public:
|
||||
GLModel() = default;
|
||||
virtual ~GLModel() { reset(); }
|
||||
|
||||
size_t vertices_count() const { return m_render_data.vertices_count > 0 ?
|
||||
m_render_data.vertices_count : m_render_data.geometry.vertices_count(); }
|
||||
size_t indices_count() const { return m_render_data.indices_count > 0 ?
|
||||
m_render_data.indices_count : m_render_data.geometry.indices_count(); }
|
||||
|
||||
size_t vertices_size_floats() const { return vertices_count() * Geometry::vertex_stride_floats(m_render_data.geometry.format); }
|
||||
size_t vertices_size_bytes() const { return vertices_size_floats() * sizeof(float); }
|
||||
|
||||
size_t indices_size_bytes() const { return indices_count() * Geometry::index_stride_bytes(m_render_data.geometry); }
|
||||
|
||||
const Geometry& get_geometry() const { return m_render_data.geometry; }
|
||||
|
||||
void init_from(Geometry&& data);
|
||||
#if ENABLE_SMOOTH_NORMALS
|
||||
void init_from(const TriangleMesh& mesh, bool smooth_normals = false);
|
||||
#else
|
||||
void init_from(const TriangleMesh& mesh);
|
||||
#endif // ENABLE_SMOOTH_NORMALS
|
||||
void init_from(const indexed_triangle_set& its);
|
||||
void init_from(const Polygon& polygon, float z);
|
||||
void init_from(const Polygons& polygons, float z);
|
||||
bool init_from_file(const std::string& filename);
|
||||
|
||||
void set_color(const ColorRGBA& color) { m_render_data.geometry.color = color; }
|
||||
const ColorRGBA& get_color() const { return m_render_data.geometry.color; }
|
||||
|
||||
void reset();
|
||||
void render();
|
||||
void render(const std::pair<size_t, size_t>& range);
|
||||
void render_instanced(unsigned int instances_vbo, unsigned int instances_count);
|
||||
|
||||
bool is_initialized() const { return vertices_count() > 0 && indices_count() > 0; }
|
||||
bool is_empty() const { return m_render_data.geometry.is_empty(); }
|
||||
|
||||
const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; }
|
||||
const std::string& get_filename() const { return m_filename; }
|
||||
|
||||
bool is_render_disabled() const { return m_render_disabled; }
|
||||
void enable_render() { m_render_disabled = false; }
|
||||
void disable_render() { m_render_disabled = true; }
|
||||
|
||||
size_t cpu_memory_used() const {
|
||||
size_t ret = 0;
|
||||
if (!m_render_data.geometry.vertices.empty())
|
||||
ret += vertices_size_bytes();
|
||||
if (!m_render_data.geometry.indices.empty())
|
||||
ret += indices_size_bytes();
|
||||
return ret;
|
||||
}
|
||||
size_t gpu_memory_used() const {
|
||||
size_t ret = 0;
|
||||
if (m_render_data.geometry.vertices.empty())
|
||||
ret += vertices_size_bytes();
|
||||
if (m_render_data.geometry.indices.empty())
|
||||
ret += indices_size_bytes();
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if ENABLE_GLMODEL_STATISTICS
|
||||
static void render_statistics();
|
||||
static void reset_statistics_counters() {
|
||||
s_statistics.render_calls = 0;
|
||||
s_statistics.render_instanced_calls = 0;
|
||||
}
|
||||
#endif // ENABLE_GLMODEL_STATISTICS
|
||||
|
||||
private:
|
||||
bool send_to_gpu();
|
||||
};
|
||||
|
||||
bool contains(const BuildVolume& volume, const GLModel& model, bool ignore_bottom = true);
|
||||
|
||||
// create an arrow with cylindrical stem and conical tip, with the given dimensions and resolution
|
||||
// the origin of the arrow is in the center of the stem cap
|
||||
// the arrow has its axis of symmetry along the Z axis and is pointing upward
|
||||
// used to render bed axes and sequential marker
|
||||
GLModel::Geometry stilized_arrow(unsigned int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height);
|
||||
|
||||
// create an arrow whose stem is a quarter of circle, with the given dimensions and resolution
|
||||
// the origin of the arrow is in the center of the circle
|
||||
// the arrow is contained in the 1st quadrant of the XY plane and is pointing counterclockwise
|
||||
// used to render sidebar hints for rotations
|
||||
GLModel::Geometry circular_arrow(unsigned int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness);
|
||||
|
||||
// create an arrow with the given dimensions
|
||||
// the origin of the arrow is in the center of the stem cap
|
||||
// the arrow is contained in XY plane and has its main axis along the Y axis
|
||||
// used to render sidebar hints for position and scale
|
||||
GLModel::Geometry straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness);
|
||||
|
||||
// create a diamond with the given resolution
|
||||
// the origin of the diamond is in its center
|
||||
// the diamond is contained into a box with size [1, 1, 1]
|
||||
GLModel::Geometry diamond(unsigned int resolution);
|
||||
|
||||
// create a sphere with smooth normals
|
||||
// the origin of the sphere is in its center
|
||||
GLModel::Geometry smooth_sphere(unsigned int resolution, float radius);
|
||||
// create a cylinder with smooth normals
|
||||
// the axis of the cylinder is the Z axis
|
||||
// the origin of the cylinder is the center of its bottom cap face
|
||||
GLModel::Geometry smooth_cylinder(unsigned int resolution, float radius, float height);
|
||||
// create a torus with smooth normals
|
||||
// the axis of the torus is the Z axis
|
||||
// the origin of the torus is in its center
|
||||
GLModel::Geometry smooth_torus(unsigned int primary_resolution, unsigned int secondary_resolution, float radius, float thickness);
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLModel_hpp_
|
||||
|
||||
187
src/slic3r/GUI/GLSelectionRectangle.cpp
Normal file
187
src/slic3r/GUI/GLSelectionRectangle.cpp
Normal file
@@ -0,0 +1,187 @@
|
||||
#include "GLSelectionRectangle.hpp"
|
||||
#include "Camera.hpp"
|
||||
#include "CameraUtils.hpp"
|
||||
#include "3DScene.hpp"
|
||||
#include "GLCanvas3D.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include <igl/project.h>
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
void GLSelectionRectangle::start_dragging(const Vec2d& mouse_position, EState state)
|
||||
{
|
||||
if (is_dragging() || state == EState::Off)
|
||||
return;
|
||||
|
||||
m_state = state;
|
||||
m_start_corner = mouse_position;
|
||||
m_end_corner = mouse_position;
|
||||
}
|
||||
|
||||
void GLSelectionRectangle::dragging(const Vec2d& mouse_position)
|
||||
{
|
||||
if (!is_dragging())
|
||||
return;
|
||||
|
||||
m_end_corner = mouse_position;
|
||||
}
|
||||
|
||||
std::vector<unsigned int> GLSelectionRectangle::contains(const std::vector<Vec3d>& points) const
|
||||
{
|
||||
std::vector<unsigned int> out;
|
||||
|
||||
// bounding box created from the rectangle corners - will take care of order of the corners
|
||||
const BoundingBox rectangle(Points{ Point(m_start_corner.cast<coord_t>()), Point(m_end_corner.cast<coord_t>()) });
|
||||
|
||||
// Iterate over all points and determine whether they're in the rectangle.
|
||||
const Camera &camera = wxGetApp().plater()->get_camera();
|
||||
Points points_2d = CameraUtils::project(camera, points);
|
||||
unsigned int size = static_cast<unsigned int>(points.size());
|
||||
for (unsigned int i = 0; i< size; ++i)
|
||||
if (rectangle.contains(points_2d[i]))
|
||||
out.push_back(i);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void GLSelectionRectangle::stop_dragging()
|
||||
{
|
||||
if (is_dragging())
|
||||
m_state = EState::Off;
|
||||
}
|
||||
|
||||
void GLSelectionRectangle::render(const GLCanvas3D& canvas)
|
||||
{
|
||||
if (!is_dragging())
|
||||
return;
|
||||
|
||||
const Size cnv_size = canvas.get_canvas_size();
|
||||
const float cnv_width = (float)cnv_size.get_width();
|
||||
const float cnv_height = (float)cnv_size.get_height();
|
||||
if (cnv_width == 0.0f || cnv_height == 0.0f)
|
||||
return;
|
||||
|
||||
const float cnv_inv_width = 1.0f / cnv_width;
|
||||
const float cnv_inv_height = 1.0f / cnv_height;
|
||||
const float left = 2.0f * (get_left() * cnv_inv_width - 0.5f);
|
||||
const float right = 2.0f * (get_right() * cnv_inv_width - 0.5f);
|
||||
const float top = -2.0f * (get_top() * cnv_inv_height - 0.5f);
|
||||
const float bottom = -2.0f * (get_bottom() * cnv_inv_height - 0.5f);
|
||||
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
const bool core_profile = OpenGLManager::get_gl_info().is_core_profile();
|
||||
if (!core_profile)
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
glsafe(::glLineWidth(1.5f));
|
||||
|
||||
glsafe(::glDisable(GL_DEPTH_TEST));
|
||||
|
||||
#if !ENABLE_OPENGL_ES
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
if (!core_profile) {
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
glsafe(::glPushAttrib(GL_ENABLE_BIT));
|
||||
glsafe(::glLineStipple(4, 0xAAAA));
|
||||
glsafe(::glEnable(GL_LINE_STIPPLE));
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
}
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
#endif // !ENABLE_OPENGL_ES
|
||||
|
||||
#if ENABLE_OPENGL_ES
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("dashed_lines");
|
||||
#elif ENABLE_GL_CORE_PROFILE
|
||||
GLShaderProgram* shader = core_profile ? wxGetApp().get_shader("dashed_thick_lines") : wxGetApp().get_shader("flat");
|
||||
#else
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("flat");
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
|
||||
if (!m_rectangle.is_initialized() || !m_old_start_corner.isApprox(m_start_corner) || !m_old_end_corner.isApprox(m_end_corner)) {
|
||||
m_old_start_corner = m_start_corner;
|
||||
m_old_end_corner = m_end_corner;
|
||||
m_rectangle.reset();
|
||||
|
||||
GLModel::Geometry init_data;
|
||||
#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES
|
||||
init_data.format = { GLModel::Geometry::EPrimitiveType::Lines, GLModel::Geometry::EVertexLayout::P4 };
|
||||
init_data.reserve_vertices(5);
|
||||
init_data.reserve_indices(8);
|
||||
#else
|
||||
init_data.format = { GLModel::Geometry::EPrimitiveType::LineLoop, GLModel::Geometry::EVertexLayout::P2 };
|
||||
init_data.reserve_vertices(4);
|
||||
init_data.reserve_indices(4);
|
||||
#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES
|
||||
|
||||
// vertices
|
||||
#if ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES
|
||||
const float width = right - left;
|
||||
const float height = top - bottom;
|
||||
float perimeter = 0.0f;
|
||||
|
||||
init_data.add_vertex(Vec4f(left, bottom, 0.0f, perimeter));
|
||||
perimeter += width;
|
||||
init_data.add_vertex(Vec4f(right, bottom, 0.0f, perimeter));
|
||||
perimeter += height;
|
||||
init_data.add_vertex(Vec4f(right, top, 0.0f, perimeter));
|
||||
perimeter += width;
|
||||
init_data.add_vertex(Vec4f(left, top, 0.0f, perimeter));
|
||||
perimeter += height;
|
||||
init_data.add_vertex(Vec4f(left, bottom, 0.0f, perimeter));
|
||||
|
||||
// indices
|
||||
init_data.add_line(0, 1);
|
||||
init_data.add_line(1, 2);
|
||||
init_data.add_line(2, 3);
|
||||
init_data.add_line(3, 4);
|
||||
#else
|
||||
init_data.add_vertex(Vec2f(left, bottom));
|
||||
init_data.add_vertex(Vec2f(right, bottom));
|
||||
init_data.add_vertex(Vec2f(right, top));
|
||||
init_data.add_vertex(Vec2f(left, top));
|
||||
|
||||
// indices
|
||||
init_data.add_index(0);
|
||||
init_data.add_index(1);
|
||||
init_data.add_index(2);
|
||||
init_data.add_index(3);
|
||||
#endif // ENABLE_GL_CORE_PROFILE || ENABLE_OPENGL_ES
|
||||
|
||||
m_rectangle.init_from(std::move(init_data));
|
||||
}
|
||||
|
||||
shader->set_uniform("view_model_matrix", Transform3d::Identity());
|
||||
shader->set_uniform("projection_matrix", Transform3d::Identity());
|
||||
#if ENABLE_OPENGL_ES
|
||||
shader->set_uniform("dash_size", 0.01f);
|
||||
shader->set_uniform("gap_size", 0.0075f);
|
||||
#elif ENABLE_GL_CORE_PROFILE
|
||||
if (core_profile) {
|
||||
const std::array<int, 4>& viewport = wxGetApp().plater()->get_camera().get_viewport();
|
||||
shader->set_uniform("viewport_size", Vec2d(double(viewport[2]), double(viewport[3])));
|
||||
shader->set_uniform("width", 0.25f);
|
||||
shader->set_uniform("dash_size", 0.01f);
|
||||
shader->set_uniform("gap_size", 0.0075f);
|
||||
}
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
|
||||
m_rectangle.set_color(ColorRGBA((m_state == EState::Select) ? 0.3f : 1.0f, (m_state == EState::Select) ? 1.0f : 0.3f, 0.3f, 1.0f));
|
||||
m_rectangle.render();
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
#if !ENABLE_OPENGL_ES
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
if (!core_profile)
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
glsafe(::glPopAttrib());
|
||||
#endif // !ENABLE_OPENGL_ES
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
62
src/slic3r/GUI/GLSelectionRectangle.hpp
Normal file
62
src/slic3r/GUI/GLSelectionRectangle.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#ifndef slic3r_GLSelectionRectangle_hpp_
|
||||
#define slic3r_GLSelectionRectangle_hpp_
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "GLModel.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
struct Camera;
|
||||
class GLCanvas3D;
|
||||
|
||||
class GLSelectionRectangle {
|
||||
public:
|
||||
enum class EState {
|
||||
Off,
|
||||
Select,
|
||||
Deselect
|
||||
};
|
||||
|
||||
// Initiates the rectangle.
|
||||
void start_dragging(const Vec2d& mouse_position, EState state);
|
||||
|
||||
// To be called on mouse move.
|
||||
void dragging(const Vec2d& mouse_position);
|
||||
|
||||
// Given a vector of points in world coordinates, the function returns indices of those
|
||||
// that are in the rectangle.
|
||||
std::vector<unsigned int> contains(const std::vector<Vec3d>& points) const;
|
||||
|
||||
// Disables the rectangle.
|
||||
void stop_dragging();
|
||||
|
||||
void render(const GLCanvas3D& canvas);
|
||||
|
||||
bool is_dragging() const { return m_state != EState::Off; }
|
||||
bool is_empty() const { return m_state == EState::Off || m_start_corner.isApprox(m_end_corner); }
|
||||
|
||||
EState get_state() const { return m_state; }
|
||||
|
||||
float get_width() const { return std::abs(m_start_corner.x() - m_end_corner.x()); }
|
||||
float get_height() const { return std::abs(m_start_corner.y() - m_end_corner.y()); }
|
||||
float get_left() const { return std::min(m_start_corner.x(), m_end_corner.x()); }
|
||||
float get_right() const { return std::max(m_start_corner.x(), m_end_corner.x()); }
|
||||
float get_top() const { return std::max(m_start_corner.y(), m_end_corner.y()); }
|
||||
float get_bottom() const { return std::min(m_start_corner.y(), m_end_corner.y()); }
|
||||
|
||||
private:
|
||||
EState m_state{ EState::Off };
|
||||
Vec2d m_start_corner{ Vec2d::Zero() };
|
||||
Vec2d m_end_corner{ Vec2d::Zero() };
|
||||
GLModel m_rectangle;
|
||||
Vec2d m_old_start_corner{ Vec2d::Zero() };
|
||||
Vec2d m_old_end_corner{ Vec2d::Zero() };
|
||||
};
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
|
||||
#endif // slic3r_GLGizmoSlaSupports_hpp_
|
||||
388
src/slic3r/GUI/GLShader.cpp
Normal file
388
src/slic3r/GUI/GLShader.cpp
Normal file
@@ -0,0 +1,388 @@
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "GLShader.hpp"
|
||||
|
||||
#include "3DScene.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/format.hpp"
|
||||
#include "libslic3r/Color.hpp"
|
||||
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <GL/glew.h>
|
||||
#include <cassert>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
GLShaderProgram::~GLShaderProgram()
|
||||
{
|
||||
if (m_id > 0)
|
||||
glsafe(::glDeleteProgram(m_id));
|
||||
}
|
||||
|
||||
bool GLShaderProgram::init_from_files(const std::string& name, const ShaderFilenames& filenames, const std::initializer_list<std::string_view> &defines)
|
||||
{
|
||||
// Load a shader program from file, prepend defs block.
|
||||
auto load_from_file = [](const std::string& filename, const std::string &defs) {
|
||||
std::string path = resources_dir() + "/shaders/" + filename;
|
||||
boost::nowide::ifstream s(path, boost::nowide::ifstream::binary);
|
||||
if (!s.good()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Couldn't open file: '" << path << "'";
|
||||
return std::string();
|
||||
}
|
||||
|
||||
s.seekg(0, s.end);
|
||||
int file_length = static_cast<int>(s.tellg());
|
||||
s.seekg(0, s.beg);
|
||||
std::string source(defs.size() + file_length, '\0');
|
||||
memcpy(source.data(), defs.c_str(), defs.size());
|
||||
s.read(source.data() + defs.size(), file_length);
|
||||
if (!s.good()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Error while loading file: '" << path << "'";
|
||||
return std::string();
|
||||
}
|
||||
s.close();
|
||||
|
||||
if (! defs.empty()) {
|
||||
// Extract the version and flip the order of "defines" and version in the source block.
|
||||
size_t idx = source.find("\n", defs.size());
|
||||
if (idx != std::string::npos && strncmp(source.c_str() + defs.size(), "#version", 8) == 0) {
|
||||
// Swap the version line with the defines.
|
||||
size_t len = idx - defs.size() + 1;
|
||||
memmove(source.data(), source.c_str() + defs.size(), len);
|
||||
memcpy(source.data() + len, defs.c_str(), defs.size());
|
||||
}
|
||||
}
|
||||
|
||||
return source;
|
||||
};
|
||||
|
||||
// Create a block of C "defines" from list of symbols.
|
||||
std::string defines_program;
|
||||
for (std::string_view def : defines)
|
||||
// Our shaders are stored with "\r\n", thus replicate the same here for consistency. Likely "\n" would suffice,
|
||||
// but we don't know all the OpenGL shader compilers around.
|
||||
defines_program += format("#define %s\r\n", def);
|
||||
|
||||
ShaderSources sources = {};
|
||||
for (size_t i = 0; i < static_cast<size_t>(EShaderType::Count); ++i) {
|
||||
sources[i] = filenames[i].empty() ? std::string() : load_from_file(filenames[i], defines_program);
|
||||
}
|
||||
|
||||
bool valid = !sources[static_cast<size_t>(EShaderType::Vertex)].empty() && !sources[static_cast<size_t>(EShaderType::Fragment)].empty() && sources[static_cast<size_t>(EShaderType::Compute)].empty();
|
||||
valid |= !sources[static_cast<size_t>(EShaderType::Compute)].empty() && sources[static_cast<size_t>(EShaderType::Vertex)].empty() && sources[static_cast<size_t>(EShaderType::Fragment)].empty() &&
|
||||
sources[static_cast<size_t>(EShaderType::Geometry)].empty() && sources[static_cast<size_t>(EShaderType::TessEvaluation)].empty() && sources[static_cast<size_t>(EShaderType::TessControl)].empty();
|
||||
|
||||
return valid ? init_from_texts(name, sources) : false;
|
||||
}
|
||||
|
||||
bool GLShaderProgram::init_from_texts(const std::string& name, const ShaderSources& sources)
|
||||
{
|
||||
auto shader_type_as_string = [](EShaderType type) {
|
||||
switch (type)
|
||||
{
|
||||
case EShaderType::Vertex: { return "vertex"; }
|
||||
case EShaderType::Fragment: { return "fragment"; }
|
||||
case EShaderType::Geometry: { return "geometry"; }
|
||||
case EShaderType::TessEvaluation: { return "tesselation evaluation"; }
|
||||
case EShaderType::TessControl: { return "tesselation control"; }
|
||||
case EShaderType::Compute: { return "compute"; }
|
||||
default: { return "unknown"; }
|
||||
}
|
||||
};
|
||||
|
||||
auto create_shader = [](EShaderType type) {
|
||||
GLuint id = 0;
|
||||
switch (type)
|
||||
{
|
||||
case EShaderType::Vertex: { id = ::glCreateShader(GL_VERTEX_SHADER); glcheck(); break; }
|
||||
case EShaderType::Fragment: { id = ::glCreateShader(GL_FRAGMENT_SHADER); glcheck(); break; }
|
||||
case EShaderType::Geometry: { id = ::glCreateShader(GL_GEOMETRY_SHADER); glcheck(); break; }
|
||||
case EShaderType::TessEvaluation: { id = ::glCreateShader(GL_TESS_EVALUATION_SHADER); glcheck(); break; }
|
||||
case EShaderType::TessControl: { id = ::glCreateShader(GL_TESS_CONTROL_SHADER); glcheck(); break; }
|
||||
case EShaderType::Compute: { id = ::glCreateShader(GL_COMPUTE_SHADER); glcheck(); break; }
|
||||
default: { break; }
|
||||
}
|
||||
|
||||
return (id == 0) ? std::make_pair(false, GLuint(0)) : std::make_pair(true, id);
|
||||
};
|
||||
|
||||
auto release_shaders = [](const std::array<GLuint, static_cast<size_t>(EShaderType::Count)>& shader_ids) {
|
||||
for (size_t i = 0; i < static_cast<size_t>(EShaderType::Count); ++i) {
|
||||
if (shader_ids[i] > 0)
|
||||
glsafe(::glDeleteShader(shader_ids[i]));
|
||||
}
|
||||
};
|
||||
|
||||
assert(m_id == 0);
|
||||
|
||||
m_name = name;
|
||||
|
||||
std::array<GLuint, static_cast<size_t>(EShaderType::Count)> shader_ids = { 0 };
|
||||
|
||||
for (size_t i = 0; i < static_cast<size_t>(EShaderType::Count); ++i) {
|
||||
const std::string& source = sources[i];
|
||||
if (!source.empty()) {
|
||||
EShaderType type = static_cast<EShaderType>(i);
|
||||
auto [result, id] = create_shader(type);
|
||||
if (result)
|
||||
shader_ids[i] = id;
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(error) << "glCreateShader() failed for " << shader_type_as_string(type) << " shader of shader program '" << name << "'";
|
||||
|
||||
// release shaders
|
||||
release_shaders(shader_ids);
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* source_ptr = source.c_str();
|
||||
glsafe(::glShaderSource(id, 1, &source_ptr, nullptr));
|
||||
glsafe(::glCompileShader(id));
|
||||
GLint params;
|
||||
glsafe(::glGetShaderiv(id, GL_COMPILE_STATUS, ¶ms));
|
||||
if (params == GL_FALSE) {
|
||||
// Compilation failed.
|
||||
glsafe(::glGetShaderiv(id, GL_INFO_LOG_LENGTH, ¶ms));
|
||||
std::vector<char> msg(params);
|
||||
glsafe(::glGetShaderInfoLog(id, params, ¶ms, msg.data()));
|
||||
BOOST_LOG_TRIVIAL(error) << "Unable to compile " << shader_type_as_string(type) << " shader of shader program '" << name << "':\n" << msg.data();
|
||||
|
||||
// release shaders
|
||||
release_shaders(shader_ids);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_id = ::glCreateProgram();
|
||||
glcheck();
|
||||
if (m_id == 0) {
|
||||
BOOST_LOG_TRIVIAL(error) << "glCreateProgram() failed for shader program '" << name << "'";
|
||||
|
||||
// release shaders
|
||||
release_shaders(shader_ids);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < static_cast<size_t>(EShaderType::Count); ++i) {
|
||||
if (shader_ids[i] > 0)
|
||||
glsafe(::glAttachShader(m_id, shader_ids[i]));
|
||||
}
|
||||
|
||||
glsafe(::glLinkProgram(m_id));
|
||||
GLint params;
|
||||
glsafe(::glGetProgramiv(m_id, GL_LINK_STATUS, ¶ms));
|
||||
if (params == GL_FALSE) {
|
||||
// Linking failed.
|
||||
glsafe(::glGetProgramiv(m_id, GL_INFO_LOG_LENGTH, ¶ms));
|
||||
std::vector<char> msg(params);
|
||||
glsafe(::glGetProgramInfoLog(m_id, params, ¶ms, msg.data()));
|
||||
BOOST_LOG_TRIVIAL(error) << "Unable to link shader program '" << name << "':\n" << msg.data();
|
||||
|
||||
// release shaders
|
||||
release_shaders(shader_ids);
|
||||
|
||||
// release shader program
|
||||
glsafe(::glDeleteProgram(m_id));
|
||||
m_id = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// release shaders, they are no more needed
|
||||
release_shaders(shader_ids);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLShaderProgram::start_using() const
|
||||
{
|
||||
assert(m_id > 0);
|
||||
glsafe(::glUseProgram(m_id));
|
||||
}
|
||||
|
||||
void GLShaderProgram::stop_using() const
|
||||
{
|
||||
glsafe(::glUseProgram(0));
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, int value) const
|
||||
{
|
||||
if (id >= 0)
|
||||
glsafe(::glUniform1i(id, value));
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, bool value) const
|
||||
{
|
||||
set_uniform(id, value ? 1 : 0);
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, float value) const
|
||||
{
|
||||
if (id >= 0)
|
||||
glsafe(::glUniform1f(id, value));
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, double value) const
|
||||
{
|
||||
set_uniform(id, static_cast<float>(value));
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const std::array<int, 2>& value) const
|
||||
{
|
||||
if (id >= 0)
|
||||
glsafe(::glUniform2iv(id, 1, static_cast<const GLint*>(value.data())));
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const std::array<int, 3>& value) const
|
||||
{
|
||||
if (id >= 0)
|
||||
glsafe(::glUniform3iv(id, 1, static_cast<const GLint*>(value.data())));
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const std::array<int, 4>& value) const
|
||||
{
|
||||
if (id >= 0)
|
||||
glsafe(::glUniform4iv(id, 1, static_cast<const GLint*>(value.data())));
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const std::array<float, 2>& value) const
|
||||
{
|
||||
if (id >= 0)
|
||||
glsafe(::glUniform2fv(id, 1, static_cast<const GLfloat*>(value.data())));
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const std::array<float, 3>& value) const
|
||||
{
|
||||
if (id >= 0)
|
||||
glsafe(::glUniform3fv(id, 1, static_cast<const GLfloat*>(value.data())));
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const std::array<float, 4>& value) const
|
||||
{
|
||||
if (id >= 0)
|
||||
glsafe(::glUniform4fv(id, 1, static_cast<const GLfloat*>(value.data())));
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const std::array<double, 4>& value) const
|
||||
{
|
||||
const std::array<float, 4> f_value = { float(value[0]), float(value[1]), float(value[2]), float(value[3]) };
|
||||
set_uniform(id, f_value);
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const float* value, size_t size) const
|
||||
{
|
||||
if (id >= 0) {
|
||||
if (size == 1)
|
||||
set_uniform(id, value[0]);
|
||||
else if (size == 2)
|
||||
glsafe(::glUniform2fv(id, 1, static_cast<const GLfloat*>(value)));
|
||||
else if (size == 3)
|
||||
glsafe(::glUniform3fv(id, 1, static_cast<const GLfloat*>(value)));
|
||||
else if (size == 4)
|
||||
glsafe(::glUniform4fv(id, 1, static_cast<const GLfloat*>(value)));
|
||||
}
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const Transform3f& value) const
|
||||
{
|
||||
if (id >= 0)
|
||||
glsafe(::glUniformMatrix4fv(id, 1, GL_FALSE, static_cast<const GLfloat*>(value.matrix().data())));
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const Transform3d& value) const
|
||||
{
|
||||
set_uniform(id, value.cast<float>());
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const Matrix3f& value) const
|
||||
{
|
||||
if (id >= 0)
|
||||
glsafe(::glUniformMatrix3fv(id, 1, GL_FALSE, static_cast<const GLfloat*>(value.data())));
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const Matrix3d& value) const
|
||||
{
|
||||
set_uniform(id, (Matrix3f)value.cast<float>());
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const Matrix4f& value) const
|
||||
{
|
||||
if (id >= 0)
|
||||
glsafe(::glUniformMatrix4fv(id, 1, GL_FALSE, static_cast<const GLfloat*>(value.data())));
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const Matrix4d& value) const
|
||||
{
|
||||
set_uniform(id, (Matrix4f)value.cast<float>());
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const Vec2f& value) const
|
||||
{
|
||||
if (id >= 0)
|
||||
glsafe(::glUniform2fv(id, 1, static_cast<const GLfloat*>(value.data())));
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const Vec2d& value) const
|
||||
{
|
||||
set_uniform(id, static_cast<Vec2f>(value.cast<float>()));
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const Vec3f& value) const
|
||||
{
|
||||
if (id >= 0)
|
||||
glsafe(::glUniform3fv(id, 1, static_cast<const GLfloat*>(value.data())));
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const Vec3d& value) const
|
||||
{
|
||||
set_uniform(id, static_cast<Vec3f>(value.cast<float>()));
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const ColorRGB& value) const
|
||||
{
|
||||
set_uniform(id, value.data(), 3);
|
||||
}
|
||||
|
||||
void GLShaderProgram::set_uniform(int id, const ColorRGBA& value) const
|
||||
{
|
||||
set_uniform(id, value.data(), 4);
|
||||
}
|
||||
|
||||
int GLShaderProgram::get_attrib_location(const char* name) const
|
||||
{
|
||||
assert(m_id > 0);
|
||||
|
||||
if (m_id <= 0)
|
||||
// Shader program not loaded. This should not happen.
|
||||
return -1;
|
||||
|
||||
auto it = std::find_if(m_attrib_location_cache.begin(), m_attrib_location_cache.end(), [name](const auto& p) { return p.first == name; });
|
||||
if (it != m_attrib_location_cache.end())
|
||||
// Attrib ID cached.
|
||||
return it->second;
|
||||
|
||||
int id = ::glGetAttribLocation(m_id, name);
|
||||
const_cast<GLShaderProgram*>(this)->m_attrib_location_cache.push_back({ name, id });
|
||||
return id;
|
||||
}
|
||||
|
||||
int GLShaderProgram::get_uniform_location(const char* name) const
|
||||
{
|
||||
assert(m_id > 0);
|
||||
|
||||
if (m_id <= 0)
|
||||
// Shader program not loaded. This should not happen.
|
||||
return -1;
|
||||
|
||||
auto it = std::find_if(m_uniform_location_cache.begin(), m_uniform_location_cache.end(), [name](const auto &p) { return p.first == name; });
|
||||
if (it != m_uniform_location_cache.end())
|
||||
// Uniform ID cached.
|
||||
return it->second;
|
||||
|
||||
int id = ::glGetUniformLocation(m_id, name);
|
||||
const_cast<GLShaderProgram*>(this)->m_uniform_location_cache.push_back({ name, id });
|
||||
return id;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
108
src/slic3r/GUI/GLShader.hpp
Normal file
108
src/slic3r/GUI/GLShader.hpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#ifndef slic3r_GLShader_hpp_
|
||||
#define slic3r_GLShader_hpp_
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class ColorRGB;
|
||||
class ColorRGBA;
|
||||
|
||||
class GLShaderProgram
|
||||
{
|
||||
public:
|
||||
enum class EShaderType
|
||||
{
|
||||
Vertex,
|
||||
Fragment,
|
||||
Geometry,
|
||||
TessEvaluation,
|
||||
TessControl,
|
||||
Compute,
|
||||
Count
|
||||
};
|
||||
|
||||
typedef std::array<std::string, static_cast<size_t>(EShaderType::Count)> ShaderFilenames;
|
||||
typedef std::array<std::string, static_cast<size_t>(EShaderType::Count)> ShaderSources;
|
||||
|
||||
private:
|
||||
std::string m_name;
|
||||
unsigned int m_id{ 0 };
|
||||
std::vector<std::pair<std::string, int>> m_attrib_location_cache;
|
||||
std::vector<std::pair<std::string, int>> m_uniform_location_cache;
|
||||
|
||||
public:
|
||||
~GLShaderProgram();
|
||||
|
||||
bool init_from_files(const std::string& name, const ShaderFilenames& filenames, const std::initializer_list<std::string_view> &defines = {});
|
||||
bool init_from_texts(const std::string& name, const ShaderSources& sources);
|
||||
|
||||
const std::string& get_name() const { return m_name; }
|
||||
unsigned int get_id() const { return m_id; }
|
||||
|
||||
void start_using() const;
|
||||
void stop_using() const;
|
||||
|
||||
void set_uniform(const char* name, int value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, bool value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, float value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, double value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const std::array<int, 2>& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const std::array<int, 3>& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const std::array<int, 4>& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const std::array<float, 2>& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const std::array<float, 3>& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const std::array<float, 4>& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const std::array<double, 4>& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const float* value, size_t size) const { set_uniform(get_uniform_location(name), value, size); }
|
||||
void set_uniform(const char* name, const Transform3f& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const Transform3d& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const Matrix3f& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const Matrix3d& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const Matrix4f& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const Matrix4d& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const Vec2f& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const Vec2d& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const Vec3f& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const Vec3d& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const ColorRGB& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
void set_uniform(const char* name, const ColorRGBA& value) const { set_uniform(get_uniform_location(name), value); }
|
||||
|
||||
void set_uniform(int id, int value) const;
|
||||
void set_uniform(int id, bool value) const;
|
||||
void set_uniform(int id, float value) const;
|
||||
void set_uniform(int id, double value) const;
|
||||
void set_uniform(int id, const std::array<int, 2>& value) const;
|
||||
void set_uniform(int id, const std::array<int, 3>& value) const;
|
||||
void set_uniform(int id, const std::array<int, 4>& value) const;
|
||||
void set_uniform(int id, const std::array<float, 2>& value) const;
|
||||
void set_uniform(int id, const std::array<float, 3>& value) const;
|
||||
void set_uniform(int id, const std::array<float, 4>& value) const;
|
||||
void set_uniform(int id, const std::array<double, 4>& value) const;
|
||||
void set_uniform(int id, const float* value, size_t size) const;
|
||||
void set_uniform(int id, const Transform3f& value) const;
|
||||
void set_uniform(int id, const Transform3d& value) const;
|
||||
void set_uniform(int id, const Matrix3f& value) const;
|
||||
void set_uniform(int id, const Matrix3d& value) const;
|
||||
void set_uniform(int id, const Matrix4f& value) const;
|
||||
void set_uniform(int id, const Matrix4d& value) const;
|
||||
void set_uniform(int id, const Vec2f& value) const;
|
||||
void set_uniform(int id, const Vec2d& value) const;
|
||||
void set_uniform(int id, const Vec3f& value) const;
|
||||
void set_uniform(int id, const Vec3d& value) const;
|
||||
void set_uniform(int id, const ColorRGB& value) const;
|
||||
void set_uniform(int id, const ColorRGBA& value) const;
|
||||
|
||||
// returns -1 if not found
|
||||
int get_attrib_location(const char* name) const;
|
||||
// returns -1 if not found
|
||||
int get_uniform_location(const char* name) const;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_GLShader_hpp_ */
|
||||
123
src/slic3r/GUI/GLShadersManager.cpp
Normal file
123
src/slic3r/GUI/GLShadersManager.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Platform.hpp"
|
||||
#include "GLShadersManager.hpp"
|
||||
#include "3DScene.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
#include "OpenGLManager.hpp"
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
|
||||
#include <cassert>
|
||||
#include <algorithm>
|
||||
#include <string_view>
|
||||
using namespace std::literals;
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
std::pair<bool, std::string> GLShadersManager::init()
|
||||
{
|
||||
std::string error;
|
||||
|
||||
auto append_shader = [this, &error](const std::string& name, const GLShaderProgram::ShaderFilenames& filenames,
|
||||
const std::initializer_list<std::string_view> &defines = {}) {
|
||||
m_shaders.push_back(std::make_unique<GLShaderProgram>());
|
||||
if (!m_shaders.back()->init_from_files(name, filenames, defines)) {
|
||||
error += name + "\n";
|
||||
// if any error happens while initializating the shader, we remove it from the list
|
||||
m_shaders.pop_back();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
assert(m_shaders.empty());
|
||||
|
||||
bool valid = true;
|
||||
|
||||
#if ENABLE_OPENGL_ES
|
||||
const std::string prefix = "ES/";
|
||||
// used to render wireframed triangles
|
||||
valid &= append_shader("wireframe", { prefix + "wireframe.vs", prefix + "wireframe.fs" });
|
||||
#else
|
||||
const std::string prefix = GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 1) ? "140/" : "110/";
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
// imgui shader
|
||||
valid &= append_shader("imgui", { prefix + "imgui.vs", prefix + "imgui.fs" });
|
||||
// basic shader, used to render all what was previously rendered using the immediate mode
|
||||
valid &= append_shader("flat", { prefix + "flat.vs", prefix + "flat.fs" });
|
||||
// basic shader with plane clipping, used to render volumes in picking pass
|
||||
valid &= append_shader("flat_clip", { prefix + "flat_clip.vs", prefix + "flat_clip.fs" });
|
||||
// basic shader for textures, used to render textures
|
||||
valid &= append_shader("flat_texture", { prefix + "flat_texture.vs", prefix + "flat_texture.fs" });
|
||||
// used to render 3D scene background
|
||||
valid &= append_shader("background", { prefix + "background.vs", prefix + "background.fs" });
|
||||
#if ENABLE_OPENGL_ES
|
||||
// used to render dashed lines
|
||||
valid &= append_shader("dashed_lines", { prefix + "dashed_lines.vs", prefix + "dashed_lines.fs" });
|
||||
#elif ENABLE_GL_CORE_PROFILE
|
||||
if (GUI::OpenGLManager::get_gl_info().is_core_profile())
|
||||
// used to render thick and/or dashed lines
|
||||
valid &= append_shader("dashed_thick_lines", { prefix + "dashed_thick_lines.vs", prefix + "dashed_thick_lines.fs", prefix + "dashed_thick_lines.gs" });
|
||||
#endif // ENABLE_OPENGL_ES
|
||||
// used to render toolpaths center of gravity
|
||||
valid &= append_shader("toolpaths_cog", { prefix + "toolpaths_cog.vs", prefix + "toolpaths_cog.fs" });
|
||||
// used to render bed axes and model, selection hints, gcode sequential view marker model, preview shells, options in gcode preview
|
||||
valid &= append_shader("gouraud_light", { prefix + "gouraud_light.vs", prefix + "gouraud_light.fs" });
|
||||
// extend "gouraud_light" by adding clipping, used in sla gizmos
|
||||
valid &= append_shader("gouraud_light_clip", { prefix + "gouraud_light_clip.vs", prefix + "gouraud_light_clip.fs" });
|
||||
// used to render printbed
|
||||
valid &= append_shader("printbed", { prefix + "printbed.vs", prefix + "printbed.fs" });
|
||||
// used to render options in gcode preview
|
||||
if (GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 3)) {
|
||||
valid &= append_shader("gouraud_light_instanced", { prefix + "gouraud_light_instanced.vs", prefix + "gouraud_light_instanced.fs" });
|
||||
}
|
||||
// used to render objects in 3d editor
|
||||
valid &= append_shader("gouraud", { prefix + "gouraud.vs", prefix + "gouraud.fs" }
|
||||
#if ENABLE_ENVIRONMENT_MAP
|
||||
, { "ENABLE_ENVIRONMENT_MAP"sv }
|
||||
#endif // ENABLE_ENVIRONMENT_MAP
|
||||
);
|
||||
// used to render variable layers heights in 3d editor
|
||||
valid &= append_shader("variable_layer_height", { prefix + "variable_layer_height.vs", prefix + "variable_layer_height.fs" });
|
||||
// used to render highlight contour around selected triangles inside the multi-material gizmo
|
||||
valid &= append_shader("mm_contour", { prefix + "mm_contour.vs", prefix + "mm_contour.fs" });
|
||||
// Used to render painted triangles inside the multi-material gizmo. Triangle normals are computed inside fragment shader.
|
||||
// For Apple's on Arm CPU computed triangle normals inside fragment shader using dFdx and dFdy has the opposite direction.
|
||||
// Because of this, objects had darker colors inside the multi-material gizmo.
|
||||
// Based on https://stackoverflow.com/a/66206648, the similar behavior was also spotted on some other devices with Arm CPU.
|
||||
// Since macOS 12 (Monterey), this issue with the opposite direction on Apple's Arm CPU seems to be fixed, and computed
|
||||
// triangle normals inside fragment shader have the right direction.
|
||||
if (platform_flavor() == PlatformFlavor::OSXOnArm && wxPlatformInfo::Get().GetOSMajorVersion() < 12)
|
||||
valid &= append_shader("mm_gouraud", { prefix + "mm_gouraud.vs", prefix + "mm_gouraud.fs" }, { "FLIP_TRIANGLE_NORMALS"sv });
|
||||
else
|
||||
valid &= append_shader("mm_gouraud", { prefix + "mm_gouraud.vs", prefix + "mm_gouraud.fs" });
|
||||
|
||||
return { valid, error };
|
||||
}
|
||||
|
||||
void GLShadersManager::shutdown()
|
||||
{
|
||||
m_shaders.clear();
|
||||
}
|
||||
|
||||
GLShaderProgram* GLShadersManager::get_shader(const std::string& shader_name)
|
||||
{
|
||||
auto it = std::find_if(m_shaders.begin(), m_shaders.end(), [&shader_name](std::unique_ptr<GLShaderProgram>& p) { return p->get_name() == shader_name; });
|
||||
return (it != m_shaders.end()) ? it->get() : nullptr;
|
||||
}
|
||||
|
||||
GLShaderProgram* GLShadersManager::get_current_shader()
|
||||
{
|
||||
GLint id = 0;
|
||||
glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, &id));
|
||||
if (id == 0)
|
||||
return nullptr;
|
||||
|
||||
auto it = std::find_if(m_shaders.begin(), m_shaders.end(), [id](std::unique_ptr<GLShaderProgram>& p) { return static_cast<GLint>(p->get_id()) == id; });
|
||||
return (it != m_shaders.end()) ? it->get() : nullptr;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
30
src/slic3r/GUI/GLShadersManager.hpp
Normal file
30
src/slic3r/GUI/GLShadersManager.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef slic3r_GLShadersManager_hpp_
|
||||
#define slic3r_GLShadersManager_hpp_
|
||||
|
||||
#include "GLShader.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class GLShadersManager
|
||||
{
|
||||
std::vector<std::unique_ptr<GLShaderProgram>> m_shaders;
|
||||
|
||||
public:
|
||||
std::pair<bool, std::string> init();
|
||||
// call this method before to release the OpenGL context
|
||||
void shutdown();
|
||||
|
||||
// returns nullptr if not found
|
||||
GLShaderProgram* get_shader(const std::string& shader_name);
|
||||
|
||||
// returns currently active shader, nullptr if none
|
||||
GLShaderProgram* get_current_shader();
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLShadersManager_hpp_
|
||||
689
src/slic3r/GUI/GLTexture.cpp
Normal file
689
src/slic3r/GUI/GLTexture.cpp
Normal file
@@ -0,0 +1,689 @@
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "GLTexture.hpp"
|
||||
|
||||
#include "3DScene.hpp"
|
||||
#include "OpenGLManager.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "GLModel.hpp"
|
||||
#include "BitmapCache.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include <wx/image.h>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
|
||||
#define STB_DXT_IMPLEMENTATION
|
||||
#include "stb_dxt/stb_dxt.h"
|
||||
|
||||
#include <nanosvg/nanosvg.h>
|
||||
#include <nanosvg/nanosvgrast.h>
|
||||
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
void GLTexture::Compressor::reset()
|
||||
{
|
||||
if (m_thread.joinable()) {
|
||||
m_abort_compressing = true;
|
||||
m_thread.join();
|
||||
m_levels.clear();
|
||||
m_num_levels_compressed = 0;
|
||||
m_abort_compressing = false;
|
||||
}
|
||||
assert(m_levels.empty());
|
||||
assert(m_abort_compressing == false);
|
||||
assert(m_num_levels_compressed == 0);
|
||||
}
|
||||
|
||||
void GLTexture::Compressor::start_compressing()
|
||||
{
|
||||
// The worker thread should be stopped already.
|
||||
assert(! m_thread.joinable());
|
||||
assert(! m_levels.empty());
|
||||
assert(m_abort_compressing == false);
|
||||
assert(m_num_levels_compressed == 0);
|
||||
if (! m_levels.empty()) {
|
||||
std::thread thrd(&GLTexture::Compressor::compress, this);
|
||||
m_thread = std::move(thrd);
|
||||
}
|
||||
}
|
||||
|
||||
bool GLTexture::Compressor::unsent_compressed_data_available() const
|
||||
{
|
||||
if (m_levels.empty())
|
||||
return false;
|
||||
// Querying the atomic m_num_levels_compressed value synchronizes processor caches, so that the data of m_levels modified by the worker thread are accessible to the calling thread.
|
||||
unsigned int num_compressed = m_num_levels_compressed;
|
||||
for (unsigned int i = 0; i < num_compressed; ++ i)
|
||||
if (! m_levels[i].sent_to_gpu && ! m_levels[i].compressed_data.empty())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void GLTexture::Compressor::send_compressed_data_to_gpu()
|
||||
{
|
||||
// this method should be called inside the main thread of Slicer or a new OpenGL context (sharing resources) would be needed
|
||||
if (m_levels.empty())
|
||||
return;
|
||||
|
||||
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, m_texture.m_id));
|
||||
// Querying the atomic m_num_levels_compressed value synchronizes processor caches, so that the dat of m_levels modified by the worker thread are accessible to the calling thread.
|
||||
int num_compressed = (int)m_num_levels_compressed;
|
||||
for (int i = 0; i < num_compressed; ++ i) {
|
||||
Level& level = m_levels[i];
|
||||
if (! level.sent_to_gpu && ! level.compressed_data.empty()) {
|
||||
glsafe(::glCompressedTexSubImage2D(GL_TEXTURE_2D, (GLint)i, 0, 0, (GLsizei)level.w, (GLsizei)level.h, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)level.compressed_data.size(), (const GLvoid*)level.compressed_data.data()));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, i));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (i > 0) ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR));
|
||||
level.sent_to_gpu = true;
|
||||
// we are done with the compressed data, we can discard it
|
||||
level.compressed_data.clear();
|
||||
}
|
||||
}
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
|
||||
if (num_compressed == (int)m_levels.size())
|
||||
// Finalize the worker thread, close it.
|
||||
this->reset();
|
||||
}
|
||||
|
||||
void GLTexture::Compressor::compress()
|
||||
{
|
||||
// reference: https://github.com/Cyan4973/RygsDXTc
|
||||
|
||||
assert(m_num_levels_compressed == 0);
|
||||
assert(m_abort_compressing == false);
|
||||
|
||||
for (Level& level : m_levels) {
|
||||
if (m_abort_compressing)
|
||||
break;
|
||||
|
||||
// stb_dxt library, despite claiming that the needed size of the destination buffer is equal to (source buffer size)/4,
|
||||
// crashes if doing so, requiring a minimum of 64 bytes and up to a third of the source buffer size, so we set the destination buffer initial size to be half the source buffer size
|
||||
level.compressed_data = std::vector<unsigned char>(std::max((unsigned int)64, (unsigned int)level.src_data.size() / 2), 0);
|
||||
int compressed_size = 0;
|
||||
rygCompress(level.compressed_data.data(), level.src_data.data(), level.w, level.h, 1, compressed_size);
|
||||
level.compressed_data.resize(compressed_size);
|
||||
|
||||
// we are done with the source data, we can discard it
|
||||
level.src_data.clear();
|
||||
++ m_num_levels_compressed;
|
||||
}
|
||||
}
|
||||
|
||||
GLTexture::Quad_UVs GLTexture::FullTextureUVs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } };
|
||||
|
||||
GLTexture::GLTexture()
|
||||
: m_compressor(*this)
|
||||
{
|
||||
}
|
||||
|
||||
GLTexture::~GLTexture()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy)
|
||||
{
|
||||
reset();
|
||||
|
||||
if (!boost::filesystem::exists(filename))
|
||||
return false;
|
||||
|
||||
if (boost::algorithm::iends_with(filename, ".png"))
|
||||
return load_from_png(filename, use_mipmaps, compression_type, apply_anisotropy);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GLTexture::load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px)
|
||||
{
|
||||
reset();
|
||||
|
||||
if (!boost::filesystem::exists(filename))
|
||||
return false;
|
||||
|
||||
if (boost::algorithm::iends_with(filename, ".svg"))
|
||||
return load_from_svg(filename, use_mipmaps, compress, apply_anisotropy, max_size_px);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GLTexture::load_from_svg_files_as_sprites_array(const std::vector<std::string>& filenames, const std::vector<std::pair<int, bool>>& states, unsigned int sprite_size_px, bool compress)
|
||||
{
|
||||
reset();
|
||||
|
||||
if (filenames.empty() || states.empty() || sprite_size_px == 0)
|
||||
return false;
|
||||
|
||||
// every tile needs to have a 1px border around it to avoid artifacts when linear sampling on its edges
|
||||
unsigned int sprite_size_px_ex = sprite_size_px + 1;
|
||||
|
||||
m_width = 1 + (int)(sprite_size_px_ex * states.size());
|
||||
m_height = 1 + (int)(sprite_size_px_ex * filenames.size());
|
||||
|
||||
int n_pixels = m_width * m_height;
|
||||
int sprite_n_pixels = sprite_size_px_ex * sprite_size_px_ex;
|
||||
int sprite_stride = sprite_size_px_ex * 4;
|
||||
int sprite_bytes = sprite_n_pixels * 4;
|
||||
|
||||
if (n_pixels <= 0) {
|
||||
reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<unsigned char> data(n_pixels * 4, 0);
|
||||
std::vector<unsigned char> sprite_data(sprite_bytes, 0);
|
||||
std::vector<unsigned char> sprite_white_only_data(sprite_bytes, 0);
|
||||
std::vector<unsigned char> sprite_gray_only_data(sprite_bytes, 0);
|
||||
std::vector<unsigned char> output_data(sprite_bytes, 0);
|
||||
|
||||
NSVGrasterizer* rast = nsvgCreateRasterizer();
|
||||
if (rast == nullptr) {
|
||||
reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
int sprite_id = -1;
|
||||
for (const std::string& filename : filenames) {
|
||||
++sprite_id;
|
||||
|
||||
if (!boost::filesystem::exists(filename))
|
||||
continue;
|
||||
|
||||
if (!boost::algorithm::iends_with(filename, ".svg"))
|
||||
continue;
|
||||
|
||||
NSVGimage* image = BitmapCache::nsvgParseFromFileWithReplace(filename.c_str(), "px", 96.0f, {});
|
||||
if (image == nullptr)
|
||||
continue;
|
||||
|
||||
float scale = (float)sprite_size_px / std::max(image->width, image->height);
|
||||
|
||||
// offset by 1 to leave the first pixel empty (both in x and y)
|
||||
nsvgRasterize(rast, image, 1, 1, scale, sprite_data.data(), sprite_size_px_ex, sprite_size_px_ex, sprite_stride);
|
||||
|
||||
// makes white only copy of the sprite
|
||||
::memcpy((void*)sprite_white_only_data.data(), (const void*)sprite_data.data(), sprite_bytes);
|
||||
for (int i = 0; i < sprite_n_pixels; ++i) {
|
||||
int offset = i * 4;
|
||||
if (sprite_white_only_data.data()[offset] != 0)
|
||||
//B13
|
||||
//::memset((void*)&sprite_white_only_data.data()[offset], 255, 3);
|
||||
::memset((void*)&sprite_white_only_data.data()[offset], 61, 1);
|
||||
if (sprite_white_only_data.data()[offset+1] != 0)
|
||||
::memset((void*)&sprite_white_only_data.data()[offset+1], 121, 1);
|
||||
if (sprite_white_only_data.data()[offset+2] != 0)
|
||||
::memset((void*)&sprite_white_only_data.data()[offset+2], 251, 1);
|
||||
}
|
||||
|
||||
// makes gray only copy of the sprite
|
||||
::memcpy((void*)sprite_gray_only_data.data(), (const void*)sprite_data.data(), sprite_bytes);
|
||||
for (int i = 0; i < sprite_n_pixels; ++i) {
|
||||
int offset = i * 4;
|
||||
if (sprite_gray_only_data.data()[offset] != 0)
|
||||
//B13
|
||||
//::memset((void*)&sprite_gray_only_data.data()[offset], 128, 3);
|
||||
::memset((void*)&sprite_gray_only_data.data()[offset], 180, 1);
|
||||
if (sprite_gray_only_data.data()[offset+1] != 0)
|
||||
::memset((void*)&sprite_gray_only_data.data()[offset+1], 201, 1);
|
||||
if (sprite_gray_only_data.data()[offset+2] != 0)
|
||||
::memset((void*)&sprite_gray_only_data.data()[offset+2], 253, 1);
|
||||
}
|
||||
|
||||
int sprite_offset_px = sprite_id * (int)sprite_size_px_ex * m_width;
|
||||
int state_id = -1;
|
||||
for (const std::pair<int, bool>& state : states) {
|
||||
++state_id;
|
||||
|
||||
// select the sprite variant
|
||||
std::vector<unsigned char>* src = nullptr;
|
||||
switch (state.first)
|
||||
{
|
||||
case 1: { src = &sprite_white_only_data; break; }
|
||||
case 2: { src = &sprite_gray_only_data; break; }
|
||||
default: { src = &sprite_data; break; }
|
||||
}
|
||||
|
||||
::memcpy((void*)output_data.data(), (const void*)src->data(), sprite_bytes);
|
||||
// applies background, if needed
|
||||
if (state.second) {
|
||||
float inv_255 = 1.0f / 255.0f;
|
||||
// offset by 1 to leave the first pixel empty (both in x and y)
|
||||
for (unsigned int r = 1; r <= sprite_size_px; ++r) {
|
||||
unsigned int offset_r = r * sprite_size_px_ex;
|
||||
for (unsigned int c = 1; c <= sprite_size_px; ++c) {
|
||||
unsigned int offset = (offset_r + c) * 4;
|
||||
float alpha = (float)output_data.data()[offset + 3] * inv_255;
|
||||
output_data.data()[offset + 0] = (unsigned char)(output_data.data()[offset + 0] * alpha);
|
||||
output_data.data()[offset + 1] = (unsigned char)(output_data.data()[offset + 1] * alpha);
|
||||
output_data.data()[offset + 2] = (unsigned char)(output_data.data()[offset + 2] * alpha);
|
||||
output_data.data()[offset + 3] = (unsigned char)(128 * (1.0f - alpha) + output_data.data()[offset + 3] * alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int state_offset_px = sprite_offset_px + state_id * sprite_size_px_ex;
|
||||
for (int j = 0; j < (int)sprite_size_px_ex; ++j) {
|
||||
::memcpy((void*)&data.data()[(state_offset_px + j * m_width) * 4], (const void*)&output_data.data()[j * sprite_stride], sprite_stride);
|
||||
}
|
||||
}
|
||||
|
||||
nsvgDelete(image);
|
||||
}
|
||||
|
||||
nsvgDeleteRasterizer(rast);
|
||||
|
||||
// sends data to gpu
|
||||
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
|
||||
glsafe(::glGenTextures(1, &m_id));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, m_id));
|
||||
if (compress && OpenGLManager::are_compressed_textures_supported())
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
|
||||
else
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
|
||||
m_source = filenames.front();
|
||||
|
||||
#if 0
|
||||
// debug output
|
||||
static int pass = 0;
|
||||
++pass;
|
||||
|
||||
wxImage output(m_width, m_height);
|
||||
output.InitAlpha();
|
||||
|
||||
for (int h = 0; h < m_height; ++h) {
|
||||
int px_h = h * m_width;
|
||||
for (int w = 0; w < m_width; ++w) {
|
||||
int offset = (px_h + w) * 4;
|
||||
output.SetRGB(w, h, data.data()[offset + 0], data.data()[offset + 1], data.data()[offset + 2]);
|
||||
output.SetAlpha(w, h, data.data()[offset + 3]);
|
||||
}
|
||||
}
|
||||
|
||||
std::string out_filename = resources_dir() + "/icons/test_" + std::to_string(pass) + ".png";
|
||||
output.SaveFile(out_filename, wxBITMAP_TYPE_PNG);
|
||||
#endif // 0
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GLTexture::reset()
|
||||
{
|
||||
if (m_id != 0)
|
||||
glsafe(::glDeleteTextures(1, &m_id));
|
||||
|
||||
m_id = 0;
|
||||
m_width = 0;
|
||||
m_height = 0;
|
||||
m_source = "";
|
||||
m_compressor.reset();
|
||||
}
|
||||
|
||||
void GLTexture::render_texture(unsigned int tex_id, float left, float right, float bottom, float top)
|
||||
{
|
||||
render_sub_texture(tex_id, left, right, bottom, top, FullTextureUVs);
|
||||
}
|
||||
|
||||
void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const GLTexture::Quad_UVs& uvs)
|
||||
{
|
||||
glsafe(::glEnable(GL_BLEND));
|
||||
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
|
||||
|
||||
#if !ENABLE_GL_CORE_PROFILE && !ENABLE_OPENGL_ES
|
||||
glsafe(::glEnable(GL_TEXTURE_2D));
|
||||
glsafe(::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE));
|
||||
#endif // !ENABLE_GL_CORE_PROFILE && !ENABLE_OPENGL_ES
|
||||
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)tex_id));
|
||||
|
||||
GLModel::Geometry init_data;
|
||||
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P2T2 };
|
||||
init_data.reserve_vertices(4);
|
||||
init_data.reserve_indices(6);
|
||||
|
||||
// vertices
|
||||
init_data.add_vertex(Vec2f(left, bottom), Vec2f(uvs.left_bottom.u, uvs.left_bottom.v));
|
||||
init_data.add_vertex(Vec2f(right, bottom), Vec2f(uvs.right_bottom.u, uvs.right_bottom.v));
|
||||
init_data.add_vertex(Vec2f(right, top), Vec2f(uvs.right_top.u, uvs.right_top.v));
|
||||
init_data.add_vertex(Vec2f(left, top), Vec2f(uvs.left_top.u, uvs.left_top.v));
|
||||
|
||||
// indices
|
||||
init_data.add_triangle(0, 1, 2);
|
||||
init_data.add_triangle(2, 3, 0);
|
||||
|
||||
GLModel model;
|
||||
model.init_from(std::move(init_data));
|
||||
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("flat_texture");
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
shader->set_uniform("view_model_matrix", Transform3d::Identity());
|
||||
shader->set_uniform("projection_matrix", Transform3d::Identity());
|
||||
model.render();
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
|
||||
#if !ENABLE_GL_CORE_PROFILE && !ENABLE_OPENGL_ES
|
||||
glsafe(::glDisable(GL_TEXTURE_2D));
|
||||
#endif // !ENABLE_GL_CORE_PROFILE && !ENABLE_OPENGL_ES
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
}
|
||||
|
||||
static bool to_squared_power_of_two(const std::string& filename, int max_size_px, int& w, int& h)
|
||||
{
|
||||
auto is_power_of_two = [](int v) { return v != 0 && (v & (v - 1)) == 0; };
|
||||
auto upper_power_of_two = [](int v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v; };
|
||||
|
||||
int new_w = std::max(w, h);
|
||||
if (!is_power_of_two(new_w))
|
||||
new_w = upper_power_of_two(new_w);
|
||||
|
||||
while (new_w > max_size_px) {
|
||||
new_w /= 2;
|
||||
}
|
||||
|
||||
const int new_h = new_w;
|
||||
const bool ret = (new_w != w || new_h != h);
|
||||
w = new_w;
|
||||
h = new_h;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy)
|
||||
{
|
||||
const bool compression_enabled = (compression_type != None) && OpenGLManager::are_compressed_textures_supported();
|
||||
|
||||
// Load a PNG with an alpha channel.
|
||||
wxImage image;
|
||||
if (!image.LoadFile(wxString::FromUTF8(filename.c_str()), wxBITMAP_TYPE_PNG)) {
|
||||
reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_width = image.GetWidth();
|
||||
m_height = image.GetHeight();
|
||||
|
||||
bool requires_rescale = false;
|
||||
|
||||
if (use_mipmaps && compression_enabled && OpenGLManager::force_power_of_two_textures()) {
|
||||
if (to_squared_power_of_two(boost::filesystem::path(filename).filename().string(), OpenGLManager::get_gl_info().get_max_tex_size(), m_width, m_height))
|
||||
requires_rescale = true;
|
||||
}
|
||||
|
||||
if (compression_enabled && compression_type == MultiThreaded) {
|
||||
// the stb_dxt compression library seems to like only texture sizes which are a multiple of 4
|
||||
int width_rem = m_width % 4;
|
||||
int height_rem = m_height % 4;
|
||||
|
||||
if (width_rem != 0) {
|
||||
m_width += (4 - width_rem);
|
||||
requires_rescale = true;
|
||||
}
|
||||
|
||||
if (height_rem != 0) {
|
||||
m_height += (4 - height_rem);
|
||||
requires_rescale = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (requires_rescale)
|
||||
image = image.ResampleBicubic(m_width, m_height);
|
||||
|
||||
int n_pixels = m_width * m_height;
|
||||
if (n_pixels <= 0) {
|
||||
reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get RGB & alpha raw data from wxImage, pack them into an array.
|
||||
unsigned char* img_rgb = image.GetData();
|
||||
if (img_rgb == nullptr) {
|
||||
reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char* img_alpha = image.GetAlpha();
|
||||
|
||||
std::vector<unsigned char> data(n_pixels * 4, 0);
|
||||
for (int i = 0; i < n_pixels; ++i) {
|
||||
int data_id = i * 4;
|
||||
int img_id = i * 3;
|
||||
data[data_id + 0] = img_rgb[img_id + 0];
|
||||
data[data_id + 1] = img_rgb[img_id + 1];
|
||||
data[data_id + 2] = img_rgb[img_id + 2];
|
||||
data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255;
|
||||
}
|
||||
|
||||
// sends data to gpu
|
||||
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
|
||||
glsafe(::glGenTextures(1, &m_id));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, m_id));
|
||||
|
||||
if (apply_anisotropy) {
|
||||
GLfloat max_anisotropy = OpenGLManager::get_gl_info().get_max_anisotropy();
|
||||
if (max_anisotropy > 1.0f)
|
||||
glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy));
|
||||
}
|
||||
|
||||
if (compression_enabled) {
|
||||
if (compression_type == SingleThreaded)
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
|
||||
else {
|
||||
// initializes the texture on GPU
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
|
||||
// and send the uncompressed data to the compressor
|
||||
m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data);
|
||||
}
|
||||
}
|
||||
else
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
|
||||
|
||||
if (use_mipmaps) {
|
||||
// we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards
|
||||
int lod_w = m_width;
|
||||
int lod_h = m_height;
|
||||
GLint level = 0;
|
||||
while (lod_w > 1 || lod_h > 1) {
|
||||
++level;
|
||||
|
||||
lod_w = std::max(lod_w / 2, 1);
|
||||
lod_h = std::max(lod_h / 2, 1);
|
||||
n_pixels = lod_w * lod_h;
|
||||
|
||||
image = image.ResampleBicubic(lod_w, lod_h);
|
||||
|
||||
data.resize(n_pixels * 4);
|
||||
|
||||
img_rgb = image.GetData();
|
||||
img_alpha = image.GetAlpha();
|
||||
|
||||
for (int i = 0; i < n_pixels; ++i) {
|
||||
int data_id = i * 4;
|
||||
int img_id = i * 3;
|
||||
data[data_id + 0] = img_rgb[img_id + 0];
|
||||
data[data_id + 1] = img_rgb[img_id + 1];
|
||||
data[data_id + 2] = img_rgb[img_id + 2];
|
||||
data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255;
|
||||
}
|
||||
|
||||
if (compression_enabled) {
|
||||
if (compression_type == SingleThreaded)
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
|
||||
else {
|
||||
// initializes the texture on GPU
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
|
||||
// and send the uncompressed data to the compressor
|
||||
m_compressor.add_level((unsigned int)lod_w, (unsigned int)lod_h, data);
|
||||
}
|
||||
}
|
||||
else
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
|
||||
}
|
||||
|
||||
if (!compression_enabled) {
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR));
|
||||
}
|
||||
}
|
||||
else {
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
|
||||
}
|
||||
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
|
||||
m_source = filename;
|
||||
|
||||
if (compression_type == MultiThreaded)
|
||||
// start asynchronous compression
|
||||
m_compressor.start_compressing();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px)
|
||||
{
|
||||
const bool compression_enabled = compress && OpenGLManager::are_compressed_textures_supported();
|
||||
|
||||
NSVGimage* image = BitmapCache::nsvgParseFromFileWithReplace(filename.c_str(), "px", 96.0f, {});
|
||||
if (image == nullptr) {
|
||||
reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
const float scale = (float)max_size_px / std::max(image->width, image->height);
|
||||
|
||||
m_width = (int)(scale * image->width);
|
||||
m_height = (int)(scale * image->height);
|
||||
|
||||
if (use_mipmaps && compression_enabled && OpenGLManager::force_power_of_two_textures())
|
||||
to_squared_power_of_two(boost::filesystem::path(filename).filename().string(), max_size_px, m_width, m_height);
|
||||
|
||||
float scale_w = (float)m_width / image->width;
|
||||
float scale_h = (float)m_height / image->height;
|
||||
|
||||
if (compression_enabled) {
|
||||
// the stb_dxt compression library seems to like only texture sizes which are a multiple of 4
|
||||
int width_rem = m_width % 4;
|
||||
int height_rem = m_height % 4;
|
||||
|
||||
if (width_rem != 0)
|
||||
m_width += (4 - width_rem);
|
||||
|
||||
if (height_rem != 0)
|
||||
m_height += (4 - height_rem);
|
||||
}
|
||||
|
||||
const int n_pixels = m_width * m_height;
|
||||
|
||||
if (n_pixels <= 0) {
|
||||
reset();
|
||||
nsvgDelete(image);
|
||||
return false;
|
||||
}
|
||||
|
||||
NSVGrasterizer* rast = nsvgCreateRasterizer();
|
||||
if (rast == nullptr) {
|
||||
nsvgDelete(image);
|
||||
reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
// creates the temporary buffer only once, with max size, and reuse it for all the levels, if generating mipmaps
|
||||
std::vector<unsigned char> data(n_pixels * 4, 0);
|
||||
nsvgRasterizeXY(rast, image, 0, 0, scale_w, scale_h, data.data(), m_width, m_height, m_width * 4);
|
||||
|
||||
// sends data to gpu
|
||||
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
|
||||
glsafe(::glGenTextures(1, &m_id));
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, m_id));
|
||||
|
||||
if (apply_anisotropy) {
|
||||
GLfloat max_anisotropy = OpenGLManager::get_gl_info().get_max_anisotropy();
|
||||
if (max_anisotropy > 1.0f)
|
||||
glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy));
|
||||
}
|
||||
|
||||
if (compression_enabled) {
|
||||
// initializes the texture on GPU
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
|
||||
// and send the uncompressed data to the compressor
|
||||
m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data);
|
||||
}
|
||||
else
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
|
||||
|
||||
if (use_mipmaps) {
|
||||
// we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards
|
||||
int lod_w = m_width;
|
||||
int lod_h = m_height;
|
||||
GLint level = 0;
|
||||
while (lod_w > 1 || lod_h > 1) {
|
||||
++level;
|
||||
|
||||
lod_w = std::max(lod_w / 2, 1);
|
||||
lod_h = std::max(lod_h / 2, 1);
|
||||
scale_w /= 2.0f;
|
||||
scale_h /= 2.0f;
|
||||
|
||||
data.resize(lod_w * lod_h * 4);
|
||||
|
||||
nsvgRasterizeXY(rast, image, 0, 0, scale_w, scale_h, data.data(), lod_w, lod_h, lod_w * 4);
|
||||
if (compression_enabled) {
|
||||
// initializes the texture on GPU
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
|
||||
// and send the uncompressed data to the compressor
|
||||
m_compressor.add_level((unsigned int)lod_w, (unsigned int)lod_h, data);
|
||||
}
|
||||
else
|
||||
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
|
||||
}
|
||||
|
||||
if (!compression_enabled) {
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR));
|
||||
}
|
||||
}
|
||||
else {
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
|
||||
}
|
||||
|
||||
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
||||
|
||||
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
||||
|
||||
m_source = filename;
|
||||
|
||||
if (compression_enabled)
|
||||
// start asynchronous compression
|
||||
m_compressor.start_compressing();
|
||||
|
||||
nsvgDeleteRasterizer(rast);
|
||||
nsvgDelete(image);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
129
src/slic3r/GUI/GLTexture.hpp
Normal file
129
src/slic3r/GUI/GLTexture.hpp
Normal file
@@ -0,0 +1,129 @@
|
||||
#ifndef slic3r_GLTexture_hpp_
|
||||
#define slic3r_GLTexture_hpp_
|
||||
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
|
||||
class wxImage;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class GLTexture
|
||||
{
|
||||
class Compressor
|
||||
{
|
||||
struct Level
|
||||
{
|
||||
unsigned int w;
|
||||
unsigned int h;
|
||||
std::vector<unsigned char> src_data;
|
||||
std::vector<unsigned char> compressed_data;
|
||||
bool sent_to_gpu;
|
||||
|
||||
Level(unsigned int w, unsigned int h, const std::vector<unsigned char>& data) : w(w), h(h), src_data(data), sent_to_gpu(false) {}
|
||||
};
|
||||
|
||||
GLTexture& m_texture;
|
||||
std::vector<Level> m_levels;
|
||||
std::thread m_thread;
|
||||
// Does the caller want the background thread to stop?
|
||||
// This atomic also works as a memory barrier for synchronizing the cancel event with the worker thread.
|
||||
std::atomic<bool> m_abort_compressing;
|
||||
// How many levels were compressed since the start of the background processing thread?
|
||||
// This atomic also works as a memory barrier for synchronizing results of the worker thread with the calling thread.
|
||||
std::atomic<unsigned int> m_num_levels_compressed;
|
||||
|
||||
public:
|
||||
explicit Compressor(GLTexture& texture) : m_texture(texture), m_abort_compressing(false), m_num_levels_compressed(0) {}
|
||||
~Compressor() { reset(); }
|
||||
|
||||
void reset();
|
||||
|
||||
void add_level(unsigned int w, unsigned int h, const std::vector<unsigned char>& data) { m_levels.emplace_back(w, h, data); }
|
||||
|
||||
void start_compressing();
|
||||
|
||||
bool unsent_compressed_data_available() const;
|
||||
void send_compressed_data_to_gpu();
|
||||
bool all_compressed_data_sent_to_gpu() const { return m_levels.empty(); }
|
||||
|
||||
private:
|
||||
void compress();
|
||||
};
|
||||
|
||||
public:
|
||||
enum ECompressionType : unsigned char
|
||||
{
|
||||
None,
|
||||
SingleThreaded,
|
||||
MultiThreaded
|
||||
};
|
||||
|
||||
struct UV
|
||||
{
|
||||
float u{ 0.0f };
|
||||
float v{ 0.0f };
|
||||
};
|
||||
|
||||
struct Quad_UVs
|
||||
{
|
||||
UV left_bottom;
|
||||
UV right_bottom;
|
||||
UV right_top;
|
||||
UV left_top;
|
||||
};
|
||||
|
||||
static Quad_UVs FullTextureUVs;
|
||||
|
||||
protected:
|
||||
unsigned int m_id{ 0 };
|
||||
int m_width{ 0 };
|
||||
int m_height{ 0 };
|
||||
std::string m_source;
|
||||
Compressor m_compressor;
|
||||
|
||||
public:
|
||||
GLTexture();
|
||||
virtual ~GLTexture();
|
||||
|
||||
bool load_from_file(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy);
|
||||
bool load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px);
|
||||
// meanings of states: (std::pair<int, bool>)
|
||||
// first field (int):
|
||||
// 0 -> no changes
|
||||
// 1 -> use white only color variant
|
||||
// 2 -> use gray only color variant
|
||||
// second field (bool):
|
||||
// false -> no changes
|
||||
// true -> add background color
|
||||
bool load_from_svg_files_as_sprites_array(const std::vector<std::string>& filenames, const std::vector<std::pair<int, bool>>& states, unsigned int sprite_size_px, bool compress);
|
||||
void reset();
|
||||
|
||||
unsigned int get_id() const { return m_id; }
|
||||
int get_width() const { return m_width; }
|
||||
int get_height() const { return m_height; }
|
||||
|
||||
const std::string& get_source() const { return m_source; }
|
||||
|
||||
bool unsent_compressed_data_available() const { return m_compressor.unsent_compressed_data_available(); }
|
||||
void send_compressed_data_to_gpu() { m_compressor.send_compressed_data_to_gpu(); }
|
||||
bool all_compressed_data_sent_to_gpu() const { return m_compressor.all_compressed_data_sent_to_gpu(); }
|
||||
|
||||
static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top);
|
||||
static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs);
|
||||
|
||||
private:
|
||||
bool load_from_png(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy);
|
||||
bool load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px);
|
||||
|
||||
friend class Compressor;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLTexture_hpp_
|
||||
|
||||
1384
src/slic3r/GUI/GLToolbar.cpp
Normal file
1384
src/slic3r/GUI/GLToolbar.cpp
Normal file
File diff suppressed because it is too large
Load Diff
363
src/slic3r/GUI/GLToolbar.hpp
Normal file
363
src/slic3r/GUI/GLToolbar.hpp
Normal file
@@ -0,0 +1,363 @@
|
||||
#ifndef slic3r_GLToolbar_hpp_
|
||||
#define slic3r_GLToolbar_hpp_
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "GLTexture.hpp"
|
||||
#include "Event.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
class wxEvtHandler;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class GLCanvas3D;
|
||||
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_COPY, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_PASTE, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_FEWER, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_SPLIT_VOLUMES, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLTOOLBAR_LAYERSEDITING, SimpleEvent);
|
||||
|
||||
wxDECLARE_EVENT(EVT_GLVIEWTOOLBAR_3D, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLVIEWTOOLBAR_PREVIEW, SimpleEvent);
|
||||
|
||||
class GLToolbarItem
|
||||
{
|
||||
public:
|
||||
typedef std::function<void()> ActionCallback;
|
||||
typedef std::function<bool()> VisibilityCallback;
|
||||
typedef std::function<bool()> EnablingCallback;
|
||||
typedef std::function<void(float, float, float, float)> RenderCallback;
|
||||
|
||||
enum EType : unsigned char
|
||||
{
|
||||
Action,
|
||||
Separator,
|
||||
Num_Types
|
||||
};
|
||||
|
||||
enum EActionType : unsigned char
|
||||
{
|
||||
Undefined,
|
||||
Left,
|
||||
Right,
|
||||
Num_Action_Types
|
||||
};
|
||||
|
||||
enum EState : unsigned char
|
||||
{
|
||||
Normal,
|
||||
Pressed,
|
||||
Disabled,
|
||||
Hover,
|
||||
HoverPressed,
|
||||
HoverDisabled,
|
||||
Num_States
|
||||
};
|
||||
|
||||
enum EHighlightState : unsigned char
|
||||
{
|
||||
HighlightedShown,
|
||||
HighlightedHidden,
|
||||
Num_Rendered_Highlight_States,
|
||||
NotHighlighted
|
||||
};
|
||||
|
||||
struct Data
|
||||
{
|
||||
struct Option
|
||||
{
|
||||
bool toggable;
|
||||
ActionCallback action_callback;
|
||||
RenderCallback render_callback;
|
||||
|
||||
Option();
|
||||
|
||||
bool can_render() const { return toggable && (render_callback != nullptr); }
|
||||
};
|
||||
|
||||
std::string name;
|
||||
std::string icon_filename;
|
||||
std::string tooltip;
|
||||
std::string additional_tooltip;
|
||||
unsigned int sprite_id;
|
||||
// mouse left click
|
||||
Option left;
|
||||
// mouse right click
|
||||
Option right;
|
||||
bool visible;
|
||||
VisibilityCallback visibility_callback;
|
||||
EnablingCallback enabling_callback;
|
||||
|
||||
Data();
|
||||
};
|
||||
|
||||
static const ActionCallback Default_Action_Callback;
|
||||
static const VisibilityCallback Default_Visibility_Callback;
|
||||
static const EnablingCallback Default_Enabling_Callback;
|
||||
static const RenderCallback Default_Render_Callback;
|
||||
|
||||
private:
|
||||
EType m_type;
|
||||
EState m_state;
|
||||
Data m_data;
|
||||
EActionType m_last_action_type;
|
||||
EHighlightState m_highlight_state;
|
||||
public:
|
||||
GLToolbarItem(EType type, const Data& data);
|
||||
|
||||
EState get_state() const { return m_state; }
|
||||
void set_state(EState state) { m_state = state; }
|
||||
|
||||
EHighlightState get_highlight() const { return m_highlight_state; }
|
||||
void set_highlight(EHighlightState state) { m_highlight_state = state; }
|
||||
|
||||
const std::string& get_name() const { return m_data.name; }
|
||||
const std::string& get_icon_filename() const { return m_data.icon_filename; }
|
||||
const std::string& get_tooltip() const { return m_data.tooltip; }
|
||||
const std::string& get_additional_tooltip() const { return m_data.additional_tooltip; }
|
||||
void set_additional_tooltip(const std::string& text) { m_data.additional_tooltip = text; }
|
||||
void set_tooltip(const std::string& text) { m_data.tooltip = text; }
|
||||
|
||||
void do_left_action() { m_last_action_type = Left; m_data.left.action_callback(); }
|
||||
void do_right_action() { m_last_action_type = Right; m_data.right.action_callback(); }
|
||||
|
||||
bool is_enabled() const { return (m_state != Disabled) && (m_state != HoverDisabled); }
|
||||
bool is_disabled() const { return (m_state == Disabled) || (m_state == HoverDisabled); }
|
||||
bool is_hovered() const { return (m_state == Hover) || (m_state == HoverPressed) || (m_state == HoverDisabled); }
|
||||
bool is_pressed() const { return (m_state == Pressed) || (m_state == HoverPressed); }
|
||||
bool is_visible() const { return m_data.visible; }
|
||||
bool is_separator() const { return m_type == Separator; }
|
||||
|
||||
bool is_left_toggable() const { return m_data.left.toggable; }
|
||||
bool is_right_toggable() const { return m_data.right.toggable; }
|
||||
|
||||
bool has_left_render_callback() const { return m_data.left.render_callback != nullptr; }
|
||||
bool has_right_render_callback() const { return m_data.right.render_callback != nullptr; }
|
||||
|
||||
EActionType get_last_action_type() const { return m_last_action_type; }
|
||||
void reset_last_action_type() { m_last_action_type = Undefined; }
|
||||
|
||||
// returns true if the state changes
|
||||
bool update_visibility();
|
||||
// returns true if the state changes
|
||||
bool update_enabled_state();
|
||||
|
||||
void render(const GLCanvas3D& parent, unsigned int tex_id, float left, float right, float bottom, float top, unsigned int tex_width, unsigned int tex_height, unsigned int icon_size) const;
|
||||
|
||||
private:
|
||||
void set_visible(bool visible) { m_data.visible = visible; }
|
||||
|
||||
friend class GLToolbar;
|
||||
};
|
||||
|
||||
struct BackgroundTexture
|
||||
{
|
||||
struct Metadata
|
||||
{
|
||||
// path of the file containing the background texture
|
||||
std::string filename;
|
||||
// size of the left edge, in pixels
|
||||
unsigned int left;
|
||||
// size of the right edge, in pixels
|
||||
unsigned int right;
|
||||
// size of the top edge, in pixels
|
||||
unsigned int top;
|
||||
// size of the bottom edge, in pixels
|
||||
unsigned int bottom;
|
||||
|
||||
Metadata();
|
||||
};
|
||||
|
||||
GLTexture texture;
|
||||
Metadata metadata;
|
||||
};
|
||||
|
||||
class GLToolbar
|
||||
{
|
||||
public:
|
||||
static const float Default_Icons_Size;
|
||||
|
||||
enum EType : unsigned char
|
||||
{
|
||||
Normal,
|
||||
Radio,
|
||||
Num_Types
|
||||
};
|
||||
|
||||
struct Layout
|
||||
{
|
||||
enum EType : unsigned char
|
||||
{
|
||||
Horizontal,
|
||||
Vertical,
|
||||
Num_Types
|
||||
};
|
||||
|
||||
enum EHorizontalOrientation : unsigned char
|
||||
{
|
||||
HO_Left,
|
||||
HO_Center,
|
||||
HO_Right,
|
||||
Num_Horizontal_Orientations
|
||||
};
|
||||
|
||||
enum EVerticalOrientation : unsigned char
|
||||
{
|
||||
VO_Top,
|
||||
VO_Center,
|
||||
VO_Bottom,
|
||||
Num_Vertical_Orientations
|
||||
};
|
||||
|
||||
EType type;
|
||||
EHorizontalOrientation horizontal_orientation;
|
||||
EVerticalOrientation vertical_orientation;
|
||||
float top;
|
||||
float left;
|
||||
float border;
|
||||
float separator_size;
|
||||
float gap_size;
|
||||
float icons_size;
|
||||
float scale;
|
||||
|
||||
float width;
|
||||
float height;
|
||||
bool dirty;
|
||||
|
||||
Layout();
|
||||
};
|
||||
|
||||
private:
|
||||
typedef std::vector<GLToolbarItem*> ItemsList;
|
||||
|
||||
EType m_type;
|
||||
std::string m_name;
|
||||
bool m_enabled;
|
||||
GLTexture m_icons_texture;
|
||||
bool m_icons_texture_dirty;
|
||||
BackgroundTexture m_background_texture;
|
||||
GLTexture m_arrow_texture;
|
||||
Layout m_layout;
|
||||
|
||||
ItemsList m_items;
|
||||
|
||||
struct MouseCapture
|
||||
{
|
||||
bool left;
|
||||
bool middle;
|
||||
bool right;
|
||||
GLCanvas3D* parent;
|
||||
|
||||
MouseCapture() { reset(); }
|
||||
|
||||
bool any() const { return left || middle || right; }
|
||||
void reset() { left = middle = right = false; parent = nullptr; }
|
||||
};
|
||||
|
||||
MouseCapture m_mouse_capture;
|
||||
int m_pressed_toggable_id;
|
||||
|
||||
public:
|
||||
GLToolbar(EType type, const std::string& name);
|
||||
~GLToolbar();
|
||||
|
||||
bool init(const BackgroundTexture::Metadata& background_texture);
|
||||
|
||||
bool init_arrow(const std::string& filename);
|
||||
|
||||
Layout::EType get_layout_type() const;
|
||||
void set_layout_type(Layout::EType type);
|
||||
Layout::EHorizontalOrientation get_horizontal_orientation() const { return m_layout.horizontal_orientation; }
|
||||
void set_horizontal_orientation(Layout::EHorizontalOrientation orientation) { m_layout.horizontal_orientation = orientation; }
|
||||
Layout::EVerticalOrientation get_vertical_orientation() const { return m_layout.vertical_orientation; }
|
||||
void set_vertical_orientation(Layout::EVerticalOrientation orientation) { m_layout.vertical_orientation = orientation; }
|
||||
|
||||
void set_position(float top, float left);
|
||||
void set_border(float border);
|
||||
void set_separator_size(float size);
|
||||
void set_gap_size(float size);
|
||||
void set_icons_size(float size);
|
||||
void set_scale(float scale);
|
||||
|
||||
bool is_enabled() const { return m_enabled; }
|
||||
void set_enabled(bool enable) { m_enabled = enable; }
|
||||
|
||||
bool add_item(const GLToolbarItem::Data& data);
|
||||
bool add_separator();
|
||||
|
||||
float get_width();
|
||||
float get_height();
|
||||
|
||||
void select_item(const std::string& name);
|
||||
|
||||
bool is_item_pressed(const std::string& name) const;
|
||||
bool is_item_disabled(const std::string& name) const;
|
||||
bool is_item_visible(const std::string& name) const;
|
||||
|
||||
bool is_any_item_pressed() const;
|
||||
|
||||
unsigned int get_items_count() const { return (unsigned int)m_items.size(); }
|
||||
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); }
|
||||
void force_right_action(int item_id, GLCanvas3D& parent) { do_action(GLToolbarItem::Right, item_id, parent, false); }
|
||||
|
||||
std::string get_tooltip() const;
|
||||
|
||||
void get_additional_tooltip(int item_id, std::string& text);
|
||||
void set_additional_tooltip(int item_id, const std::string& text);
|
||||
void set_tooltip(int item_id, const std::string& text);
|
||||
int get_visible_items_cnt() const;
|
||||
|
||||
// returns true if any item changed its state
|
||||
bool update_items_state();
|
||||
|
||||
void render(const GLCanvas3D& parent);
|
||||
void render_arrow(const GLCanvas3D& parent, GLToolbarItem* highlighted_item);
|
||||
|
||||
bool on_mouse(wxMouseEvent& evt, GLCanvas3D& parent);
|
||||
// get item pointer for highlighter timer
|
||||
GLToolbarItem* get_item(const std::string& item_name);
|
||||
private:
|
||||
void calc_layout();
|
||||
float get_width_horizontal() const;
|
||||
float get_width_vertical() const;
|
||||
float get_height_horizontal() const;
|
||||
float get_height_vertical() const;
|
||||
float get_main_size() const;
|
||||
void do_action(GLToolbarItem::EActionType type, int item_id, GLCanvas3D& parent, bool check_hover);
|
||||
void update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent);
|
||||
void update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent);
|
||||
void update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent);
|
||||
// returns the id of the item under the given mouse position or -1 if none
|
||||
int contains_mouse(const Vec2d& mouse_pos, const GLCanvas3D& parent) const;
|
||||
int contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3D& parent) const;
|
||||
int contains_mouse_vertical(const Vec2d& mouse_pos, const GLCanvas3D& parent) const;
|
||||
|
||||
void render_background(float left, float top, float right, float bottom, float border_w, float border_h) const;
|
||||
void render_horizontal(const GLCanvas3D& parent);
|
||||
void render_vertical(const GLCanvas3D& parent);
|
||||
|
||||
bool generate_icons_texture();
|
||||
|
||||
// returns true if any item changed its state
|
||||
bool update_items_visibility();
|
||||
// returns true if any item changed its state
|
||||
bool update_items_enabled_state();
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLToolbar_hpp_
|
||||
598
src/slic3r/GUI/GUI.cpp
Normal file
598
src/slic3r/GUI/GUI.cpp
Normal file
@@ -0,0 +1,598 @@
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "format.hpp"
|
||||
#include "I18N.hpp"
|
||||
|
||||
#include "libslic3r/LocalesUtils.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/any.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#if __APPLE__
|
||||
#import <IOKit/pwr_mgt/IOPMLib.h>
|
||||
#elif _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
#include "boost/nowide/convert.hpp"
|
||||
#endif
|
||||
|
||||
#include "AboutDialog.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
#include "libslic3r/Print.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class AppConfig;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
#if __APPLE__
|
||||
IOPMAssertionID assertionID;
|
||||
#endif
|
||||
|
||||
void disable_screensaver()
|
||||
{
|
||||
#if __APPLE__
|
||||
CFStringRef reasonForActivity = CFSTR("Slic3r");
|
||||
[[maybe_unused]]IOReturn success = IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep,
|
||||
kIOPMAssertionLevelOn, reasonForActivity, &assertionID);
|
||||
// ignore result: success == kIOReturnSuccess
|
||||
#elif _WIN32
|
||||
SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_CONTINUOUS);
|
||||
#endif
|
||||
}
|
||||
|
||||
void enable_screensaver()
|
||||
{
|
||||
#if __APPLE__
|
||||
IOPMAssertionRelease(assertionID);
|
||||
#elif _WIN32
|
||||
SetThreadExecutionState(ES_CONTINUOUS);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool debugged()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return IsDebuggerPresent() == TRUE;
|
||||
#else
|
||||
return false;
|
||||
#endif /* _WIN32 */
|
||||
}
|
||||
|
||||
void break_to_debugger()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (IsDebuggerPresent())
|
||||
DebugBreak();
|
||||
#endif /* _WIN32 */
|
||||
}
|
||||
|
||||
const std::string& shortkey_ctrl_prefix()
|
||||
{
|
||||
static const std::string str =
|
||||
#ifdef __APPLE__
|
||||
"⌘"
|
||||
#else
|
||||
"Ctrl+"
|
||||
#endif
|
||||
;
|
||||
return str;
|
||||
}
|
||||
|
||||
const std::string& shortkey_alt_prefix()
|
||||
{
|
||||
static const std::string str =
|
||||
#ifdef __APPLE__
|
||||
"⌥"
|
||||
#else
|
||||
"Alt+"
|
||||
#endif
|
||||
;
|
||||
return str;
|
||||
}
|
||||
|
||||
// opt_index = 0, by the reason of zero-index in ConfigOptionVector by default (in case only one element)
|
||||
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index /*= 0*/)
|
||||
{
|
||||
try{
|
||||
|
||||
if (config.def()->get(opt_key)->type == coBools && config.def()->get(opt_key)->nullable) {
|
||||
ConfigOptionBoolsNullable* vec_new = new ConfigOptionBoolsNullable{ boost::any_cast<unsigned char>(value) };
|
||||
config.option<ConfigOptionBoolsNullable>(opt_key)->set_at(vec_new, opt_index, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const ConfigOptionDef *opt_def = config.def()->get(opt_key);
|
||||
switch (opt_def->type) {
|
||||
case coFloatOrPercent:{
|
||||
std::string str = boost::any_cast<std::string>(value);
|
||||
bool percent = false;
|
||||
if (str.back() == '%') {
|
||||
str.pop_back();
|
||||
percent = true;
|
||||
}
|
||||
double val = std::stod(str); // locale-dependent (on purpose - the input is the actual content of the field)
|
||||
config.set_key_value(opt_key, new ConfigOptionFloatOrPercent(val, percent));
|
||||
break;}
|
||||
case coPercent:
|
||||
config.set_key_value(opt_key, new ConfigOptionPercent(boost::any_cast<double>(value)));
|
||||
break;
|
||||
case coFloat:{
|
||||
double& val = config.opt_float(opt_key);
|
||||
val = boost::any_cast<double>(value);
|
||||
break;
|
||||
}
|
||||
case coFloatsOrPercents:{
|
||||
std::string str = boost::any_cast<std::string>(value);
|
||||
bool percent = false;
|
||||
if (str.back() == '%') {
|
||||
str.pop_back();
|
||||
percent = true;
|
||||
}
|
||||
double val = std::stod(str); // locale-dependent (on purpose - the input is the actual content of the field)
|
||||
ConfigOptionFloatsOrPercents* vec_new = new ConfigOptionFloatsOrPercents({ {val, percent} });
|
||||
config.option<ConfigOptionFloatsOrPercents>(opt_key)->set_at(vec_new, opt_index, opt_index);
|
||||
break;
|
||||
}
|
||||
case coPercents:{
|
||||
ConfigOptionPercents* vec_new = new ConfigOptionPercents{ boost::any_cast<double>(value) };
|
||||
config.option<ConfigOptionPercents>(opt_key)->set_at(vec_new, opt_index, opt_index);
|
||||
break;
|
||||
}
|
||||
case coFloats:{
|
||||
ConfigOptionFloats* vec_new = new ConfigOptionFloats{ boost::any_cast<double>(value) };
|
||||
config.option<ConfigOptionFloats>(opt_key)->set_at(vec_new, opt_index, opt_index);
|
||||
break;
|
||||
}
|
||||
case coString:
|
||||
config.set_key_value(opt_key, new ConfigOptionString(boost::any_cast<std::string>(value)));
|
||||
break;
|
||||
case coStrings:{
|
||||
if (opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "gcode_substitutions") {
|
||||
config.option<ConfigOptionStrings>(opt_key)->values =
|
||||
boost::any_cast<std::vector<std::string>>(value);
|
||||
}
|
||||
else if (config.def()->get(opt_key)->gui_flags.compare("serialized") == 0) {
|
||||
std::string str = boost::any_cast<std::string>(value);
|
||||
std::vector<std::string> values {};
|
||||
if (!str.empty()) {
|
||||
if (str.back() == ';') str.pop_back();
|
||||
// Split a string to multiple strings by a semi - colon.This is the old way of storing multi - string values.
|
||||
// Currently used for the post_process config value only.
|
||||
boost::split(values, str, boost::is_any_of(";"));
|
||||
if (values.size() == 1 && values[0] == "")
|
||||
values.resize(0);
|
||||
}
|
||||
config.option<ConfigOptionStrings>(opt_key)->values = values;
|
||||
}
|
||||
else{
|
||||
ConfigOptionStrings* vec_new = new ConfigOptionStrings{ boost::any_cast<std::string>(value) };
|
||||
config.option<ConfigOptionStrings>(opt_key)->set_at(vec_new, opt_index, 0);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case coBool:
|
||||
config.set_key_value(opt_key, new ConfigOptionBool(boost::any_cast<bool>(value)));
|
||||
break;
|
||||
case coBools:{
|
||||
ConfigOptionBools* vec_new = new ConfigOptionBools{ boost::any_cast<unsigned char>(value) != 0 };
|
||||
config.option<ConfigOptionBools>(opt_key)->set_at(vec_new, opt_index, 0);
|
||||
break;}
|
||||
case coInt:
|
||||
config.set_key_value(opt_key, new ConfigOptionInt(boost::any_cast<int>(value)));
|
||||
break;
|
||||
case coInts:{
|
||||
ConfigOptionInts* vec_new = new ConfigOptionInts{ boost::any_cast<int>(value) };
|
||||
config.option<ConfigOptionInts>(opt_key)->set_at(vec_new, opt_index, 0);
|
||||
}
|
||||
break;
|
||||
case coEnum:{
|
||||
auto *opt = opt_def->default_value.get()->clone();
|
||||
opt->setInt(boost::any_cast<int>(value));
|
||||
config.set_key_value(opt_key, opt);
|
||||
}
|
||||
break;
|
||||
case coPoints:{
|
||||
if (opt_key == "bed_shape" || opt_key == "thumbnails") {
|
||||
config.option<ConfigOptionPoints>(opt_key)->values = boost::any_cast<std::vector<Vec2d>>(value);
|
||||
break;
|
||||
}
|
||||
ConfigOptionPoints* vec_new = new ConfigOptionPoints{ boost::any_cast<Vec2d>(value) };
|
||||
config.option<ConfigOptionPoints>(opt_key)->set_at(vec_new, opt_index, 0);
|
||||
}
|
||||
break;
|
||||
case coNone:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
wxLogError(format_wxstr("Internal error when changing value for %1%: %2%", opt_key, e.what()));
|
||||
}
|
||||
}
|
||||
|
||||
void show_error(wxWindow* parent, const wxString& message, bool monospaced_font)
|
||||
{
|
||||
ErrorDialog msg(parent, message, monospaced_font);
|
||||
msg.ShowModal();
|
||||
}
|
||||
|
||||
void show_error(wxWindow* parent, const char* message, bool monospaced_font)
|
||||
{
|
||||
assert(message);
|
||||
show_error(parent, wxString::FromUTF8(message), monospaced_font);
|
||||
}
|
||||
|
||||
void show_error_id(int id, const std::string& message)
|
||||
{
|
||||
auto *parent = id != 0 ? wxWindow::FindWindowById(id) : nullptr;
|
||||
show_error(parent, message);
|
||||
}
|
||||
|
||||
void show_info(wxWindow* parent, const wxString& message, const wxString& title)
|
||||
{
|
||||
//wxMessageDialog msg_wingow(parent, message, wxString(SLIC3R_APP_NAME " - ") + (title.empty() ? _L("Notice") : title), wxOK | wxICON_INFORMATION);
|
||||
MessageDialog msg_wingow(parent, message, wxString(SLIC3R_APP_NAME " - ") + (title.empty() ? _L("Notice") : title), wxOK | wxICON_INFORMATION);
|
||||
msg_wingow.ShowModal();
|
||||
}
|
||||
|
||||
void show_info(wxWindow* parent, const char* message, const char* title)
|
||||
{
|
||||
assert(message);
|
||||
show_info(parent, wxString::FromUTF8(message), title ? wxString::FromUTF8(title) : wxString());
|
||||
}
|
||||
|
||||
void warning_catcher(wxWindow* parent, const wxString& message)
|
||||
{
|
||||
//wxMessageDialog msg(parent, message, _L("Warning"), wxOK | wxICON_WARNING);
|
||||
MessageDialog msg(parent, message, _L("Warning"), wxOK | wxICON_WARNING);
|
||||
msg.ShowModal();
|
||||
}
|
||||
|
||||
static wxString bold(const wxString& str)
|
||||
{
|
||||
return wxString::Format("<b>%s</b>", str);
|
||||
};
|
||||
|
||||
static wxString bold_string(const wxString& str)
|
||||
{
|
||||
return wxString::Format("<b>\"%s\"</b>", str);
|
||||
};
|
||||
|
||||
static void add_config_substitutions(const ConfigSubstitutions& conf_substitutions, wxString& changes)
|
||||
{
|
||||
changes += "<table>";
|
||||
for (const ConfigSubstitution& conf_substitution : conf_substitutions) {
|
||||
wxString new_val;
|
||||
const ConfigOptionDef* def = conf_substitution.opt_def;
|
||||
if (!def)
|
||||
continue;
|
||||
switch (def->type) {
|
||||
case coEnum:
|
||||
{
|
||||
auto opt = def->enum_def->enum_to_index(conf_substitution.new_value->getInt());
|
||||
new_val = opt.has_value() ?
|
||||
wxString("\"") + def->enum_def->value(*opt) + "\"" + " (" +
|
||||
_(from_u8(def->enum_def->label(*opt))) + ")" :
|
||||
_L("Undefined");
|
||||
break;
|
||||
}
|
||||
case coBool:
|
||||
new_val = conf_substitution.new_value->getBool() ? "true" : "false";
|
||||
break;
|
||||
case coBools:
|
||||
if (conf_substitution.new_value->nullable())
|
||||
for (const char v : static_cast<const ConfigOptionBoolsNullable*>(conf_substitution.new_value.get())->values)
|
||||
new_val += std::string(v == ConfigOptionBoolsNullable::nil_value() ? "nil" : v ? "true" : "false") + ", ";
|
||||
else
|
||||
for (const char v : static_cast<const ConfigOptionBools*>(conf_substitution.new_value.get())->values)
|
||||
new_val += std::string(v ? "true" : "false") + ", ";
|
||||
if (! new_val.empty())
|
||||
new_val.erase(new_val.begin() + new_val.size() - 2, new_val.end());
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
changes += format_wxstr("<tr><td><b>\"%1%\" (%2%)</b></td><td>: ", def->opt_key, _(def->label)) +
|
||||
format_wxstr(_L("%1% was substituted with %2%"), bold_string(conf_substitution.old_value), bold(new_val)) +
|
||||
"</td></tr>";
|
||||
}
|
||||
changes += "</table>";
|
||||
}
|
||||
|
||||
static wxString substitution_message(const wxString& changes)
|
||||
{
|
||||
return
|
||||
_L("Most likely the configuration was produced by a newer version of QIDISlicer or by some QIDISlicer fork.") + " " +
|
||||
_L("The following values were substituted:") + "\n" + changes + "\n\n" +
|
||||
_L("Review the substitutions and adjust them if needed.");
|
||||
}
|
||||
|
||||
void show_substitutions_info(const PresetsConfigSubstitutions& presets_config_substitutions)
|
||||
{
|
||||
wxString changes;
|
||||
|
||||
auto preset_type_name = [](Preset::Type type) {
|
||||
switch (type) {
|
||||
case Preset::TYPE_PRINT: return _L("Print settings");
|
||||
case Preset::TYPE_SLA_PRINT: return _L("SLA print settings");
|
||||
case Preset::TYPE_FILAMENT: return _L("Filament");
|
||||
case Preset::TYPE_SLA_MATERIAL: return _L("SLA material");
|
||||
case Preset::TYPE_PRINTER: return _L("Printer");
|
||||
case Preset::TYPE_PHYSICAL_PRINTER: return _L("Physical Printer");
|
||||
default: assert(false); return wxString();
|
||||
}
|
||||
};
|
||||
|
||||
for (const PresetConfigSubstitutions& substitution : presets_config_substitutions) {
|
||||
changes += "\n\n" + format_wxstr("%1% : %2%", preset_type_name(substitution.preset_type), bold_string(substitution.preset_name));
|
||||
if (!substitution.preset_file.empty())
|
||||
changes += format_wxstr(" (%1%)", substitution.preset_file);
|
||||
|
||||
add_config_substitutions(substitution.substitutions, changes);
|
||||
}
|
||||
|
||||
InfoDialog msg(nullptr, _L("Configuration bundle was loaded, however some configuration values were not recognized."), substitution_message(changes), true);
|
||||
msg.ShowModal();
|
||||
}
|
||||
|
||||
void show_substitutions_info(const ConfigSubstitutions& config_substitutions, const std::string& filename)
|
||||
{
|
||||
wxString changes = "\n";
|
||||
add_config_substitutions(config_substitutions, changes);
|
||||
|
||||
InfoDialog msg(nullptr,
|
||||
format_wxstr(_L("Configuration file \"%1%\" was loaded, however some configuration values were not recognized."), from_u8(filename)),
|
||||
substitution_message(changes), true);
|
||||
msg.ShowModal();
|
||||
}
|
||||
|
||||
void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items)
|
||||
{
|
||||
if (comboCtrl == nullptr)
|
||||
return;
|
||||
wxGetApp().UpdateDarkUI(comboCtrl);
|
||||
|
||||
wxCheckListBoxComboPopup* popup = new wxCheckListBoxComboPopup;
|
||||
if (popup != nullptr) {
|
||||
// FIXME If the following line is removed, the combo box popup list will not react to mouse clicks.
|
||||
// On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10.
|
||||
comboCtrl->UseAltPopupWindow();
|
||||
|
||||
int max_width = 0;
|
||||
|
||||
// the following line messes up the popup size the first time it is shown on wxWidgets 3.1.3
|
||||
// comboCtrl->EnablePopupAnimation(false);
|
||||
#ifdef _WIN32
|
||||
popup->SetFont(comboCtrl->GetFont());
|
||||
#endif // _WIN32
|
||||
comboCtrl->SetPopupControl(popup);
|
||||
wxString title = from_u8(text);
|
||||
max_width = std::max(max_width, 60 + comboCtrl->GetTextExtent(title).x);
|
||||
popup->SetStringValue(title);
|
||||
popup->Bind(wxEVT_CHECKLISTBOX, [popup](wxCommandEvent& evt) { popup->OnCheckListBox(evt); });
|
||||
popup->Bind(wxEVT_LISTBOX, [popup](wxCommandEvent& evt) { popup->OnListBoxSelection(evt); });
|
||||
popup->Bind(wxEVT_KEY_DOWN, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); });
|
||||
popup->Bind(wxEVT_KEY_UP, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); });
|
||||
|
||||
std::vector<std::string> items_str;
|
||||
boost::split(items_str, items, boost::is_any_of("|"), boost::token_compress_off);
|
||||
|
||||
// each item must be composed by 2 parts
|
||||
assert(items_str.size() %2 == 0);
|
||||
|
||||
for (size_t i = 0; i < items_str.size(); i += 2) {
|
||||
wxString label = from_u8(items_str[i]);
|
||||
max_width = std::max(max_width, 60 + popup->GetTextExtent(label).x);
|
||||
popup->Append(label);
|
||||
popup->Check(i / 2, items_str[i + 1] == "1");
|
||||
}
|
||||
|
||||
comboCtrl->SetMinClientSize(wxSize(max_width, -1));
|
||||
wxGetApp().UpdateDarkUI(popup);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int combochecklist_get_flags(wxComboCtrl* comboCtrl)
|
||||
{
|
||||
unsigned int flags = 0;
|
||||
|
||||
wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup);
|
||||
if (popup != nullptr) {
|
||||
for (unsigned int i = 0; i < popup->GetCount(); ++i) {
|
||||
if (popup->IsChecked(i))
|
||||
flags |= 1 << i;
|
||||
}
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
void combochecklist_set_flags(wxComboCtrl* comboCtrl, unsigned int flags)
|
||||
{
|
||||
wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup);
|
||||
if (popup != nullptr) {
|
||||
for (unsigned int i = 0; i < popup->GetCount(); ++i) {
|
||||
popup->Check(i, (flags & (1 << i)) != 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppConfig* get_app_config()
|
||||
{
|
||||
return wxGetApp().app_config;
|
||||
}
|
||||
|
||||
wxString from_u8(const std::string &str)
|
||||
{
|
||||
return wxString::FromUTF8(str.c_str());
|
||||
}
|
||||
|
||||
std::string into_u8(const wxString &str)
|
||||
{
|
||||
auto buffer_utf8 = str.utf8_str();
|
||||
return std::string(buffer_utf8.data());
|
||||
}
|
||||
|
||||
wxString from_path(const boost::filesystem::path &path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return wxString(path.string<std::wstring>());
|
||||
#else
|
||||
return from_u8(path.string<std::string>());
|
||||
#endif
|
||||
}
|
||||
|
||||
boost::filesystem::path into_path(const wxString &str)
|
||||
{
|
||||
return boost::filesystem::path(str.wx_str());
|
||||
}
|
||||
|
||||
void about()
|
||||
{
|
||||
AboutDialog dlg;
|
||||
dlg.ShowModal();
|
||||
}
|
||||
|
||||
void desktop_open_datadir_folder()
|
||||
{
|
||||
boost::filesystem::path path(data_dir());
|
||||
desktop_open_folder(std::move(path));
|
||||
}
|
||||
|
||||
void desktop_open_folder(const boost::filesystem::path& path)
|
||||
{
|
||||
if (!boost::filesystem::is_directory(path))
|
||||
return;
|
||||
|
||||
// Execute command to open a file explorer, platform dependent.
|
||||
#ifdef _WIN32
|
||||
const wxString widepath = path.wstring();
|
||||
const wchar_t* argv[] = { L"explorer", widepath.GetData(), nullptr };
|
||||
::wxExecute(const_cast<wchar_t**>(argv), wxEXEC_ASYNC, nullptr);
|
||||
#elif __APPLE__
|
||||
const char* argv[] = { "open", path.string().c_str(), nullptr };
|
||||
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr);
|
||||
#else
|
||||
const char* argv[] = { "xdg-open", path.string().c_str(), nullptr };
|
||||
desktop_execute(argv);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
namespace {
|
||||
wxExecuteEnv get_appimage_exec_env()
|
||||
{
|
||||
// If we're running in an AppImage container, we need to remove AppImage's env vars,
|
||||
// because they may mess up the environment expected by the file manager.
|
||||
// Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure.
|
||||
wxEnvVariableHashMap env_vars;
|
||||
wxGetEnvMap(&env_vars);
|
||||
|
||||
env_vars.erase("APPIMAGE");
|
||||
env_vars.erase("APPDIR");
|
||||
env_vars.erase("LD_LIBRARY_PATH");
|
||||
env_vars.erase("LD_PRELOAD");
|
||||
env_vars.erase("UNION_PRELOAD");
|
||||
|
||||
wxExecuteEnv exec_env;
|
||||
exec_env.env = std::move(env_vars);
|
||||
|
||||
wxString owd;
|
||||
if (wxGetEnv("OWD", &owd)) {
|
||||
// This is the original work directory from which the AppImage image was run,
|
||||
// set it as CWD for the child process:
|
||||
exec_env.cwd = std::move(owd);
|
||||
}
|
||||
return exec_env;
|
||||
}
|
||||
} // namespace
|
||||
void desktop_execute(const char* argv[])
|
||||
{
|
||||
// Check if we're running in an AppImage container, if so, we need to remove AppImage's env vars,
|
||||
// because they may mess up the environment expected by the file manager.
|
||||
// Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure.
|
||||
if (wxGetEnv("APPIMAGE", nullptr)) {
|
||||
// We're running from AppImage
|
||||
wxExecuteEnv exec_env = get_appimage_exec_env();
|
||||
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, &exec_env);
|
||||
}
|
||||
else {
|
||||
// Looks like we're NOT running from AppImage, we'll make no changes to the environment.
|
||||
::wxExecute(const_cast<char**>(argv), wxEXEC_ASYNC, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
void desktop_execute_get_result(wxString command, wxArrayString& output)
|
||||
{
|
||||
output.Clear();
|
||||
//Check if we're running in an AppImage container, if so, we need to remove AppImage's env vars,
|
||||
// because they may mess up the environment expected by the file manager.
|
||||
// Mostly this is about LD_LIBRARY_PATH, but we remove a few more too for good measure.
|
||||
if (wxGetEnv("APPIMAGE", nullptr)) {
|
||||
// We're running from AppImage
|
||||
wxExecuteEnv exec_env = get_appimage_exec_env();
|
||||
::wxExecute(command, output, wxEXEC_SYNC | wxEXEC_NOEVENTS, &exec_env);
|
||||
} else {
|
||||
// Looks like we're NOT running from AppImage, we'll make no changes to the environment.
|
||||
::wxExecute(command, output, wxEXEC_SYNC | wxEXEC_NOEVENTS);
|
||||
}
|
||||
}
|
||||
#endif // __linux__
|
||||
|
||||
#ifdef _WIN32
|
||||
bool create_process(const boost::filesystem::path& path, const std::wstring& cmd_opt, std::string& error_msg)
|
||||
{
|
||||
// find updater exe
|
||||
if (boost::filesystem::exists(path)) {
|
||||
// Using quoted string as mentioned in CreateProcessW docs.
|
||||
std::wstring wcmd = L"\"" + path.wstring() + L"\"";
|
||||
if (!cmd_opt.empty())
|
||||
wcmd += L" " + cmd_opt;
|
||||
|
||||
// additional information
|
||||
STARTUPINFOW si;
|
||||
PROCESS_INFORMATION pi;
|
||||
|
||||
// set the size of the structures
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
|
||||
// start the program up
|
||||
if (CreateProcessW(NULL, // the path
|
||||
wcmd.data(), // Command line
|
||||
NULL, // Process handle not inheritable
|
||||
NULL, // Thread handle not inheritable
|
||||
FALSE, // Set handle inheritance to FALSE
|
||||
0, // No creation flags
|
||||
NULL, // Use parent's environment block
|
||||
NULL, // Use parent's starting directory
|
||||
&si, // Pointer to STARTUPINFO structure
|
||||
&pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
|
||||
)) {
|
||||
// Close process and thread handles.
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
error_msg = "CreateProcessW failed to create process " + boost::nowide::narrow(path.wstring());
|
||||
}
|
||||
else
|
||||
error_msg = "Executable doesn't exists. Path: " + boost::nowide::narrow(path.wstring());
|
||||
return false;
|
||||
}
|
||||
#endif //_WIN32
|
||||
|
||||
} } // namespaces GUI / Slic3r
|
||||
106
src/slic3r/GUI/GUI.hpp
Normal file
106
src/slic3r/GUI/GUI.hpp
Normal file
@@ -0,0 +1,106 @@
|
||||
#ifndef slic3r_GUI_hpp_
|
||||
#define slic3r_GUI_hpp_
|
||||
|
||||
namespace boost { class any; }
|
||||
namespace boost::filesystem { class path; }
|
||||
|
||||
#include <wx/string.h>
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
|
||||
class wxWindow;
|
||||
class wxMenuBar;
|
||||
class wxComboCtrl;
|
||||
class wxFileDialog;
|
||||
class wxArrayString;
|
||||
class wxTopLevelWindow;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class AppConfig;
|
||||
class DynamicPrintConfig;
|
||||
class Print;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
void disable_screensaver();
|
||||
void enable_screensaver();
|
||||
bool debugged();
|
||||
void break_to_debugger();
|
||||
|
||||
// Platform specific Ctrl+/Alt+ (Windows, Linux) vs. ⌘/⌥ (OSX) prefixes
|
||||
extern const std::string& shortkey_ctrl_prefix();
|
||||
extern const std::string& shortkey_alt_prefix();
|
||||
|
||||
extern AppConfig* get_app_config();
|
||||
|
||||
extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change);
|
||||
|
||||
// Change option value in config
|
||||
void change_opt_value(DynamicPrintConfig& config, const t_config_option_key& opt_key, const boost::any& value, int opt_index = 0);
|
||||
|
||||
// If monospaced_font is true, the error message is displayed using html <code><pre></pre></code> tags,
|
||||
// so that the code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
|
||||
void show_error(wxWindow* parent, const wxString& message, bool monospaced_font = false);
|
||||
void show_error(wxWindow* parent, const char* message, bool monospaced_font = false);
|
||||
inline void show_error(wxWindow* parent, const std::string& message, bool monospaced_font = false) { show_error(parent, message.c_str(), monospaced_font); }
|
||||
void show_error_id(int id, const std::string& message); // For Perl
|
||||
void show_info(wxWindow* parent, const wxString& message, const wxString& title = wxString());
|
||||
void show_info(wxWindow* parent, const char* message, const char* title = nullptr);
|
||||
inline void show_info(wxWindow* parent, const std::string& message,const std::string& title = std::string()) { show_info(parent, message.c_str(), title.c_str()); }
|
||||
void warning_catcher(wxWindow* parent, const wxString& message);
|
||||
void show_substitutions_info(const PresetsConfigSubstitutions& presets_config_substitutions);
|
||||
void show_substitutions_info(const ConfigSubstitutions& config_substitutions, const std::string& filename);
|
||||
|
||||
// Creates a wxCheckListBoxComboPopup inside the given wxComboCtrl, filled with the given text and items.
|
||||
// Items data must be separated by '|', and contain the item name to be shown followed by its initial value (0 for false, 1 for true).
|
||||
// For example "Item1|0|Item2|1|Item3|0", and so on.
|
||||
void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items);
|
||||
|
||||
// Returns the current state of the items listed in the wxCheckListBoxComboPopup contained in the given wxComboCtrl,
|
||||
// encoded inside an unsigned int.
|
||||
unsigned int combochecklist_get_flags(wxComboCtrl* comboCtrl);
|
||||
|
||||
// Sets the current state of the items listed in the wxCheckListBoxComboPopup contained in the given wxComboCtrl,
|
||||
// with the flags encoded in the given unsigned int.
|
||||
void combochecklist_set_flags(wxComboCtrl* comboCtrl, unsigned int flags);
|
||||
|
||||
// wxString conversions:
|
||||
|
||||
// wxString from std::string in UTF8
|
||||
wxString from_u8(const std::string &str);
|
||||
// std::string in UTF8 from wxString
|
||||
std::string into_u8(const wxString &str);
|
||||
// wxString from boost path
|
||||
wxString from_path(const boost::filesystem::path &path);
|
||||
// boost path from wxString
|
||||
boost::filesystem::path into_path(const wxString &str);
|
||||
|
||||
// Display an About dialog
|
||||
extern void about();
|
||||
// Ask the destop to open the datadir using the default file explorer.
|
||||
extern void desktop_open_datadir_folder();
|
||||
// Ask the destop to open the directory specified by path using the default file explorer.
|
||||
void desktop_open_folder(const boost::filesystem::path& path);
|
||||
|
||||
#ifdef __linux__
|
||||
// Calling wxExecute on Linux with proper handling of AppImage's env vars.
|
||||
// argv example: { "xdg-open", path.c_str(), nullptr }
|
||||
void desktop_execute(const char* argv[]);
|
||||
void desktop_execute_get_result(wxString command, wxArrayString& output);
|
||||
#endif // __linux__
|
||||
|
||||
#ifdef _WIN32
|
||||
// Call CreateProcessW to start external proccess on path
|
||||
// returns true on success
|
||||
// path should contain path to the process
|
||||
// cmd_opt can be empty or contain command line options. Example: L"/silent"
|
||||
// error_msg will contain error message if create_process return false
|
||||
bool create_process(const boost::filesystem::path& path, const std::wstring& cmd_opt, std::string& error_msg);
|
||||
#endif //_WIN32
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
3521
src/slic3r/GUI/GUI_App.cpp
Normal file
3521
src/slic3r/GUI/GUI_App.cpp
Normal file
File diff suppressed because it is too large
Load Diff
415
src/slic3r/GUI/GUI_App.hpp
Normal file
415
src/slic3r/GUI/GUI_App.hpp
Normal file
@@ -0,0 +1,415 @@
|
||||
#ifndef slic3r_GUI_App_hpp_
|
||||
#define slic3r_GUI_App_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "ImGuiWrapper.hpp"
|
||||
#include "ConfigWizard.hpp"
|
||||
//B19
|
||||
#include "BugWizard.hpp"
|
||||
#include "OpenGLManager.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
|
||||
#include <wx/app.h>
|
||||
#include <wx/colour.h>
|
||||
#include <wx/font.h>
|
||||
#include <wx/string.h>
|
||||
#include <wx/snglinst.h>
|
||||
|
||||
#include <mutex>
|
||||
#include <stack>
|
||||
|
||||
class wxMenuItem;
|
||||
class wxMenuBar;
|
||||
class wxTopLevelWindow;
|
||||
class wxDataViewCtrl;
|
||||
class wxBookCtrlBase;
|
||||
struct wxLanguageInfo;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class AppConfig;
|
||||
class PresetBundle;
|
||||
class PresetUpdater;
|
||||
class ModelObject;
|
||||
class PrintHostJobQueue;
|
||||
class Model;
|
||||
class AppUpdater;
|
||||
|
||||
namespace GUI{
|
||||
|
||||
class RemovableDriveManager;
|
||||
class OtherInstanceMessageHandler;
|
||||
class MainFrame;
|
||||
class Sidebar;
|
||||
class ObjectManipulation;
|
||||
class ObjectSettings;
|
||||
class ObjectList;
|
||||
class ObjectLayers;
|
||||
class Plater;
|
||||
class NotificationManager;
|
||||
class Downloader;
|
||||
struct GUI_InitParams;
|
||||
class GalleryDialog;
|
||||
|
||||
|
||||
|
||||
enum FileType
|
||||
{
|
||||
FT_STL,
|
||||
FT_OBJ,
|
||||
FT_OBJECT,
|
||||
FT_STEP,
|
||||
FT_AMF,
|
||||
FT_3MF,
|
||||
FT_GCODE,
|
||||
FT_MODEL,
|
||||
FT_PROJECT,
|
||||
FT_FONTS,
|
||||
FT_GALLERY,
|
||||
|
||||
FT_INI,
|
||||
FT_SVG,
|
||||
|
||||
FT_TEX,
|
||||
|
||||
FT_SL1,
|
||||
|
||||
FT_ZIP,
|
||||
|
||||
FT_SIZE,
|
||||
};
|
||||
|
||||
#if ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR
|
||||
extern wxString file_wildcards(FileType file_type);
|
||||
#else
|
||||
extern wxString file_wildcards(FileType file_type, const std::string &custom_extension = std::string{});
|
||||
#endif // ENABLE_ALTERNATIVE_FILE_WILDCARDS_GENERATOR
|
||||
|
||||
enum ConfigMenuIDs {
|
||||
ConfigMenuWizard,
|
||||
ConfigMenuSnapshots,
|
||||
ConfigMenuTakeSnapshot,
|
||||
ConfigMenuUpdateConf,
|
||||
ConfigMenuUpdateApp,
|
||||
ConfigMenuDesktopIntegration,
|
||||
ConfigMenuPreferences,
|
||||
ConfigMenuModeSimple,
|
||||
ConfigMenuModeAdvanced,
|
||||
ConfigMenuModeExpert,
|
||||
ConfigMenuLanguage,
|
||||
ConfigMenuFlashFirmware,
|
||||
ConfigMenuCnt,
|
||||
};
|
||||
|
||||
class Tab;
|
||||
class ConfigWizard;
|
||||
|
||||
static wxString dots("…", wxConvUTF8);
|
||||
|
||||
// Does our wxWidgets version support markup?
|
||||
// https://github.com/qidi3d/QIDISlicer/issues/4282#issuecomment-634676371
|
||||
#if wxUSE_MARKUP && wxCHECK_VERSION(3, 1, 1)
|
||||
#define SUPPORTS_MARKUP
|
||||
#endif
|
||||
|
||||
class GUI_App : public wxApp
|
||||
{
|
||||
public:
|
||||
enum class EAppMode : unsigned char
|
||||
{
|
||||
Editor,
|
||||
GCodeViewer
|
||||
};
|
||||
|
||||
private:
|
||||
bool m_initialized { false };
|
||||
bool m_post_initialized { false };
|
||||
bool m_app_conf_exists{ false };
|
||||
bool m_last_app_conf_lower_version{ false };
|
||||
EAppMode m_app_mode{ EAppMode::Editor };
|
||||
bool m_is_recreating_gui{ false };
|
||||
bool m_opengl_initialized{ false };
|
||||
|
||||
wxColour m_color_label_modified;
|
||||
wxColour m_color_label_sys;
|
||||
wxColour m_color_label_default;
|
||||
wxColour m_color_window_default;
|
||||
#ifdef _WIN32
|
||||
wxColour m_color_highlight_label_default;
|
||||
wxColour m_color_hovered_btn_label;
|
||||
wxColour m_color_default_btn_label;
|
||||
wxColour m_color_highlight_default;
|
||||
//B10
|
||||
wxColour m_tap_color_highlight_default;
|
||||
wxColour m_color_selected_btn_bg;
|
||||
bool m_force_colors_update { false };
|
||||
#endif
|
||||
std::vector<std::string> m_mode_palette;
|
||||
|
||||
wxFont m_small_font;
|
||||
wxFont m_bold_font;
|
||||
wxFont m_normal_font;
|
||||
wxFont m_code_font;
|
||||
wxFont m_link_font;
|
||||
|
||||
int m_em_unit; // width of a "m"-symbol in pixels for current system font
|
||||
// Note: for 100% Scale m_em_unit = 10 -> it's a good enough coefficient for a size setting of controls
|
||||
|
||||
std::unique_ptr<wxLocale> m_wxLocale;
|
||||
// System language, from locales, owned by wxWidgets.
|
||||
const wxLanguageInfo *m_language_info_system = nullptr;
|
||||
// Best translation language, provided by Windows or OSX, owned by wxWidgets.
|
||||
const wxLanguageInfo *m_language_info_best = nullptr;
|
||||
|
||||
OpenGLManager m_opengl_mgr;
|
||||
//B4
|
||||
bool m_adding_script_handler{false};
|
||||
|
||||
std::unique_ptr<RemovableDriveManager> m_removable_drive_manager;
|
||||
|
||||
std::unique_ptr<ImGuiWrapper> m_imgui;
|
||||
std::unique_ptr<PrintHostJobQueue> m_printhost_job_queue;
|
||||
std::unique_ptr <OtherInstanceMessageHandler> m_other_instance_message_handler;
|
||||
std::unique_ptr <AppUpdater> m_app_updater;
|
||||
std::unique_ptr <wxSingleInstanceChecker> m_single_instance_checker;
|
||||
std::unique_ptr <Downloader> m_downloader;
|
||||
std::string m_instance_hash_string;
|
||||
size_t m_instance_hash_int;
|
||||
|
||||
public:
|
||||
bool OnInit() override;
|
||||
bool initialized() const { return m_initialized; }
|
||||
|
||||
explicit GUI_App(EAppMode mode = EAppMode::Editor);
|
||||
~GUI_App() override;
|
||||
|
||||
EAppMode get_app_mode() const { return m_app_mode; }
|
||||
bool is_editor() const { return m_app_mode == EAppMode::Editor; }
|
||||
bool is_gcode_viewer() const { return m_app_mode == EAppMode::GCodeViewer; }
|
||||
bool is_recreating_gui() const { return m_is_recreating_gui; }
|
||||
std::string logo_name() const { return is_editor() ? "QIDISlicer" : "QIDISlicer-gcodeviewer"; }
|
||||
|
||||
// To be called after the GUI is fully built up.
|
||||
// Process command line parameters cached in this->init_params,
|
||||
// load configs, STLs etc.
|
||||
void post_init();
|
||||
// If formatted for github, plaintext with OpenGL extensions enclosed into <details>.
|
||||
// Otherwise HTML formatted for the system info dialog.
|
||||
static std::string get_gl_info(bool for_github);
|
||||
wxGLContext* init_glcontext(wxGLCanvas& canvas);
|
||||
bool init_opengl();
|
||||
|
||||
static unsigned get_colour_approx_luma(const wxColour &colour);
|
||||
static bool dark_mode();
|
||||
const wxColour get_label_default_clr_system();
|
||||
const wxColour get_label_default_clr_modified();
|
||||
const std::vector<std::string> get_mode_default_palette();
|
||||
void init_ui_colours();
|
||||
void update_ui_colours_from_appconfig();
|
||||
void update_label_colours();
|
||||
// update color mode for window
|
||||
void UpdateDarkUI(wxWindow *window, bool highlited = false, bool just_font = false);
|
||||
// update color mode for whole dialog including all children
|
||||
void UpdateDlgDarkUI(wxDialog* dlg, bool just_buttons_update = false);
|
||||
// update color mode for DataViewControl
|
||||
void UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited = false);
|
||||
// update color mode for panel including all static texts controls
|
||||
void UpdateAllStaticTextDarkUI(wxWindow* parent);
|
||||
void init_fonts();
|
||||
void update_fonts(const MainFrame *main_frame = nullptr);
|
||||
void set_label_clr_modified(const wxColour& clr);
|
||||
void set_label_clr_sys(const wxColour& clr);
|
||||
//B4
|
||||
bool is_adding_script_handler() { return m_adding_script_handler; }
|
||||
void set_adding_script_handler(bool status) { m_adding_script_handler = status; }
|
||||
|
||||
const wxColour& get_label_clr_modified(){ return m_color_label_modified; }
|
||||
const wxColour& get_label_clr_sys() { return m_color_label_sys; }
|
||||
const wxColour& get_label_clr_default() { return m_color_label_default; }
|
||||
const wxColour& get_window_default_clr(){ return m_color_window_default; }
|
||||
|
||||
const std::string& get_mode_btn_color(int mode_id);
|
||||
std::vector<wxColour> get_mode_palette();
|
||||
void set_mode_palette(const std::vector<wxColour> &palette);
|
||||
|
||||
#ifdef _WIN32
|
||||
const wxColour& get_label_highlight_clr() { return m_color_highlight_label_default; }
|
||||
const wxColour& get_highlight_default_clr() { return m_color_highlight_default; }
|
||||
//B10
|
||||
const wxColour& get_tap_highlight_default_clr() { return m_tap_color_highlight_default; }
|
||||
const wxColour& get_color_hovered_btn_label() { return m_color_hovered_btn_label; }
|
||||
const wxColour& get_color_selected_btn_bg() { return m_color_selected_btn_bg; }
|
||||
void force_colors_update();
|
||||
#ifdef _MSW_DARK_MODE
|
||||
void force_menu_update();
|
||||
#endif //_MSW_DARK_MODE
|
||||
#endif
|
||||
|
||||
const wxFont& small_font() { return m_small_font; }
|
||||
const wxFont& bold_font() { return m_bold_font; }
|
||||
const wxFont& normal_font() { return m_normal_font; }
|
||||
const wxFont& code_font() { return m_code_font; }
|
||||
const wxFont& link_font() { return m_link_font; }
|
||||
int em_unit() const { return m_em_unit; }
|
||||
bool tabs_as_menu() const;
|
||||
wxSize get_min_size() const;
|
||||
float toolbar_icon_scale(const bool is_limited = false) const;
|
||||
void set_auto_toolbar_icon_scale(float scale) const;
|
||||
void check_printer_presets();
|
||||
|
||||
void recreate_GUI(const wxString& message);
|
||||
void system_info();
|
||||
void keyboard_shortcuts();
|
||||
void load_project(wxWindow *parent, wxString& input_file) const;
|
||||
void import_model(wxWindow *parent, wxArrayString& input_files) const;
|
||||
void import_zip(wxWindow* parent, wxString& input_file) const;
|
||||
void load_gcode(wxWindow* parent, wxString& input_file) const;
|
||||
|
||||
static bool catch_error(std::function<void()> cb, const std::string& err);
|
||||
|
||||
void persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false);
|
||||
void update_ui_from_settings();
|
||||
|
||||
bool switch_language();
|
||||
bool load_language(wxString language, bool initial);
|
||||
|
||||
Tab* get_tab(Preset::Type type);
|
||||
ConfigOptionMode get_mode();
|
||||
bool save_mode(const /*ConfigOptionMode*/int mode) ;
|
||||
void update_mode();
|
||||
|
||||
void add_config_menu(wxMenuBar *menu);
|
||||
bool has_unsaved_preset_changes() const;
|
||||
bool has_current_preset_changes() const;
|
||||
void update_saved_preset_from_current_preset();
|
||||
std::vector<const PresetCollection*> get_active_preset_collections() const;
|
||||
bool check_and_save_current_preset_changes(const wxString& caption, const wxString& header, bool remember_choice = true, bool use_dont_save_insted_of_discard = false);
|
||||
void apply_keeped_preset_modifications();
|
||||
bool check_and_keep_current_preset_changes(const wxString& caption, const wxString& header, int action_buttons, bool* postponed_apply_of_keeped_changes = nullptr);
|
||||
bool can_load_project();
|
||||
bool check_print_host_queue();
|
||||
bool checked_tab(Tab* tab);
|
||||
void load_current_presets(bool check_printer_presets = true);
|
||||
|
||||
wxString current_language_code() const { return m_wxLocale->GetCanonicalName(); }
|
||||
// Translate the language code to a code, for which QIDI Research maintains translations. Defaults to "en_US".
|
||||
wxString current_language_code_safe() const;
|
||||
bool is_localized() const { return m_wxLocale->GetLocale() != "English"; }
|
||||
|
||||
void open_preferences(const std::string& highlight_option = std::string(), const std::string& tab_name = std::string());
|
||||
|
||||
virtual bool OnExceptionInMainLoop() override;
|
||||
// Calls wxLaunchDefaultBrowser if user confirms in dialog.
|
||||
// Add "Rememeber my choice" checkbox to question dialog, when it is forced or a "suppress_hyperlinks" option has empty value
|
||||
bool open_browser_with_warning_dialog(const wxString& url, wxWindow* parent = nullptr, bool force_remember_choice = true, int flags = 0);
|
||||
#ifdef __APPLE__
|
||||
void OSXStoreOpenFiles(const wxArrayString &files) override;
|
||||
// wxWidgets override to get an event on open files.
|
||||
void MacOpenFiles(const wxArrayString &fileNames) override;
|
||||
void MacOpenURL(const wxString& url) override;
|
||||
#endif /* __APPLE */
|
||||
|
||||
Sidebar& sidebar();
|
||||
ObjectManipulation* obj_manipul();
|
||||
ObjectSettings* obj_settings();
|
||||
ObjectList* obj_list();
|
||||
ObjectLayers* obj_layers();
|
||||
Plater* plater();
|
||||
const Plater* plater() const;
|
||||
Model& model();
|
||||
NotificationManager* notification_manager();
|
||||
GalleryDialog * gallery_dialog();
|
||||
Downloader* downloader();
|
||||
|
||||
// Parameters extracted from the command line to be passed to GUI after initialization.
|
||||
GUI_InitParams* init_params { nullptr };
|
||||
|
||||
AppConfig* app_config{ nullptr };
|
||||
PresetBundle* preset_bundle{ nullptr };
|
||||
PresetUpdater* preset_updater{ nullptr };
|
||||
MainFrame* mainframe{ nullptr };
|
||||
Plater* plater_{ nullptr };
|
||||
|
||||
PresetUpdater* get_preset_updater() { return preset_updater; }
|
||||
|
||||
wxBookCtrlBase* tab_panel() const ;
|
||||
int extruders_cnt() const;
|
||||
int extruders_edited_cnt() const;
|
||||
|
||||
std::vector<Tab *> tabs_list;
|
||||
|
||||
RemovableDriveManager* removable_drive_manager() { return m_removable_drive_manager.get(); }
|
||||
OtherInstanceMessageHandler* other_instance_message_handler() { return m_other_instance_message_handler.get(); }
|
||||
wxSingleInstanceChecker* single_instance_checker() {return m_single_instance_checker.get();}
|
||||
|
||||
void init_single_instance_checker(const std::string &name, const std::string &path);
|
||||
void set_instance_hash (const size_t hash) { m_instance_hash_int = hash; m_instance_hash_string = std::to_string(hash); }
|
||||
std::string get_instance_hash_string () { return m_instance_hash_string; }
|
||||
size_t get_instance_hash_int () { return m_instance_hash_int; }
|
||||
|
||||
ImGuiWrapper* imgui() { return m_imgui.get(); }
|
||||
|
||||
PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); }
|
||||
|
||||
void open_web_page_localized(const std::string &http_address);
|
||||
bool may_switch_to_SLA_preset(const wxString& caption);
|
||||
bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME);
|
||||
//B19
|
||||
bool run_bwizard(BugWizard::BugRunReason reason, BugWizard::BugStartPage start_page = BugWizard::SP_WELCOME);
|
||||
void show_desktop_integration_dialog();
|
||||
void show_downloader_registration_dialog();
|
||||
|
||||
#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_DEBUG
|
||||
|
||||
GLShaderProgram* get_shader(const std::string& shader_name) { return m_opengl_mgr.get_shader(shader_name); }
|
||||
GLShaderProgram* get_current_shader() { return m_opengl_mgr.get_current_shader(); }
|
||||
|
||||
bool is_gl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { return m_opengl_mgr.get_gl_info().is_version_greater_or_equal_to(major, minor); }
|
||||
bool is_glsl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { return m_opengl_mgr.get_gl_info().is_glsl_version_greater_or_equal_to(major, minor); }
|
||||
int GetSingleChoiceIndex(const wxString& message, const wxString& caption, const wxArrayString& choices, int initialSelection);
|
||||
|
||||
#ifdef __WXMSW__
|
||||
void associate_3mf_files();
|
||||
void associate_stl_files();
|
||||
void associate_gcode_files();
|
||||
#endif // __WXMSW__
|
||||
|
||||
|
||||
// URL download - QIDISlicer gets system call to open qidislicer:// URL which should contain address of download
|
||||
void start_download(std::string url);
|
||||
|
||||
private:
|
||||
bool on_init_inner();
|
||||
void init_app_config();
|
||||
// returns old config path to copy from if such exists,
|
||||
// returns an empty string if such config path does not exists or if it cannot be loaded.
|
||||
std::string check_older_app_config(Semver current_version, bool backup);
|
||||
void window_pos_save(wxTopLevelWindow* window, const std::string &name);
|
||||
void window_pos_restore(wxTopLevelWindow* window, const std::string &name, bool default_maximized = false);
|
||||
void window_pos_sanitize(wxTopLevelWindow* window);
|
||||
bool select_language();
|
||||
|
||||
bool config_wizard_startup();
|
||||
// Returns true if the configuration is fine.
|
||||
// Returns true if the configuration is not compatible and the user decided to rather close the slicer instead of reconfiguring.
|
||||
bool check_updates(const bool verbose);
|
||||
void on_version_read(wxCommandEvent& evt);
|
||||
// if the data from version file are already downloaded, shows dialogs to start download of new version of app
|
||||
void app_updater(bool from_user);
|
||||
// inititate read of version file online in separate thread
|
||||
void app_version_check(bool from_user);
|
||||
|
||||
bool m_datadir_redefined { false };
|
||||
|
||||
};
|
||||
|
||||
DECLARE_APP(GUI_App)
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
#endif // slic3r_GUI_App_hpp_
|
||||
1362
src/slic3r/GUI/GUI_Factories.cpp
Normal file
1362
src/slic3r/GUI/GUI_Factories.cpp
Normal file
File diff suppressed because it is too large
Load Diff
118
src/slic3r/GUI/GUI_Factories.hpp
Normal file
118
src/slic3r/GUI/GUI_Factories.hpp
Normal file
@@ -0,0 +1,118 @@
|
||||
#ifndef slic3r_GUI_Factories_hpp_
|
||||
#define slic3r_GUI_Factories_hpp_
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
#include <wx/bitmap.h>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
class wxMenu;
|
||||
class wxMenuItem;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class ModelVolumeType : int;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
struct SettingsFactory
|
||||
{
|
||||
// category -> vector ( option )
|
||||
typedef std::map<std::string, std::vector<std::string>> Bundle;
|
||||
static std::map<std::string, std::string> CATEGORY_ICON;
|
||||
|
||||
static wxBitmapBundle* get_category_bitmap(const std::string& category_name);
|
||||
static Bundle get_bundle(const DynamicPrintConfig* config, bool is_object_settings);
|
||||
static std::vector<std::string> get_options(bool is_part);
|
||||
};
|
||||
|
||||
class MenuFactory
|
||||
{
|
||||
public:
|
||||
static std::vector<wxBitmapBundle*> get_volume_bitmaps();
|
||||
static std::vector<wxBitmapBundle*> get_text_volume_bitmaps();
|
||||
|
||||
MenuFactory();
|
||||
~MenuFactory() = default;
|
||||
|
||||
void init(wxWindow* parent);
|
||||
void update();
|
||||
void update_objects_menu();
|
||||
void update_default_menu();
|
||||
void sys_color_changed();
|
||||
|
||||
static void sys_color_changed(wxMenuBar* menu_bar);
|
||||
|
||||
wxMenu* default_menu();
|
||||
wxMenu* object_menu();
|
||||
wxMenu* sla_object_menu();
|
||||
wxMenu* part_menu();
|
||||
wxMenu* text_part_menu();
|
||||
wxMenu* instance_menu();
|
||||
wxMenu* layer_menu();
|
||||
wxMenu* multi_selection_menu();
|
||||
|
||||
private:
|
||||
enum MenuType {
|
||||
mtObjectFFF = 0,
|
||||
mtObjectSLA,
|
||||
mtCount
|
||||
};
|
||||
|
||||
wxWindow* m_parent {nullptr};
|
||||
|
||||
MenuWithSeparators m_object_menu;
|
||||
MenuWithSeparators m_part_menu;
|
||||
MenuWithSeparators m_text_part_menu;
|
||||
MenuWithSeparators m_sla_object_menu;
|
||||
MenuWithSeparators m_default_menu;
|
||||
MenuWithSeparators m_instance_menu;
|
||||
|
||||
// Removed/Prepended Items according to the view mode
|
||||
std::array<wxMenuItem*, mtCount> items_increase;
|
||||
std::array<wxMenuItem*, mtCount> items_decrease;
|
||||
std::array<wxMenuItem*, mtCount> items_set_number_of_copies;
|
||||
|
||||
void create_default_menu();
|
||||
void create_common_object_menu(wxMenu *menu);
|
||||
void append_immutable_part_menu_items(wxMenu* menu);
|
||||
void append_mutable_part_menu_items(wxMenu* menu);
|
||||
void create_part_menu();
|
||||
void create_text_part_menu();
|
||||
void create_instance_menu();
|
||||
|
||||
wxMenu* append_submenu_add_generic(wxMenu* menu, ModelVolumeType type);
|
||||
void append_menu_item_add_text(wxMenu* menu, ModelVolumeType type, bool is_submenu_item = true);
|
||||
void append_menu_items_add_volume(MenuType type);
|
||||
wxMenuItem* append_menu_item_layers_editing(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_settings(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_change_type(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_instance_to_object(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_printable(wxMenu* menu);
|
||||
void append_menu_item_invalidate_cut_info(wxMenu *menu);
|
||||
void append_menu_items_osx(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu);
|
||||
wxMenuItem* append_menu_item_simplify(wxMenu* menu);
|
||||
void append_menu_item_export_stl(wxMenu* menu);
|
||||
void append_menu_item_reload_from_disk(wxMenu* menu);
|
||||
void append_menu_item_replace_with_stl(wxMenu* menu);
|
||||
void append_menu_item_change_extruder(wxMenu* menu);
|
||||
void append_menu_item_delete(wxMenu* menu);
|
||||
void append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu);
|
||||
void append_menu_items_convert_unit(wxMenu* menu, int insert_pos = 1); // Add "Conver/Revert..." menu items (from/to inches/meters) after "Reload From Disk"
|
||||
void append_menu_item_merge_to_multipart_object(wxMenu *menu);
|
||||
// void append_menu_item_merge_to_single_object(wxMenu *menu);
|
||||
void append_menu_items_mirror(wxMenu *menu);
|
||||
void append_menu_item_edit_text(wxMenu *menu);
|
||||
void append_menu_items_instance_manipulation(wxMenu *menu);
|
||||
void update_menu_items_instance_manipulation(MenuType type);
|
||||
void append_menu_items_split(wxMenu *menu);
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
#endif //slic3r_GUI_Factories_hpp_
|
||||
9
src/slic3r/GUI/GUI_Geometry.cpp
Normal file
9
src/slic3r/GUI/GUI_Geometry.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "GUI_Geometry.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
} // namespace GUI
|
||||
78
src/slic3r/GUI/GUI_Geometry.hpp
Normal file
78
src/slic3r/GUI/GUI_Geometry.hpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#ifndef slic3r_GUI_Geometry_hpp_
|
||||
#define slic3r_GUI_Geometry_hpp_
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
enum class ECoordinatesType : unsigned char
|
||||
{
|
||||
World,
|
||||
Instance,
|
||||
Local
|
||||
};
|
||||
|
||||
class TransformationType
|
||||
{
|
||||
public:
|
||||
enum Enum {
|
||||
// Transforming in a world coordinate system
|
||||
World = 0,
|
||||
// Transforming in a instance coordinate system
|
||||
Instance = 1,
|
||||
// Transforming in a local coordinate system
|
||||
Local = 2,
|
||||
// Absolute transformations, allowed in local coordinate system only.
|
||||
Absolute = 0,
|
||||
// Relative transformations, allowed in both local and world coordinate system.
|
||||
Relative = 4,
|
||||
// For group selection, the transformation is performed as if the group made a single solid body.
|
||||
Joint = 0,
|
||||
// For group selection, the transformation is performed on each object independently.
|
||||
Independent = 8,
|
||||
|
||||
World_Relative_Joint = World | Relative | Joint,
|
||||
World_Relative_Independent = World | Relative | Independent,
|
||||
Instance_Absolute_Joint = Instance | Absolute | Joint,
|
||||
Instance_Absolute_Independent = Instance | Absolute | Independent,
|
||||
Instance_Relative_Joint = Instance | Relative | Joint,
|
||||
Instance_Relative_Independent = Instance | Relative | Independent,
|
||||
Local_Absolute_Joint = Local | Absolute | Joint,
|
||||
Local_Absolute_Independent = Local | Absolute | Independent,
|
||||
Local_Relative_Joint = Local | Relative | Joint,
|
||||
Local_Relative_Independent = Local | Relative | Independent,
|
||||
};
|
||||
|
||||
TransformationType() : m_value(World) {}
|
||||
TransformationType(Enum value) : m_value(value) {}
|
||||
TransformationType& operator=(Enum value) { m_value = value; return *this; }
|
||||
|
||||
Enum operator()() const { return m_value; }
|
||||
bool has(Enum v) const { return ((unsigned int)m_value & (unsigned int)v) != 0; }
|
||||
|
||||
void set_world() { this->remove(Instance); this->remove(Local); }
|
||||
void set_instance() { this->remove(Local); this->add(Instance); }
|
||||
void set_local() { this->remove(Instance); this->add(Local); }
|
||||
void set_absolute() { this->remove(Relative); }
|
||||
void set_relative() { this->add(Relative); }
|
||||
void set_joint() { this->remove(Independent); }
|
||||
void set_independent() { this->add(Independent); }
|
||||
|
||||
bool world() const { return !this->has(Instance) && !this->has(Local); }
|
||||
bool instance() const { return this->has(Instance); }
|
||||
bool local() const { return this->has(Local); }
|
||||
bool absolute() const { return !this->has(Relative); }
|
||||
bool relative() const { return this->has(Relative); }
|
||||
bool joint() const { return !this->has(Independent); }
|
||||
bool independent() const { return this->has(Independent); }
|
||||
|
||||
private:
|
||||
void add(Enum v) { m_value = Enum((unsigned int)m_value | (unsigned int)v); }
|
||||
void remove(Enum v) { m_value = Enum((unsigned int)m_value & (~(unsigned int)v)); }
|
||||
|
||||
Enum m_value;
|
||||
};
|
||||
|
||||
} // namespace Slic3r
|
||||
} // namespace GUI
|
||||
|
||||
#endif // slic3r_GUI_Geometry_hpp_
|
||||
75
src/slic3r/GUI/GUI_Init.cpp
Normal file
75
src/slic3r/GUI/GUI_Init.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "libslic3r/Technologies.hpp"
|
||||
#include "GUI_Init.hpp"
|
||||
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/3DScene.hpp"
|
||||
#include "slic3r/GUI/InstanceCheck.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "slic3r/GUI/MainFrame.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
|
||||
// To show a message box if GUI initialization ends up with an exception thrown.
|
||||
#include <wx/msgdlg.h>
|
||||
|
||||
#include <boost/nowide/iostream.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
|
||||
#if __APPLE__
|
||||
#include <signal.h>
|
||||
#endif // __APPLE__
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
const std::vector<std::string> OpenGLVersions::core_str = { "3.2", "3.3", "4.0", "4.1", "4.2", "4.3", "4.4", "4.5", "4.6" };
|
||||
const std::vector<std::string> OpenGLVersions::precore_str = { "2.0", "2.1", "3.0", "3.1" };
|
||||
|
||||
const std::vector<std::pair<int, int>> OpenGLVersions::core = { {3,2}, {3,3}, {4,0}, {4,1}, {4,2}, {4,3}, {4,4}, {4,5}, {4,6} };
|
||||
const std::vector<std::pair<int, int>> OpenGLVersions::precore = { {2,0}, {2,1}, {3,0}, {3,1} };
|
||||
|
||||
int GUI_Run(GUI_InitParams ¶ms)
|
||||
{
|
||||
#if __APPLE__
|
||||
// On OSX, we use boost::process::spawn() to launch new instances of QIDISlicer from another QIDISlicer.
|
||||
// boost::process::spawn() sets SIGCHLD to SIGIGN for the child process, thus if a child QIDISlicer spawns another
|
||||
// subprocess and the subrocess dies, the child QIDISlicer will not receive information on end of subprocess
|
||||
// (posix waitpid() call will always fail).
|
||||
// https://jmmv.dev/2008/10/boostprocess-and-sigchld.html
|
||||
// The child instance of QIDISlicer has to reset SIGCHLD to its default, so that posix waitpid() and similar continue to work.
|
||||
// See GH issue #5507
|
||||
signal(SIGCHLD, SIG_DFL);
|
||||
#endif // __APPLE__
|
||||
|
||||
try {
|
||||
GUI::GUI_App* gui = new GUI::GUI_App(params.start_as_gcodeviewer ? GUI::GUI_App::EAppMode::GCodeViewer : GUI::GUI_App::EAppMode::Editor);
|
||||
if (gui->get_app_mode() != GUI::GUI_App::EAppMode::GCodeViewer) {
|
||||
// G-code viewer is currently not performing instance check, a new G-code viewer is started every time.
|
||||
bool gui_single_instance_setting = gui->app_config->get_bool("single_instance");
|
||||
if (Slic3r::instance_check(params.argc, params.argv, gui_single_instance_setting)) {
|
||||
//TODO: do we have delete gui and other stuff?
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
GUI::GUI_App::SetInstance(gui);
|
||||
gui->init_params = ¶ms;
|
||||
return wxEntry(params.argc, params.argv);
|
||||
} catch (const Slic3r::Exception &ex) {
|
||||
boost::nowide::cerr << ex.what() << std::endl;
|
||||
wxMessageBox(boost::nowide::widen(ex.what()), _L("QIDISlicer GUI initialization failed"), wxICON_STOP);
|
||||
} catch (const std::exception &ex) {
|
||||
boost::nowide::cerr << "QIDISlicer GUI initialization failed: " << ex.what() << std::endl;
|
||||
wxMessageBox(format_wxstr(_L("Fatal error, exception catched: %1%"), ex.what()), _L("QIDISlicer GUI initialization failed"), wxICON_STOP);
|
||||
}
|
||||
|
||||
// error
|
||||
return 1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
49
src/slic3r/GUI/GUI_Init.hpp
Normal file
49
src/slic3r/GUI/GUI_Init.hpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#ifndef slic3r_GUI_Init_hpp_
|
||||
#define slic3r_GUI_Init_hpp_
|
||||
|
||||
#include <libslic3r/Preset.hpp>
|
||||
#include <libslic3r/PrintConfig.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace GUI {
|
||||
|
||||
struct OpenGLVersions
|
||||
{
|
||||
static const std::vector<std::string> core_str;
|
||||
static const std::vector<std::string> precore_str;
|
||||
|
||||
static const std::vector<std::pair<int, int>> core;
|
||||
static const std::vector<std::pair<int, int>> precore;
|
||||
};
|
||||
|
||||
struct GUI_InitParams
|
||||
{
|
||||
int argc;
|
||||
char **argv;
|
||||
|
||||
// Substitutions of unknown configuration values done during loading of user presets.
|
||||
PresetsConfigSubstitutions preset_substitutions;
|
||||
|
||||
std::vector<std::string> load_configs;
|
||||
DynamicPrintConfig extra_config;
|
||||
std::vector<std::string> input_files;
|
||||
|
||||
bool start_as_gcodeviewer;
|
||||
bool start_downloader;
|
||||
bool delete_after_load;
|
||||
std::string download_url;
|
||||
#if ENABLE_GL_CORE_PROFILE
|
||||
std::pair<int, int> opengl_version;
|
||||
#if ENABLE_OPENGL_DEBUG_OPTION
|
||||
bool opengl_debug;
|
||||
#endif // ENABLE_OPENGL_DEBUG_OPTION
|
||||
#endif // ENABLE_GL_CORE_PROFILE
|
||||
};
|
||||
|
||||
int GUI_Run(GUI_InitParams ¶ms);
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GUI_Init_hpp_
|
||||
448
src/slic3r/GUI/GUI_ObjectLayers.cpp
Normal file
448
src/slic3r/GUI/GUI_ObjectLayers.cpp
Normal file
@@ -0,0 +1,448 @@
|
||||
#include "GUI_ObjectLayers.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
|
||||
#include "OptionsGroup.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "GLCanvas3D.hpp"
|
||||
#include "Plater.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "I18N.hpp"
|
||||
|
||||
#include <wx/wupdlock.h>
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
namespace GUI
|
||||
{
|
||||
|
||||
ObjectLayers::ObjectLayers(wxWindow* parent) :
|
||||
OG_Settings(parent, true)
|
||||
{
|
||||
m_grid_sizer = new wxFlexGridSizer(3, 5, wxGetApp().em_unit()); // "Min Z", "Max Z", "Layer height" & buttons sizer
|
||||
m_grid_sizer->SetFlexibleDirection(wxHORIZONTAL);
|
||||
|
||||
// Legend for object layers
|
||||
for (const std::string col : { L("Start at height"), L("Stop at height"), L("Layer height") }) {
|
||||
auto temp = new wxStaticText(m_parent, wxID_ANY, _(col), wxDefaultPosition, wxDefaultSize, wxST_ELLIPSIZE_MIDDLE);
|
||||
temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
temp->SetFont(wxGetApp().bold_font());
|
||||
|
||||
m_grid_sizer->Add(temp);
|
||||
}
|
||||
|
||||
m_og->activate();
|
||||
m_og->sizer->Clear(true);
|
||||
m_og->sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX ? 0 : 5);
|
||||
|
||||
m_bmp_delete = ScalableBitmap(parent, "remove_copies"/*"cross"*/);
|
||||
m_bmp_add = ScalableBitmap(parent, "add_copies");
|
||||
}
|
||||
|
||||
void ObjectLayers::select_editor(LayerRangeEditor* editor, const bool is_last_edited_range)
|
||||
{
|
||||
if (is_last_edited_range && m_selection_type == editor->type()) {
|
||||
/* Workaround! Under OSX we should use CallAfter() for SetFocus() after LayerEditors "reorganizations",
|
||||
* because of selected control's strange behavior:
|
||||
* cursor is set to the control, but blue border - doesn't.
|
||||
* And as a result we couldn't edit this control.
|
||||
* */
|
||||
#ifdef __WXOSX__
|
||||
wxTheApp->CallAfter([editor]() {
|
||||
#endif
|
||||
editor->SetFocus();
|
||||
editor->SetInsertionPointEnd();
|
||||
#ifdef __WXOSX__
|
||||
});
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range, PlusMinusButton *delete_button, PlusMinusButton *add_button)
|
||||
{
|
||||
const bool is_last_edited_range = range == m_selectable_range;
|
||||
|
||||
auto set_focus_data = [range, this](const EditorType type)
|
||||
{
|
||||
m_selectable_range = range;
|
||||
m_selection_type = type;
|
||||
};
|
||||
|
||||
auto update_focus_data = [range, this](const t_layer_height_range& new_range, EditorType type, bool enter_pressed)
|
||||
{
|
||||
// change selectable range for new one, if enter was pressed or if same range was selected
|
||||
if (enter_pressed || m_selectable_range == range)
|
||||
m_selectable_range = new_range;
|
||||
if (enter_pressed)
|
||||
m_selection_type = type;
|
||||
};
|
||||
|
||||
// Add control for the "Min Z"
|
||||
|
||||
auto editor = new LayerRangeEditor(this, double_to_string(range.first), etMinZ, set_focus_data,
|
||||
[range, update_focus_data, this, delete_button, add_button](coordf_t min_z, bool enter_pressed, bool dont_update_ui)
|
||||
{
|
||||
if (fabs(min_z - range.first) < EPSILON) {
|
||||
m_selection_type = etUndef;
|
||||
return false;
|
||||
}
|
||||
|
||||
// data for next focusing
|
||||
coordf_t max_z = min_z < range.second ? range.second : min_z + 0.5;
|
||||
const t_layer_height_range new_range = { min_z, max_z };
|
||||
if (delete_button)
|
||||
delete_button->range = new_range;
|
||||
if (add_button)
|
||||
add_button->range = new_range;
|
||||
update_focus_data(new_range, etMinZ, enter_pressed);
|
||||
|
||||
return wxGetApp().obj_list()->edit_layer_range(range, new_range, dont_update_ui);
|
||||
});
|
||||
|
||||
select_editor(editor, is_last_edited_range);
|
||||
m_grid_sizer->Add(editor);
|
||||
|
||||
// Add control for the "Max Z"
|
||||
|
||||
editor = new LayerRangeEditor(this, double_to_string(range.second), etMaxZ, set_focus_data,
|
||||
[range, update_focus_data, this, delete_button, add_button](coordf_t max_z, bool enter_pressed, bool dont_update_ui)
|
||||
{
|
||||
if (fabs(max_z - range.second) < EPSILON || range.first > max_z) {
|
||||
m_selection_type = etUndef;
|
||||
return false; // LayersList would not be updated/recreated
|
||||
}
|
||||
|
||||
// data for next focusing
|
||||
const t_layer_height_range& new_range = { range.first, max_z };
|
||||
if (delete_button)
|
||||
delete_button->range = new_range;
|
||||
if (add_button)
|
||||
add_button->range = new_range;
|
||||
update_focus_data(new_range, etMaxZ, enter_pressed);
|
||||
|
||||
return wxGetApp().obj_list()->edit_layer_range(range, new_range, dont_update_ui);
|
||||
});
|
||||
|
||||
select_editor(editor, is_last_edited_range);
|
||||
m_grid_sizer->Add(editor);
|
||||
|
||||
// Add control for the "Layer height"
|
||||
|
||||
editor = new LayerRangeEditor(this, double_to_string(m_object->layer_config_ranges[range].option("layer_height")->getFloat()), etLayerHeight, set_focus_data,
|
||||
[range](coordf_t layer_height, bool, bool)
|
||||
{
|
||||
return wxGetApp().obj_list()->edit_layer_range(range, layer_height);
|
||||
});
|
||||
|
||||
select_editor(editor, is_last_edited_range);
|
||||
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(editor);
|
||||
|
||||
auto temp = new wxStaticText(m_parent, wxID_ANY, _L("mm"));
|
||||
temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
temp->SetFont(wxGetApp().normal_font());
|
||||
sizer->Add(temp, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, wxGetApp().em_unit());
|
||||
|
||||
m_grid_sizer->Add(sizer);
|
||||
|
||||
return sizer;
|
||||
}
|
||||
|
||||
void ObjectLayers::create_layers_list()
|
||||
{
|
||||
for (const auto &layer : m_object->layer_config_ranges) {
|
||||
const t_layer_height_range& range = layer.first;
|
||||
auto del_btn = new PlusMinusButton(m_parent, m_bmp_delete, range);
|
||||
del_btn->SetToolTip(_L("Remove layer range"));
|
||||
|
||||
auto add_btn = new PlusMinusButton(m_parent, m_bmp_add, range);
|
||||
wxString tooltip = wxGetApp().obj_list()->can_add_new_range_after_current(range);
|
||||
add_btn->SetToolTip(tooltip.IsEmpty() ? _L("Add layer range") : tooltip);
|
||||
add_btn->Enable(tooltip.IsEmpty());
|
||||
|
||||
auto sizer = create_layer(range, del_btn, add_btn);
|
||||
sizer->Add(del_btn, 0, wxRIGHT | wxLEFT, em_unit(m_parent));
|
||||
sizer->Add(add_btn);
|
||||
|
||||
del_btn->Bind(wxEVT_BUTTON, [del_btn](wxEvent &) {
|
||||
wxGetApp().obj_list()->del_layer_range(del_btn->range);
|
||||
});
|
||||
|
||||
add_btn->Bind(wxEVT_BUTTON, [add_btn](wxEvent &) {
|
||||
wxGetApp().obj_list()->add_layer_range_after_current(add_btn->range);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectLayers::update_layers_list()
|
||||
{
|
||||
ObjectList* objects_ctrl = wxGetApp().obj_list();
|
||||
if (objects_ctrl->multiple_selection()) return;
|
||||
|
||||
const auto item = objects_ctrl->GetSelection();
|
||||
if (!item) return;
|
||||
|
||||
const int obj_idx = objects_ctrl->get_selected_obj_idx();
|
||||
if (obj_idx < 0) return;
|
||||
|
||||
const ItemType type = objects_ctrl->GetModel()->GetItemType(item);
|
||||
if (!(type & (itLayerRoot | itLayer))) return;
|
||||
|
||||
m_object = objects_ctrl->object(obj_idx);
|
||||
if (!m_object || m_object->layer_config_ranges.empty()) return;
|
||||
|
||||
// Delete all controls from options group except of the legends
|
||||
|
||||
const int cols = m_grid_sizer->GetEffectiveColsCount();
|
||||
const int rows = m_grid_sizer->GetEffectiveRowsCount();
|
||||
for (int idx = cols*rows-1; idx >= cols; idx--) {
|
||||
wxSizerItem* t = m_grid_sizer->GetItem(idx);
|
||||
if (t->IsSizer())
|
||||
t->GetSizer()->Clear(true);
|
||||
else
|
||||
t->DeleteWindows();
|
||||
m_grid_sizer->Remove(idx);
|
||||
}
|
||||
|
||||
// Add new control according to the selected item
|
||||
|
||||
if (type & itLayerRoot)
|
||||
create_layers_list();
|
||||
else
|
||||
create_layer(objects_ctrl->GetModel()->GetLayerRangeByItem(item), nullptr, nullptr);
|
||||
|
||||
m_parent->Layout();
|
||||
}
|
||||
|
||||
void ObjectLayers::update_scene_from_editor_selection() const
|
||||
{
|
||||
// needed to show the visual hints in 3D scene
|
||||
wxGetApp().plater()->canvas3D()->handle_layers_data_focus_event(m_selectable_range, m_selection_type);
|
||||
}
|
||||
|
||||
void ObjectLayers::UpdateAndShow(const bool show)
|
||||
{
|
||||
if (show)
|
||||
update_layers_list();
|
||||
|
||||
OG_Settings::UpdateAndShow(show);
|
||||
}
|
||||
|
||||
void ObjectLayers::msw_rescale()
|
||||
{
|
||||
//m_bmp_delete.msw_rescale();
|
||||
//m_bmp_add.msw_rescale();
|
||||
|
||||
//m_grid_sizer->SetHGap(wxGetApp().em_unit());
|
||||
|
||||
//// rescale edit-boxes
|
||||
//const int cells_cnt = m_grid_sizer->GetCols() * m_grid_sizer->GetEffectiveRowsCount();
|
||||
//for (int i = 0; i < cells_cnt; ++i) {
|
||||
// const wxSizerItem* item = m_grid_sizer->GetItem(i);
|
||||
// if (item->IsWindow()) {
|
||||
// LayerRangeEditor* editor = dynamic_cast<LayerRangeEditor*>(item->GetWindow());
|
||||
// if (editor != nullptr)
|
||||
// editor->msw_rescale();
|
||||
// }
|
||||
// else if (item->IsSizer()) // case when we have editor with buttons
|
||||
// {
|
||||
// wxSizerItem* e_item = item->GetSizer()->GetItem(size_t(0)); // editor
|
||||
// if (e_item->IsWindow()) {
|
||||
// LayerRangeEditor* editor = dynamic_cast<LayerRangeEditor*>(e_item->GetWindow());
|
||||
// if (editor != nullptr)
|
||||
// editor->msw_rescale();
|
||||
// }
|
||||
|
||||
// if (item->GetSizer()->GetItemCount() > 2) // if there are Add/Del buttons
|
||||
// for (size_t btn : {2, 3}) { // del_btn, add_btn
|
||||
// wxSizerItem* b_item = item->GetSizer()->GetItem(btn);
|
||||
// if (b_item->IsWindow()) {
|
||||
// auto button = dynamic_cast<PlusMinusButton*>(b_item->GetWindow());
|
||||
// if (button != nullptr)
|
||||
// button->msw_rescale();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
m_grid_sizer->Layout();
|
||||
}
|
||||
|
||||
void ObjectLayers::sys_color_changed()
|
||||
{
|
||||
m_bmp_delete.sys_color_changed();
|
||||
m_bmp_add.sys_color_changed();
|
||||
|
||||
// rescale edit-boxes
|
||||
const int cells_cnt = m_grid_sizer->GetCols() * m_grid_sizer->GetEffectiveRowsCount();
|
||||
for (int i = 0; i < cells_cnt; ++i) {
|
||||
const wxSizerItem* item = m_grid_sizer->GetItem(i);
|
||||
if (item->IsSizer()) {// case when we have editor with buttons
|
||||
for (size_t btn : {2, 3}) { // del_btn, add_btn
|
||||
wxSizerItem* b_item = item->GetSizer()->GetItem(btn);
|
||||
if (b_item->IsWindow()) {
|
||||
auto button = dynamic_cast<PlusMinusButton*>(b_item->GetWindow());
|
||||
if (button != nullptr)
|
||||
button->sys_color_changed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
m_og->sys_color_changed();
|
||||
for (int i = 0; i < cells_cnt; ++i) {
|
||||
const wxSizerItem* item = m_grid_sizer->GetItem(i);
|
||||
if (item->IsWindow()) {
|
||||
if (LayerRangeEditor* editor = dynamic_cast<LayerRangeEditor*>(item->GetWindow()))
|
||||
wxGetApp().UpdateDarkUI(editor);
|
||||
}
|
||||
else if (item->IsSizer()) {// case when we have editor with buttons
|
||||
if (wxSizerItem* e_item = item->GetSizer()->GetItem(size_t(0)); e_item->IsWindow()) {
|
||||
if (LayerRangeEditor* editor = dynamic_cast<LayerRangeEditor*>(e_item->GetWindow()))
|
||||
wxGetApp().UpdateDarkUI(editor);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void ObjectLayers::reset_selection()
|
||||
{
|
||||
m_selectable_range = { 0.0, 0.0 };
|
||||
m_selection_type = etLayerHeight;
|
||||
}
|
||||
|
||||
LayerRangeEditor::LayerRangeEditor( ObjectLayers* parent,
|
||||
const wxString& value,
|
||||
EditorType type,
|
||||
std::function<void(EditorType)> set_focus_data_fn,
|
||||
std::function<bool(coordf_t, bool, bool)> edit_fn
|
||||
) :
|
||||
m_valid_value(value),
|
||||
m_type(type),
|
||||
m_set_focus_data(set_focus_data_fn),
|
||||
wxTextCtrl(parent->m_parent, wxID_ANY, value, wxDefaultPosition,
|
||||
wxSize(8 * em_unit(parent->m_parent), wxDefaultCoord), wxTE_PROCESS_ENTER
|
||||
#ifdef _WIN32
|
||||
| wxBORDER_SIMPLE
|
||||
#endif
|
||||
)
|
||||
{
|
||||
this->SetFont(wxGetApp().normal_font());
|
||||
wxGetApp().UpdateDarkUI(this);
|
||||
|
||||
// Reset m_enter_pressed flag to _false_, when value is editing
|
||||
this->Bind(wxEVT_TEXT, [this](wxEvent&) { m_enter_pressed = false; }, this->GetId());
|
||||
|
||||
this->Bind(wxEVT_TEXT_ENTER, [this, edit_fn](wxEvent&)
|
||||
{
|
||||
m_enter_pressed = true;
|
||||
// Workaround! Under Linux we have to use CallAfter() to avoid crash after pressing ENTER key
|
||||
// see #7531, #8055, #8408
|
||||
#ifdef __linux__
|
||||
wxTheApp->CallAfter([this, edit_fn]() {
|
||||
#endif
|
||||
// If LayersList wasn't updated/recreated, we can call wxEVT_KILL_FOCUS.Skip()
|
||||
if (m_type & etLayerHeight) {
|
||||
if (!edit_fn(get_value(), true, false))
|
||||
SetValue(m_valid_value);
|
||||
else
|
||||
m_valid_value = double_to_string(get_value());
|
||||
m_call_kill_focus = true;
|
||||
}
|
||||
else if (!edit_fn(get_value(), true, false)) {
|
||||
SetValue(m_valid_value);
|
||||
m_call_kill_focus = true;
|
||||
}
|
||||
#ifdef __linux__
|
||||
});
|
||||
#endif
|
||||
}, this->GetId());
|
||||
|
||||
this->Bind(wxEVT_KILL_FOCUS, [this, edit_fn](wxFocusEvent& e)
|
||||
{
|
||||
if (!m_enter_pressed) {
|
||||
#ifndef __WXGTK__
|
||||
/* Update data for next editor selection.
|
||||
* But under GTK it looks like there is no information about selected control at e.GetWindow(),
|
||||
* so we'll take it from wxEVT_LEFT_DOWN event
|
||||
* */
|
||||
LayerRangeEditor* new_editor = dynamic_cast<LayerRangeEditor*>(e.GetWindow());
|
||||
if (new_editor)
|
||||
new_editor->set_focus_data();
|
||||
#endif // not __WXGTK__
|
||||
// If LayersList wasn't updated/recreated, we should call e.Skip()
|
||||
if (m_type & etLayerHeight) {
|
||||
if (!edit_fn(get_value(), false, dynamic_cast<ObjectLayers::PlusMinusButton*>(e.GetWindow()) != nullptr))
|
||||
SetValue(m_valid_value);
|
||||
else
|
||||
m_valid_value = double_to_string(get_value());
|
||||
e.Skip();
|
||||
}
|
||||
else if (!edit_fn(get_value(), false, dynamic_cast<ObjectLayers::PlusMinusButton*>(e.GetWindow()) != nullptr)) {
|
||||
SetValue(m_valid_value);
|
||||
e.Skip();
|
||||
}
|
||||
}
|
||||
else if (m_call_kill_focus) {
|
||||
m_call_kill_focus = false;
|
||||
e.Skip();
|
||||
}
|
||||
}, this->GetId());
|
||||
|
||||
this->Bind(wxEVT_SET_FOCUS, [this, parent](wxFocusEvent& e)
|
||||
{
|
||||
set_focus_data();
|
||||
parent->update_scene_from_editor_selection();
|
||||
e.Skip();
|
||||
}, this->GetId());
|
||||
|
||||
#ifdef __WXGTK__ // Workaround! To take information about selectable range
|
||||
this->Bind(wxEVT_LEFT_DOWN, [this](wxEvent& e)
|
||||
{
|
||||
set_focus_data();
|
||||
e.Skip();
|
||||
}, this->GetId());
|
||||
#endif //__WXGTK__
|
||||
|
||||
this->Bind(wxEVT_CHAR, ([this](wxKeyEvent& event)
|
||||
{
|
||||
// select all text using Ctrl+A
|
||||
if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL))
|
||||
this->SetSelection(-1, -1); //select all
|
||||
event.Skip();
|
||||
}));
|
||||
}
|
||||
|
||||
coordf_t LayerRangeEditor::get_value()
|
||||
{
|
||||
wxString str = GetValue();
|
||||
|
||||
coordf_t layer_height;
|
||||
const char dec_sep = is_decimal_separator_point() ? '.' : ',';
|
||||
const char dec_sep_alt = dec_sep == '.' ? ',' : '.';
|
||||
// Replace the first incorrect separator in decimal number.
|
||||
str.Replace(dec_sep_alt, dec_sep, false);
|
||||
|
||||
if (str == ".")
|
||||
layer_height = 0.0;
|
||||
else if (!str.ToDouble(&layer_height) || layer_height < 0.0f) {
|
||||
show_error(m_parent, _L("Invalid numeric input."));
|
||||
assert(m_valid_value.ToDouble(&layer_height));
|
||||
}
|
||||
|
||||
return layer_height;
|
||||
}
|
||||
|
||||
void LayerRangeEditor::msw_rescale()
|
||||
{
|
||||
SetMinSize(wxSize(8 * wxGetApp().em_unit(), wxDefaultCoord));
|
||||
}
|
||||
|
||||
} //namespace GUI
|
||||
} //namespace Slic3r
|
||||
106
src/slic3r/GUI/GUI_ObjectLayers.hpp
Normal file
106
src/slic3r/GUI/GUI_ObjectLayers.hpp
Normal file
@@ -0,0 +1,106 @@
|
||||
#ifndef slic3r_GUI_ObjectLayers_hpp_
|
||||
#define slic3r_GUI_ObjectLayers_hpp_
|
||||
|
||||
#include "GUI_ObjectSettings.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
#ifdef __WXOSX__
|
||||
#include "../libslic3r/PrintConfig.hpp"
|
||||
#endif
|
||||
|
||||
class wxBoxSizer;
|
||||
|
||||
namespace Slic3r {
|
||||
class ModelObject;
|
||||
|
||||
namespace GUI {
|
||||
class ConfigOptionsGroup;
|
||||
|
||||
typedef double coordf_t;
|
||||
typedef std::pair<coordf_t, coordf_t> t_layer_height_range;
|
||||
|
||||
class ObjectLayers;
|
||||
|
||||
enum EditorType
|
||||
{
|
||||
etUndef = 0,
|
||||
etMinZ = 1,
|
||||
etMaxZ = 2,
|
||||
etLayerHeight = 4,
|
||||
};
|
||||
|
||||
class LayerRangeEditor : public wxTextCtrl
|
||||
{
|
||||
bool m_enter_pressed { false };
|
||||
bool m_call_kill_focus { false };
|
||||
wxString m_valid_value;
|
||||
EditorType m_type;
|
||||
|
||||
std::function<void(EditorType)> m_set_focus_data;
|
||||
|
||||
public:
|
||||
LayerRangeEditor( ObjectLayers* parent,
|
||||
const wxString& value = wxEmptyString,
|
||||
EditorType type = etUndef,
|
||||
std::function<void(EditorType)> set_focus_data_fn = [](EditorType) {;},
|
||||
// callback parameters: new value, from enter, dont't update panel UI (when called from edit field's kill focus handler for the PlusMinusButton)
|
||||
std::function<bool(coordf_t, bool, bool)> edit_fn = [](coordf_t, bool, bool) {return false; }
|
||||
);
|
||||
~LayerRangeEditor() {}
|
||||
|
||||
EditorType type() const {return m_type;}
|
||||
void set_focus_data() const { m_set_focus_data(m_type);}
|
||||
void msw_rescale();
|
||||
|
||||
private:
|
||||
coordf_t get_value();
|
||||
};
|
||||
|
||||
class ObjectLayers : public OG_Settings
|
||||
{
|
||||
ScalableBitmap m_bmp_delete;
|
||||
ScalableBitmap m_bmp_add;
|
||||
ModelObject* m_object {nullptr};
|
||||
|
||||
wxFlexGridSizer* m_grid_sizer;
|
||||
t_layer_height_range m_selectable_range;
|
||||
EditorType m_selection_type {etUndef};
|
||||
|
||||
public:
|
||||
ObjectLayers(wxWindow* parent);
|
||||
~ObjectLayers() {}
|
||||
|
||||
|
||||
// Button remembers the layer height range, for which it has been created.
|
||||
// The layer height range for this button is updated when the low or high boundary of the layer height range is updated
|
||||
// by the respective text edit field, so that this button emits an action for an up to date layer height range value.
|
||||
class PlusMinusButton : public ScalableButton
|
||||
{
|
||||
public:
|
||||
PlusMinusButton(wxWindow *parent, const ScalableBitmap &bitmap, std::pair<coordf_t, coordf_t> range) : ScalableButton(parent, wxID_ANY, bitmap), range(range) {}
|
||||
// updated when the text edit field loses focus for any PlusMinusButton.
|
||||
std::pair<coordf_t, coordf_t> range;
|
||||
};
|
||||
|
||||
void select_editor(LayerRangeEditor* editor, const bool is_last_edited_range);
|
||||
// Create sizer with layer height range and layer height text edit fields, without buttons.
|
||||
// If the delete and add buttons are provided, the respective text edit fields will modify the layer height ranges of thes buttons
|
||||
// on value change, so that these buttons work with up to date values.
|
||||
wxSizer* create_layer(const t_layer_height_range& range, PlusMinusButton *delete_button, PlusMinusButton *add_button);
|
||||
void create_layers_list();
|
||||
void update_layers_list();
|
||||
|
||||
void update_scene_from_editor_selection() const;
|
||||
|
||||
void UpdateAndShow(const bool show) override;
|
||||
void msw_rescale();
|
||||
void sys_color_changed();
|
||||
void reset_selection();
|
||||
void set_selectable_range(const t_layer_height_range& range) { m_selectable_range = range; }
|
||||
|
||||
friend class LayerRangeEditor;
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
#endif // slic3r_GUI_ObjectLayers_hpp_
|
||||
5069
src/slic3r/GUI/GUI_ObjectList.cpp
Normal file
5069
src/slic3r/GUI/GUI_ObjectList.cpp
Normal file
File diff suppressed because it is too large
Load Diff
436
src/slic3r/GUI/GUI_ObjectList.hpp
Normal file
436
src/slic3r/GUI/GUI_ObjectList.hpp
Normal file
@@ -0,0 +1,436 @@
|
||||
#ifndef slic3r_GUI_ObjectList_hpp_
|
||||
#define slic3r_GUI_ObjectList_hpp_
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
#include <wx/bitmap.h>
|
||||
#include <wx/dataview.h>
|
||||
#include <wx/menu.h>
|
||||
|
||||
#include "Event.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "ObjectDataViewModel.hpp"
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
class wxBoxSizer;
|
||||
class wxBitmapComboBox;
|
||||
class wxMenuItem;
|
||||
class MenuWithSeparators;
|
||||
|
||||
namespace Slic3r {
|
||||
class ConfigOptionsGroup;
|
||||
class DynamicPrintConfig;
|
||||
class ModelConfig;
|
||||
class ModelObject;
|
||||
class ModelVolume;
|
||||
class TriangleMesh;
|
||||
struct TextConfiguration;
|
||||
enum class ModelVolumeType : int;
|
||||
|
||||
// FIXME: broken build on mac os because of this is missing:
|
||||
typedef std::vector<std::string> t_config_option_keys;
|
||||
typedef std::vector<ModelVolume*> ModelVolumePtrs;
|
||||
typedef double coordf_t;
|
||||
typedef std::pair<coordf_t, coordf_t> t_layer_height_range;
|
||||
typedef std::map<t_layer_height_range, ModelConfig> t_layer_config_ranges;
|
||||
|
||||
// Manifold mesh may contain self-intersections, so we want to always allow fixing the mesh.
|
||||
#define FIX_THROUGH_NETFABB_ALWAYS 1
|
||||
|
||||
namespace GUI {
|
||||
|
||||
wxDECLARE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent);
|
||||
class BitmapComboBox;
|
||||
|
||||
struct ItemForDelete
|
||||
{
|
||||
ItemType type;
|
||||
int obj_idx;
|
||||
int sub_obj_idx;
|
||||
|
||||
ItemForDelete(ItemType type, int obj_idx, int sub_obj_idx)
|
||||
: type(type), obj_idx(obj_idx), sub_obj_idx(sub_obj_idx)
|
||||
{}
|
||||
|
||||
bool operator==(const ItemForDelete& r) const
|
||||
{
|
||||
return (type == r.type && obj_idx == r.obj_idx && sub_obj_idx == r.sub_obj_idx);
|
||||
}
|
||||
|
||||
bool operator<(const ItemForDelete& r) const
|
||||
{
|
||||
if (obj_idx != r.obj_idx)
|
||||
return (obj_idx < r.obj_idx);
|
||||
return (sub_obj_idx < r.sub_obj_idx);
|
||||
}
|
||||
};
|
||||
|
||||
struct MeshErrorsInfo
|
||||
{
|
||||
wxString tooltip;
|
||||
std::string warning_icon_name;
|
||||
};
|
||||
|
||||
class ObjectList : public wxDataViewCtrl
|
||||
{
|
||||
public:
|
||||
enum SELECTION_MODE
|
||||
{
|
||||
smUndef = 0,
|
||||
smVolume = 1,
|
||||
smInstance = 2,
|
||||
smLayer = 4,
|
||||
smSettings = 8, // used for undo/redo
|
||||
smLayerRoot = 16, // used for undo/redo
|
||||
};
|
||||
|
||||
struct Clipboard
|
||||
{
|
||||
void reset() {
|
||||
m_type = itUndef;
|
||||
m_layer_config_ranges_cache .clear();
|
||||
m_config_cache.clear();
|
||||
}
|
||||
bool empty() const { return m_type == itUndef; }
|
||||
ItemType get_type() const { return m_type; }
|
||||
void set_type(ItemType type) { m_type = type; }
|
||||
|
||||
t_layer_config_ranges& get_ranges_cache() { return m_layer_config_ranges_cache; }
|
||||
DynamicPrintConfig& get_config_cache() { return m_config_cache; }
|
||||
|
||||
private:
|
||||
ItemType m_type {itUndef};
|
||||
t_layer_config_ranges m_layer_config_ranges_cache;
|
||||
DynamicPrintConfig m_config_cache;
|
||||
};
|
||||
|
||||
private:
|
||||
SELECTION_MODE m_selection_mode {smUndef};
|
||||
int m_selected_layers_range_idx {-1};
|
||||
|
||||
Clipboard m_clipboard;
|
||||
|
||||
struct dragged_item_data
|
||||
{
|
||||
void init(const int obj_idx, const int subobj_idx, const ItemType type) {
|
||||
m_obj_idx = obj_idx;
|
||||
m_type = type;
|
||||
if (m_type&itVolume)
|
||||
m_vol_idx = subobj_idx;
|
||||
else
|
||||
m_inst_idxs.insert(subobj_idx);
|
||||
}
|
||||
|
||||
void init(const int obj_idx, const ItemType type) {
|
||||
m_obj_idx = obj_idx;
|
||||
m_type = type;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
m_obj_idx = -1;
|
||||
m_vol_idx = -1;
|
||||
m_inst_idxs.clear();
|
||||
m_type = itUndef;
|
||||
}
|
||||
|
||||
int obj_idx() const { return m_obj_idx; }
|
||||
int sub_obj_idx() const { return m_vol_idx; }
|
||||
ItemType type() const { return m_type; }
|
||||
std::set<int>& inst_idxs() { return m_inst_idxs; }
|
||||
|
||||
private:
|
||||
int m_obj_idx = -1;
|
||||
int m_vol_idx = -1;
|
||||
std::set<int> m_inst_idxs{};
|
||||
ItemType m_type = itUndef;
|
||||
|
||||
} m_dragged_data;
|
||||
|
||||
wxBoxSizer *m_sizer {nullptr};
|
||||
|
||||
ObjectDataViewModel *m_objects_model{ nullptr };
|
||||
ModelConfig *m_config {nullptr};
|
||||
std::vector<ModelObject*> *m_objects{ nullptr };
|
||||
|
||||
BitmapComboBox *m_extruder_editor { nullptr };
|
||||
|
||||
std::vector<wxBitmap*> m_bmp_vector;
|
||||
|
||||
int m_selected_object_id = -1;
|
||||
bool m_prevent_list_events = false; // We use this flag to avoid circular event handling Select()
|
||||
// happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler
|
||||
// calls this method again and again and again
|
||||
|
||||
bool m_prevent_update_extruder_in_config = false; // We use this flag to avoid updating of the extruder value in config
|
||||
// during updating of the extruder count.
|
||||
|
||||
bool m_prevent_canvas_selection_update = false; // This flag prevents changing selection on the canvas. See function
|
||||
// update_settings_items - updating canvas selection is undesirable,
|
||||
// because it would turn off the gizmos (mainly a problem for the SLA gizmo)
|
||||
|
||||
wxDataViewItem m_last_selected_item {nullptr};
|
||||
#ifdef __WXMSW__
|
||||
// Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected.
|
||||
int m_last_selected_column = -1;
|
||||
#endif /* __MSW__ */
|
||||
bool m_is_editing_started{ false };
|
||||
|
||||
#if 0
|
||||
SettingsFactory::Bundle m_freq_settings_fff;
|
||||
SettingsFactory::Bundle m_freq_settings_sla;
|
||||
#endif
|
||||
|
||||
size_t m_items_count { size_t(-1) };
|
||||
|
||||
inline void ensure_current_item_visible()
|
||||
{
|
||||
if (const auto &item = this->GetCurrentItem())
|
||||
this->EnsureVisible(item);
|
||||
}
|
||||
|
||||
public:
|
||||
ObjectList(wxWindow* parent);
|
||||
~ObjectList() override;
|
||||
|
||||
void set_min_height();
|
||||
void update_min_height();
|
||||
|
||||
ObjectDataViewModel* GetModel() const { return m_objects_model; }
|
||||
ModelConfig* config() const { return m_config; }
|
||||
std::vector<ModelObject*>* objects() const { return m_objects; }
|
||||
|
||||
ModelObject* object(const int obj_idx) const ;
|
||||
|
||||
void create_objects_ctrl();
|
||||
void update_objects_list_extruder_column(size_t extruders_count);
|
||||
void update_extruder_colors();
|
||||
// show/hide "Extruder" column for Objects List
|
||||
void set_extruder_column_hidden(const bool hide) const;
|
||||
// update extruder in current config
|
||||
void update_extruder_in_config(const wxDataViewItem& item);
|
||||
// update changed name in the object model
|
||||
void update_name_in_model(const wxDataViewItem& item) const;
|
||||
void update_name_in_list(int obj_idx, int vol_idx) const;
|
||||
void update_extruder_values_for_items(const size_t max_extruder);
|
||||
|
||||
// Get obj_idx and vol_idx values for the selected (by default) or an adjusted item
|
||||
void get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& item = wxDataViewItem(0));
|
||||
void get_selection_indexes(std::vector<int>& obj_idxs, std::vector<int>& vol_idxs);
|
||||
// Get count of errors in the mesh
|
||||
int get_repaired_errors_count(const int obj_idx, const int vol_idx = -1) const;
|
||||
// Get list of errors in the mesh and name of the warning icon
|
||||
// Return value is a pair <Tooltip, warning_icon_name>, used for the tooltip and related warning icon
|
||||
// Function without parameters is for a call from Manipulation panel,
|
||||
// when we don't know parameters of selected item
|
||||
MeshErrorsInfo get_mesh_errors_info(const int obj_idx, const int vol_idx = -1, wxString* sidebar_info = nullptr) const;
|
||||
MeshErrorsInfo get_mesh_errors_info(wxString* sidebar_info = nullptr);
|
||||
void set_tooltip_for_item(const wxPoint& pt);
|
||||
|
||||
void selection_changed();
|
||||
void show_context_menu(const bool evt_context_menu);
|
||||
void extruder_editing();
|
||||
#ifndef __WXOSX__
|
||||
void key_event(wxKeyEvent& event);
|
||||
#endif /* __WXOSX__ */
|
||||
|
||||
void copy();
|
||||
void paste();
|
||||
bool copy_to_clipboard();
|
||||
bool paste_from_clipboard();
|
||||
void undo();
|
||||
void redo();
|
||||
void increase_instances();
|
||||
void decrease_instances();
|
||||
|
||||
void add_category_to_settings_from_selection(const std::vector< std::pair<std::string, bool> >& category_options, wxDataViewItem item);
|
||||
void add_category_to_settings_from_frequent(const std::vector<std::string>& category_options, wxDataViewItem item);
|
||||
void show_settings(const wxDataViewItem settings_item);
|
||||
bool is_instance_or_object_selected();
|
||||
bool is_selected_object_cut();
|
||||
void load_subobject(ModelVolumeType type, bool from_galery = false);
|
||||
// ! ysFIXME - delete commented code after testing and rename "load_modifier" to something common
|
||||
//void load_part(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery = false);
|
||||
void load_modifier(const wxArrayString& input_files, ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery = false);
|
||||
void load_generic_subobject(const std::string& type_name, const ModelVolumeType type);
|
||||
void load_shape_object(const std::string &type_name);
|
||||
void load_shape_object_from_gallery();
|
||||
void load_shape_object_from_gallery(const wxArrayString& input_files);
|
||||
void load_mesh_object(const TriangleMesh &mesh, const std::string &name, bool center = true,
|
||||
const TextConfiguration* text_config = nullptr, const Transform3d* transformation = nullptr);
|
||||
bool del_object(const int obj_idx);
|
||||
bool del_subobject_item(wxDataViewItem& item);
|
||||
void del_settings_from_config(const wxDataViewItem& parent_item);
|
||||
void del_instances_from_object(const int obj_idx);
|
||||
void del_layer_from_object(const int obj_idx, const t_layer_height_range& layer_range);
|
||||
void del_layers_from_object(const int obj_idx);
|
||||
bool del_from_cut_object(bool is_connector, bool is_model_part = false, bool is_negative_volume = false);
|
||||
bool del_subobject_from_object(const int obj_idx, const int idx, const int type);
|
||||
void del_info_item(const int obj_idx, InfoItemType type);
|
||||
void split();
|
||||
void merge(bool to_multipart_object);
|
||||
void layers_editing();
|
||||
|
||||
wxDataViewItem add_layer_root_item(const wxDataViewItem obj_item);
|
||||
wxDataViewItem add_settings_item(wxDataViewItem parent_item, const DynamicPrintConfig* config);
|
||||
|
||||
DynamicPrintConfig get_default_layer_config(const int obj_idx);
|
||||
bool get_volume_by_item(const wxDataViewItem& item, ModelVolume*& volume);
|
||||
bool is_splittable(bool to_objects);
|
||||
bool selected_instances_of_same_object();
|
||||
bool can_split_instances();
|
||||
bool has_selected_cut_object() const;
|
||||
void invalidate_cut_info_for_selection();
|
||||
void invalidate_cut_info_for_object(int obj_idx);
|
||||
void delete_all_connectors_for_selection();
|
||||
void delete_all_connectors_for_object(int obj_idx);
|
||||
bool can_merge_to_multipart_object() const;
|
||||
bool can_merge_to_single_object() const;
|
||||
|
||||
wxPoint get_mouse_position_in_control() const;
|
||||
wxBoxSizer* get_sizer() {return m_sizer;}
|
||||
int get_selected_obj_idx() const;
|
||||
ModelConfig& get_item_config(const wxDataViewItem& item) const;
|
||||
|
||||
void changed_object(const int obj_idx = -1) const;
|
||||
void part_selection_changed();
|
||||
|
||||
// Add object's volumes to the list
|
||||
// Return selected items, if add_to_selection is defined
|
||||
wxDataViewItemArray add_volumes_to_object_in_list(size_t obj_idx, std::function<bool(const ModelVolume*)> add_to_selection = nullptr);
|
||||
// Add object to the list
|
||||
void add_object_to_list(size_t obj_idx, bool call_selection_changed = true);
|
||||
// Delete object from the list
|
||||
void delete_object_from_list();
|
||||
void delete_object_from_list(const size_t obj_idx);
|
||||
void delete_volume_from_list(const size_t obj_idx, const size_t vol_idx);
|
||||
void delete_instance_from_list(const size_t obj_idx, const size_t inst_idx);
|
||||
void update_lock_icons_for_model();
|
||||
bool delete_from_model_and_list(const ItemType type, const int obj_idx, const int sub_obj_idx);
|
||||
bool delete_from_model_and_list(const std::vector<ItemForDelete>& items_for_delete);
|
||||
// Delete all objects from the list
|
||||
void delete_all_objects_from_list();
|
||||
// Increase instances count
|
||||
void increase_object_instances(const size_t obj_idx, const size_t num);
|
||||
// Decrease instances count
|
||||
void decrease_object_instances(const size_t obj_idx, const size_t num);
|
||||
|
||||
// #ys_FIXME_to_delete
|
||||
// Unselect all objects in the list on c++ side
|
||||
void unselect_objects();
|
||||
// Select object item in the ObjectList, when some gizmo is activated
|
||||
// "is_msr_gizmo" indicates if Move/Scale/Rotate gizmo was activated
|
||||
void select_object_item(bool is_msr_gizmo);
|
||||
|
||||
// Remove objects/sub-object from the list
|
||||
void remove();
|
||||
void del_layer_range(const t_layer_height_range& range);
|
||||
// Add a new layer height after the current range if possible.
|
||||
// The current range is shortened and the new range is entered after the shortened current range if it fits.
|
||||
// If no range fits after the current range, then no range is inserted.
|
||||
// The layer range panel is updated even if this function does not change the layer ranges, as the panel update
|
||||
// may have been postponed from the "kill focus" event of a text field, if the focus was lost for the "add layer" button.
|
||||
// Rather providing the range by a value than by a reference, so that the memory referenced cannot be invalidated.
|
||||
void add_layer_range_after_current(const t_layer_height_range current_range);
|
||||
wxString can_add_new_range_after_current( t_layer_height_range current_range);
|
||||
void add_layer_item (const t_layer_height_range& range,
|
||||
const wxDataViewItem layers_item,
|
||||
const int layer_idx = -1);
|
||||
bool edit_layer_range(const t_layer_height_range& range, coordf_t layer_height);
|
||||
// This function may be called when a text field loses focus for a "add layer" or "remove layer" button.
|
||||
// In that case we don't want to destroy the panel with that "add layer" or "remove layer" buttons, as some messages
|
||||
// are already planned for them and destroying these widgets leads to crashes at least on OSX.
|
||||
// In that case the "add layer" or "remove layer" button handlers are responsible for always rebuilding the panel
|
||||
// even if the "add layer" or "remove layer" buttons did not update the layer spans or layer heights.
|
||||
bool edit_layer_range(const t_layer_height_range& range,
|
||||
const t_layer_height_range& new_range,
|
||||
// Don't destroy the panel with the "add layer" or "remove layer" buttons.
|
||||
bool suppress_ui_update = false);
|
||||
|
||||
void init_objects();
|
||||
bool multiple_selection() const ;
|
||||
bool is_selected(const ItemType type) const;
|
||||
bool is_connectors_item_selected() const;
|
||||
bool is_connectors_item_selected(const wxDataViewItemArray& sels) const;
|
||||
int get_selected_layers_range_idx() const;
|
||||
void set_selected_layers_range_idx(const int range_idx) { m_selected_layers_range_idx = range_idx; }
|
||||
void set_selection_mode(SELECTION_MODE mode) { m_selection_mode = mode; }
|
||||
void update_selections();
|
||||
void update_selections_on_canvas();
|
||||
void select_item(const wxDataViewItem& item);
|
||||
void select_item(std::function<wxDataViewItem()> get_item);
|
||||
void select_items(const wxDataViewItemArray& sels);
|
||||
void select_all();
|
||||
void select_item_all_children();
|
||||
void update_selection_mode();
|
||||
bool check_last_selection(wxString& msg_str);
|
||||
// correct current selections to avoid of the possible conflicts
|
||||
void fix_multiselection_conflicts();
|
||||
// correct selection in respect to the cut_id if any exists
|
||||
void fix_cut_selection();
|
||||
bool fix_cut_selection(wxDataViewItemArray& sels);
|
||||
|
||||
ModelVolume* get_selected_model_volume();
|
||||
void change_part_type();
|
||||
|
||||
void last_volume_is_deleted(const int obj_idx);
|
||||
void update_and_show_object_settings_item();
|
||||
void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections);
|
||||
void update_object_list_by_printer_technology();
|
||||
void update_info_items(size_t obj_idx, wxDataViewItemArray* selections = nullptr, bool added_object = false);
|
||||
|
||||
void instances_to_separated_object(const int obj_idx, const std::set<int>& inst_idx);
|
||||
void instances_to_separated_objects(const int obj_idx);
|
||||
void split_instances();
|
||||
void rename_item();
|
||||
void fix_through_netfabb();
|
||||
void simplify();
|
||||
void update_item_error_icon(const int obj_idx, int vol_idx) const ;
|
||||
|
||||
void copy_layers_to_clipboard();
|
||||
void paste_layers_into_list();
|
||||
void copy_settings_to_clipboard();
|
||||
void paste_settings_into_list();
|
||||
bool clipboard_is_empty() const { return m_clipboard.empty(); }
|
||||
void paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes);
|
||||
void paste_objects_into_list(const std::vector<size_t>& object_idxs);
|
||||
|
||||
void msw_rescale();
|
||||
void sys_color_changed();
|
||||
|
||||
void update_after_undo_redo();
|
||||
//update printable state for item from objects model
|
||||
void update_printable_state(int obj_idx, int instance_idx);
|
||||
void toggle_printable_state();
|
||||
|
||||
void set_extruder_for_selected_items(const int extruder) const ;
|
||||
wxDataViewItemArray reorder_volumes_and_get_selection(size_t obj_idx, std::function<bool(const ModelVolume*)> add_to_selection = nullptr);
|
||||
void apply_volumes_order();
|
||||
bool has_paint_on_segmentation();
|
||||
|
||||
bool is_editing() const { return m_is_editing_started; }
|
||||
|
||||
private:
|
||||
#ifdef __WXOSX__
|
||||
// void OnChar(wxKeyEvent& event);
|
||||
#endif /* __WXOSX__ */
|
||||
void OnContextMenu(wxDataViewEvent &event);
|
||||
void list_manipulation(const wxPoint& mouse_pos, bool evt_context_menu = false);
|
||||
|
||||
void OnBeginDrag(wxDataViewEvent &event);
|
||||
void OnDropPossible(wxDataViewEvent &event);
|
||||
void OnDrop(wxDataViewEvent &event);
|
||||
bool can_drop(const wxDataViewItem& item) const ;
|
||||
|
||||
void ItemValueChanged(wxDataViewEvent &event);
|
||||
// Workaround for entering the column editing mode on Windows. Simulate keyboard enter when another column of the active line is selected.
|
||||
void OnEditingStarted(wxDataViewEvent &event);
|
||||
void OnEditingDone(wxDataViewEvent &event);
|
||||
};
|
||||
|
||||
|
||||
}}
|
||||
|
||||
#endif //slic3r_GUI_ObjectList_hpp_
|
||||
1301
src/slic3r/GUI/GUI_ObjectManipulation.cpp
Normal file
1301
src/slic3r/GUI/GUI_ObjectManipulation.cpp
Normal file
File diff suppressed because it is too large
Load Diff
250
src/slic3r/GUI/GUI_ObjectManipulation.hpp
Normal file
250
src/slic3r/GUI/GUI_ObjectManipulation.hpp
Normal file
@@ -0,0 +1,250 @@
|
||||
#ifndef slic3r_GUI_ObjectManipulation_hpp_
|
||||
#define slic3r_GUI_ObjectManipulation_hpp_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "GUI_ObjectSettings.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
#include "GUI_Geometry.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include <float.h>
|
||||
|
||||
#ifdef __WXOSX__
|
||||
class wxBitmapComboBox;
|
||||
#else
|
||||
class wxComboBox;
|
||||
#endif // __WXOSX__
|
||||
class wxStaticText;
|
||||
class LockButton;
|
||||
class wxStaticBitmap;
|
||||
class wxCheckBox;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
#ifdef _WIN32
|
||||
class BitmapComboBox;
|
||||
#endif
|
||||
|
||||
#ifdef __WXOSX__
|
||||
static_assert(wxMAJOR_VERSION >= 3, "Use of wxBitmapComboBox on Manipulation panel requires wxWidgets 3.0 and newer");
|
||||
using choice_ctrl = wxBitmapComboBox;
|
||||
#else
|
||||
#ifdef _WIN32
|
||||
using choice_ctrl = BitmapComboBox;
|
||||
#else
|
||||
using choice_ctrl = wxComboBox;
|
||||
#endif
|
||||
#endif // __WXOSX__
|
||||
|
||||
class Selection;
|
||||
|
||||
class ObjectManipulation;
|
||||
class ManipulationEditor : public wxTextCtrl
|
||||
{
|
||||
std::string m_opt_key;
|
||||
int m_axis;
|
||||
bool m_enter_pressed { false };
|
||||
wxString m_valid_value {wxEmptyString};
|
||||
|
||||
std::string m_full_opt_name;
|
||||
|
||||
public:
|
||||
ManipulationEditor(ObjectManipulation* parent, const std::string& opt_key, int axis);
|
||||
~ManipulationEditor() {}
|
||||
|
||||
void msw_rescale();
|
||||
void sys_color_changed(ObjectManipulation* parent);
|
||||
void set_value(const wxString& new_value);
|
||||
void kill_focus(ObjectManipulation *parent);
|
||||
|
||||
const std::string& get_full_opt_name() const { return m_full_opt_name; }
|
||||
|
||||
bool has_opt_key(const std::string& key) { return m_opt_key == key; }
|
||||
|
||||
private:
|
||||
double get_value();
|
||||
};
|
||||
|
||||
|
||||
class ObjectManipulation : public OG_Settings
|
||||
{
|
||||
public:
|
||||
static const double in_to_mm;
|
||||
static const double mm_to_in;
|
||||
|
||||
private:
|
||||
struct Cache
|
||||
{
|
||||
Vec3d position;
|
||||
Vec3d position_rounded;
|
||||
Vec3d rotation;
|
||||
Vec3d rotation_rounded;
|
||||
Vec3d scale;
|
||||
Vec3d scale_rounded;
|
||||
Vec3d size;
|
||||
Vec3d size_inches;
|
||||
Vec3d size_rounded;
|
||||
|
||||
wxString move_label_string;
|
||||
wxString rotate_label_string;
|
||||
wxString scale_label_string;
|
||||
|
||||
Cache() { reset(); }
|
||||
void reset()
|
||||
{
|
||||
position = position_rounded = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
|
||||
rotation = rotation_rounded = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
|
||||
scale = scale_rounded = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
|
||||
size = size_rounded = Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
|
||||
move_label_string = wxString();
|
||||
rotate_label_string = wxString();
|
||||
scale_label_string = wxString();
|
||||
}
|
||||
bool is_valid() const { return position != Vec3d(DBL_MAX, DBL_MAX, DBL_MAX); }
|
||||
};
|
||||
|
||||
Cache m_cache;
|
||||
|
||||
wxStaticText* m_move_Label = nullptr;
|
||||
wxStaticText* m_scale_Label = nullptr;
|
||||
wxStaticText* m_rotate_Label = nullptr;
|
||||
|
||||
bool m_imperial_units { false };
|
||||
bool m_use_colors { false };
|
||||
wxStaticText* m_position_unit { nullptr };
|
||||
wxStaticText* m_size_unit { nullptr };
|
||||
|
||||
wxStaticText* m_item_name = nullptr;
|
||||
wxStaticText* m_empty_str = nullptr;
|
||||
|
||||
// Non-owning pointers to the reset buttons, so we can hide and show them.
|
||||
ScalableButton* m_reset_scale_button{ nullptr };
|
||||
ScalableButton* m_reset_rotation_button{ nullptr };
|
||||
ScalableButton* m_reset_skew_button{ nullptr };
|
||||
ScalableButton* m_drop_to_bed_button{ nullptr };
|
||||
|
||||
wxCheckBox* m_check_inch {nullptr};
|
||||
|
||||
std::array<ScalableButton*, 3> m_mirror_buttons;
|
||||
|
||||
// Bitmaps for the mirroring buttons.
|
||||
ScalableBitmap m_mirror_bitmap_on;
|
||||
|
||||
// Needs to be updated from OnIdle?
|
||||
bool m_dirty = false;
|
||||
// Cached labels for the delayed update, not localized!
|
||||
std::string m_new_move_label_string;
|
||||
std::string m_new_rotate_label_string;
|
||||
std::string m_new_scale_label_string;
|
||||
Vec3d m_new_position;
|
||||
Vec3d m_new_rotation;
|
||||
Vec3d m_new_scale;
|
||||
Vec3d m_new_size;
|
||||
bool m_new_enabled {true};
|
||||
bool m_uniform_scale {true};
|
||||
ECoordinatesType m_coordinates_type{ ECoordinatesType::World };
|
||||
LockButton* m_lock_bnt{ nullptr };
|
||||
choice_ctrl* m_word_local_combo { nullptr };
|
||||
|
||||
ScalableBitmap m_manifold_warning_bmp;
|
||||
wxStaticBitmap* m_fix_throught_netfab_bitmap{ nullptr };
|
||||
wxStaticBitmap* m_mirror_warning_bitmap{ nullptr };
|
||||
|
||||
// Currently focused editor (nullptr if none)
|
||||
ManipulationEditor* m_focused_editor{ nullptr };
|
||||
|
||||
wxFlexGridSizer* m_main_grid_sizer;
|
||||
wxFlexGridSizer* m_labels_grid_sizer;
|
||||
|
||||
wxStaticText* m_skew_label{ nullptr };
|
||||
|
||||
// sizers, used for msw_rescale
|
||||
wxBoxSizer* m_word_local_combo_sizer;
|
||||
std::vector<wxBoxSizer*> m_rescalable_sizers;
|
||||
|
||||
std::vector<ManipulationEditor*> m_editors;
|
||||
|
||||
// parameters for enabling/disabling of editors
|
||||
bool m_is_enabled { true };
|
||||
bool m_is_enabled_size_and_scale { true };
|
||||
|
||||
|
||||
public:
|
||||
ObjectManipulation(wxWindow* parent);
|
||||
~ObjectManipulation() {}
|
||||
|
||||
void Show(const bool show) override;
|
||||
bool IsShown() override;
|
||||
void UpdateAndShow(const bool show) override;
|
||||
void Enable(const bool enadle = true);
|
||||
void Disable() { Enable(false); }
|
||||
void DisableScale();
|
||||
void DisableUnuniformScale();
|
||||
void update_ui_from_settings();
|
||||
bool use_colors() { return m_use_colors; }
|
||||
|
||||
void set_dirty() { m_dirty = true; }
|
||||
// Called from the App to update the UI if dirty.
|
||||
void update_if_dirty();
|
||||
|
||||
void set_uniform_scaling(const bool use_uniform_scale);
|
||||
bool get_uniform_scaling() const { return m_uniform_scale; }
|
||||
|
||||
void set_coordinates_type(ECoordinatesType type);
|
||||
ECoordinatesType get_coordinates_type() const;
|
||||
bool is_world_coordinates() const { return m_coordinates_type == ECoordinatesType::World; }
|
||||
bool is_instance_coordinates() const { return m_coordinates_type == ECoordinatesType::Instance; }
|
||||
bool is_local_coordinates() const { return m_coordinates_type == ECoordinatesType::Local; }
|
||||
|
||||
void reset_cache() { m_cache.reset(); }
|
||||
#ifndef __APPLE__
|
||||
// On Windows and Linux, emulates a kill focus event on the currently focused option (if any)
|
||||
// Used only in ObjectList wxEVT_DATAVIEW_SELECTION_CHANGED handler which is called before the regular kill focus event
|
||||
// bound to this class when changing selection in the objects list
|
||||
void emulate_kill_focus();
|
||||
#endif // __APPLE__
|
||||
|
||||
void update_item_name(const wxString &item_name);
|
||||
void update_warning_icon_state(const MeshErrorsInfo& warning);
|
||||
void msw_rescale();
|
||||
void sys_color_changed();
|
||||
void on_change(const std::string& opt_key, int axis, double new_value);
|
||||
void set_focused_editor(ManipulationEditor* focused_editor) {
|
||||
m_focused_editor = focused_editor;
|
||||
}
|
||||
|
||||
ManipulationEditor* get_focused_editor() { return m_focused_editor; }
|
||||
|
||||
static wxString coordinate_type_str(ECoordinatesType type);
|
||||
|
||||
bool is_enabled() const { return m_is_enabled; }
|
||||
bool is_enabled_size_and_scale()const { return m_is_enabled_size_and_scale; }
|
||||
|
||||
#if ENABLE_OBJECT_MANIPULATION_DEBUG
|
||||
void render_debug_window();
|
||||
#endif // ENABLE_OBJECT_MANIPULATION_DEBUG
|
||||
|
||||
private:
|
||||
void reset_settings_value();
|
||||
void update_settings_value(const Selection& selection);
|
||||
|
||||
// Show or hide scale/rotation reset buttons if needed
|
||||
void update_reset_buttons_visibility();
|
||||
//Show or hide mirror buttons
|
||||
void update_mirror_buttons_visibility();
|
||||
|
||||
// change values
|
||||
void change_position_value(int axis, double value);
|
||||
void change_rotation_value(int axis, double value);
|
||||
void change_scale_value(int axis, double value);
|
||||
void change_size_value(int axis, double value);
|
||||
void do_scale(int axis, const Vec3d &scale) const;
|
||||
void do_size(int axis, const Vec3d& scale) const;
|
||||
|
||||
void set_coordinates_type(const wxString& type_string);
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
#endif // slic3r_GUI_ObjectManipulation_hpp_
|
||||
281
src/slic3r/GUI/GUI_ObjectSettings.cpp
Normal file
281
src/slic3r/GUI/GUI_ObjectSettings.cpp
Normal file
@@ -0,0 +1,281 @@
|
||||
#include "GUI_ObjectSettings.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
#include "GUI_Factories.hpp"
|
||||
|
||||
#include "OptionsGroup.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include "I18N.hpp"
|
||||
#include "format.hpp"
|
||||
#include "ConfigManipulation.hpp"
|
||||
|
||||
#include <wx/wupdlock.h>
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
namespace GUI
|
||||
{
|
||||
|
||||
OG_Settings::OG_Settings(wxWindow* parent, const bool staticbox) :
|
||||
m_parent(parent)
|
||||
{
|
||||
wxString title = staticbox ? " " : ""; // temporary workaround - #ys_FIXME
|
||||
m_og = std::make_shared<ConfigOptionsGroup>(parent, title);
|
||||
}
|
||||
|
||||
bool OG_Settings::IsShown()
|
||||
{
|
||||
return m_og->sizer->IsEmpty() ? false : m_og->sizer->IsShown(size_t(0));
|
||||
}
|
||||
|
||||
void OG_Settings::Show(const bool show)
|
||||
{
|
||||
m_og->Show(show);
|
||||
}
|
||||
|
||||
void OG_Settings::Hide()
|
||||
{
|
||||
Show(false);
|
||||
}
|
||||
|
||||
void OG_Settings::UpdateAndShow(const bool show)
|
||||
{
|
||||
Show(show);
|
||||
// m_parent->Layout();
|
||||
}
|
||||
|
||||
wxSizer* OG_Settings::get_sizer()
|
||||
{
|
||||
return m_og->sizer;
|
||||
}
|
||||
|
||||
|
||||
|
||||
ObjectSettings::ObjectSettings(wxWindow* parent) :
|
||||
OG_Settings(parent, true)
|
||||
{
|
||||
m_og->activate();
|
||||
m_og->set_name(_(L("Additional Settings")));
|
||||
|
||||
m_settings_list_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_og->sizer->Add(m_settings_list_sizer, 1, wxEXPAND | wxLEFT, 5);
|
||||
|
||||
m_bmp_delete = ScalableBitmap(parent, "cross");
|
||||
m_bmp_delete_focus = ScalableBitmap(parent, "cross_focus");
|
||||
}
|
||||
|
||||
bool ObjectSettings::update_settings_list()
|
||||
{
|
||||
m_settings_list_sizer->Clear(true);
|
||||
m_og_settings.resize(0);
|
||||
|
||||
auto objects_ctrl = wxGetApp().obj_list();
|
||||
auto objects_model = wxGetApp().obj_list()->GetModel();
|
||||
auto config = wxGetApp().obj_list()->config();
|
||||
|
||||
const auto item = objects_ctrl->GetSelection();
|
||||
|
||||
if (!item || !objects_model->IsSettingsItem(item) || !config || objects_ctrl->multiple_selection())
|
||||
return false;
|
||||
|
||||
const bool is_object_settings = objects_model->GetItemType(objects_model->GetParent(item)) == itObject;
|
||||
SettingsFactory::Bundle cat_options = SettingsFactory::get_bundle(&config->get(), is_object_settings);
|
||||
|
||||
if (!cat_options.empty())
|
||||
{
|
||||
std::vector<std::string> categories;
|
||||
categories.reserve(cat_options.size());
|
||||
|
||||
auto extra_column = [config, this](wxWindow* parent, const Line& line)
|
||||
{
|
||||
auto opt_key = (line.get_options())[0].opt_id; //we assume that we have one option per line
|
||||
|
||||
auto btn = new ScalableButton(parent, wxID_ANY, m_bmp_delete);
|
||||
btn->SetToolTip(_(L("Remove parameter")));
|
||||
|
||||
btn->SetBitmapFocus(m_bmp_delete_focus.bmp());
|
||||
btn->SetBitmapCurrent(m_bmp_delete_focus.bmp());
|
||||
|
||||
btn->Bind(wxEVT_BUTTON, [opt_key, config, this](wxEvent &event) {
|
||||
wxGetApp().plater()->take_snapshot(format_wxstr(_L("Delete Option %s"), opt_key));
|
||||
config->erase(opt_key);
|
||||
wxGetApp().obj_list()->changed_object();
|
||||
wxTheApp->CallAfter([this]() {
|
||||
wxWindowUpdateLocker noUpdates(m_parent);
|
||||
update_settings_list();
|
||||
m_parent->Layout();
|
||||
});
|
||||
});
|
||||
return btn;
|
||||
};
|
||||
|
||||
for (auto& cat : cat_options)
|
||||
{
|
||||
categories.push_back(cat.first);
|
||||
|
||||
auto optgroup = std::make_shared<ConfigOptionsGroup>(m_og->ctrl_parent(), _(cat.first), config, false, extra_column);
|
||||
optgroup->label_width = 15;
|
||||
optgroup->sidetext_width = 5;
|
||||
|
||||
optgroup->m_on_change = [this, config](const t_config_option_key& opt_id, const boost::any& value) {
|
||||
this->update_config_values(config);
|
||||
wxGetApp().obj_list()->changed_object(); };
|
||||
|
||||
// call back for rescaling of the extracolumn control
|
||||
optgroup->rescale_extra_column_item = [this](wxWindow* win) {
|
||||
auto *ctrl = dynamic_cast<ScalableButton*>(win);
|
||||
if (ctrl == nullptr)
|
||||
return;
|
||||
ctrl->SetBitmap_(m_bmp_delete);
|
||||
ctrl->SetBitmapFocus(m_bmp_delete_focus.bmp());
|
||||
ctrl->SetBitmapCurrent(m_bmp_delete_focus.bmp());
|
||||
};
|
||||
|
||||
const bool is_extruders_cat = cat.first == "Extruders";
|
||||
for (auto& opt : cat.second)
|
||||
{
|
||||
Option option = optgroup->get_option(opt);
|
||||
option.opt.width = 12;
|
||||
if (!option.opt.full_label.empty())
|
||||
option.opt.label = option.opt.full_label;
|
||||
if (is_extruders_cat)
|
||||
option.opt.max = wxGetApp().extruders_edited_cnt();
|
||||
optgroup->append_single_option_line(option);
|
||||
}
|
||||
optgroup->activate();
|
||||
for (auto& opt : cat.second)
|
||||
optgroup->get_field(opt)->m_on_change = [optgroup](const std::string& opt_id, const boost::any& value) {
|
||||
// first of all take a snapshot and then change value in configuration
|
||||
wxGetApp().plater()->take_snapshot(format_wxstr(_L("Change Option %s"), opt_id));
|
||||
optgroup->on_change_OG(opt_id, value);
|
||||
};
|
||||
|
||||
optgroup->reload_config();
|
||||
|
||||
m_settings_list_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0);
|
||||
m_og_settings.push_back(optgroup);
|
||||
}
|
||||
|
||||
if (!categories.empty()) {
|
||||
objects_model->UpdateSettingsDigest(item, categories);
|
||||
update_config_values(config);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
objects_ctrl->select_item(objects_model->Delete(item));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ObjectSettings::add_missed_options(ModelConfig* config_to, const DynamicPrintConfig& config_from)
|
||||
{
|
||||
const DynamicPrintConfig& print_config = wxGetApp().plater()->printer_technology() == ptFFF ?
|
||||
wxGetApp().preset_bundle->prints.get_edited_preset().config :
|
||||
wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
|
||||
bool is_added = false;
|
||||
|
||||
for (auto opt_key : config_from.diff(print_config))
|
||||
if (!config_to->has(opt_key)) {
|
||||
config_to->set_key_value(opt_key, config_from.option(opt_key)->clone());
|
||||
is_added = true;
|
||||
}
|
||||
|
||||
return is_added;
|
||||
}
|
||||
|
||||
void ObjectSettings::update_config_values(ModelConfig* config)
|
||||
{
|
||||
const auto objects_model = wxGetApp().obj_list()->GetModel();
|
||||
const auto item = wxGetApp().obj_list()->GetSelection();
|
||||
const auto printer_technology = wxGetApp().plater()->printer_technology();
|
||||
const bool is_object_settings = objects_model->GetItemType(objects_model->GetParent(item)) == itObject;
|
||||
|
||||
if (!item || !objects_model->IsSettingsItem(item) || !config)
|
||||
return;
|
||||
|
||||
// update config values according to configuration hierarchy
|
||||
DynamicPrintConfig main_config = printer_technology == ptFFF ?
|
||||
wxGetApp().preset_bundle->prints.get_edited_preset().config :
|
||||
wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
|
||||
|
||||
auto load_config = [this, config, &main_config]()
|
||||
{
|
||||
/* Additional check for overrided options.
|
||||
* There is a case, when some options should to be added,
|
||||
* to avoid check loop in the next configuration update
|
||||
*/
|
||||
bool is_added = add_missed_options(config, main_config);
|
||||
|
||||
// load checked values from main_config to config
|
||||
config->apply_only(main_config, config->keys(), true);
|
||||
// Initialize UI components with the config values.
|
||||
for (auto og : m_og_settings)
|
||||
og->reload_config();
|
||||
// next config check
|
||||
update_config_values(config);
|
||||
|
||||
if (is_added) {
|
||||
// #ysFIXME - Delete after testing! Very likely this CallAfret is no needed
|
||||
// wxTheApp->CallAfter([this]() {
|
||||
wxWindowUpdateLocker noUpdates(m_parent);
|
||||
update_settings_list();
|
||||
m_parent->Layout();
|
||||
// });
|
||||
}
|
||||
};
|
||||
|
||||
auto toggle_field = [this](const t_config_option_key & opt_key, bool toggle, int opt_index)
|
||||
{
|
||||
Field* field = nullptr;
|
||||
for (auto og : m_og_settings) {
|
||||
field = og->get_fieldc(opt_key, opt_index);
|
||||
if (field != nullptr)
|
||||
break;
|
||||
}
|
||||
if (field)
|
||||
field->toggle(toggle);
|
||||
};
|
||||
|
||||
ConfigManipulation config_manipulation(load_config, toggle_field, nullptr, config);
|
||||
|
||||
if (!is_object_settings)
|
||||
{
|
||||
const int obj_idx = objects_model->GetObjectIdByItem(item);
|
||||
assert(obj_idx >= 0);
|
||||
// for object's part first of all update konfiguration from object
|
||||
main_config.apply(wxGetApp().model().objects[obj_idx]->config.get(), true);
|
||||
// and then from its own config
|
||||
}
|
||||
|
||||
main_config.apply(config->get(), true);
|
||||
printer_technology == ptFFF ? config_manipulation.update_print_fff_config(&main_config) :
|
||||
config_manipulation.update_print_sla_config(&main_config) ;
|
||||
|
||||
printer_technology == ptFFF ? config_manipulation.toggle_print_fff_options(&main_config) :
|
||||
config_manipulation.toggle_print_sla_options(&main_config) ;
|
||||
}
|
||||
|
||||
void ObjectSettings::UpdateAndShow(const bool show)
|
||||
{
|
||||
OG_Settings::UpdateAndShow(show ? update_settings_list() : false);
|
||||
}
|
||||
|
||||
void ObjectSettings::sys_color_changed()
|
||||
{
|
||||
m_og->sys_color_changed();
|
||||
|
||||
for (auto group : m_og_settings)
|
||||
group->sys_color_changed();
|
||||
}
|
||||
|
||||
} //namespace GUI
|
||||
} //namespace Slic3r
|
||||
65
src/slic3r/GUI/GUI_ObjectSettings.hpp
Normal file
65
src/slic3r/GUI/GUI_ObjectSettings.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#ifndef slic3r_GUI_ObjectSettings_hpp_
|
||||
#define slic3r_GUI_ObjectSettings_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <wx/panel.h>
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
class wxBoxSizer;
|
||||
|
||||
namespace Slic3r {
|
||||
class DynamicPrintConfig;
|
||||
class ModelConfig;
|
||||
namespace GUI {
|
||||
class ConfigOptionsGroup;
|
||||
|
||||
class OG_Settings
|
||||
{
|
||||
protected:
|
||||
std::shared_ptr<ConfigOptionsGroup> m_og;
|
||||
wxWindow* m_parent;
|
||||
public:
|
||||
OG_Settings(wxWindow* parent, const bool staticbox);
|
||||
virtual ~OG_Settings() {}
|
||||
|
||||
virtual bool IsShown();
|
||||
virtual void Show(const bool show);
|
||||
virtual void Hide();
|
||||
virtual void UpdateAndShow(const bool show);
|
||||
|
||||
virtual wxSizer* get_sizer();
|
||||
ConfigOptionsGroup* get_og() { return m_og.get(); }
|
||||
const ConfigOptionsGroup* get_og() const { return m_og.get(); }
|
||||
wxWindow* parent() const {return m_parent; }
|
||||
};
|
||||
|
||||
|
||||
class ObjectSettings : public OG_Settings
|
||||
{
|
||||
// sizer for extra Object/Part's settings
|
||||
wxBoxSizer* m_settings_list_sizer{ nullptr };
|
||||
// option groups for settings
|
||||
std::vector <std::shared_ptr<ConfigOptionsGroup>> m_og_settings;
|
||||
|
||||
ScalableBitmap m_bmp_delete;
|
||||
ScalableBitmap m_bmp_delete_focus;
|
||||
|
||||
public:
|
||||
ObjectSettings(wxWindow* parent);
|
||||
~ObjectSettings() {}
|
||||
|
||||
bool update_settings_list();
|
||||
/* Additional check for override options: Add options, if its needed.
|
||||
* Example: if Infill is set to 100%, and Fill Pattern is missed in config_to,
|
||||
* we should add fill_pattern to avoid endless loop in update
|
||||
*/
|
||||
bool add_missed_options(ModelConfig *config_to, const DynamicPrintConfig &config_from);
|
||||
void update_config_values(ModelConfig *config);
|
||||
void UpdateAndShow(const bool show) override;
|
||||
void sys_color_changed();
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
#endif // slic3r_GUI_ObjectSettings_hpp_
|
||||
917
src/slic3r/GUI/GUI_Preview.cpp
Normal file
917
src/slic3r/GUI/GUI_Preview.cpp
Normal file
@@ -0,0 +1,917 @@
|
||||
//#include "stdlib.h"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Layer.hpp"
|
||||
#include "GUI_Preview.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "3DScene.hpp"
|
||||
#include "BackgroundSlicingProcess.hpp"
|
||||
#include "OpenGLManager.hpp"
|
||||
#include "GLCanvas3D.hpp"
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "DoubleSlider.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
#include <wx/listbook.h>
|
||||
#include <wx/notebook.h>
|
||||
#include <wx/glcanvas.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/choice.h>
|
||||
#include <wx/combo.h>
|
||||
#include <wx/combobox.h>
|
||||
#include <wx/checkbox.h>
|
||||
|
||||
// this include must follow the wxWidgets ones or it won't compile on Windows -> see http://trac.wxwidgets.org/ticket/2421
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
#include "NotificationManager.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "BitmapComboBox.hpp"
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
View3D::View3D(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process)
|
||||
: m_canvas_widget(nullptr)
|
||||
, m_canvas(nullptr)
|
||||
{
|
||||
init(parent, bed, model, config, process);
|
||||
}
|
||||
|
||||
View3D::~View3D()
|
||||
{
|
||||
delete m_canvas;
|
||||
delete m_canvas_widget;
|
||||
}
|
||||
|
||||
bool View3D::init(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process)
|
||||
{
|
||||
if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */))
|
||||
return false;
|
||||
|
||||
m_canvas_widget = OpenGLManager::create_wxglcanvas(*this);
|
||||
if (m_canvas_widget == nullptr)
|
||||
return false;
|
||||
|
||||
m_canvas = new GLCanvas3D(m_canvas_widget, bed);
|
||||
m_canvas->set_context(wxGetApp().init_glcontext(*m_canvas_widget));
|
||||
|
||||
m_canvas->allow_multisample(OpenGLManager::can_multisample());
|
||||
|
||||
m_canvas->enable_picking(true);
|
||||
m_canvas->get_selection().set_mode(Selection::Instance);
|
||||
m_canvas->enable_moving(true);
|
||||
// XXX: more config from 3D.pm
|
||||
m_canvas->set_model(model);
|
||||
m_canvas->set_process(process);
|
||||
m_canvas->set_config(config);
|
||||
m_canvas->enable_gizmos(true);
|
||||
m_canvas->enable_selection(true);
|
||||
m_canvas->enable_main_toolbar(true);
|
||||
m_canvas->enable_undoredo_toolbar(true);
|
||||
m_canvas->enable_labels(true);
|
||||
m_canvas->enable_slope(true);
|
||||
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
main_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0);
|
||||
|
||||
SetSizer(main_sizer);
|
||||
SetMinSize(GetSize());
|
||||
GetSizer()->SetSizeHints(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void View3D::set_as_dirty()
|
||||
{
|
||||
if (m_canvas != nullptr)
|
||||
m_canvas->set_as_dirty();
|
||||
}
|
||||
|
||||
void View3D::bed_shape_changed()
|
||||
{
|
||||
if (m_canvas != nullptr)
|
||||
m_canvas->bed_shape_changed();
|
||||
}
|
||||
|
||||
void View3D::select_view(const std::string& direction)
|
||||
{
|
||||
if (m_canvas != nullptr)
|
||||
m_canvas->select_view(direction);
|
||||
}
|
||||
|
||||
void View3D::select_all()
|
||||
{
|
||||
if (m_canvas != nullptr)
|
||||
m_canvas->select_all();
|
||||
}
|
||||
|
||||
void View3D::deselect_all()
|
||||
{
|
||||
if (m_canvas != nullptr)
|
||||
m_canvas->deselect_all();
|
||||
}
|
||||
|
||||
void View3D::delete_selected()
|
||||
{
|
||||
if (m_canvas != nullptr)
|
||||
m_canvas->delete_selected();
|
||||
}
|
||||
|
||||
void View3D::mirror_selection(Axis axis)
|
||||
{
|
||||
if (m_canvas != nullptr)
|
||||
m_canvas->mirror_selection(axis);
|
||||
}
|
||||
|
||||
bool View3D::is_layers_editing_enabled() const
|
||||
{
|
||||
return (m_canvas != nullptr) ? m_canvas->is_layers_editing_enabled() : false;
|
||||
}
|
||||
|
||||
bool View3D::is_layers_editing_allowed() const
|
||||
{
|
||||
return (m_canvas != nullptr) ? m_canvas->is_layers_editing_allowed() : false;
|
||||
}
|
||||
|
||||
void View3D::enable_layers_editing(bool enable)
|
||||
{
|
||||
if (m_canvas != nullptr)
|
||||
m_canvas->enable_layers_editing(enable);
|
||||
}
|
||||
|
||||
bool View3D::is_dragging() const
|
||||
{
|
||||
return (m_canvas != nullptr) ? m_canvas->is_dragging() : false;
|
||||
}
|
||||
|
||||
bool View3D::is_reload_delayed() const
|
||||
{
|
||||
return (m_canvas != nullptr) ? m_canvas->is_reload_delayed() : false;
|
||||
}
|
||||
|
||||
void View3D::reload_scene(bool refresh_immediately, bool force_full_scene_refresh)
|
||||
{
|
||||
if (m_canvas != nullptr)
|
||||
m_canvas->reload_scene(refresh_immediately, force_full_scene_refresh);
|
||||
}
|
||||
|
||||
void View3D::render()
|
||||
{
|
||||
if (m_canvas != nullptr)
|
||||
//m_canvas->render();
|
||||
m_canvas->set_as_dirty();
|
||||
}
|
||||
|
||||
Preview::Preview(
|
||||
wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig* config,
|
||||
BackgroundSlicingProcess* process, GCodeProcessorResult* gcode_result, std::function<void()> schedule_background_process_func)
|
||||
: m_config(config)
|
||||
, m_process(process)
|
||||
, m_gcode_result(gcode_result)
|
||||
, m_schedule_background_process(schedule_background_process_func)
|
||||
{
|
||||
if (init(parent, bed, model))
|
||||
load_print();
|
||||
}
|
||||
|
||||
bool Preview::init(wxWindow* parent, Bed3D& bed, Model* model)
|
||||
{
|
||||
if (!Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 /* disable wxTAB_TRAVERSAL */))
|
||||
return false;
|
||||
|
||||
// to match the background of the sliders
|
||||
#ifdef _WIN32
|
||||
wxGetApp().UpdateDarkUI(this);
|
||||
#else
|
||||
SetBackgroundColour(GetParent()->GetBackgroundColour());
|
||||
#endif // _WIN32
|
||||
|
||||
m_canvas_widget = OpenGLManager::create_wxglcanvas(*this);
|
||||
if (m_canvas_widget == nullptr)
|
||||
return false;
|
||||
|
||||
m_canvas = new GLCanvas3D(m_canvas_widget, bed);
|
||||
m_canvas->set_context(wxGetApp().init_glcontext(*m_canvas_widget));
|
||||
m_canvas->allow_multisample(OpenGLManager::can_multisample());
|
||||
m_canvas->set_config(m_config);
|
||||
m_canvas->set_model(model);
|
||||
m_canvas->set_process(m_process);
|
||||
m_canvas->enable_legend_texture(true);
|
||||
m_canvas->enable_dynamic_background(true);
|
||||
|
||||
m_layers_slider_sizer = create_layers_slider_sizer();
|
||||
|
||||
wxGetApp().UpdateDarkUI(m_bottom_toolbar_panel = new wxPanel(this));
|
||||
|
||||
m_left_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
m_left_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0);
|
||||
|
||||
wxBoxSizer* right_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
right_sizer->Add(m_layers_slider_sizer, 1, wxEXPAND, 0);
|
||||
|
||||
m_moves_slider = new DoubleSlider::Control(m_bottom_toolbar_panel, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL);
|
||||
m_moves_slider->SetDrawMode(DoubleSlider::dmSequentialGCodeView);
|
||||
|
||||
wxBoxSizer* bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
bottom_toolbar_sizer->Add(m_moves_slider, 1, wxALL | wxEXPAND, 0);
|
||||
m_bottom_toolbar_panel->SetSizer(bottom_toolbar_sizer);
|
||||
|
||||
m_left_sizer->Add(m_bottom_toolbar_panel, 0, wxALL | wxEXPAND, 0);
|
||||
m_left_sizer->Hide(m_bottom_toolbar_panel);
|
||||
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
main_sizer->Add(m_left_sizer, 1, wxALL | wxEXPAND, 0);
|
||||
main_sizer->Add(right_sizer, 0, wxALL | wxEXPAND, 0);
|
||||
|
||||
SetSizer(main_sizer);
|
||||
SetMinSize(GetSize());
|
||||
GetSizer()->SetSizeHints(this);
|
||||
|
||||
bind_event_handlers();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Preview::~Preview()
|
||||
{
|
||||
unbind_event_handlers();
|
||||
|
||||
if (m_canvas != nullptr)
|
||||
delete m_canvas;
|
||||
|
||||
if (m_canvas_widget != nullptr)
|
||||
delete m_canvas_widget;
|
||||
}
|
||||
|
||||
void Preview::set_as_dirty()
|
||||
{
|
||||
if (m_canvas != nullptr)
|
||||
m_canvas->set_as_dirty();
|
||||
}
|
||||
|
||||
void Preview::bed_shape_changed()
|
||||
{
|
||||
if (m_canvas != nullptr)
|
||||
m_canvas->bed_shape_changed();
|
||||
}
|
||||
|
||||
void Preview::select_view(const std::string& direction)
|
||||
{
|
||||
m_canvas->select_view(direction);
|
||||
}
|
||||
|
||||
void Preview::set_drop_target(wxDropTarget* target)
|
||||
{
|
||||
if (target != nullptr)
|
||||
SetDropTarget(target);
|
||||
}
|
||||
|
||||
void Preview::load_print(bool keep_z_range)
|
||||
{
|
||||
PrinterTechnology tech = m_process->current_printer_technology();
|
||||
if (tech == ptFFF)
|
||||
load_print_as_fff(keep_z_range);
|
||||
else if (tech == ptSLA)
|
||||
load_print_as_sla();
|
||||
|
||||
Layout();
|
||||
}
|
||||
|
||||
void Preview::reload_print(bool keep_volumes)
|
||||
{
|
||||
#ifdef __linux__
|
||||
// We are getting mysterious crashes on Linux in gtk due to OpenGL context activation GH #1874 #1955.
|
||||
// So we are applying a workaround here: a delayed release of OpenGL vertex buffers.
|
||||
if (!IsShown())
|
||||
{
|
||||
m_volumes_cleanup_required = !keep_volumes;
|
||||
return;
|
||||
}
|
||||
#endif /* __linux__ */
|
||||
if (
|
||||
#ifdef __linux__
|
||||
m_volumes_cleanup_required ||
|
||||
#endif /* __linux__ */
|
||||
!keep_volumes)
|
||||
{
|
||||
m_canvas->reset_volumes();
|
||||
m_loaded = false;
|
||||
#ifdef __linux__
|
||||
m_volumes_cleanup_required = false;
|
||||
#endif /* __linux__ */
|
||||
}
|
||||
|
||||
load_print();
|
||||
}
|
||||
|
||||
void Preview::refresh_print()
|
||||
{
|
||||
m_loaded = false;
|
||||
|
||||
if (!IsShown())
|
||||
return;
|
||||
|
||||
load_print(true);
|
||||
}
|
||||
|
||||
void Preview::msw_rescale()
|
||||
{
|
||||
// rescale slider
|
||||
if (m_layers_slider != nullptr) m_layers_slider->msw_rescale();
|
||||
if (m_moves_slider != nullptr) m_moves_slider->msw_rescale();
|
||||
|
||||
// rescale warning legend on the canvas
|
||||
get_canvas3d()->msw_rescale();
|
||||
|
||||
// rescale legend
|
||||
refresh_print();
|
||||
}
|
||||
|
||||
void Preview::sys_color_changed()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
wxWindowUpdateLocker noUpdates(this);
|
||||
wxGetApp().UpdateAllStaticTextDarkUI(m_bottom_toolbar_panel);
|
||||
#endif // _WIN32
|
||||
|
||||
if (m_layers_slider != nullptr)
|
||||
m_layers_slider->sys_color_changed();
|
||||
}
|
||||
|
||||
void Preview::jump_layers_slider(wxKeyEvent& evt)
|
||||
{
|
||||
if (m_layers_slider) m_layers_slider->OnChar(evt);
|
||||
}
|
||||
|
||||
void Preview::move_layers_slider(wxKeyEvent& evt)
|
||||
{
|
||||
if (m_layers_slider != nullptr) m_layers_slider->OnKeyDown(evt);
|
||||
}
|
||||
|
||||
void Preview::edit_layers_slider(wxKeyEvent& evt)
|
||||
{
|
||||
if (m_layers_slider != nullptr) m_layers_slider->OnChar(evt);
|
||||
}
|
||||
|
||||
void Preview::bind_event_handlers()
|
||||
{
|
||||
Bind(wxEVT_SIZE, &Preview::on_size, this);
|
||||
m_moves_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this);
|
||||
}
|
||||
|
||||
void Preview::unbind_event_handlers()
|
||||
{
|
||||
Unbind(wxEVT_SIZE, &Preview::on_size, this);
|
||||
m_moves_slider->Unbind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this);
|
||||
}
|
||||
|
||||
void Preview::move_moves_slider(wxKeyEvent& evt)
|
||||
{
|
||||
if (m_moves_slider != nullptr) m_moves_slider->OnKeyDown(evt);
|
||||
}
|
||||
|
||||
void Preview::hide_layers_slider()
|
||||
{
|
||||
m_layers_slider_sizer->Hide((size_t)0);
|
||||
Layout();
|
||||
}
|
||||
|
||||
void Preview::on_size(wxSizeEvent& evt)
|
||||
{
|
||||
evt.Skip();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
wxBoxSizer* Preview::create_layers_slider_sizer()
|
||||
{
|
||||
wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
m_layers_slider = new DoubleSlider::Control(this, wxID_ANY, 0, 0, 0, 100);
|
||||
|
||||
m_layers_slider->SetDrawMode(wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA,
|
||||
wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("complete_objects"));
|
||||
m_layers_slider->enable_action_icon(wxGetApp().is_editor());
|
||||
|
||||
sizer->Add(m_layers_slider, 0, wxEXPAND, 0);
|
||||
|
||||
// sizer, m_canvas_widget
|
||||
m_canvas_widget->Bind(wxEVT_KEY_DOWN, &Preview::update_layers_slider_from_canvas, this);
|
||||
m_canvas_widget->Bind(wxEVT_KEY_UP, [this](wxKeyEvent& event) {
|
||||
if (event.GetKeyCode() == WXK_SHIFT)
|
||||
m_layers_slider->UseDefaultColors(true);
|
||||
event.Skip();
|
||||
});
|
||||
|
||||
m_layers_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_layers_slider_scroll_changed, this);
|
||||
|
||||
Bind(DoubleSlider::wxCUSTOMEVT_TICKSCHANGED, [this](wxEvent&) {
|
||||
Model& model = wxGetApp().plater()->model();
|
||||
model.custom_gcode_per_print_z = m_layers_slider->GetTicksValues();
|
||||
m_schedule_background_process();
|
||||
|
||||
m_keep_current_preview_type = false;
|
||||
reload_print(false);
|
||||
});
|
||||
|
||||
return sizer;
|
||||
}
|
||||
|
||||
// Find an index of a value in a sorted vector, which is in <z-eps, z+eps>.
|
||||
// Returns -1 if there is no such member.
|
||||
static int find_close_layer_idx(const std::vector<double>& zs, double &z, double eps)
|
||||
{
|
||||
if (zs.empty())
|
||||
return -1;
|
||||
auto it_h = std::lower_bound(zs.begin(), zs.end(), z);
|
||||
if (it_h == zs.end()) {
|
||||
auto it_l = it_h;
|
||||
-- it_l;
|
||||
if (z - *it_l < eps)
|
||||
return int(zs.size() - 1);
|
||||
} else if (it_h == zs.begin()) {
|
||||
if (*it_h - z < eps)
|
||||
return 0;
|
||||
} else {
|
||||
auto it_l = it_h;
|
||||
-- it_l;
|
||||
double dist_l = z - *it_l;
|
||||
double dist_h = *it_h - z;
|
||||
if (std::min(dist_l, dist_h) < eps) {
|
||||
return (dist_l < dist_h) ? int(it_l - zs.begin()) : int(it_h - zs.begin());
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Preview::check_layers_slider_values(std::vector<CustomGCode::Item>& 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](CustomGCode::Item val)
|
||||
{
|
||||
auto it = std::lower_bound(layers_z.begin(), layers_z.end(), val.print_z - 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_layers_slider(const std::vector<double>& layers_z, bool keep_z_range)
|
||||
{
|
||||
// Save the initial slider span.
|
||||
double z_low = m_layers_slider->GetLowerValueD();
|
||||
double z_high = m_layers_slider->GetHigherValueD();
|
||||
bool was_empty = m_layers_slider->GetMaxValue() == 0;
|
||||
|
||||
bool force_sliders_full_range = was_empty;
|
||||
if (!keep_z_range)
|
||||
{
|
||||
bool span_changed = layers_z.empty() || std::abs(layers_z.back() - m_layers_slider->GetMaxValueD()) > DoubleSlider::epsilon()/*1e-6*/;
|
||||
force_sliders_full_range |= span_changed;
|
||||
}
|
||||
bool snap_to_min = force_sliders_full_range || m_layers_slider->is_lower_at_min();
|
||||
bool snap_to_max = force_sliders_full_range || m_layers_slider->is_higher_at_max();
|
||||
|
||||
// Detect and set manipulation mode for double slider
|
||||
update_layers_slider_mode();
|
||||
|
||||
Plater* plater = wxGetApp().plater();
|
||||
CustomGCode::Info ticks_info_from_model;
|
||||
if (wxGetApp().is_editor())
|
||||
ticks_info_from_model = plater->model().custom_gcode_per_print_z;
|
||||
else {
|
||||
ticks_info_from_model.mode = CustomGCode::Mode::SingleExtruder;
|
||||
ticks_info_from_model.gcodes = m_canvas->get_custom_gcode_per_print_z();
|
||||
}
|
||||
check_layers_slider_values(ticks_info_from_model.gcodes, layers_z);
|
||||
|
||||
//first of all update extruder colors to avoid crash, when we are switching printer preset from MM to SM
|
||||
m_layers_slider->SetExtruderColors(plater->get_extruder_colors_from_plater_config(wxGetApp().is_editor() ? nullptr : m_gcode_result));
|
||||
m_layers_slider->SetSliderValues(layers_z);
|
||||
assert(m_layers_slider->GetMinValue() == 0);
|
||||
m_layers_slider->SetMaxValue(layers_z.empty() ? 0 : layers_z.size() - 1);
|
||||
|
||||
int idx_low = 0;
|
||||
int idx_high = m_layers_slider->GetMaxValue();
|
||||
if (!layers_z.empty()) {
|
||||
if (!snap_to_min) {
|
||||
int idx_new = find_close_layer_idx(layers_z, z_low, DoubleSlider::epsilon()/*1e-6*/);
|
||||
if (idx_new != -1)
|
||||
idx_low = idx_new;
|
||||
}
|
||||
if (!snap_to_max) {
|
||||
int idx_new = find_close_layer_idx(layers_z, z_high, DoubleSlider::epsilon()/*1e-6*/);
|
||||
if (idx_new != -1)
|
||||
idx_high = idx_new;
|
||||
}
|
||||
}
|
||||
m_layers_slider->SetSelectionSpan(idx_low, idx_high);
|
||||
m_layers_slider->SetTicksValues(ticks_info_from_model);
|
||||
|
||||
bool sla_print_technology = plater->printer_technology() == ptSLA;
|
||||
bool sequential_print = wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_bool("complete_objects");
|
||||
m_layers_slider->SetDrawMode(sla_print_technology, sequential_print);
|
||||
if (sla_print_technology)
|
||||
m_layers_slider->SetLayersTimes(plater->sla_print().print_statistics().layers_times);
|
||||
else {
|
||||
auto print_mode_stat = m_gcode_result->print_statistics.modes.front();
|
||||
m_layers_slider->SetLayersTimes(print_mode_stat.layers_times, print_mode_stat.time);
|
||||
}
|
||||
|
||||
// check if ticks_info_from_model contains ColorChange g-code
|
||||
bool color_change_already_exists = false;
|
||||
for (const CustomGCode::Item& gcode: ticks_info_from_model.gcodes)
|
||||
if (gcode.type == CustomGCode::Type::ColorChange) {
|
||||
color_change_already_exists = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Suggest the auto color change, if model looks like sign
|
||||
if (!color_change_already_exists &&
|
||||
wxGetApp().app_config->get_bool("allow_auto_color_change") &&
|
||||
m_layers_slider->IsNewPrint())
|
||||
{
|
||||
const Print& print = wxGetApp().plater()->fff_print();
|
||||
|
||||
//bool is_possible_auto_color_change = false;
|
||||
for (auto object : print.objects()) {
|
||||
double object_x = double(object->size().x());
|
||||
double object_y = double(object->size().y());
|
||||
|
||||
// if it's sign, than object have not to be a too height
|
||||
double height = object->height();
|
||||
coord_t longer_side = std::max(object_x, object_y);
|
||||
auto num_layers = int(object->layers().size());
|
||||
if (height / longer_side > 0.3 || num_layers < 2)
|
||||
continue;
|
||||
|
||||
const ExPolygons& bottom = object->get_layer(0)->lslices;
|
||||
double bottom_area = area(bottom);
|
||||
|
||||
// at least 25% of object's height have to be a solid
|
||||
int i, min_solid_height = int(0.25 * num_layers);
|
||||
for (i = 1; i <= min_solid_height; ++ i) {
|
||||
double cur_area = area(object->get_layer(i)->lslices);
|
||||
if (!DoubleSlider::equivalent_areas(bottom_area, cur_area)) {
|
||||
// but due to the elephant foot compensation, the first layer may be slightly smaller than the others
|
||||
if (i == 1 && fabs(cur_area - bottom_area) / bottom_area < 0.1) {
|
||||
// So, let process this case and use second layer as a bottom
|
||||
bottom_area = cur_area;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i < min_solid_height)
|
||||
continue;
|
||||
|
||||
if (DoubleSlider::check_color_change(object, i, num_layers, true, [this, object](const Layer*) {
|
||||
NotificationManager* notif_mngr = wxGetApp().plater()->get_notification_manager();
|
||||
notif_mngr->push_notification(
|
||||
NotificationType::SignDetected, NotificationManager::NotificationLevel::PrintInfoNotificationLevel,
|
||||
_u8L("NOTE:") + "\n" +
|
||||
format(_u8L("Sliced object \"%1%\" looks like a logo or a sign"), object->model_object()->name) + "\n",
|
||||
_u8L("Apply color change automatically"),
|
||||
[this](wxEvtHandler*) {
|
||||
m_layers_slider->auto_color_change();
|
||||
return true;
|
||||
});
|
||||
|
||||
notif_mngr->apply_in_preview();
|
||||
return true;
|
||||
}) )
|
||||
// first object with color chnages is found
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_layers_slider_sizer->Show((size_t)0);
|
||||
Layout();
|
||||
}
|
||||
|
||||
void Preview::update_layers_slider_mode()
|
||||
{
|
||||
// true -> single-extruder printer profile OR
|
||||
// multi-extruder printer profile , but whole model is printed by only one extruder
|
||||
// false -> multi-extruder printer profile , and model is printed by several extruders
|
||||
bool one_extruder_printed_model = true;
|
||||
|
||||
// extruder used for whole model for multi-extruder printer profile
|
||||
int only_extruder = -1;
|
||||
|
||||
if (wxGetApp().extruders_edited_cnt() > 1)
|
||||
{
|
||||
const ModelObjectPtrs& objects = wxGetApp().plater()->model().objects;
|
||||
|
||||
// check if whole model uses just only one extruder
|
||||
if (!objects.empty())
|
||||
{
|
||||
const int extruder = objects[0]->config.has("extruder") ?
|
||||
objects[0]->config.option("extruder")->getInt() : 0;
|
||||
|
||||
auto is_one_extruder_printed_model = [objects, extruder]()
|
||||
{
|
||||
for (ModelObject* object : objects)
|
||||
{
|
||||
if (object->config.has("extruder") &&
|
||||
object->config.option("extruder")->getInt() != extruder)
|
||||
return false;
|
||||
|
||||
for (ModelVolume* volume : object->volumes)
|
||||
if ((volume->config.has("extruder") &&
|
||||
volume->config.option("extruder")->getInt() != 0 && // extruder isn't default
|
||||
volume->config.option("extruder")->getInt() != extruder) ||
|
||||
!volume->mmu_segmentation_facets.empty())
|
||||
return false;
|
||||
|
||||
for (const auto& range : object->layer_config_ranges)
|
||||
if (range.second.has("extruder") &&
|
||||
range.second.option("extruder")->getInt() != 0 && // extruder isn't default
|
||||
range.second.option("extruder")->getInt() != extruder)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (is_one_extruder_printed_model())
|
||||
only_extruder = extruder;
|
||||
else
|
||||
one_extruder_printed_model = false;
|
||||
}
|
||||
}
|
||||
|
||||
m_layers_slider->SetModeAndOnlyExtruder(one_extruder_printed_model, only_extruder);
|
||||
}
|
||||
|
||||
void Preview::reset_layers_slider()
|
||||
{
|
||||
m_layers_slider->SetHigherValue(0);
|
||||
m_layers_slider->SetLowerValue(0);
|
||||
}
|
||||
|
||||
void Preview::update_layers_slider_from_canvas(wxKeyEvent& event)
|
||||
{
|
||||
if (event.HasModifiers()) {
|
||||
event.Skip();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto key = event.GetKeyCode();
|
||||
|
||||
if (key == 'S' || key == 'W') {
|
||||
const int new_pos = key == 'W' ? m_layers_slider->GetHigherValue() + 1 : m_layers_slider->GetHigherValue() - 1;
|
||||
m_layers_slider->SetHigherValue(new_pos);
|
||||
if (event.ShiftDown() || m_layers_slider->is_one_layer()) m_layers_slider->SetLowerValue(m_layers_slider->GetHigherValue());
|
||||
}
|
||||
else if (key == 'A' || key == 'D') {
|
||||
const int new_pos = key == 'D' ? m_moves_slider->GetHigherValue() + 1 : m_moves_slider->GetHigherValue() - 1;
|
||||
m_moves_slider->SetHigherValue(new_pos);
|
||||
if (event.ShiftDown() || m_moves_slider->is_one_layer()) m_moves_slider->SetLowerValue(m_moves_slider->GetHigherValue());
|
||||
}
|
||||
else if (key == 'X')
|
||||
m_layers_slider->ChangeOneLayerLock();
|
||||
else if (key == WXK_SHIFT)
|
||||
m_layers_slider->UseDefaultColors(false);
|
||||
else
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void Preview::update_moves_slider()
|
||||
{
|
||||
const GCodeViewer::SequentialView& view = m_canvas->get_gcode_sequential_view();
|
||||
// this should not be needed, but it is here to try to prevent rambling crashes on Mac Asan
|
||||
if (view.endpoints.last < view.endpoints.first)
|
||||
return;
|
||||
|
||||
assert(view.endpoints.first <= view.current.first && view.current.first <= view.endpoints.last);
|
||||
assert(view.endpoints.first <= view.current.last && view.current.last <= view.endpoints.last);
|
||||
|
||||
std::vector<double> values;
|
||||
values.reserve(view.endpoints.last - view.endpoints.first + 1);
|
||||
std::vector<double> alternate_values;
|
||||
alternate_values.reserve(view.endpoints.last - view.endpoints.first + 1);
|
||||
unsigned int last_gcode_id = view.gcode_ids[view.endpoints.first];
|
||||
for (unsigned int i = view.endpoints.first; i <= view.endpoints.last; ++i) {
|
||||
if (i > view.endpoints.first) {
|
||||
// skip consecutive moves with same gcode id (resulting from processing G2 and G3 lines)
|
||||
if (last_gcode_id == view.gcode_ids[i]) {
|
||||
values.back() = static_cast<double>(i + 1);
|
||||
alternate_values.back() = static_cast<double>(view.gcode_ids[i]);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
last_gcode_id = view.gcode_ids[i];
|
||||
}
|
||||
|
||||
values.emplace_back(static_cast<double>(i + 1));
|
||||
alternate_values.emplace_back(static_cast<double>(view.gcode_ids[i]));
|
||||
}
|
||||
|
||||
m_moves_slider->SetSliderValues(values);
|
||||
m_moves_slider->SetSliderAlternateValues(alternate_values);
|
||||
m_moves_slider->SetMaxValue(int(values.size()) - 1);
|
||||
m_moves_slider->SetSelectionSpan(values.front() - 1 - view.endpoints.first, values.back() - 1 - view.endpoints.first);
|
||||
}
|
||||
|
||||
void Preview::enable_moves_slider(bool enable)
|
||||
{
|
||||
bool render_as_disabled = !enable;
|
||||
if (m_moves_slider != nullptr && m_moves_slider->is_rendering_as_disabled() != render_as_disabled) {
|
||||
m_moves_slider->set_render_as_disabled(render_as_disabled);
|
||||
m_moves_slider->Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
void Preview::load_print_as_fff(bool keep_z_range)
|
||||
{
|
||||
if (wxGetApp().mainframe == nullptr || wxGetApp().is_recreating_gui())
|
||||
// avoid processing while mainframe is being constructed
|
||||
return;
|
||||
|
||||
if (m_loaded || m_process->current_printer_technology() != ptFFF)
|
||||
return;
|
||||
|
||||
// we require that there's at least one object and the posSlice step
|
||||
// is performed on all of them(this ensures that _shifted_copies was
|
||||
// populated and we know the number of layers)
|
||||
bool has_layers = false;
|
||||
const Print *print = m_process->fff_print();
|
||||
if (print->is_step_done(posSlice)) {
|
||||
for (const PrintObject* print_object : print->objects())
|
||||
if (! print_object->layers().empty()) {
|
||||
has_layers = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (print->is_step_done(posSupportMaterial)) {
|
||||
for (const PrintObject* print_object : print->objects())
|
||||
if (! print_object->support_layers().empty()) {
|
||||
has_layers = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (wxGetApp().is_editor() && !has_layers) {
|
||||
hide_layers_slider();
|
||||
m_left_sizer->Hide(m_bottom_toolbar_panel);
|
||||
m_left_sizer->Layout();
|
||||
Refresh();
|
||||
m_canvas_widget->Refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
GCodeViewer::EViewType gcode_view_type = m_canvas->get_gcode_view_preview_type();
|
||||
bool gcode_preview_data_valid = !m_gcode_result->moves.empty();
|
||||
|
||||
// Collect colors per extruder.
|
||||
std::vector<std::string> colors;
|
||||
std::vector<CustomGCode::Item> color_print_values = {};
|
||||
// set color print values, if it si selected "ColorPrint" view type
|
||||
if (gcode_view_type == GCodeViewer::EViewType::ColorPrint) {
|
||||
colors = wxGetApp().plater()->get_colors_for_color_print(m_gcode_result);
|
||||
|
||||
if (!gcode_preview_data_valid) {
|
||||
if (wxGetApp().is_editor())
|
||||
color_print_values = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes;
|
||||
else
|
||||
color_print_values = m_canvas->get_custom_gcode_per_print_z();
|
||||
colors.push_back("#808080"); // gray color for pause print or custom G-code
|
||||
}
|
||||
}
|
||||
else if (gcode_preview_data_valid || gcode_view_type == GCodeViewer::EViewType::Tool) {
|
||||
colors = wxGetApp().plater()->get_extruder_colors_from_plater_config(m_gcode_result);
|
||||
color_print_values.clear();
|
||||
}
|
||||
|
||||
std::vector<double> zs;
|
||||
|
||||
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_result, colors);
|
||||
m_left_sizer->Layout();
|
||||
Refresh();
|
||||
zs = m_canvas->get_gcode_layers_zs();
|
||||
if (!zs.empty())
|
||||
m_left_sizer->Show(m_bottom_toolbar_panel);
|
||||
m_loaded = true;
|
||||
}
|
||||
else if (wxGetApp().is_editor()) {
|
||||
// Load the initial preview based on slices, not the final G-code.
|
||||
m_canvas->load_preview(colors, color_print_values);
|
||||
m_left_sizer->Hide(m_bottom_toolbar_panel);
|
||||
m_left_sizer->Layout();
|
||||
Refresh();
|
||||
zs = m_canvas->get_volumes_print_zs(true);
|
||||
}
|
||||
else {
|
||||
m_left_sizer->Hide(m_bottom_toolbar_panel);
|
||||
m_left_sizer->Layout();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
if (!zs.empty() && !m_keep_current_preview_type) {
|
||||
unsigned int number_extruders = wxGetApp().is_editor() ?
|
||||
(unsigned int)print->extruders().size() :
|
||||
m_canvas->get_gcode_extruders_count();
|
||||
std::vector<Item> gcodes = wxGetApp().is_editor() ?
|
||||
wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes :
|
||||
m_canvas->get_custom_gcode_per_print_z();
|
||||
const bool contains_color_gcodes = std::any_of(std::begin(gcodes), std::end(gcodes),
|
||||
[] (auto const& item) { return item.type == CustomGCode::Type::ColorChange; });
|
||||
const GCodeViewer::EViewType choice = contains_color_gcodes ?
|
||||
GCodeViewer::EViewType::ColorPrint :
|
||||
(number_extruders > 1) ? GCodeViewer::EViewType::Tool : GCodeViewer::EViewType::FeatureType;
|
||||
if (choice != gcode_view_type) {
|
||||
m_canvas->set_gcode_view_preview_type(choice);
|
||||
if (wxGetApp().is_gcode_viewer())
|
||||
m_keep_current_preview_type = true;
|
||||
refresh_print();
|
||||
}
|
||||
}
|
||||
|
||||
if (zs.empty()) {
|
||||
// all layers filtered out
|
||||
hide_layers_slider();
|
||||
m_canvas_widget->Refresh();
|
||||
} else
|
||||
update_layers_slider(zs, keep_z_range);
|
||||
}
|
||||
}
|
||||
|
||||
void Preview::load_print_as_sla()
|
||||
{
|
||||
if (m_loaded || (m_process->current_printer_technology() != ptSLA))
|
||||
return;
|
||||
|
||||
unsigned int n_layers = 0;
|
||||
const SLAPrint* print = m_process->sla_print();
|
||||
|
||||
std::vector<double> zs;
|
||||
double initial_layer_height = print->material_config().initial_layer_height.value;
|
||||
for (const SLAPrintObject* obj : print->objects())
|
||||
if (obj->is_step_done(slaposSliceSupports) && !obj->get_slice_index().empty()) {
|
||||
auto low_coord = obj->get_slice_index().front().print_level();
|
||||
for (auto& rec : obj->get_slice_index())
|
||||
zs.emplace_back(initial_layer_height + (rec.print_level() - low_coord) * SCALING_FACTOR);
|
||||
}
|
||||
sort_remove_duplicates(zs);
|
||||
|
||||
m_canvas->reset_clipping_planes_cache();
|
||||
m_canvas->set_use_clipping_planes(true);
|
||||
|
||||
n_layers = (unsigned int)zs.size();
|
||||
if (n_layers == 0) {
|
||||
hide_layers_slider();
|
||||
m_canvas_widget->Refresh();
|
||||
}
|
||||
|
||||
if (IsShown()) {
|
||||
m_canvas->load_sla_preview();
|
||||
m_left_sizer->Hide(m_bottom_toolbar_panel);
|
||||
m_left_sizer->Layout();
|
||||
Refresh();
|
||||
|
||||
if (n_layers > 0)
|
||||
update_layers_slider(zs);
|
||||
|
||||
m_loaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Preview::on_layers_slider_scroll_changed(wxCommandEvent& event)
|
||||
{
|
||||
if (IsShown()) {
|
||||
PrinterTechnology tech = m_process->current_printer_technology();
|
||||
if (tech == ptFFF) {
|
||||
m_canvas->set_volumes_z_range({ m_layers_slider->GetLowerValueD(), m_layers_slider->GetHigherValueD() });
|
||||
m_canvas->set_toolpaths_z_range({ static_cast<unsigned int>(m_layers_slider->GetLowerValue()), static_cast<unsigned int>(m_layers_slider->GetHigherValue()) });
|
||||
m_canvas->set_as_dirty();
|
||||
}
|
||||
else if (tech == ptSLA) {
|
||||
m_canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -m_layers_slider->GetLowerValueD()));
|
||||
m_canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), m_layers_slider->GetHigherValueD()));
|
||||
m_canvas->render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Preview::on_moves_slider_scroll_changed(wxCommandEvent& event)
|
||||
{
|
||||
m_canvas->update_gcode_sequential_view_current(static_cast<unsigned int>(m_moves_slider->GetLowerValueD() - 1.0), static_cast<unsigned int>(m_moves_slider->GetHigherValueD() - 1.0));
|
||||
m_canvas->render();
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
182
src/slic3r/GUI/GUI_Preview.hpp
Normal file
182
src/slic3r/GUI/GUI_Preview.hpp
Normal file
@@ -0,0 +1,182 @@
|
||||
#ifndef slic3r_GUI_Preview_hpp_
|
||||
#define slic3r_GUI_Preview_hpp_
|
||||
|
||||
#include <wx/panel.h>
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/CustomGCode.hpp"
|
||||
|
||||
#include <string>
|
||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||
|
||||
class wxGLCanvas;
|
||||
class wxBoxSizer;
|
||||
class wxStaticText;
|
||||
class wxComboBox;
|
||||
class wxComboCtrl;
|
||||
class wxCheckBox;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class Print;
|
||||
class BackgroundSlicingProcess;
|
||||
class Model;
|
||||
|
||||
namespace DoubleSlider {
|
||||
class Control;
|
||||
};
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class GLCanvas3D;
|
||||
class GLToolbar;
|
||||
class Bed3D;
|
||||
struct Camera;
|
||||
class Plater;
|
||||
#ifdef _WIN32
|
||||
class BitmapComboBox;
|
||||
#endif
|
||||
|
||||
class View3D : public wxPanel
|
||||
{
|
||||
wxGLCanvas* m_canvas_widget;
|
||||
GLCanvas3D* m_canvas;
|
||||
|
||||
public:
|
||||
View3D(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process);
|
||||
virtual ~View3D();
|
||||
|
||||
wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; }
|
||||
GLCanvas3D* get_canvas3d() { return m_canvas; }
|
||||
|
||||
void set_as_dirty();
|
||||
void bed_shape_changed();
|
||||
|
||||
void select_view(const std::string& direction);
|
||||
void select_all();
|
||||
void deselect_all();
|
||||
void delete_selected();
|
||||
void mirror_selection(Axis axis);
|
||||
|
||||
bool is_layers_editing_enabled() const;
|
||||
bool is_layers_editing_allowed() const;
|
||||
void enable_layers_editing(bool enable);
|
||||
|
||||
bool is_dragging() const;
|
||||
bool is_reload_delayed() const;
|
||||
|
||||
void reload_scene(bool refresh_immediately, bool force_full_scene_refresh = false);
|
||||
void render();
|
||||
|
||||
private:
|
||||
bool init(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process);
|
||||
};
|
||||
|
||||
class Preview : public wxPanel
|
||||
{
|
||||
wxGLCanvas* m_canvas_widget { nullptr };
|
||||
GLCanvas3D* m_canvas { nullptr };
|
||||
wxBoxSizer* m_left_sizer { nullptr };
|
||||
wxBoxSizer* m_layers_slider_sizer { nullptr };
|
||||
wxPanel* m_bottom_toolbar_panel { nullptr };
|
||||
|
||||
DynamicPrintConfig* m_config;
|
||||
BackgroundSlicingProcess* m_process;
|
||||
GCodeProcessorResult* m_gcode_result;
|
||||
|
||||
#ifdef __linux__
|
||||
// We are getting mysterious crashes on Linux in gtk due to OpenGL context activation GH #1874 #1955.
|
||||
// So we are applying a workaround here.
|
||||
bool m_volumes_cleanup_required { false };
|
||||
#endif /* __linux__ */
|
||||
|
||||
// Calling this function object forces Plater::schedule_background_process.
|
||||
std::function<void()> m_schedule_background_process;
|
||||
|
||||
unsigned int m_number_extruders { 1 };
|
||||
bool m_keep_current_preview_type{ false };
|
||||
|
||||
bool m_loaded { false };
|
||||
|
||||
DoubleSlider::Control* m_layers_slider{ nullptr };
|
||||
DoubleSlider::Control* m_moves_slider{ nullptr };
|
||||
|
||||
public:
|
||||
enum class OptionType : unsigned int
|
||||
{
|
||||
Travel,
|
||||
Wipe,
|
||||
Retractions,
|
||||
Unretractions,
|
||||
Seams,
|
||||
ToolChanges,
|
||||
ColorChanges,
|
||||
PausePrints,
|
||||
CustomGCodes,
|
||||
CenterOfGravity,
|
||||
Shells,
|
||||
ToolMarker,
|
||||
};
|
||||
|
||||
Preview(wxWindow* parent, Bed3D& bed, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process,
|
||||
GCodeProcessorResult* gcode_result, std::function<void()> schedule_background_process = []() {});
|
||||
virtual ~Preview();
|
||||
|
||||
wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; }
|
||||
GLCanvas3D* get_canvas3d() { return m_canvas; }
|
||||
|
||||
void set_as_dirty();
|
||||
|
||||
void bed_shape_changed();
|
||||
void select_view(const std::string& direction);
|
||||
void set_drop_target(wxDropTarget* target);
|
||||
|
||||
void load_print(bool keep_z_range = false);
|
||||
void reload_print(bool keep_volumes = false);
|
||||
void refresh_print();
|
||||
|
||||
void msw_rescale();
|
||||
void sys_color_changed();
|
||||
void jump_layers_slider(wxKeyEvent& evt);
|
||||
void move_layers_slider(wxKeyEvent& evt);
|
||||
void edit_layers_slider(wxKeyEvent& evt);
|
||||
|
||||
bool is_loaded() const { return m_loaded; }
|
||||
|
||||
void update_moves_slider();
|
||||
void enable_moves_slider(bool enable);
|
||||
void move_moves_slider(wxKeyEvent& evt);
|
||||
void hide_layers_slider();
|
||||
|
||||
void set_keep_current_preview_type(bool value) { m_keep_current_preview_type = value; }
|
||||
|
||||
private:
|
||||
bool init(wxWindow* parent, Bed3D& bed, Model* model);
|
||||
|
||||
void bind_event_handlers();
|
||||
void unbind_event_handlers();
|
||||
|
||||
void on_size(wxSizeEvent& evt);
|
||||
|
||||
// Create/Update/Reset double slider on 3dPreview
|
||||
wxBoxSizer* create_layers_slider_sizer();
|
||||
void check_layers_slider_values(std::vector<CustomGCode::Item>& ticks_from_model,
|
||||
const std::vector<double>& layers_z);
|
||||
void reset_layers_slider();
|
||||
void update_layers_slider(const std::vector<double>& layers_z, bool keep_z_range = false);
|
||||
void update_layers_slider_mode();
|
||||
// update vertical DoubleSlider after keyDown in canvas
|
||||
void update_layers_slider_from_canvas(wxKeyEvent& event);
|
||||
|
||||
void load_print_as_fff(bool keep_z_range = false);
|
||||
void load_print_as_sla();
|
||||
|
||||
void on_layers_slider_scroll_changed(wxCommandEvent& event);
|
||||
void on_moves_slider_scroll_changed(wxCommandEvent& event);
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GUI_Preview_hpp_
|
||||
314
src/slic3r/GUI/GUI_Utils.cpp
Normal file
314
src/slic3r/GUI/GUI_Utils.cpp
Normal file
@@ -0,0 +1,314 @@
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include <wx/msw/registry.h>
|
||||
#endif // _WIN32
|
||||
|
||||
#include <wx/toplevel.h>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/checkbox.h>
|
||||
#include <wx/dcclient.h>
|
||||
#include <wx/font.h>
|
||||
#include <wx/fontutil.h>
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
#ifdef _WIN32
|
||||
wxDEFINE_EVENT(EVT_HID_DEVICE_ATTACHED, HIDDeviceAttachedEvent);
|
||||
wxDEFINE_EVENT(EVT_HID_DEVICE_DETACHED, HIDDeviceDetachedEvent);
|
||||
wxDEFINE_EVENT(EVT_VOLUME_ATTACHED, VolumeAttachedEvent);
|
||||
wxDEFINE_EVENT(EVT_VOLUME_DETACHED, VolumeDetachedEvent);
|
||||
#endif // _WIN32
|
||||
|
||||
wxTopLevelWindow* find_toplevel_parent(wxWindow *window)
|
||||
{
|
||||
for (; window != nullptr; window = window->GetParent()) {
|
||||
if (window->IsTopLevel()) {
|
||||
return dynamic_cast<wxTopLevelWindow*>(window);
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void on_window_geometry(wxTopLevelWindow *tlw, std::function<void()> callback)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// On windows, the wxEVT_SHOW is not received if the window is created maximized
|
||||
// cf. https://groups.google.com/forum/#!topic/wx-users/c7ntMt6piRI
|
||||
// OTOH the geometry is available very soon, so we can call the callback right away
|
||||
callback();
|
||||
#elif defined __linux__
|
||||
tlw->Bind(wxEVT_SHOW, [=](wxShowEvent &evt) {
|
||||
// On Linux, the geometry is only available after wxEVT_SHOW + CallAfter
|
||||
// cf. https://groups.google.com/forum/?pli=1#!topic/wx-users/fERSXdpVwAI
|
||||
tlw->CallAfter([=]() { callback(); });
|
||||
evt.Skip();
|
||||
});
|
||||
#elif defined __APPLE__
|
||||
tlw->Bind(wxEVT_SHOW, [=](wxShowEvent &evt) {
|
||||
callback();
|
||||
evt.Skip();
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
|
||||
wxDEFINE_EVENT(EVT_DPI_CHANGED_SLICER, DpiChangedEvent);
|
||||
#endif // !wxVERSION_EQUAL_OR_GREATER_THAN
|
||||
|
||||
#ifdef _WIN32
|
||||
template<class F> typename F::FN winapi_get_function(const wchar_t *dll, const char *fn_name) {
|
||||
static HINSTANCE dll_handle = LoadLibraryExW(dll, nullptr, 0);
|
||||
|
||||
if (dll_handle == nullptr) { return nullptr; }
|
||||
return (typename F::FN)GetProcAddress(dll_handle, fn_name);
|
||||
}
|
||||
#endif
|
||||
|
||||
// If called with nullptr, a DPI for the primary monitor is returned.
|
||||
int get_dpi_for_window(const wxWindow *window)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
enum MONITOR_DPI_TYPE_ {
|
||||
// This enum is inlined here to avoid build-time dependency
|
||||
MDT_EFFECTIVE_DPI_ = 0,
|
||||
MDT_ANGULAR_DPI_ = 1,
|
||||
MDT_RAW_DPI_ = 2,
|
||||
MDT_DEFAULT_ = MDT_EFFECTIVE_DPI_,
|
||||
};
|
||||
|
||||
// Need strong types for winapi_get_function() to work
|
||||
struct GetDpiForWindow_t { typedef HRESULT (WINAPI *FN)(HWND hwnd); };
|
||||
struct GetDpiForMonitor_t { typedef HRESULT (WINAPI *FN)(HMONITOR hmonitor, MONITOR_DPI_TYPE_ dpiType, UINT *dpiX, UINT *dpiY); };
|
||||
|
||||
static auto GetDpiForWindow_fn = winapi_get_function<GetDpiForWindow_t>(L"User32.dll", "GetDpiForWindow");
|
||||
static auto GetDpiForMonitor_fn = winapi_get_function<GetDpiForMonitor_t>(L"Shcore.dll", "GetDpiForMonitor");
|
||||
|
||||
// Desktop Window is the window of the primary monitor.
|
||||
const HWND hwnd = (window == nullptr) ? ::GetDesktopWindow() : window->GetHandle();
|
||||
|
||||
if (GetDpiForWindow_fn != nullptr) {
|
||||
// We're on Windows 10, we have per-screen DPI settings
|
||||
return GetDpiForWindow_fn(hwnd);
|
||||
} else if (GetDpiForMonitor_fn != nullptr) {
|
||||
// We're on Windows 8.1, we have per-system DPI
|
||||
// Note: MonitorFromWindow() is available on all Windows.
|
||||
|
||||
const HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
||||
UINT dpiX;
|
||||
UINT dpiY;
|
||||
return GetDpiForMonitor_fn(monitor, MDT_EFFECTIVE_DPI_, &dpiX, &dpiY) == S_OK ? dpiX : DPI_DEFAULT;
|
||||
} else {
|
||||
// We're on Windows earlier than 8.1, use DC
|
||||
|
||||
const HDC hdc = GetDC(hwnd);
|
||||
if (hdc == NULL) { return DPI_DEFAULT; }
|
||||
return GetDeviceCaps(hdc, LOGPIXELSX);
|
||||
}
|
||||
#elif defined __linux__
|
||||
// TODO
|
||||
return DPI_DEFAULT;
|
||||
#elif defined __APPLE__
|
||||
// TODO
|
||||
return DPI_DEFAULT;
|
||||
#else // freebsd and others
|
||||
// TODO
|
||||
return DPI_DEFAULT;
|
||||
#endif
|
||||
}
|
||||
|
||||
wxFont get_default_font_for_dpi(const wxWindow *window, int dpi)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// First try to load the font with the Windows 10 specific way.
|
||||
struct SystemParametersInfoForDpi_t { typedef BOOL (WINAPI *FN)(UINT uiAction, UINT uiParam, PVOID pvParam, UINT fWinIni, UINT dpi); };
|
||||
static auto SystemParametersInfoForDpi_fn = winapi_get_function<SystemParametersInfoForDpi_t>(L"User32.dll", "SystemParametersInfoForDpi");
|
||||
if (SystemParametersInfoForDpi_fn != nullptr) {
|
||||
NONCLIENTMETRICS nm;
|
||||
memset(&nm, 0, sizeof(NONCLIENTMETRICS));
|
||||
nm.cbSize = sizeof(NONCLIENTMETRICS);
|
||||
if (SystemParametersInfoForDpi_fn(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &nm, 0, dpi))
|
||||
return wxFont(wxNativeFontInfo(nm.lfMessageFont, window));
|
||||
}
|
||||
// Then try to guesstimate the font DPI scaling on Windows 8.
|
||||
// Let's hope that the font returned by the SystemParametersInfo(), which is used by wxWidgets internally, makes sense.
|
||||
int dpi_primary = get_dpi_for_window(nullptr);
|
||||
if (dpi_primary != dpi) {
|
||||
// Rescale the font.
|
||||
return wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Scaled(float(dpi) / float(dpi_primary));
|
||||
}
|
||||
#endif
|
||||
return wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
}
|
||||
|
||||
bool check_dark_mode() {
|
||||
#if wxCHECK_VERSION(3,1,3)
|
||||
return wxSystemSettings::GetAppearance().IsDark();
|
||||
#else
|
||||
const unsigned luma = wxGetApp().get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
return luma < 128;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
void update_dark_ui(wxWindow* window)
|
||||
{
|
||||
bool is_dark = wxGetApp().app_config->get_bool("dark_color_mode");// ? true : check_dark_mode();// #ysDarkMSW - Allow it when we deside to support the sustem colors for application
|
||||
window->SetBackgroundColour(is_dark ? wxColour(43, 43, 43) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
window->SetForegroundColour(is_dark ? wxColour(250, 250, 250) : wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
CheckboxFileDialog::ExtraPanel::ExtraPanel(wxWindow *parent)
|
||||
: wxPanel(parent, wxID_ANY)
|
||||
{
|
||||
// WARN: wxMSW does some extra shenanigans to calc the extra control size.
|
||||
// It first calls the create function with a dummy empty wxDialog parent and saves its size.
|
||||
// Afterwards, the create function is called again with the real parent.
|
||||
// Additionally there's no way to pass any extra data to the create function (no closure),
|
||||
// which is why we have to this stuff here. Grrr!
|
||||
auto *dlg = dynamic_cast<CheckboxFileDialog*>(parent);
|
||||
const wxString checkbox_label(dlg != nullptr ? dlg->checkbox_label : wxString("String long enough to contain dlg->checkbox_label"));
|
||||
|
||||
auto* sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
cbox = new wxCheckBox(this, wxID_ANY, checkbox_label);
|
||||
cbox->SetValue(true);
|
||||
sizer->AddSpacer(5);
|
||||
sizer->Add(this->cbox, 0, wxEXPAND | wxALL, 5);
|
||||
SetSizer(sizer);
|
||||
sizer->SetSizeHints(this);
|
||||
}
|
||||
|
||||
wxWindow* CheckboxFileDialog::ExtraPanel::ctor(wxWindow *parent) {
|
||||
return new ExtraPanel(parent);
|
||||
}
|
||||
|
||||
CheckboxFileDialog::CheckboxFileDialog(wxWindow *parent,
|
||||
const wxString &checkbox_label,
|
||||
bool checkbox_value,
|
||||
const wxString &message,
|
||||
const wxString &default_dir,
|
||||
const wxString &default_file,
|
||||
const wxString &wildcard,
|
||||
long style,
|
||||
const wxPoint &pos,
|
||||
const wxSize &size,
|
||||
const wxString &name
|
||||
)
|
||||
: wxFileDialog(parent, message, default_dir, default_file, wildcard, style, pos, size, name)
|
||||
, checkbox_label(checkbox_label)
|
||||
{
|
||||
if (checkbox_label.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetExtraControlCreator(ExtraPanel::ctor);
|
||||
}
|
||||
|
||||
bool CheckboxFileDialog::get_checkbox_value() const
|
||||
{
|
||||
auto *extra_panel = dynamic_cast<ExtraPanel*>(GetExtraControl());
|
||||
return extra_panel != nullptr ? extra_panel->cbox->GetValue() : false;
|
||||
}
|
||||
|
||||
|
||||
WindowMetrics WindowMetrics::from_window(wxTopLevelWindow *window)
|
||||
{
|
||||
WindowMetrics res;
|
||||
res.rect = window->GetScreenRect();
|
||||
res.maximized = window->IsMaximized();
|
||||
return res;
|
||||
}
|
||||
|
||||
boost::optional<WindowMetrics> WindowMetrics::deserialize(const std::string &str)
|
||||
{
|
||||
std::vector<std::string> metrics_str;
|
||||
metrics_str.reserve(5);
|
||||
|
||||
if (!unescape_strings_cstyle(str, metrics_str) || metrics_str.size() != 5) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
int metrics[5];
|
||||
try {
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
metrics[i] = boost::lexical_cast<int>(metrics_str[i]);
|
||||
}
|
||||
} catch(const boost::bad_lexical_cast &) {
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
if ((metrics[4] & ~1) != 0) { // Checks if the maximized flag is 1 or 0
|
||||
metrics[4] = 0;
|
||||
}
|
||||
|
||||
WindowMetrics res;
|
||||
res.rect = wxRect(metrics[0], metrics[1], metrics[2], metrics[3]);
|
||||
res.maximized = metrics[4] != 0;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void WindowMetrics::sanitize_for_display(const wxRect &screen_rect)
|
||||
{
|
||||
rect = rect.Intersect(screen_rect);
|
||||
|
||||
// Prevent the window from going too far towards the right and/or bottom edge
|
||||
// It's hardcoded here that the threshold is 80% of the screen size
|
||||
rect.x = std::min(rect.x, screen_rect.x + 4*screen_rect.width/5);
|
||||
rect.y = std::min(rect.y, screen_rect.y + 4*screen_rect.height/5);
|
||||
}
|
||||
|
||||
std::string WindowMetrics::serialize() const
|
||||
{
|
||||
return (boost::format("%1%; %2%; %3%; %4%; %5%")
|
||||
% rect.x
|
||||
% rect.y
|
||||
% rect.width
|
||||
% rect.height
|
||||
% static_cast<int>(maximized)
|
||||
).str();
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream &os, const WindowMetrics& metrics)
|
||||
{
|
||||
return os << '(' << metrics.serialize() << ')';
|
||||
}
|
||||
|
||||
|
||||
TaskTimer::TaskTimer(std::string task_name):
|
||||
task_name(task_name.empty() ? "task" : task_name)
|
||||
{
|
||||
start_timer = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch());
|
||||
}
|
||||
|
||||
TaskTimer::~TaskTimer()
|
||||
{
|
||||
std::chrono::milliseconds stop_timer = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch());
|
||||
auto process_duration = std::chrono::milliseconds(stop_timer - start_timer).count();
|
||||
std::string out = (boost::format("\n!!! %1% duration = %2% ms \n\n") % task_name % process_duration).str();
|
||||
printf("%s", out.c_str());
|
||||
#ifdef __WXMSW__
|
||||
std::wstring stemp = std::wstring(out.begin(), out.end());
|
||||
OutputDebugString(stemp.c_str());
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
434
src/slic3r/GUI/GUI_Utils.hpp
Normal file
434
src/slic3r/GUI/GUI_Utils.hpp
Normal file
@@ -0,0 +1,434 @@
|
||||
#ifndef slic3r_GUI_Utils_hpp_
|
||||
#define slic3r_GUI_Utils_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <ostream>
|
||||
#include <functional>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include <wx/frame.h>
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/gdicmn.h>
|
||||
#include <wx/panel.h>
|
||||
#include <wx/dcclient.h>
|
||||
#include <wx/debug.h>
|
||||
#include <wx/settings.h>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "Event.hpp"
|
||||
|
||||
class wxCheckBox;
|
||||
class wxTopLevelWindow;
|
||||
class wxRect;
|
||||
|
||||
#define wxVERSION_EQUAL_OR_GREATER_THAN(major, minor, release) ((wxMAJOR_VERSION > major) || ((wxMAJOR_VERSION == major) && (wxMINOR_VERSION > minor)) || ((wxMAJOR_VERSION == major) && (wxMINOR_VERSION == minor) && (wxRELEASE_NUMBER >= release)))
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
#ifdef _WIN32
|
||||
// USB HID attach / detach events from Windows OS.
|
||||
using HIDDeviceAttachedEvent = Event<std::string>;
|
||||
using HIDDeviceDetachedEvent = Event<std::string>;
|
||||
wxDECLARE_EVENT(EVT_HID_DEVICE_ATTACHED, HIDDeviceAttachedEvent);
|
||||
wxDECLARE_EVENT(EVT_HID_DEVICE_DETACHED, HIDDeviceDetachedEvent);
|
||||
|
||||
// Disk aka Volume attach / detach events from Windows OS.
|
||||
using VolumeAttachedEvent = SimpleEvent;
|
||||
using VolumeDetachedEvent = SimpleEvent;
|
||||
wxDECLARE_EVENT(EVT_VOLUME_ATTACHED, VolumeAttachedEvent);
|
||||
wxDECLARE_EVENT(EVT_VOLUME_DETACHED, VolumeDetachedEvent);
|
||||
#endif /* _WIN32 */
|
||||
|
||||
wxTopLevelWindow* find_toplevel_parent(wxWindow *window);
|
||||
|
||||
void on_window_geometry(wxTopLevelWindow *tlw, std::function<void()> callback);
|
||||
|
||||
enum { DPI_DEFAULT = 96 };
|
||||
|
||||
int get_dpi_for_window(const wxWindow *window);
|
||||
wxFont get_default_font_for_dpi(const wxWindow* window, int dpi);
|
||||
inline wxFont get_default_font(const wxWindow* window) { return get_default_font_for_dpi(window, get_dpi_for_window(window)); }
|
||||
|
||||
bool check_dark_mode();
|
||||
#ifdef _WIN32
|
||||
void update_dark_ui(wxWindow* window);
|
||||
#endif
|
||||
|
||||
#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
|
||||
struct DpiChangedEvent : public wxEvent {
|
||||
int dpi;
|
||||
wxRect rect;
|
||||
|
||||
DpiChangedEvent(wxEventType eventType, int dpi, wxRect rect)
|
||||
: wxEvent(0, eventType), dpi(dpi), rect(rect)
|
||||
{}
|
||||
|
||||
virtual wxEvent *Clone() const
|
||||
{
|
||||
return new DpiChangedEvent(*this);
|
||||
}
|
||||
};
|
||||
|
||||
wxDECLARE_EVENT(EVT_DPI_CHANGED_SLICER, DpiChangedEvent);
|
||||
#endif // !wxVERSION_EQUAL_OR_GREATER_THAN
|
||||
|
||||
template<class P> class DPIAware : public P
|
||||
{
|
||||
public:
|
||||
DPIAware(wxWindow *parent, wxWindowID id, const wxString &title, const wxPoint &pos=wxDefaultPosition,
|
||||
const wxSize &size=wxDefaultSize, long style=wxDEFAULT_FRAME_STYLE, const wxString &name= wxFrameNameStr, const int font_point_size = -1)
|
||||
: P(parent, id, title, pos, size, style, name)
|
||||
{
|
||||
int dpi = get_dpi_for_window(this);
|
||||
m_scale_factor = (float)dpi / (float)DPI_DEFAULT;
|
||||
m_prev_scale_factor = m_scale_factor;
|
||||
m_normal_font = get_default_font_for_dpi(this, dpi);
|
||||
|
||||
if (font_point_size > 0)
|
||||
m_normal_font.SetPointSize(font_point_size);
|
||||
|
||||
/* Because of default window font is a primary display font,
|
||||
* We should set correct font for window before getting em_unit value.
|
||||
*/
|
||||
#ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList
|
||||
this->SetFont(m_normal_font);
|
||||
#endif
|
||||
this->CenterOnParent();
|
||||
#ifdef _WIN32
|
||||
update_dark_ui(this);
|
||||
#endif
|
||||
|
||||
// Linux specific issue : get_dpi_for_window(this) still doesn't responce to the Display's scale in new wxWidgets(3.1.3).
|
||||
// So, calculate the m_em_unit value from the font size, as before
|
||||
#if !defined(__WXGTK__)
|
||||
m_em_unit = std::max<size_t>(10, 10.0f * m_scale_factor);
|
||||
#else
|
||||
// initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window.
|
||||
m_em_unit = std::max<size_t>(10, this->GetTextExtent("m").x - 1);
|
||||
#endif // __WXGTK__
|
||||
|
||||
// recalc_font();
|
||||
|
||||
#ifndef __WXOSX__
|
||||
#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
|
||||
this->Bind(wxEVT_DPI_CHANGED, [this](wxDPIChangedEvent& evt) {
|
||||
m_scale_factor = (float)evt.GetNewDPI().x / (float)DPI_DEFAULT;
|
||||
m_new_font_point_size = get_default_font_for_dpi(this, evt.GetNewDPI().x).GetPointSize();
|
||||
if (m_can_rescale && (m_force_rescale || is_new_scale_factor()))
|
||||
rescale(wxRect());
|
||||
});
|
||||
#else
|
||||
this->Bind(EVT_DPI_CHANGED_SLICER, [this](const DpiChangedEvent& evt) {
|
||||
m_scale_factor = (float)evt.dpi / (float)DPI_DEFAULT;
|
||||
|
||||
m_new_font_point_size = get_default_font_for_dpi(this, evt.dpi).GetPointSize();
|
||||
|
||||
if (!m_can_rescale)
|
||||
return;
|
||||
|
||||
if (m_force_rescale || is_new_scale_factor())
|
||||
rescale(evt.rect);
|
||||
});
|
||||
#endif // wxVERSION_EQUAL_OR_GREATER_THAN
|
||||
#endif // no __WXOSX__
|
||||
|
||||
this->Bind(wxEVT_MOVE_START, [this](wxMoveEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
|
||||
// Suppress application rescaling, when a MainFrame moving is not ended
|
||||
m_can_rescale = false;
|
||||
});
|
||||
|
||||
this->Bind(wxEVT_MOVE_END, [this](wxMoveEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
|
||||
m_can_rescale = is_new_scale_factor();
|
||||
|
||||
// If scale factor is different after moving of MainFrame ...
|
||||
if (m_can_rescale)
|
||||
// ... rescale application
|
||||
rescale(event.GetRect());
|
||||
else
|
||||
// set value to _true_ in purpose of possibility of a display dpi changing from System Settings
|
||||
m_can_rescale = true;
|
||||
});
|
||||
|
||||
this->Bind(wxEVT_SYS_COLOUR_CHANGED, [this](wxSysColourChangedEvent& event)
|
||||
{
|
||||
event.Skip();
|
||||
on_sys_color_changed();
|
||||
});
|
||||
}
|
||||
|
||||
virtual ~DPIAware() {}
|
||||
|
||||
float scale_factor() const { return m_scale_factor; }
|
||||
float prev_scale_factor() const { return m_prev_scale_factor; }
|
||||
|
||||
int em_unit() const { return m_em_unit; }
|
||||
// int font_size() const { return m_font_size; }
|
||||
const wxFont& normal_font() const { return m_normal_font; }
|
||||
void enable_force_rescale() { m_force_rescale = true; }
|
||||
|
||||
#ifdef _WIN32
|
||||
void force_color_changed()
|
||||
{
|
||||
update_dark_ui(this);
|
||||
on_sys_color_changed();
|
||||
}
|
||||
#endif
|
||||
|
||||
protected:
|
||||
virtual void on_dpi_changed(const wxRect &suggested_rect) = 0;
|
||||
virtual void on_sys_color_changed() {};
|
||||
|
||||
private:
|
||||
float m_scale_factor;
|
||||
int m_em_unit;
|
||||
// int m_font_size;
|
||||
|
||||
wxFont m_normal_font;
|
||||
float m_prev_scale_factor;
|
||||
bool m_can_rescale{ true };
|
||||
bool m_force_rescale{ false };
|
||||
|
||||
int m_new_font_point_size;
|
||||
|
||||
// void recalc_font()
|
||||
// {
|
||||
// wxClientDC dc(this);
|
||||
// const auto metrics = dc.GetFontMetrics();
|
||||
// m_font_size = metrics.height;
|
||||
// m_em_unit = metrics.averageWidth;
|
||||
// }
|
||||
|
||||
// check if new scale is differ from previous
|
||||
bool is_new_scale_factor() const { return fabs(m_scale_factor - m_prev_scale_factor) > 0.001; }
|
||||
|
||||
// function for a font scaling of the window
|
||||
void scale_win_font(wxWindow *window, const int font_point_size)
|
||||
{
|
||||
wxFont new_font(window->GetFont());
|
||||
new_font.SetPointSize(font_point_size);
|
||||
window->SetFont(new_font);
|
||||
}
|
||||
|
||||
// recursive function for scaling fonts for all controls in Window
|
||||
void scale_controls_fonts(wxWindow *window, const int font_point_size)
|
||||
{
|
||||
auto children = window->GetChildren();
|
||||
|
||||
for (auto child : children) {
|
||||
scale_controls_fonts(child, font_point_size);
|
||||
scale_win_font(child, font_point_size);
|
||||
}
|
||||
|
||||
window->Layout();
|
||||
}
|
||||
|
||||
void rescale(const wxRect &suggested_rect)
|
||||
{
|
||||
this->Freeze();
|
||||
|
||||
m_force_rescale = false;
|
||||
#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3)
|
||||
// rescale fonts of all controls
|
||||
scale_controls_fonts(this, m_new_font_point_size);
|
||||
// rescale current window font
|
||||
scale_win_font(this, m_new_font_point_size);
|
||||
#endif // wxVERSION_EQUAL_OR_GREATER_THAN
|
||||
|
||||
// set normal application font as a current window font
|
||||
m_normal_font = this->GetFont();
|
||||
|
||||
// update em_unit value for new window font
|
||||
m_em_unit = std::max<int>(10, 10.0f * m_scale_factor);
|
||||
|
||||
// rescale missed controls sizes and images
|
||||
on_dpi_changed(suggested_rect);
|
||||
|
||||
this->Layout();
|
||||
this->Thaw();
|
||||
|
||||
// reset previous scale factor from current scale factor value
|
||||
m_prev_scale_factor = m_scale_factor;
|
||||
}
|
||||
|
||||
#if 0 //#ifdef _WIN32 // #ysDarkMSW - Allow it when we deside to support the sustem colors for application
|
||||
bool HandleSettingChange(WXWPARAM wParam, WXLPARAM lParam) override
|
||||
{
|
||||
update_dark_ui(this);
|
||||
on_sys_color_changed();
|
||||
|
||||
// let the system handle it
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
typedef DPIAware<wxFrame> DPIFrame;
|
||||
typedef DPIAware<wxDialog> DPIDialog;
|
||||
|
||||
|
||||
class EventGuard
|
||||
{
|
||||
// This is a RAII-style smart-ptr-like guard that will bind any event to any event handler
|
||||
// and unbind it as soon as it goes out of scope or unbind() is called.
|
||||
// This can be used to solve the annoying problem of wx events being delivered to freed objects.
|
||||
|
||||
private:
|
||||
// This is a way to type-erase both the event type as well as the handler:
|
||||
|
||||
struct EventStorageBase {
|
||||
virtual ~EventStorageBase() {}
|
||||
};
|
||||
|
||||
template<class EvTag, class Fun>
|
||||
struct EventStorageFun : EventStorageBase {
|
||||
wxEvtHandler *emitter;
|
||||
EvTag tag;
|
||||
Fun fun;
|
||||
|
||||
EventStorageFun(wxEvtHandler *emitter, const EvTag &tag, Fun fun)
|
||||
: emitter(emitter)
|
||||
, tag(tag)
|
||||
, fun(std::move(fun))
|
||||
{
|
||||
emitter->Bind(this->tag, this->fun);
|
||||
}
|
||||
|
||||
virtual ~EventStorageFun() { emitter->Unbind(tag, fun); }
|
||||
};
|
||||
|
||||
template<typename EvTag, typename Class, typename EvArg, typename EvHandler>
|
||||
struct EventStorageMethod : EventStorageBase {
|
||||
typedef void(Class::* MethodPtr)(EvArg &);
|
||||
|
||||
wxEvtHandler *emitter;
|
||||
EvTag tag;
|
||||
MethodPtr method;
|
||||
EvHandler *handler;
|
||||
|
||||
EventStorageMethod(wxEvtHandler *emitter, const EvTag &tag, MethodPtr method, EvHandler *handler)
|
||||
: emitter(emitter)
|
||||
, tag(tag)
|
||||
, method(method)
|
||||
, handler(handler)
|
||||
{
|
||||
emitter->Bind(tag, method, handler);
|
||||
}
|
||||
|
||||
virtual ~EventStorageMethod() { emitter->Unbind(tag, method, handler); }
|
||||
};
|
||||
|
||||
std::unique_ptr<EventStorageBase> event_storage;
|
||||
public:
|
||||
EventGuard() {}
|
||||
EventGuard(const EventGuard&) = delete;
|
||||
EventGuard(EventGuard &&other) : event_storage(std::move(other.event_storage)) {}
|
||||
|
||||
template<class EvTag, class Fun>
|
||||
EventGuard(wxEvtHandler *emitter, const EvTag &tag, Fun fun)
|
||||
:event_storage(new EventStorageFun<EvTag, Fun>(emitter, tag, std::move(fun)))
|
||||
{}
|
||||
|
||||
template<typename EvTag, typename Class, typename EvArg, typename EvHandler>
|
||||
EventGuard(wxEvtHandler *emitter, const EvTag &tag, void(Class::* method)(EvArg &), EvHandler *handler)
|
||||
:event_storage(new EventStorageMethod<EvTag, Class, EvArg, EvHandler>(emitter, tag, method, handler))
|
||||
{}
|
||||
|
||||
EventGuard& operator=(const EventGuard&) = delete;
|
||||
EventGuard& operator=(EventGuard &&other)
|
||||
{
|
||||
event_storage = std::move(other.event_storage);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void unbind() { event_storage.reset(nullptr); }
|
||||
explicit operator bool() const { return !!event_storage; }
|
||||
};
|
||||
|
||||
|
||||
class CheckboxFileDialog : public wxFileDialog
|
||||
{
|
||||
public:
|
||||
CheckboxFileDialog(wxWindow *parent,
|
||||
const wxString &checkbox_label,
|
||||
bool checkbox_value,
|
||||
const wxString &message = wxFileSelectorPromptStr,
|
||||
const wxString &default_dir = wxEmptyString,
|
||||
const wxString &default_file = wxEmptyString,
|
||||
const wxString &wildcard = wxFileSelectorDefaultWildcardStr,
|
||||
long style = wxFD_DEFAULT_STYLE,
|
||||
const wxPoint &pos = wxDefaultPosition,
|
||||
const wxSize &size = wxDefaultSize,
|
||||
const wxString &name = wxFileDialogNameStr
|
||||
);
|
||||
|
||||
bool get_checkbox_value() const;
|
||||
|
||||
private:
|
||||
struct ExtraPanel : public wxPanel
|
||||
{
|
||||
wxCheckBox *cbox;
|
||||
|
||||
ExtraPanel(wxWindow *parent);
|
||||
static wxWindow* ctor(wxWindow *parent);
|
||||
};
|
||||
|
||||
wxString checkbox_label;
|
||||
};
|
||||
|
||||
|
||||
class WindowMetrics
|
||||
{
|
||||
private:
|
||||
wxRect rect;
|
||||
bool maximized;
|
||||
|
||||
WindowMetrics() : maximized(false) {}
|
||||
public:
|
||||
static WindowMetrics from_window(wxTopLevelWindow *window);
|
||||
static boost::optional<WindowMetrics> deserialize(const std::string &str);
|
||||
|
||||
const wxRect& get_rect() const { return rect; }
|
||||
bool get_maximized() const { return maximized; }
|
||||
|
||||
void sanitize_for_display(const wxRect &screen_rect);
|
||||
std::string serialize() const;
|
||||
};
|
||||
|
||||
std::ostream& operator<<(std::ostream &os, const WindowMetrics& metrics);
|
||||
|
||||
class TaskTimer
|
||||
{
|
||||
std::chrono::milliseconds start_timer;
|
||||
std::string task_name;
|
||||
public:
|
||||
TaskTimer(std::string task_name);
|
||||
|
||||
~TaskTimer();
|
||||
};
|
||||
|
||||
class KeyAutoRepeatFilter
|
||||
{
|
||||
size_t m_count{ 0 };
|
||||
|
||||
public:
|
||||
void increase_count() { ++m_count; }
|
||||
void reset_count() { m_count = 0; }
|
||||
bool is_first() const { return m_count == 0; }
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
||||
620
src/slic3r/GUI/GalleryDialog.cpp
Normal file
620
src/slic3r/GUI/GalleryDialog.cpp
Normal file
@@ -0,0 +1,620 @@
|
||||
#include "GalleryDialog.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/statbox.h>
|
||||
#include <wx/wupdlock.h>
|
||||
#include <wx/notebook.h>
|
||||
#include <wx/listctrl.h>
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "format.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "Notebook.hpp"
|
||||
#include "3DScene.hpp"
|
||||
#include "GLCanvas3D.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/BuildVolume.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||
#include "libslic3r/Format/OBJ.hpp"
|
||||
#include "../Utils/MacDarkMode.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
#define BORDER_W 10
|
||||
#define IMG_PX_CNT 64
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
// Gallery::DropTarget
|
||||
class GalleryDropTarget : public wxFileDropTarget
|
||||
{
|
||||
public:
|
||||
GalleryDropTarget(GalleryDialog* gallery_dlg) : gallery_dlg(gallery_dlg) { this->SetDefaultAction(wxDragCopy); }
|
||||
|
||||
bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) override;
|
||||
|
||||
private:
|
||||
GalleryDialog* gallery_dlg {nullptr};
|
||||
};
|
||||
|
||||
bool GalleryDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames)
|
||||
{
|
||||
#ifdef WIN32
|
||||
// hides the system icon
|
||||
this->MSWUpdateDragImageOnLeave();
|
||||
#endif // WIN32
|
||||
return gallery_dlg ? gallery_dlg->load_files(filenames) : false;
|
||||
}
|
||||
|
||||
|
||||
GalleryDialog::GalleryDialog(wxWindow* parent) :
|
||||
DPIDialog(parent, wxID_ANY, _L("Shape Gallery"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
#endif
|
||||
SetFont(wxGetApp().normal_font());
|
||||
|
||||
wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Select shape from the gallery") + ":");
|
||||
|
||||
m_list_ctrl = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(50 * wxGetApp().em_unit(), 35 * wxGetApp().em_unit()),
|
||||
wxLC_ICON | wxSIMPLE_BORDER);
|
||||
m_list_ctrl->Bind(wxEVT_LIST_ITEM_SELECTED, &GalleryDialog::select, this);
|
||||
m_list_ctrl->Bind(wxEVT_LIST_ITEM_DESELECTED, &GalleryDialog::deselect, this);
|
||||
m_list_ctrl->Bind(wxEVT_LIST_KEY_DOWN, &GalleryDialog::key_down, this);
|
||||
m_list_ctrl->Bind(wxEVT_LIST_ITEM_RIGHT_CLICK, &GalleryDialog::show_context_menu, this);
|
||||
m_list_ctrl->Bind(wxEVT_LIST_ITEM_ACTIVATED, [this](wxListEvent& event) {
|
||||
m_selected_items.clear();
|
||||
select(event);
|
||||
this->EndModal(wxID_OK);
|
||||
});
|
||||
#ifdef _WIN32
|
||||
this->Bind(wxEVT_SIZE, [this](wxSizeEvent& event) {
|
||||
event.Skip();
|
||||
m_list_ctrl->Arrange();
|
||||
});
|
||||
#endif
|
||||
|
||||
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK | wxCLOSE);
|
||||
m_ok_btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
|
||||
m_ok_btn->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_selected_items.empty()); });
|
||||
|
||||
static_cast<wxButton*>(FindWindowById(wxID_CLOSE, this))->Bind(wxEVT_BUTTON, [this](wxCommandEvent&){ this->EndModal(wxID_CLOSE); });
|
||||
this->SetEscapeId(wxID_CLOSE);
|
||||
auto add_btn = [this, buttons]( size_t pos, int& ID, wxString title, wxString tooltip,
|
||||
void (GalleryDialog::* method)(wxEvent&),
|
||||
std::function<bool()> enable_fn = []() {return true; }) {
|
||||
ID = NewControlId();
|
||||
wxButton* btn = new wxButton(this, ID, title);
|
||||
btn->SetToolTip(tooltip);
|
||||
btn->Bind(wxEVT_UPDATE_UI, [enable_fn](wxUpdateUIEvent& evt) { evt.Enable(enable_fn()); });
|
||||
buttons->Insert(pos, btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, BORDER_W);
|
||||
this->Bind(wxEVT_BUTTON, method, this, ID);
|
||||
};
|
||||
|
||||
size_t btn_pos = 0;
|
||||
add_btn(btn_pos++, ID_BTN_ADD_CUSTOM_SHAPE, _L("Add"), _L("Add one or more custom shapes"), &GalleryDialog::add_custom_shapes);
|
||||
add_btn(btn_pos++, ID_BTN_DEL_CUSTOM_SHAPE, _L("Delete"), _L("Delete one or more custom shape. You can't delete system shapes"), &GalleryDialog::del_custom_shapes, [this](){ return can_delete(); });
|
||||
//add_btn(btn_pos++, ID_BTN_REPLACE_CUSTOM_PNG, _L("Change thumbnail"), _L("Replace PNG for custom shape. You can't raplace thimbnail for system shape"), &GalleryDialog::change_thumbnail, [this](){ return can_change_thumbnail(); });
|
||||
buttons->InsertStretchSpacer(btn_pos, 2* BORDER_W);
|
||||
|
||||
load_label_icon_list();
|
||||
|
||||
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W);
|
||||
topSizer->Add(m_list_ctrl, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W);
|
||||
topSizer->Add(buttons , 0, wxEXPAND | wxALL, BORDER_W);
|
||||
|
||||
SetSizer(topSizer);
|
||||
topSizer->SetSizeHints(this);
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
this->CenterOnScreen();
|
||||
|
||||
this->SetDropTarget(new GalleryDropTarget(this));
|
||||
}
|
||||
|
||||
GalleryDialog::~GalleryDialog()
|
||||
{
|
||||
}
|
||||
|
||||
int GalleryDialog::show(bool show_from_menu)
|
||||
{
|
||||
m_ok_btn->SetLabel( show_from_menu ? _L("Add to bed") : _L("OK"));
|
||||
m_ok_btn->SetToolTip(show_from_menu ? _L("Add selected shape(s) to the bed") : "");
|
||||
|
||||
return this->ShowModal();
|
||||
}
|
||||
|
||||
bool GalleryDialog::can_delete()
|
||||
{
|
||||
if (m_selected_items.empty())
|
||||
return false;
|
||||
for (const Item& item : m_selected_items)
|
||||
if (item.is_system)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GalleryDialog::can_change_thumbnail()
|
||||
{
|
||||
return (m_selected_items.size() == 1 && !m_selected_items[0].is_system);
|
||||
}
|
||||
|
||||
void GalleryDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
{
|
||||
update();
|
||||
|
||||
const int& em = em_unit();
|
||||
msw_buttons_rescale(this, em, { ID_BTN_ADD_CUSTOM_SHAPE, ID_BTN_DEL_CUSTOM_SHAPE, ID_BTN_REPLACE_CUSTOM_PNG, wxID_OK, wxID_CLOSE });
|
||||
|
||||
wxSize size = wxSize(50 * em, 35 * em);
|
||||
m_list_ctrl->SetMinSize(size);
|
||||
m_list_ctrl->SetSize(size);
|
||||
|
||||
Fit();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
static void add_lock(wxImage& image, wxWindow* parent_win)
|
||||
{
|
||||
wxBitmapBundle* bmp_bndl = get_bmp_bundle("lock", 22);
|
||||
#ifdef __APPLE__
|
||||
wxBitmap bmp = bmp_bndl->GetBitmap(bmp_bndl->GetDefaultSize() * mac_max_scaling_factor());
|
||||
#else
|
||||
wxBitmap bmp = bmp_bndl->GetBitmapFor(parent_win);
|
||||
#endif
|
||||
|
||||
wxImage lock_image = bmp.ConvertToImage();
|
||||
if (!lock_image.IsOk() || lock_image.GetWidth() == 0 || lock_image.GetHeight() == 0)
|
||||
return;
|
||||
|
||||
auto lock_px_data = (uint8_t*)lock_image.GetData();
|
||||
auto lock_a_data = (uint8_t*)lock_image.GetAlpha();
|
||||
int lock_width = lock_image.GetWidth();
|
||||
int lock_height = lock_image.GetHeight();
|
||||
|
||||
auto px_data = (uint8_t*)image.GetData();
|
||||
auto a_data = (uint8_t*)image.GetAlpha();
|
||||
|
||||
int width = image.GetWidth();
|
||||
int height = image.GetHeight();
|
||||
|
||||
size_t beg_x = width - lock_width;
|
||||
size_t beg_y = height - lock_height;
|
||||
for (size_t x = 0; x < (size_t)lock_width; ++x) {
|
||||
for (size_t y = 0; y < (size_t)lock_height; ++y) {
|
||||
const size_t lock_idx = (x + y * lock_width);
|
||||
if (lock_a_data && lock_a_data[lock_idx] == 0)
|
||||
continue;
|
||||
|
||||
const size_t idx = (beg_x + x + (beg_y + y) * width);
|
||||
if (a_data)
|
||||
a_data[idx] = lock_a_data[lock_idx];
|
||||
|
||||
const size_t idx_rgb = (beg_x + x + (beg_y + y) * width) * 3;
|
||||
const size_t lock_idx_rgb = (x + y * lock_width) * 3;
|
||||
px_data[idx_rgb] = lock_px_data[lock_idx_rgb];
|
||||
px_data[idx_rgb + 1] = lock_px_data[lock_idx_rgb + 1];
|
||||
px_data[idx_rgb + 2] = lock_px_data[lock_idx_rgb + 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void add_default_image(wxImageList* img_list, bool is_system, wxWindow* parent_win)
|
||||
{
|
||||
wxBitmapBundle* bmp_bndl = get_bmp_bundle("cog", IMG_PX_CNT);
|
||||
#ifdef __APPLE__
|
||||
wxBitmap bmp = bmp_bndl->GetBitmap(bmp_bndl->GetDefaultSize() * mac_max_scaling_factor());
|
||||
#else
|
||||
wxBitmap bmp = bmp_bndl->GetBitmapFor(parent_win);
|
||||
#endif
|
||||
|
||||
bmp = bmp.ConvertToDisabled();
|
||||
if (is_system) {
|
||||
wxImage image = bmp.ConvertToImage();
|
||||
if (image.IsOk() && image.GetWidth() != 0 && image.GetHeight() != 0) {
|
||||
add_lock(image, parent_win);
|
||||
#ifdef __APPLE__
|
||||
bmp = wxBitmap(std::move(image), -1, mac_max_scaling_factor());
|
||||
#else
|
||||
bmp = wxBitmap(std::move(image));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
img_list->Add(bmp);
|
||||
};
|
||||
|
||||
static fs::path get_dir(bool sys_dir)
|
||||
{
|
||||
return fs::absolute(fs::path(sys_dir ? sys_shapes_dir() : custom_shapes_dir())).make_preferred();
|
||||
}
|
||||
|
||||
static std::string get_dir_path(bool sys_dir)
|
||||
{
|
||||
fs::path dir = get_dir(sys_dir);
|
||||
#ifdef __WXMSW__
|
||||
return dir.string() + "\\";
|
||||
#else
|
||||
return dir.string() + "/";
|
||||
#endif
|
||||
}
|
||||
|
||||
static void generate_thumbnail_from_model(const std::string& filename)
|
||||
{
|
||||
if (!boost::algorithm::iends_with(filename, ".stl") &&
|
||||
!boost::algorithm::iends_with(filename, ".obj")) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Found invalid file type in generate_thumbnail_from_model() [" << filename << "]";
|
||||
return;
|
||||
}
|
||||
|
||||
Model model;
|
||||
try {
|
||||
model = Model::read_from_file(filename);
|
||||
}
|
||||
catch (std::exception&) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Error loading model from " << filename << " in generate_thumbnail_from_model()";
|
||||
return;
|
||||
}
|
||||
|
||||
assert(model.objects.size() == 1);
|
||||
assert(model.objects[0]->volumes.size() == 1);
|
||||
assert(model.objects[0]->instances.size() == 1);
|
||||
|
||||
model.objects[0]->center_around_origin(false);
|
||||
model.objects[0]->ensure_on_bed(false);
|
||||
|
||||
model.center_instances_around_point(to_2d(wxGetApp().plater()->build_volume().bounding_volume().center()));
|
||||
|
||||
GLVolumeCollection volumes;
|
||||
volumes.volumes.push_back(new GLVolume());
|
||||
GLVolume* volume = volumes.volumes.back();
|
||||
volume->model.init_from(model.mesh());
|
||||
volume->set_instance_transformation(model.objects[0]->instances[0]->get_transformation());
|
||||
volume->set_volume_transformation(model.objects[0]->volumes[0]->get_transformation());
|
||||
|
||||
ThumbnailData thumbnail_data;
|
||||
const ThumbnailsParams thumbnail_params = { {}, false, false, false, true };
|
||||
wxGetApp().plater()->canvas3D()->render_thumbnail(thumbnail_data, 256, 256, thumbnail_params, volumes, Camera::EType::Perspective);
|
||||
|
||||
if (thumbnail_data.width == 0 || thumbnail_data.height == 0)
|
||||
return;
|
||||
|
||||
wxImage image(thumbnail_data.width, thumbnail_data.height);
|
||||
image.InitAlpha();
|
||||
|
||||
for (unsigned int r = 0; r < thumbnail_data.height; ++r) {
|
||||
unsigned int rr = (thumbnail_data.height - 1 - r) * thumbnail_data.width;
|
||||
for (unsigned int c = 0; c < thumbnail_data.width; ++c) {
|
||||
unsigned char* px = (unsigned char*)thumbnail_data.pixels.data() + 4 * (rr + c);
|
||||
image.SetRGB((int)c, (int)r, px[0], px[1], px[2]);
|
||||
image.SetAlpha((int)c, (int)r, px[3]);
|
||||
}
|
||||
}
|
||||
|
||||
fs::path out_path = fs::path(filename);
|
||||
out_path.replace_extension("png");
|
||||
image.SaveFile(from_u8(out_path.string()), wxBITMAP_TYPE_PNG);
|
||||
}
|
||||
|
||||
void GalleryDialog::load_label_icon_list()
|
||||
{
|
||||
// load names from files
|
||||
auto add_files_from_gallery = [](std::vector<Item>& items, bool is_sys_dir, std::string& dir_path)
|
||||
{
|
||||
fs::path dir = get_dir(is_sys_dir);
|
||||
if (!fs::exists(dir))
|
||||
return;
|
||||
|
||||
dir_path = get_dir_path(is_sys_dir);
|
||||
|
||||
std::vector<std::string> sorted_names;
|
||||
for (auto& dir_entry : fs::directory_iterator(dir)) {
|
||||
TriangleMesh mesh;
|
||||
if ((is_gallery_file(dir_entry, ".stl") && mesh.ReadSTLFile(dir_entry.path().string().c_str())) ||
|
||||
(is_gallery_file(dir_entry, ".obj") && load_obj(dir_entry.path().string().c_str(), &mesh) ) )
|
||||
sorted_names.push_back(dir_entry.path().filename().string());
|
||||
}
|
||||
|
||||
// sort the filename case insensitive
|
||||
std::sort(sorted_names.begin(), sorted_names.end(), [](const std::string& a, const std::string& b)
|
||||
{ return boost::algorithm::to_lower_copy(a) < boost::algorithm::to_lower_copy(b); });
|
||||
|
||||
for (const std::string& name : sorted_names)
|
||||
items.push_back(Item{ name, is_sys_dir });
|
||||
};
|
||||
|
||||
wxBusyCursor busy;
|
||||
|
||||
std::string m_sys_dir_path, m_cust_dir_path;
|
||||
std::vector<Item> list_items;
|
||||
add_files_from_gallery(list_items, true, m_sys_dir_path);
|
||||
add_files_from_gallery(list_items, false, m_cust_dir_path);
|
||||
|
||||
// Make an image list containing large icons
|
||||
|
||||
#ifdef __APPLE__
|
||||
m_image_list = new wxImageList(IMG_PX_CNT, IMG_PX_CNT);
|
||||
int px_cnt = IMG_PX_CNT * mac_max_scaling_factor();
|
||||
#else
|
||||
int px_cnt = (int)(em_unit() * IMG_PX_CNT * 0.1f + 0.5f);
|
||||
m_image_list = new wxImageList(px_cnt, px_cnt);
|
||||
#endif
|
||||
|
||||
for (const auto& item : list_items) {
|
||||
fs::path model_path = fs::path((item.is_system ? m_sys_dir_path : m_cust_dir_path) + item.name);
|
||||
std::string model_name = model_path.string();
|
||||
model_path.replace_extension("png");
|
||||
std::string img_name = model_path.string();
|
||||
|
||||
#if 0 // use "1" just in DEBUG mode to the generation of the thumbnails for the sistem shapes
|
||||
bool can_generate_thumbnail = true;
|
||||
#else
|
||||
bool can_generate_thumbnail = !item.is_system;
|
||||
#endif //DEBUG
|
||||
if (!fs::exists(img_name)) {
|
||||
if (can_generate_thumbnail)
|
||||
generate_thumbnail_from_model(model_name);
|
||||
else {
|
||||
add_default_image(m_image_list, item.is_system, this);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
wxImage image;
|
||||
if (!image.CanRead(from_u8(img_name)) ||
|
||||
!image.LoadFile(from_u8(img_name), wxBITMAP_TYPE_PNG) ||
|
||||
image.GetWidth() == 0 || image.GetHeight() == 0) {
|
||||
add_default_image(m_image_list, item.is_system, this);
|
||||
continue;
|
||||
}
|
||||
image.Rescale(px_cnt, px_cnt, wxIMAGE_QUALITY_BILINEAR);
|
||||
|
||||
if (item.is_system)
|
||||
add_lock(image, this);
|
||||
#ifdef __APPLE__
|
||||
wxBitmap bmp = wxBitmap(std::move(image), -1, mac_max_scaling_factor());
|
||||
#else
|
||||
wxBitmap bmp = wxBitmap(std::move(image));
|
||||
#endif
|
||||
m_image_list->Add(bmp);
|
||||
}
|
||||
|
||||
m_list_ctrl->SetImageList(m_image_list, wxIMAGE_LIST_NORMAL);
|
||||
|
||||
int img_cnt = m_image_list->GetImageCount();
|
||||
for (int i = 0; i < img_cnt; i++) {
|
||||
m_list_ctrl->InsertItem(i, from_u8(list_items[i].name), i);
|
||||
if (list_items[i].is_system)
|
||||
m_list_ctrl->SetItemFont(i, wxGetApp().bold_font());
|
||||
}
|
||||
}
|
||||
|
||||
void GalleryDialog::get_input_files(wxArrayString& input_files)
|
||||
{
|
||||
for (const Item& item : m_selected_items)
|
||||
input_files.Add(from_u8(get_dir_path(item.is_system) + item.name));
|
||||
}
|
||||
|
||||
void GalleryDialog::add_custom_shapes(wxEvent& event)
|
||||
{
|
||||
wxArrayString input_files;
|
||||
wxFileDialog dialog(this, _L("Choose one or more files (STL, OBJ):"),
|
||||
from_u8(wxGetApp().app_config->get_last_dir()), "",
|
||||
file_wildcards(FT_GALLERY), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
|
||||
|
||||
if (dialog.ShowModal() == wxID_OK)
|
||||
dialog.GetPaths(input_files);
|
||||
|
||||
if (input_files.IsEmpty())
|
||||
return;
|
||||
|
||||
load_files(input_files);
|
||||
}
|
||||
|
||||
void GalleryDialog::del_custom_shapes()
|
||||
{
|
||||
auto custom_dir = get_dir(false);
|
||||
|
||||
auto remove_file = [custom_dir](const std::string& name) {
|
||||
if (!fs::exists(custom_dir / name))
|
||||
return;
|
||||
try {
|
||||
fs::remove(custom_dir / name);
|
||||
}
|
||||
catch (fs::filesystem_error const& e) {
|
||||
std::cerr << e.what() << '\n';
|
||||
}
|
||||
};
|
||||
|
||||
for (const Item& item : m_selected_items) {
|
||||
remove_file(item.name);
|
||||
fs::path path = fs::path(item.name);
|
||||
path.replace_extension("png");
|
||||
remove_file(path.string());
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
static void show_warning(const wxString& title, const std::string& error_file_type)
|
||||
{
|
||||
const wxString msg_text = format_wxstr(_L("It looks like selected %1%-file has an error or is destructed.\n"
|
||||
"We can't load this file"), error_file_type);
|
||||
MessageDialog dialog(nullptr, msg_text, title, wxICON_WARNING | wxOK);
|
||||
dialog.ShowModal();
|
||||
}
|
||||
|
||||
void GalleryDialog::change_thumbnail()
|
||||
{
|
||||
if (m_selected_items.size() != 1 || m_selected_items[0].is_system)
|
||||
return;
|
||||
|
||||
wxFileDialog dialog(this, _L("Choose one PNG file:"),
|
||||
from_u8(wxGetApp().app_config->get_last_dir()), "",
|
||||
"PNG files (*.png)|*.png;*.PNG", wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dialog.ShowModal() != wxID_OK)
|
||||
return;
|
||||
|
||||
wxArrayString input_files;
|
||||
dialog.GetPaths(input_files);
|
||||
|
||||
if (input_files.IsEmpty())
|
||||
return;
|
||||
|
||||
if (wxImage image; !image.CanRead(input_files.Item(0))) {
|
||||
show_warning(_L("Replacing of the PNG"), "PNG");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
fs::path png_path = fs::path(get_dir(false) / m_selected_items[0].name);
|
||||
png_path.replace_extension("png");
|
||||
|
||||
fs::path current = fs::path(into_u8(input_files.Item(0)));
|
||||
fs::copy_file(current, png_path, fs::copy_option::overwrite_if_exists);
|
||||
}
|
||||
catch (fs::filesystem_error const& e) {
|
||||
std::cerr << e.what() << '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void GalleryDialog::select(wxListEvent& event)
|
||||
{
|
||||
int idx = event.GetIndex();
|
||||
Item item { into_u8(m_list_ctrl->GetItemText(idx)), m_list_ctrl->GetItemFont(idx).GetWeight() == wxFONTWEIGHT_BOLD };
|
||||
|
||||
m_selected_items.push_back(item);
|
||||
}
|
||||
|
||||
void GalleryDialog::deselect(wxListEvent& event)
|
||||
{
|
||||
if (m_list_ctrl->GetSelectedItemCount() == 0) {
|
||||
m_selected_items.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string name = into_u8(m_list_ctrl->GetItemText(event.GetIndex()));
|
||||
m_selected_items.erase(std::remove_if(m_selected_items.begin(), m_selected_items.end(), [name](Item item) { return item.name == name; }));
|
||||
}
|
||||
|
||||
void GalleryDialog::show_context_menu(wxListEvent& event)
|
||||
{
|
||||
wxMenu* menu = new wxMenu();
|
||||
if (can_delete())
|
||||
append_menu_item(menu, wxID_ANY, _L("Delete"), "", [this](wxCommandEvent&) { del_custom_shapes(); });
|
||||
if (can_change_thumbnail())
|
||||
append_menu_item(menu, wxID_ANY, _L("Change thumbnail"), "", [this](wxCommandEvent&) { change_thumbnail(); });
|
||||
|
||||
this->PopupMenu(menu);
|
||||
}
|
||||
|
||||
void GalleryDialog::key_down(wxListEvent& event)
|
||||
{
|
||||
if (can_delete() && (event.GetKeyCode() == WXK_DELETE || event.GetKeyCode() == WXK_BACK))
|
||||
del_custom_shapes();
|
||||
}
|
||||
|
||||
void GalleryDialog::update()
|
||||
{
|
||||
m_selected_items.clear();
|
||||
m_image_list->RemoveAll();
|
||||
m_list_ctrl->ClearAll();
|
||||
load_label_icon_list();
|
||||
}
|
||||
|
||||
bool GalleryDialog::load_files(const wxArrayString& input_files)
|
||||
{
|
||||
auto dest_dir = get_dir(false);
|
||||
|
||||
try {
|
||||
if (!fs::exists(dest_dir))
|
||||
if (!fs::create_directory(dest_dir)) {
|
||||
std::cerr << "Unable to create destination directory" << dest_dir.string() << '\n' ;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (fs::filesystem_error const& e) {
|
||||
std::cerr << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Iterate through the input files
|
||||
for (size_t i = 0; i < input_files.size(); ++i) {
|
||||
std::string input_file = into_u8(input_files.Item(i));
|
||||
|
||||
TriangleMesh mesh;
|
||||
if (is_gallery_file(input_file, ".stl") && !mesh.ReadSTLFile(input_file.c_str())) {
|
||||
show_warning(format_wxstr(_L("Loading of the \"%1%\""), input_file), "STL");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_gallery_file(input_file, ".obj") && !load_obj(input_file.c_str(), &mesh)) {
|
||||
show_warning(format_wxstr(_L("Loading of the \"%1%\""), input_file), "OBJ");
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
fs::path current = fs::path(input_file);
|
||||
if (!fs::exists(dest_dir / current.filename()))
|
||||
fs::copy_file(current, dest_dir / current.filename());
|
||||
else {
|
||||
std::string filename = current.stem().string();
|
||||
|
||||
int file_idx = 0;
|
||||
for (auto& dir_entry : fs::directory_iterator(dest_dir))
|
||||
if (is_gallery_file(dir_entry, ".stl") || is_gallery_file(dir_entry, ".obj")) {
|
||||
std::string name = dir_entry.path().stem().string();
|
||||
if (filename == name) {
|
||||
if (file_idx == 0)
|
||||
file_idx++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name.find(filename) != 0 ||
|
||||
name[filename.size()] != ' ' || name[filename.size()+1] != '(' || name[name.size()-1] != ')')
|
||||
continue;
|
||||
std::string idx_str = name.substr(filename.size() + 2, name.size() - filename.size() - 3);
|
||||
if (int cur_idx = atoi(idx_str.c_str()); file_idx <= cur_idx)
|
||||
file_idx = cur_idx+1;
|
||||
}
|
||||
if (file_idx > 0) {
|
||||
filename += " (" + std::to_string(file_idx) + ")." + (is_gallery_file(input_file, ".stl") ? "stl" : "obj");
|
||||
fs::copy_file(current, dest_dir / filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (fs::filesystem_error const& e) {
|
||||
std::cerr << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
68
src/slic3r/GUI/GalleryDialog.hpp
Normal file
68
src/slic3r/GUI/GalleryDialog.hpp
Normal file
@@ -0,0 +1,68 @@
|
||||
#ifndef slic3r_GalleryDialog_hpp_
|
||||
#define slic3r_GalleryDialog_hpp_
|
||||
|
||||
#include "GUI_Utils.hpp"
|
||||
|
||||
class wxListCtrl;
|
||||
class wxImageList;
|
||||
class wxListEvent;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace GUI {
|
||||
|
||||
|
||||
//------------------------------------------
|
||||
// GalleryDialog
|
||||
//------------------------------------------
|
||||
|
||||
class GalleryDialog : public DPIDialog
|
||||
{
|
||||
wxListCtrl* m_list_ctrl { nullptr };
|
||||
wxImageList* m_image_list { nullptr };
|
||||
wxButton* m_ok_btn { nullptr };
|
||||
|
||||
struct Item {
|
||||
std::string name;
|
||||
bool is_system;
|
||||
};
|
||||
std::vector<Item> m_selected_items;
|
||||
|
||||
int ID_BTN_ADD_CUSTOM_SHAPE;
|
||||
int ID_BTN_DEL_CUSTOM_SHAPE;
|
||||
int ID_BTN_REPLACE_CUSTOM_PNG;
|
||||
|
||||
void load_label_icon_list();
|
||||
void add_custom_shapes(wxEvent& event);
|
||||
void del_custom_shapes();
|
||||
void del_custom_shapes(wxEvent& event) { del_custom_shapes(); }
|
||||
void change_thumbnail();
|
||||
void change_thumbnail(wxEvent& event) { change_thumbnail(); }
|
||||
void select(wxListEvent& event);
|
||||
void deselect(wxListEvent& event);
|
||||
void show_context_menu(wxListEvent& event);
|
||||
void key_down(wxListEvent& event);
|
||||
|
||||
bool can_delete();
|
||||
bool can_change_thumbnail();
|
||||
|
||||
void update();
|
||||
|
||||
public:
|
||||
GalleryDialog(wxWindow* parent);
|
||||
~GalleryDialog();
|
||||
|
||||
int show(bool show_from_menu = false);
|
||||
void get_input_files(wxArrayString& input_files);
|
||||
bool load_files(const wxArrayString& input_files);
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
void on_sys_color_changed() override {};
|
||||
};
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif //slic3r_GalleryDialog_hpp_
|
||||
345
src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
Normal file
345
src/slic3r/GUI/Gizmos/GLGizmoBase.cpp
Normal file
@@ -0,0 +1,345 @@
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectManipulation.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
|
||||
// TODO: Display tooltips quicker on Linux
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
const float GLGizmoBase::Grabber::SizeFactor = 0.05f;
|
||||
const float GLGizmoBase::Grabber::MinHalfSize = 1.5f;
|
||||
const float GLGizmoBase::Grabber::DraggingScaleFactor = 1.25f;
|
||||
|
||||
PickingModel GLGizmoBase::Grabber::s_cube;
|
||||
PickingModel GLGizmoBase::Grabber::s_cone;
|
||||
|
||||
GLGizmoBase::Grabber::~Grabber()
|
||||
{
|
||||
if (s_cube.model.is_initialized())
|
||||
s_cube.model.reset();
|
||||
|
||||
if (s_cone.model.is_initialized())
|
||||
s_cone.model.reset();
|
||||
}
|
||||
|
||||
float GLGizmoBase::Grabber::get_half_size(float size) const
|
||||
{
|
||||
return std::max(size * SizeFactor, MinHalfSize);
|
||||
}
|
||||
|
||||
float GLGizmoBase::Grabber::get_dragging_half_size(float size) const
|
||||
{
|
||||
return get_half_size(size) * DraggingScaleFactor;
|
||||
}
|
||||
|
||||
void GLGizmoBase::Grabber::register_raycasters_for_picking(int id)
|
||||
{
|
||||
picking_id = id;
|
||||
// registration will happen on next call to render()
|
||||
}
|
||||
|
||||
void GLGizmoBase::Grabber::unregister_raycasters_for_picking()
|
||||
{
|
||||
wxGetApp().plater()->canvas3D()->remove_raycasters_for_picking(SceneRaycaster::EType::Gizmo, picking_id);
|
||||
picking_id = -1;
|
||||
raycasters = { nullptr };
|
||||
}
|
||||
|
||||
void GLGizmoBase::Grabber::render(float size, const ColorRGBA& render_color)
|
||||
{
|
||||
GLShaderProgram* shader = wxGetApp().get_current_shader();
|
||||
if (shader == nullptr)
|
||||
return;
|
||||
|
||||
if (!s_cube.model.is_initialized()) {
|
||||
// This cannot be done in constructor, OpenGL is not yet
|
||||
// initialized at that point (on Linux at least).
|
||||
indexed_triangle_set its = its_make_cube(1.0, 1.0, 1.0);
|
||||
its_translate(its, -0.5f * Vec3f::Ones());
|
||||
s_cube.model.init_from(its);
|
||||
s_cube.mesh_raycaster = std::make_unique<MeshRaycaster>(std::make_shared<const TriangleMesh>(std::move(its)));
|
||||
}
|
||||
|
||||
if (!s_cone.model.is_initialized()) {
|
||||
indexed_triangle_set its = its_make_cone(0.375, 1.5, double(PI) / 18.0);
|
||||
s_cone.model.init_from(its);
|
||||
s_cone.mesh_raycaster = std::make_unique<MeshRaycaster>(std::make_shared<const TriangleMesh>(std::move(its)));
|
||||
}
|
||||
|
||||
const float half_size = dragging ? get_dragging_half_size(size) : get_half_size(size);
|
||||
|
||||
s_cube.model.set_color(render_color);
|
||||
s_cone.model.set_color(render_color);
|
||||
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
||||
const Transform3d& view_matrix = camera.get_view_matrix();
|
||||
const Matrix3d view_matrix_no_offset = view_matrix.matrix().block(0, 0, 3, 3);
|
||||
std::vector<Transform3d> elements_matrices(GRABBER_ELEMENTS_MAX_COUNT, Transform3d::Identity());
|
||||
elements_matrices[0] = matrix * Geometry::translation_transform(center) * Geometry::rotation_transform(angles) * Geometry::scale_transform(2.0 * half_size);
|
||||
Transform3d view_model_matrix = view_matrix * elements_matrices[0];
|
||||
|
||||
shader->set_uniform("view_model_matrix", view_model_matrix);
|
||||
Matrix3d view_normal_matrix = view_matrix_no_offset * elements_matrices[0].matrix().block(0, 0, 3, 3).inverse().transpose();
|
||||
shader->set_uniform("view_normal_matrix", view_normal_matrix);
|
||||
s_cube.model.render();
|
||||
|
||||
auto render_extension = [&view_matrix, &view_matrix_no_offset, shader](const Transform3d& matrix) {
|
||||
const Transform3d view_model_matrix = view_matrix * matrix;
|
||||
shader->set_uniform("view_model_matrix", view_model_matrix);
|
||||
const Matrix3d view_normal_matrix = view_matrix_no_offset * matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
|
||||
shader->set_uniform("view_normal_matrix", view_normal_matrix);
|
||||
s_cone.model.render();
|
||||
};
|
||||
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosX)) != 0) {
|
||||
elements_matrices[1] = elements_matrices[0] * Geometry::translation_transform(Vec3d::UnitX()) * Geometry::rotation_transform({ 0.0, 0.5 * double(PI), 0.0 });
|
||||
render_extension(elements_matrices[1]);
|
||||
}
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegX)) != 0) {
|
||||
elements_matrices[2] = elements_matrices[0] * Geometry::translation_transform(-Vec3d::UnitX()) * Geometry::rotation_transform({ 0.0, -0.5 * double(PI), 0.0 });
|
||||
render_extension(elements_matrices[2]);
|
||||
}
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosY)) != 0) {
|
||||
elements_matrices[3] = elements_matrices[0] * Geometry::translation_transform(Vec3d::UnitY()) * Geometry::rotation_transform({ -0.5 * double(PI), 0.0, 0.0 });
|
||||
render_extension(elements_matrices[3]);
|
||||
}
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegY)) != 0) {
|
||||
elements_matrices[4] = elements_matrices[0] * Geometry::translation_transform(-Vec3d::UnitY()) * Geometry::rotation_transform({ 0.5 * double(PI), 0.0, 0.0 });
|
||||
render_extension(elements_matrices[4]);
|
||||
}
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosZ)) != 0) {
|
||||
elements_matrices[5] = elements_matrices[0] * Geometry::translation_transform(Vec3d::UnitZ());
|
||||
render_extension(elements_matrices[5]);
|
||||
}
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegZ)) != 0) {
|
||||
elements_matrices[6] = elements_matrices[0] * Geometry::translation_transform(-Vec3d::UnitZ()) * Geometry::rotation_transform({ double(PI), 0.0, 0.0 });
|
||||
render_extension(elements_matrices[6]);
|
||||
}
|
||||
|
||||
if (raycasters[0] == nullptr) {
|
||||
GLCanvas3D& canvas = *wxGetApp().plater()->canvas3D();
|
||||
raycasters[0] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cube.mesh_raycaster, elements_matrices[0]);
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosX)) != 0)
|
||||
raycasters[1] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cone.mesh_raycaster, elements_matrices[1]);
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegX)) != 0)
|
||||
raycasters[2] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cone.mesh_raycaster, elements_matrices[2]);
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosY)) != 0)
|
||||
raycasters[3] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cone.mesh_raycaster, elements_matrices[3]);
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegY)) != 0)
|
||||
raycasters[4] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cone.mesh_raycaster, elements_matrices[4]);
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::PosZ)) != 0)
|
||||
raycasters[5] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cone.mesh_raycaster, elements_matrices[5]);
|
||||
if ((int(extensions) & int(GLGizmoBase::EGrabberExtension::NegZ)) != 0)
|
||||
raycasters[6] = canvas.add_raycaster_for_picking(SceneRaycaster::EType::Gizmo, picking_id, *s_cone.mesh_raycaster, elements_matrices[6]);
|
||||
}
|
||||
else {
|
||||
for (size_t i = 0; i < GRABBER_ELEMENTS_MAX_COUNT; ++i) {
|
||||
if (raycasters[i] != nullptr)
|
||||
raycasters[i]->set_transform(elements_matrices[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
|
||||
: m_parent(parent)
|
||||
, m_group_id(-1)
|
||||
, m_state(Off)
|
||||
, m_shortcut_key(NO_SHORTCUT_KEY_VALUE)
|
||||
, m_icon_filename(icon_filename)
|
||||
, m_sprite_id(sprite_id)
|
||||
, m_imgui(wxGetApp().imgui())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
std::string GLGizmoBase::get_action_snapshot_name() const
|
||||
{
|
||||
return _u8L("Gizmo action");
|
||||
}
|
||||
|
||||
void GLGizmoBase::set_hover_id(int id)
|
||||
{
|
||||
// do not change hover id during dragging
|
||||
assert(!m_dragging);
|
||||
|
||||
// allow empty grabbers when not using grabbers but use hover_id - flatten, rotate
|
||||
// if (!m_grabbers.empty() && id >= (int) m_grabbers.size())
|
||||
// return;
|
||||
|
||||
m_hover_id = id;
|
||||
on_set_hover_id();
|
||||
}
|
||||
|
||||
bool GLGizmoBase::update_items_state()
|
||||
{
|
||||
bool res = m_dirty;
|
||||
m_dirty = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
void GLGizmoBase::register_grabbers_for_picking()
|
||||
{
|
||||
for (size_t i = 0; i < m_grabbers.size(); ++i) {
|
||||
m_grabbers[i].register_raycasters_for_picking((m_group_id >= 0) ? m_group_id : i);
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoBase::unregister_grabbers_for_picking()
|
||||
{
|
||||
for (size_t i = 0; i < m_grabbers.size(); ++i) {
|
||||
m_grabbers[i].unregister_raycasters_for_picking();
|
||||
}
|
||||
}
|
||||
|
||||
void GLGizmoBase::render_grabbers(const BoundingBoxf3& box) const
|
||||
{
|
||||
render_grabbers((float)((box.size().x() + box.size().y() + box.size().z()) / 3.0));
|
||||
}
|
||||
|
||||
void GLGizmoBase::render_grabbers(float size) const
|
||||
{
|
||||
render_grabbers(0, m_grabbers.size() - 1, size, false);
|
||||
}
|
||||
|
||||
void GLGizmoBase::render_grabbers(size_t first, size_t last, float size, bool force_hover) const
|
||||
{
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
|
||||
if (shader == nullptr)
|
||||
return;
|
||||
shader->start_using();
|
||||
shader->set_uniform("emission_factor", 0.1f);
|
||||
glsafe(::glDisable(GL_CULL_FACE));
|
||||
for (size_t i = first; i <= last; ++i) {
|
||||
if (m_grabbers[i].enabled)
|
||||
m_grabbers[i].render(force_hover ? true : m_hover_id == (int)i, size);
|
||||
}
|
||||
glsafe(::glEnable(GL_CULL_FACE));
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
// help function to process grabbers
|
||||
// call start_dragging, stop_dragging, on_dragging
|
||||
bool GLGizmoBase::use_grabbers(const wxMouseEvent &mouse_event) {
|
||||
bool is_dragging_finished = false;
|
||||
if (mouse_event.Moving()) {
|
||||
// it should not happen but for sure
|
||||
assert(!m_dragging);
|
||||
if (m_dragging) is_dragging_finished = true;
|
||||
else return false;
|
||||
}
|
||||
|
||||
if (mouse_event.LeftDown()) {
|
||||
Selection &selection = m_parent.get_selection();
|
||||
if (!selection.is_empty() && m_hover_id != -1 /* &&
|
||||
(m_grabbers.empty() || m_hover_id < static_cast<int>(m_grabbers.size()))*/) {
|
||||
selection.setup_cache();
|
||||
|
||||
m_dragging = true;
|
||||
for (auto &grabber : m_grabbers) grabber.dragging = false;
|
||||
// if (!m_grabbers.empty() && m_hover_id < int(m_grabbers.size()))
|
||||
// m_grabbers[m_hover_id].dragging = true;
|
||||
|
||||
on_start_dragging();
|
||||
|
||||
// Let the plater know that the dragging started
|
||||
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED));
|
||||
m_parent.set_as_dirty();
|
||||
return true;
|
||||
}
|
||||
} else if (m_dragging) {
|
||||
// when mouse cursor leave window than finish actual dragging operation
|
||||
bool is_leaving = mouse_event.Leaving();
|
||||
if (mouse_event.Dragging()) {
|
||||
Point mouse_coord(mouse_event.GetX(), mouse_event.GetY());
|
||||
auto ray = m_parent.mouse_ray(mouse_coord);
|
||||
UpdateData data(ray, mouse_coord);
|
||||
|
||||
on_dragging(data);
|
||||
|
||||
wxGetApp().obj_manipul()->set_dirty();
|
||||
m_parent.set_as_dirty();
|
||||
return true;
|
||||
}
|
||||
else if (mouse_event.LeftUp() || is_leaving || is_dragging_finished) {
|
||||
do_stop_dragging(is_leaving);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GLGizmoBase::do_stop_dragging(bool perform_mouse_cleanup)
|
||||
{
|
||||
for (auto& grabber : m_grabbers) grabber.dragging = false;
|
||||
m_dragging = false;
|
||||
|
||||
// NOTE: This should be part of GLCanvas3D
|
||||
// Reset hover_id when leave window
|
||||
if (perform_mouse_cleanup) m_parent.mouse_up_cleanup();
|
||||
|
||||
on_stop_dragging();
|
||||
|
||||
// There is prediction that after draggign, data are changed
|
||||
// Data are updated twice also by canvas3D::reload_scene.
|
||||
// Should be fixed.
|
||||
m_parent.get_gizmos_manager().update_data();
|
||||
|
||||
wxGetApp().obj_manipul()->set_dirty();
|
||||
|
||||
// Let the plater know that the dragging finished, so a delayed
|
||||
// refresh of the scene with the background processing data should
|
||||
// be performed.
|
||||
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
|
||||
// updates camera target constraints
|
||||
m_parent.refresh_camera_scene_box();
|
||||
}
|
||||
|
||||
std::string GLGizmoBase::format(float value, unsigned int decimals) const
|
||||
{
|
||||
return Slic3r::string_printf("%.*f", decimals, value);
|
||||
}
|
||||
|
||||
void GLGizmoBase::set_dirty() {
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GLGizmoBase::render_input_window(float x, float y, float bottom_limit)
|
||||
{
|
||||
on_render_input_window(x, y, bottom_limit);
|
||||
if (m_first_input_window_render) {
|
||||
// imgui windows that don't have an initial size needs to be processed once to get one
|
||||
// and are not rendered in the first frame
|
||||
// so, we forces to render another frame the first time the imgui window is shown
|
||||
// https://github.com/ocornut/imgui/issues/2949
|
||||
m_parent.set_as_dirty();
|
||||
m_parent.request_extra_frame();
|
||||
m_first_input_window_render = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::string GLGizmoBase::get_name(bool include_shortcut) const
|
||||
{
|
||||
std::string out = on_get_name();
|
||||
if (!include_shortcut) return out;
|
||||
|
||||
int key = get_shortcut_key();
|
||||
assert(key==NO_SHORTCUT_KEY_VALUE || (key >= WXK_CONTROL_A && key <= WXK_CONTROL_Z));
|
||||
out += std::string(" [") + char(int('A') + key - int(WXK_CONTROL_A)) + "]";
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
249
src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
Normal file
249
src/slic3r/GUI/Gizmos/GLGizmoBase.hpp
Normal file
@@ -0,0 +1,249 @@
|
||||
#ifndef slic3r_GLGizmoBase_hpp_
|
||||
#define slic3r_GLGizmoBase_hpp_
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Color.hpp"
|
||||
|
||||
#include "slic3r/GUI/GLModel.hpp"
|
||||
#include "slic3r/GUI/MeshUtils.hpp"
|
||||
#include "slic3r/GUI/SceneRaycaster.hpp"
|
||||
|
||||
#include <cereal/archives/binary.hpp>
|
||||
|
||||
class wxWindow;
|
||||
class wxMouseEvent;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class BoundingBoxf3;
|
||||
class Linef3;
|
||||
class ModelObject;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
static const ColorRGBA DEFAULT_BASE_COLOR = { 0.625f, 0.625f, 0.625f, 1.0f };
|
||||
static const ColorRGBA DEFAULT_DRAG_COLOR = ColorRGBA::WHITE();
|
||||
//B18
|
||||
static const ColorRGBA DEFAULT_HIGHLIGHT_COLOR = ColorRGBA::WHITE();
|
||||
static const std::array<ColorRGBA, 3> AXES_COLOR = {{ ColorRGBA::X(), ColorRGBA::Y(), ColorRGBA::Z() }};
|
||||
static const ColorRGBA CONSTRAINED_COLOR = ColorRGBA::GRAY();
|
||||
|
||||
class ImGuiWrapper;
|
||||
class GLCanvas3D;
|
||||
enum class CommonGizmosDataID;
|
||||
class CommonGizmosDataPool;
|
||||
|
||||
class GLGizmoBase
|
||||
{
|
||||
public:
|
||||
// Starting value for ids to avoid clashing with ids used by GLVolumes
|
||||
// (254 is choosen to leave some space for forward compatibility)
|
||||
static const unsigned int BASE_ID = 255 * 255 * 254;
|
||||
static const unsigned int GRABBER_ELEMENTS_MAX_COUNT = 7;
|
||||
|
||||
enum class EGrabberExtension
|
||||
{
|
||||
None = 0,
|
||||
PosX = 1 << 0,
|
||||
NegX = 1 << 1,
|
||||
PosY = 1 << 2,
|
||||
NegY = 1 << 3,
|
||||
PosZ = 1 << 4,
|
||||
NegZ = 1 << 5,
|
||||
};
|
||||
|
||||
// Represents NO key(button on keyboard) value
|
||||
static const int NO_SHORTCUT_KEY_VALUE = 0;
|
||||
|
||||
protected:
|
||||
struct Grabber
|
||||
{
|
||||
static const float SizeFactor;
|
||||
static const float MinHalfSize;
|
||||
static const float DraggingScaleFactor;
|
||||
|
||||
bool enabled{ true };
|
||||
bool dragging{ false };
|
||||
Vec3d center{ Vec3d::Zero() };
|
||||
Vec3d angles{ Vec3d::Zero() };
|
||||
Transform3d matrix{ Transform3d::Identity() };
|
||||
ColorRGBA color{ ColorRGBA::WHITE() };
|
||||
EGrabberExtension extensions{ EGrabberExtension::None };
|
||||
// the picking id shared by all the elements
|
||||
int picking_id{ -1 };
|
||||
std::array<std::shared_ptr<SceneRaycasterItem>, GRABBER_ELEMENTS_MAX_COUNT> raycasters = { nullptr };
|
||||
|
||||
Grabber() = default;
|
||||
~Grabber();
|
||||
|
||||
void render(bool hover, float size) { render(size, hover ? complementary(color) : color); }
|
||||
|
||||
float get_half_size(float size) const;
|
||||
float get_dragging_half_size(float size) const;
|
||||
|
||||
void register_raycasters_for_picking(int id);
|
||||
void unregister_raycasters_for_picking();
|
||||
|
||||
private:
|
||||
void render(float size, const ColorRGBA& render_color);
|
||||
|
||||
static PickingModel s_cube;
|
||||
static PickingModel s_cone;
|
||||
};
|
||||
|
||||
public:
|
||||
enum EState
|
||||
{
|
||||
Off,
|
||||
On,
|
||||
Num_States
|
||||
};
|
||||
|
||||
struct UpdateData
|
||||
{
|
||||
const Linef3& mouse_ray;
|
||||
const Point& mouse_pos;
|
||||
|
||||
UpdateData(const Linef3& mouse_ray, const Point& mouse_pos)
|
||||
: mouse_ray(mouse_ray), mouse_pos(mouse_pos)
|
||||
{}
|
||||
};
|
||||
|
||||
protected:
|
||||
GLCanvas3D& m_parent;
|
||||
|
||||
int m_group_id; // TODO: remove only for rotate
|
||||
EState m_state;
|
||||
int m_shortcut_key;
|
||||
std::string m_icon_filename;
|
||||
unsigned int m_sprite_id;
|
||||
int m_hover_id{ -1 };
|
||||
bool m_dragging{ false };
|
||||
mutable std::vector<Grabber> m_grabbers;
|
||||
ImGuiWrapper* m_imgui;
|
||||
bool m_first_input_window_render{ true };
|
||||
CommonGizmosDataPool* m_c{ nullptr };
|
||||
|
||||
public:
|
||||
GLGizmoBase(GLCanvas3D& parent,
|
||||
const std::string& icon_filename,
|
||||
unsigned int sprite_id);
|
||||
virtual ~GLGizmoBase() = default;
|
||||
|
||||
bool init() { return on_init(); }
|
||||
|
||||
void load(cereal::BinaryInputArchive& ar) { m_state = On; on_load(ar); }
|
||||
void save(cereal::BinaryOutputArchive& ar) const { on_save(ar); }
|
||||
|
||||
std::string get_name(bool include_shortcut = true) const;
|
||||
|
||||
EState get_state() const { return m_state; }
|
||||
void set_state(EState state) { m_state = state; on_set_state(); }
|
||||
|
||||
int get_shortcut_key() const { return m_shortcut_key; }
|
||||
|
||||
const std::string& get_icon_filename() const { return m_icon_filename; }
|
||||
|
||||
bool is_activable() const { return on_is_activable(); }
|
||||
bool is_selectable() const { return on_is_selectable(); }
|
||||
CommonGizmosDataID get_requirements() const { return on_get_requirements(); }
|
||||
virtual bool wants_enter_leave_snapshots() const { return false; }
|
||||
virtual std::string get_gizmo_entering_text() const { assert(false); return ""; }
|
||||
virtual std::string get_gizmo_leaving_text() const { assert(false); return ""; }
|
||||
virtual std::string get_action_snapshot_name() const;
|
||||
void set_common_data_pool(CommonGizmosDataPool* ptr) { m_c = ptr; }
|
||||
|
||||
unsigned int get_sprite_id() const { return m_sprite_id; }
|
||||
|
||||
int get_hover_id() const { return m_hover_id; }
|
||||
void set_hover_id(int id);
|
||||
|
||||
bool is_dragging() const { return m_dragging; }
|
||||
|
||||
// returns True when Gizmo changed its state
|
||||
bool update_items_state();
|
||||
|
||||
void render() { on_render(); }
|
||||
void render_input_window(float x, float y, float bottom_limit);
|
||||
|
||||
/// <summary>
|
||||
/// Mouse tooltip text
|
||||
/// </summary>
|
||||
/// <returns>Text to be visible in mouse tooltip</returns>
|
||||
virtual std::string get_tooltip() const { return ""; }
|
||||
|
||||
/// <summary>
|
||||
/// Is called when data (Selection) is changed
|
||||
/// </summary>
|
||||
virtual void data_changed(bool is_serializing){};
|
||||
|
||||
/// <summary>
|
||||
/// Implement when want to process mouse events in gizmo
|
||||
/// Click, Right click, move, drag, ...
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Keep information about mouse click</param>
|
||||
/// <returns>Return True when use the information and don't want to propagate it otherwise False.</returns>
|
||||
virtual bool on_mouse(const wxMouseEvent &mouse_event) { return false; }
|
||||
|
||||
void register_raycasters_for_picking() { register_grabbers_for_picking(); on_register_raycasters_for_picking(); }
|
||||
void unregister_raycasters_for_picking() { unregister_grabbers_for_picking(); on_unregister_raycasters_for_picking(); }
|
||||
|
||||
virtual bool is_in_editing_mode() const { return false; }
|
||||
virtual bool is_selection_rectangle_dragging() const { return false; }
|
||||
|
||||
protected:
|
||||
virtual bool on_init() = 0;
|
||||
virtual void on_load(cereal::BinaryInputArchive& ar) {}
|
||||
virtual void on_save(cereal::BinaryOutputArchive& ar) const {}
|
||||
virtual std::string on_get_name() const = 0;
|
||||
virtual void on_set_state() {}
|
||||
virtual void on_set_hover_id() {}
|
||||
virtual bool on_is_activable() const { return true; }
|
||||
virtual bool on_is_selectable() const { return true; }
|
||||
virtual CommonGizmosDataID on_get_requirements() const { return CommonGizmosDataID(0); }
|
||||
virtual void on_enable_grabber(unsigned int id) {}
|
||||
virtual void on_disable_grabber(unsigned int id) {}
|
||||
|
||||
// called inside use_grabbers
|
||||
virtual void on_start_dragging() {}
|
||||
virtual void on_stop_dragging() {}
|
||||
virtual void on_dragging(const UpdateData& data) {}
|
||||
|
||||
virtual void on_render() = 0;
|
||||
virtual void on_render_input_window(float x, float y, float bottom_limit) {}
|
||||
|
||||
void register_grabbers_for_picking();
|
||||
void unregister_grabbers_for_picking();
|
||||
virtual void on_register_raycasters_for_picking() {}
|
||||
virtual void on_unregister_raycasters_for_picking() {}
|
||||
|
||||
void render_grabbers(const BoundingBoxf3& box) const;
|
||||
void render_grabbers(float size) const;
|
||||
void render_grabbers(size_t first, size_t last, float size, bool force_hover) const;
|
||||
|
||||
std::string format(float value, unsigned int decimals) const;
|
||||
|
||||
// Mark gizmo as dirty to Re-Render when idle()
|
||||
void set_dirty();
|
||||
|
||||
/// <summary>
|
||||
/// function which
|
||||
/// Set up m_dragging and call functions
|
||||
/// on_start_dragging / on_dragging / on_stop_dragging
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Keep information about mouse click</param>
|
||||
/// <returns>same as on_mouse</returns>
|
||||
bool use_grabbers(const wxMouseEvent &mouse_event);
|
||||
|
||||
void do_stop_dragging(bool perform_mouse_cleanup);
|
||||
|
||||
private:
|
||||
// Flag for dirty visible state of Gizmo
|
||||
// When True then need new rendering
|
||||
bool m_dirty{ false };
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoBase_hpp_
|
||||
3005
src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
Normal file
3005
src/slic3r/GUI/Gizmos/GLGizmoCut.cpp
Normal file
File diff suppressed because it is too large
Load Diff
347
src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
Normal file
347
src/slic3r/GUI/Gizmos/GLGizmoCut.hpp
Normal file
@@ -0,0 +1,347 @@
|
||||
#ifndef slic3r_GLGizmoCut_hpp_
|
||||
#define slic3r_GLGizmoCut_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "slic3r/GUI/GLSelectionRectangle.hpp"
|
||||
#include "slic3r/GUI/GLModel.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
enum class CutConnectorType : int;
|
||||
class ModelVolume;
|
||||
struct CutConnectorAttributes;
|
||||
|
||||
namespace GUI {
|
||||
class Selection;
|
||||
|
||||
enum class SLAGizmoEventType : unsigned char;
|
||||
|
||||
namespace CommonGizmosDataObjects { class ObjectClipper; }
|
||||
|
||||
class GLGizmoCut3D : public GLGizmoBase
|
||||
{
|
||||
enum GrabberID {
|
||||
X = 0,
|
||||
Y,
|
||||
Z,
|
||||
CutPlane,
|
||||
Count,
|
||||
};
|
||||
|
||||
Transform3d m_rotation_m{ Transform3d::Identity() };
|
||||
double m_snap_step{ 1.0 };
|
||||
int m_connectors_group_id;
|
||||
|
||||
// archived values
|
||||
Vec3d m_ar_plane_center { Vec3d::Zero() };
|
||||
Transform3d m_start_dragging_m{ Transform3d::Identity() };
|
||||
|
||||
Vec3d m_plane_center{ Vec3d::Zero() };
|
||||
// data to check position of the cut palne center on gizmo activation
|
||||
Vec3d m_min_pos{ Vec3d::Zero() };
|
||||
Vec3d m_max_pos{ Vec3d::Zero() };
|
||||
Vec3d m_bb_center{ Vec3d::Zero() };
|
||||
Vec3d m_center_offset{ Vec3d::Zero() };
|
||||
|
||||
BoundingBoxf3 m_bounding_box;
|
||||
BoundingBoxf3 m_transformed_bounding_box;
|
||||
|
||||
// values from RotationGizmo
|
||||
double m_radius{ 0.0 };
|
||||
double m_grabber_radius{ 0.0 };
|
||||
double m_grabber_connection_len{ 0.0 };
|
||||
|
||||
double m_snap_coarse_in_radius{ 0.0 };
|
||||
double m_snap_coarse_out_radius{ 0.0 };
|
||||
double m_snap_fine_in_radius{ 0.0 };
|
||||
double m_snap_fine_out_radius{ 0.0 };
|
||||
|
||||
// dragging angel in hovered axes
|
||||
double m_angle{ 0.0 };
|
||||
|
||||
TriangleMesh m_connector_mesh;
|
||||
// workaround for using of the clipping plane normal
|
||||
Vec3d m_clp_normal{ Vec3d::Ones() };
|
||||
|
||||
Vec3d m_line_beg{ Vec3d::Zero() };
|
||||
Vec3d m_line_end{ Vec3d::Zero() };
|
||||
|
||||
Vec2d m_ldown_mouse_position{ Vec2d::Zero() };
|
||||
|
||||
GLModel m_grabber_connection;
|
||||
GLModel m_cut_line;
|
||||
|
||||
PickingModel m_plane;
|
||||
PickingModel m_sphere;
|
||||
PickingModel m_cone;
|
||||
std::map<CutConnectorAttributes, PickingModel> m_shapes;
|
||||
std::vector<std::shared_ptr<SceneRaycasterItem>> m_raycasters;
|
||||
|
||||
GLModel m_circle;
|
||||
GLModel m_scale;
|
||||
GLModel m_snap_radii;
|
||||
GLModel m_reference_radius;
|
||||
GLModel m_angle_arc;
|
||||
|
||||
Vec3d m_old_center;
|
||||
Vec3d m_cut_normal;
|
||||
|
||||
struct InvalidConnectorsStatistics
|
||||
{
|
||||
unsigned int outside_cut_contour;
|
||||
unsigned int outside_bb;
|
||||
bool is_overlap;
|
||||
|
||||
void invalidate() {
|
||||
outside_cut_contour = 0;
|
||||
outside_bb = 0;
|
||||
is_overlap = false;
|
||||
}
|
||||
} m_info_stats;
|
||||
|
||||
bool m_keep_upper{ true };
|
||||
bool m_keep_lower{ true };
|
||||
bool m_keep_as_parts{ false };
|
||||
bool m_place_on_cut_upper{ true };
|
||||
bool m_place_on_cut_lower{ false };
|
||||
bool m_rotate_upper{ false };
|
||||
bool m_rotate_lower{ false };
|
||||
|
||||
bool m_hide_cut_plane{ false };
|
||||
bool m_connectors_editing{ false };
|
||||
bool m_cut_plane_as_circle{ false };
|
||||
|
||||
float m_connector_depth_ratio{ 3.f };
|
||||
float m_connector_size{ 2.5f };
|
||||
|
||||
float m_connector_depth_ratio_tolerance{ 0.1f };
|
||||
float m_connector_size_tolerance{ 0.f };
|
||||
|
||||
float m_label_width{ 0.f };
|
||||
float m_control_width{ 200.f };
|
||||
bool m_imperial_units{ false };
|
||||
|
||||
float m_contour_width{ 0.4f };
|
||||
float m_cut_plane_radius_koef{ 1.5f };
|
||||
bool m_is_contour_changed{ false };
|
||||
float m_shortcut_label_width{ -1.f };
|
||||
|
||||
mutable std::vector<bool> m_selected; // which pins are currently selected
|
||||
int m_selected_count{ 0 };
|
||||
|
||||
GLSelectionRectangle m_selection_rectangle;
|
||||
|
||||
std::vector<size_t> m_invalid_connectors_idxs;
|
||||
bool m_was_cut_plane_dragged { false };
|
||||
bool m_was_contour_selected { false };
|
||||
|
||||
class PartSelection {
|
||||
public:
|
||||
PartSelection() = default;
|
||||
PartSelection(const ModelObject* mo, const Transform3d& cut_matrix, int instance_idx, const Vec3d& center, const Vec3d& normal, const CommonGizmosDataObjects::ObjectClipper& oc);
|
||||
~PartSelection() { m_model.clear_objects(); }
|
||||
|
||||
struct Part {
|
||||
GLModel glmodel;
|
||||
MeshRaycaster raycaster;
|
||||
bool selected;
|
||||
bool is_modifier;
|
||||
};
|
||||
|
||||
void render(const Vec3d* normal, GLModel& sphere_model);
|
||||
void toggle_selection(const Vec2d& mouse_pos);
|
||||
void turn_over_selection();
|
||||
ModelObject* model_object() { return m_model.objects.front(); }
|
||||
bool valid() const { return m_valid; }
|
||||
bool is_one_object() const;
|
||||
const std::vector<Part>& parts() const { return m_parts; }
|
||||
const std::vector<size_t>* get_ignored_contours_ptr() const { return (valid() ? &m_ignored_contours : nullptr); }
|
||||
|
||||
private:
|
||||
Model m_model;
|
||||
int m_instance_idx;
|
||||
std::vector<Part> m_parts;
|
||||
bool m_valid = false;
|
||||
std::vector<std::pair<std::vector<size_t>, std::vector<size_t>>> m_contour_to_parts; // for each contour, there is a vector of parts above and a vector of parts below
|
||||
std::vector<size_t> m_ignored_contours; // contour that should not be rendered (the parts on both sides will both be parts of the same object)
|
||||
|
||||
std::vector<Vec3d> m_contour_points; // Debugging
|
||||
std::vector<std::vector<Vec3d>> m_debug_pts; // Debugging
|
||||
};
|
||||
|
||||
PartSelection m_part_selection;
|
||||
|
||||
bool m_show_shortcuts{ false };
|
||||
std::vector<std::pair<wxString, wxString>> m_shortcuts;
|
||||
|
||||
enum class CutMode {
|
||||
cutPlanar
|
||||
, cutGrig
|
||||
//,cutRadial
|
||||
//,cutModular
|
||||
};
|
||||
|
||||
enum class CutConnectorMode {
|
||||
Auto
|
||||
, Manual
|
||||
};
|
||||
|
||||
// std::vector<std::string> m_modes;
|
||||
size_t m_mode{ size_t(CutMode::cutPlanar) };
|
||||
|
||||
std::vector<std::string> m_connector_modes;
|
||||
CutConnectorMode m_connector_mode{ CutConnectorMode::Manual };
|
||||
|
||||
std::vector<std::string> m_connector_types;
|
||||
CutConnectorType m_connector_type;
|
||||
|
||||
std::vector<std::string> m_connector_styles;
|
||||
int m_connector_style;
|
||||
|
||||
std::vector<std::string> m_connector_shapes;
|
||||
int m_connector_shape_id;
|
||||
|
||||
std::vector<std::string> m_axis_names;
|
||||
|
||||
std::map<std::string, wxString> m_part_orientation_names;
|
||||
|
||||
std::map<std::string, std::string> m_labels_map;
|
||||
|
||||
public:
|
||||
GLGizmoCut3D(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
|
||||
std::string get_tooltip() const override;
|
||||
bool unproject_on_cut_plane(const Vec2d& mouse_pos, Vec3d& pos, Vec3d& pos_world, bool respect_disabled_contour = true);
|
||||
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||
|
||||
bool is_in_editing_mode() const override { return m_connectors_editing; }
|
||||
bool is_selection_rectangle_dragging() const override { return m_selection_rectangle.is_dragging(); }
|
||||
bool is_looking_forward() const;
|
||||
|
||||
/// <summary>
|
||||
/// Drag of plane
|
||||
/// </summary>
|
||||
/// <param name="mouse_event">Keep information about mouse click</param>
|
||||
/// <returns>Return True when use the information otherwise False.</returns>
|
||||
bool on_mouse(const wxMouseEvent &mouse_event) override;
|
||||
|
||||
void shift_cut(double delta);
|
||||
void rotate_vec3d_around_plane_center(Vec3d&vec);
|
||||
void put_connectors_on_cut_plane(const Vec3d& cp_normal, double cp_offset);
|
||||
void update_clipper();
|
||||
void invalidate_cut_plane();
|
||||
|
||||
BoundingBoxf3 bounding_box() const;
|
||||
BoundingBoxf3 transformed_bounding_box(const Vec3d& plane_center, const Transform3d& rotation_m = Transform3d::Identity()) const;
|
||||
|
||||
protected:
|
||||
bool on_init() override;
|
||||
void on_load(cereal::BinaryInputArchive&ar) override;
|
||||
void on_save(cereal::BinaryOutputArchive&ar) const override;
|
||||
std::string on_get_name() const override;
|
||||
void on_set_state() override;
|
||||
CommonGizmosDataID on_get_requirements() const override;
|
||||
void on_set_hover_id() override;
|
||||
bool on_is_activable() const override;
|
||||
bool on_is_selectable() const override;
|
||||
Vec3d mouse_position_in_local_plane(GrabberID axis, const Linef3&mouse_ray) const;
|
||||
void dragging_grabber_z(const GLGizmoBase::UpdateData &data);
|
||||
void dragging_grabber_xy(const GLGizmoBase::UpdateData &data);
|
||||
void dragging_connector(const GLGizmoBase::UpdateData &data);
|
||||
void on_dragging(const UpdateData&data) override;
|
||||
void on_start_dragging() override;
|
||||
void on_stop_dragging() override;
|
||||
void on_render() override;
|
||||
|
||||
void render_debug_input_window(float x);
|
||||
void adjust_window_position(float x, float y, float bottom_limit);
|
||||
void unselect_all_connectors();
|
||||
void select_all_connectors();
|
||||
void render_shortcuts();
|
||||
void apply_selected_connectors(std::function<void(size_t idx)> apply_fn);
|
||||
void render_connectors_input_window(CutConnectors &connectors);
|
||||
void render_build_size();
|
||||
void reset_cut_plane();
|
||||
void set_connectors_editing(bool connectors_editing);
|
||||
void flip_cut_plane();
|
||||
void process_contours();
|
||||
void reset_cut_by_contours();
|
||||
void render_flip_plane_button(bool disable_pred = false);
|
||||
void add_vertical_scaled_interval(float interval);
|
||||
void add_horizontal_scaled_interval(float interval);
|
||||
void add_horizontal_shift(float shift);
|
||||
void render_color_marker(float size, const ImU32& color);
|
||||
void render_cut_plane_input_window(CutConnectors &connectors);
|
||||
void init_input_window_data(CutConnectors &connectors);
|
||||
void render_input_window_warning() const;
|
||||
bool add_connector(CutConnectors&connectors, const Vec2d&mouse_position);
|
||||
bool delete_selected_connectors(CutConnectors&connectors);
|
||||
void select_connector(int idx, bool select);
|
||||
bool is_selection_changed(bool alt_down, bool shift_down);
|
||||
void process_selection_rectangle(CutConnectors &connectors);
|
||||
|
||||
virtual void on_register_raycasters_for_picking() override;
|
||||
virtual void on_unregister_raycasters_for_picking() override;
|
||||
void update_raycasters_for_picking();
|
||||
void set_volumes_picking_state(bool state);
|
||||
void update_raycasters_for_picking_transform();
|
||||
|
||||
void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
|
||||
bool wants_enter_leave_snapshots() const override { return true; }
|
||||
std::string get_gizmo_entering_text() const override { return _u8L("Entering Cut gizmo"); }
|
||||
std::string get_gizmo_leaving_text() const override { return _u8L("Leaving Cut gizmo"); }
|
||||
std::string get_action_snapshot_name() const override { return _u8L("Cut gizmo editing"); }
|
||||
|
||||
void data_changed(bool is_serializing) override;
|
||||
Transform3d get_cut_matrix(const Selection& selection);
|
||||
|
||||
private:
|
||||
void set_center(const Vec3d& center, bool update_tbb = false);
|
||||
bool render_combo(const std::string& label, const std::vector<std::string>& lines, int& selection_idx);
|
||||
bool render_double_input(const std::string& label, double& value_in);
|
||||
bool render_slider_double_input(const std::string& label, float& value_in, float& tolerance_in);
|
||||
void render_move_center_input(int axis);
|
||||
void render_connect_mode_radio_button(CutConnectorMode mode);
|
||||
bool render_reset_button(const std::string& label_id, const std::string& tooltip) const;
|
||||
bool render_connect_type_radio_button(CutConnectorType type);
|
||||
bool is_outside_of_cut_contour(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos);
|
||||
bool is_conflict_for_connector(size_t idx, const CutConnectors& connectors, const Vec3d cur_pos);
|
||||
void render_connectors();
|
||||
|
||||
bool can_perform_cut() const;
|
||||
bool has_valid_contour() const;
|
||||
void apply_connectors_in_model(ModelObject* mo, bool &create_dowels_as_separate_object);
|
||||
bool cut_line_processing() const;
|
||||
void discard_cut_line_processing();
|
||||
|
||||
void render_cut_plane();
|
||||
static void render_model(GLModel& model, const ColorRGBA& color, Transform3d view_model_matrix);
|
||||
void render_line(GLModel& line_model, const ColorRGBA& color, Transform3d view_model_matrix, float width);
|
||||
void render_rotation_snapping(GrabberID axis, const ColorRGBA& color);
|
||||
void render_grabber_connection(const ColorRGBA& color, Transform3d view_matrix);
|
||||
void render_cut_plane_grabbers();
|
||||
void render_cut_line();
|
||||
void perform_cut(const Selection&selection);
|
||||
void set_center_pos(const Vec3d¢er_pos, bool update_tbb = false);
|
||||
void update_bb();
|
||||
void init_picking_models();
|
||||
void init_rendering_items();
|
||||
void render_clipper_cut();
|
||||
void clear_selection();
|
||||
void reset_connectors();
|
||||
void init_connector_shapes();
|
||||
void update_connector_shape();
|
||||
void validate_connector_settings();
|
||||
bool process_cut_line(SLAGizmoEventType action, const Vec2d& mouse_position);
|
||||
void check_and_update_connectors_state();
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoCut_hpp_
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user