Merged branch 'dev_native' into lm_sla_supports_auto

Added igl library files
This commit is contained in:
Lukas Matena 2018-10-26 15:45:52 +02:00
commit 7681d00ee5
2865 changed files with 142806 additions and 22325 deletions

183
src/slic3r/GUI/2DBed.cpp Normal file
View file

@ -0,0 +1,183 @@
#include "2DBed.hpp"
#include <wx/dcbuffer.h>
#include "BoundingBox.hpp"
#include "Geometry.hpp"
#include "ClipperUtils.hpp"
namespace Slic3r {
namespace GUI {
void Bed_2D::repaint()
{
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.
auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); //GetSystemColour
dc.SetPen(*new wxPen(color, 1, wxPENSTYLE_SOLID));
dc.SetBrush(*new wxBrush(color, wxBRUSHSTYLE_SOLID));
auto rect = GetUpdateRegion().GetBox();
dc.DrawRectangle(rect.GetLeft(), rect.GetTop(), rect.GetWidth(), rect.GetHeight());
}
// turn cw and ch from sizes to max coordinates
cw--;
ch--;
auto cbb = BoundingBoxf(Vec2d(0, 0),Vec2d(cw, ch));
// leave space for origin point
cbb.min(0) += 4;
cbb.max -= Vec2d(4., 4.);
// leave space for origin label
cbb.max(1) -= 13;
// read new size
cw = cbb.size()(0);
ch = cbb.size()(1);
auto ccenter = cbb.center();
// get bounding box of bed shape in G - code coordinates
auto bed_shape = m_bed_shape;
auto bed_polygon = Polygon::new_scale(m_bed_shape);
auto bb = BoundingBoxf(m_bed_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) - GetSize().GetHeight()));
// draw bed fill
dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxSOLID));
wxPointList pt_list;
for (auto pt: m_bed_shape)
{
Point pt_pix = to_pixels(pt);
pt_list.push_back(new wxPoint(pt_pix(0), pt_pix(1)));
}
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, wxSOLID));
for (auto pl : polylines)
{
for (size_t i = 0; i < pl.points.size()-1; i++){
Point pt1 = to_pixels(unscale(pl.points[i]));
Point pt2 = to_pixels(unscale(pl.points[i+1]));
dc.DrawLine(pt1(0), pt1(1), pt2(0), pt2(1));
}
}
// draw bed contour
dc.SetPen(wxPen(wxColour(0, 0, 0), 1, wxSOLID));
dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxTRANSPARENT));
dc.DrawPolygon(&pt_list, 0, 0);
auto origin_px = to_pixels(Vec2d(0, 0));
// 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, wxSOLID)); // 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, wxSOLID)); // 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, wxSOLID));
dc.SetBrush(wxBrush(wxColour(0, 0, 0), wxSOLID));
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, wxDEFAULT, wxNORMAL, wxNORMAL));
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);
dc.SetPen(wxPen(wxColour(200, 0, 0), 2, wxSOLID));
dc.SetBrush(wxBrush(wxColour(200, 0, 0), wxTRANSPARENT));
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);
}
m_painted = true;
}
// convert G - code coordinates into pixels
Point Bed_2D::to_pixels(Vec2d point){
auto p = point * m_scale_factor + m_shift;
return Point(p(0), GetSize().GetHeight() - p(1));
}
void Bed_2D::mouse_event(wxMouseEvent event){
if (!m_interactive) return;
if (!m_painted) return;
auto pos = event.GetPosition();
auto point = to_units(Point(pos.x, pos.y));
if (event.LeftDown() || event.Dragging()) {
if (m_on_move)
m_on_move(point) ;
Refresh();
}
}
// convert pixels into G - code coordinates
Vec2d Bed_2D::to_units(Point point){
return (Vec2d(point(0), GetSize().GetHeight() - point(1)) - m_shift) * (1. / m_scale_factor);
}
void Bed_2D::set_pos(Vec2d pos){
m_pos = pos;
Refresh();
}
} // GUI
} // Slic3r

52
src/slic3r/GUI/2DBed.hpp Normal file
View file

@ -0,0 +1,52 @@
#ifndef slic3r_2DBed_hpp_
#define slic3r_2DBed_hpp_
#include <wx/wx.h>
#include "Config.hpp"
namespace Slic3r {
namespace GUI {
class Bed_2D : public wxPanel
{
bool m_user_drawn_background = true;
bool m_painted = false;
bool m_interactive = false;
double m_scale_factor;
Vec2d m_shift = Vec2d::Zero();
Vec2d m_pos = Vec2d::Zero();
std::function<void(Vec2d)> m_on_move = nullptr;
Point to_pixels(Vec2d point);
Vec2d to_units(Point point);
void repaint();
void mouse_event(wxMouseEvent event);
void set_pos(Vec2d pos);
public:
Bed_2D(wxWindow* parent)
{
Create(parent, wxID_ANY, wxDefaultPosition, wxSize(250, -1), wxTAB_TRAVERSAL);
// m_user_drawn_background = $^O ne 'darwin';
#ifdef __APPLE__
m_user_drawn_background = false;
#endif /*__APPLE__*/
Bind(wxEVT_PAINT, ([this](wxPaintEvent e) { repaint(); }));
// EVT_ERASE_BACKGROUND($self, sub{}) if $self->{user_drawn_background};
// Bind(EVT_MOUSE_EVENTS, ([this](wxMouseEvent event){/*mouse_event()*/; }));
Bind(wxEVT_LEFT_DOWN, ([this](wxMouseEvent event){ mouse_event(event); }));
Bind(wxEVT_MOTION, ([this](wxMouseEvent event){ mouse_event(event); }));
Bind(wxEVT_SIZE, ([this](wxSizeEvent e) { Refresh(); }));
}
~Bed_2D(){}
std::vector<Vec2d> m_bed_shape;
};
} // GUI
} // Slic3r
#endif /* slic3r_2DBed_hpp_ */

2152
src/slic3r/GUI/3DScene.cpp Normal file

File diff suppressed because it is too large Load diff

622
src/slic3r/GUI/3DScene.hpp Normal file
View file

@ -0,0 +1,622 @@
#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/Model.hpp"
#include "../../slic3r/GUI/GLCanvas3DManager.hpp"
class wxBitmap;
class wxWindow;
namespace Slic3r {
class Print;
class PrintObject;
class Model;
class ModelObject;
class GCodePreviewData;
class DynamicPrintConfig;
class ExtrusionPath;
class ExtrusionMultiPath;
class ExtrusionLoop;
class ExtrusionEntity;
class ExtrusionEntityCollection;
// A container for interleaved arrays of 3D vertices and normals,
// possibly indexed by triangles and / or quads.
class GLIndexedVertexArray {
public:
GLIndexedVertexArray() :
vertices_and_normals_interleaved_VBO_id(0),
triangle_indices_VBO_id(0),
quad_indices_VBO_id(0)
{ this->setup_sizes(); }
GLIndexedVertexArray(const GLIndexedVertexArray &rhs) :
vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved),
triangle_indices(rhs.triangle_indices),
quad_indices(rhs.quad_indices),
vertices_and_normals_interleaved_VBO_id(0),
triangle_indices_VBO_id(0),
quad_indices_VBO_id(0)
{ this->setup_sizes(); }
GLIndexedVertexArray(GLIndexedVertexArray &&rhs) :
vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)),
triangle_indices(std::move(rhs.triangle_indices)),
quad_indices(std::move(rhs.quad_indices)),
vertices_and_normals_interleaved_VBO_id(0),
triangle_indices_VBO_id(0),
quad_indices_VBO_id(0)
{ this->setup_sizes(); }
GLIndexedVertexArray& operator=(const GLIndexedVertexArray &rhs)
{
assert(vertices_and_normals_interleaved_VBO_id == 0);
assert(triangle_indices_VBO_id == 0);
assert(triangle_indices_VBO_id == 0);
this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved;
this->triangle_indices = rhs.triangle_indices;
this->quad_indices = rhs.quad_indices;
this->setup_sizes();
return *this;
}
GLIndexedVertexArray& operator=(GLIndexedVertexArray &&rhs)
{
assert(vertices_and_normals_interleaved_VBO_id == 0);
assert(triangle_indices_VBO_id == 0);
assert(triangle_indices_VBO_id == 0);
this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved);
this->triangle_indices = std::move(rhs.triangle_indices);
this->quad_indices = std::move(rhs.quad_indices);
this->setup_sizes();
return *this;
}
// Vertices and their normals, interleaved to be used by void glInterleavedArrays(GL_N3F_V3F, 0, x)
std::vector<float> vertices_and_normals_interleaved;
std::vector<int> triangle_indices;
std::vector<int> quad_indices;
// When the geometry data is loaded into the graphics card as Vertex Buffer Objects,
// the above mentioned std::vectors are cleared and the following variables keep their original length.
size_t vertices_and_normals_interleaved_size;
size_t triangle_indices_size;
size_t quad_indices_size;
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
// Zero if the VBOs are not used.
unsigned int vertices_and_normals_interleaved_VBO_id;
unsigned int triangle_indices_VBO_id;
unsigned int quad_indices_VBO_id;
void load_mesh_flat_shading(const TriangleMesh &mesh);
void load_mesh_full_shading(const TriangleMesh &mesh);
inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; }
inline void reserve(size_t sz) {
this->vertices_and_normals_interleaved.reserve(sz * 6);
this->triangle_indices.reserve(sz * 3);
this->quad_indices.reserve(sz * 4);
}
inline void push_geometry(float x, float y, float z, float nx, float ny, float nz) {
if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity())
this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6));
this->vertices_and_normals_interleaved.push_back(nx);
this->vertices_and_normals_interleaved.push_back(ny);
this->vertices_and_normals_interleaved.push_back(nz);
this->vertices_and_normals_interleaved.push_back(x);
this->vertices_and_normals_interleaved.push_back(y);
this->vertices_and_normals_interleaved.push_back(z);
};
inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) {
push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz));
}
inline void push_geometry(const Vec3d& p, const Vec3d& n) {
push_geometry(p(0), p(1), p(2), n(0), n(1), n(2));
}
inline void push_triangle(int idx1, int idx2, int idx3) {
if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity())
this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3));
this->triangle_indices.push_back(idx1);
this->triangle_indices.push_back(idx2);
this->triangle_indices.push_back(idx3);
};
inline void push_quad(int idx1, int idx2, int idx3, int idx4) {
if (this->quad_indices.size() + 4 > this->vertices_and_normals_interleaved.capacity())
this->quad_indices.reserve(next_highest_power_of_2(this->quad_indices.size() + 4));
this->quad_indices.push_back(idx1);
this->quad_indices.push_back(idx2);
this->quad_indices.push_back(idx3);
this->quad_indices.push_back(idx4);
};
// Finalize the initialization of the geometry & indices,
// upload the geometry and indices to OpenGL VBO objects
// and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs.
void finalize_geometry(bool use_VBOs);
// Release the geometry data, release OpenGL VBOs.
void release_geometry();
// Render either using an immediate mode, or the VBOs.
void render() const;
void render(const std::pair<size_t, size_t> &tverts_range, const std::pair<size_t, size_t> &qverts_range) const;
// Is there any geometry data stored?
bool empty() const { return vertices_and_normals_interleaved_size == 0; }
// Is this object indexed, or is it just a set of triangles?
bool indexed() const { return ! this->empty() && this->triangle_indices_size + this->quad_indices_size > 0; }
void clear() {
this->vertices_and_normals_interleaved.clear();
this->triangle_indices.clear();
this->quad_indices.clear();
this->setup_sizes();
}
// Shrink the internal storage to tighly fit the data stored.
void shrink_to_fit() {
if (! this->has_VBOs())
this->setup_sizes();
this->vertices_and_normals_interleaved.shrink_to_fit();
this->triangle_indices.shrink_to_fit();
this->quad_indices.shrink_to_fit();
}
BoundingBoxf3 bounding_box() const {
BoundingBoxf3 bbox;
if (! this->vertices_and_normals_interleaved.empty()) {
bbox.defined = true;
bbox.min(0) = bbox.max(0) = this->vertices_and_normals_interleaved[3];
bbox.min(1) = bbox.max(1) = this->vertices_and_normals_interleaved[4];
bbox.min(2) = bbox.max(2) = this->vertices_and_normals_interleaved[5];
for (size_t i = 9; i < this->vertices_and_normals_interleaved.size(); i += 6) {
const float *verts = this->vertices_and_normals_interleaved.data() + i;
bbox.min(0) = std::min<coordf_t>(bbox.min(0), verts[0]);
bbox.min(1) = std::min<coordf_t>(bbox.min(1), verts[1]);
bbox.min(2) = std::min<coordf_t>(bbox.min(2), verts[2]);
bbox.max(0) = std::max<coordf_t>(bbox.max(0), verts[0]);
bbox.max(1) = std::max<coordf_t>(bbox.max(1), verts[1]);
bbox.max(2) = std::max<coordf_t>(bbox.max(2), verts[2]);
}
}
return bbox;
}
private:
inline void setup_sizes() {
vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size();
triangle_indices_size = this->triangle_indices.size();
quad_indices_size = this->quad_indices.size();
}
};
class LayersTexture
{
public:
LayersTexture() : width(0), height(0), levels(0), cells(0) {}
// Texture data
std::vector<char> data;
// Width of the texture, top level.
size_t width;
// Height of the texture, top level.
size_t height;
// For how many levels of detail is the data allocated?
size_t levels;
// Number of texture cells allocated for the height texture.
size_t cells;
};
class GLVolume {
struct LayerHeightTextureData
{
// ID of the layer height texture
unsigned int texture_id;
// ID of the shader used to render with the layer height texture
unsigned int shader_id;
// The print object to update when generating the layer height texture
const PrintObject* print_object;
float z_cursor_relative;
float edit_band_width;
LayerHeightTextureData() { reset(); }
void reset()
{
texture_id = 0;
shader_id = 0;
print_object = nullptr;
z_cursor_relative = 0.0f;
edit_band_width = 0.0f;
}
bool can_use() const { return (texture_id > 0) && (shader_id > 0) && (print_object != nullptr); }
};
public:
static const float SELECTED_COLOR[4];
static const float HOVER_COLOR[4];
static const float OUTSIDE_COLOR[4];
static const float SELECTED_OUTSIDE_COLOR[4];
GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f);
GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {}
private:
// Offset of the volume to be rendered.
Vec3d m_offset;
// Rotation around three axes of the volume to be rendered.
Vec3d m_rotation;
// Scale factor along the three axes of the volume to be rendered.
Vec3d m_scaling_factor;
#if ENABLE_MIRROR
// Mirroring along the three axes of the volume to be rendered.
Vec3d m_mirror;
#endif // ENABLE_MIRROR
// World matrix of the volume to be rendered.
mutable Transform3f m_world_matrix;
// Whether or not is needed to recalculate the world matrix.
mutable bool m_world_matrix_dirty;
// Bounding box of this volume, in unscaled coordinates.
mutable BoundingBoxf3 m_transformed_bounding_box;
// Whether or not is needed to recalculate the transformed bounding box.
mutable bool m_transformed_bounding_box_dirty;
// Pointer to convex hull of the original mesh, if any.
const TriangleMesh* m_convex_hull;
// Bounding box of this volume, in unscaled coordinates.
mutable BoundingBoxf3 m_transformed_convex_hull_bounding_box;
// Whether or not is needed to recalculate the transformed convex hull bounding box.
mutable bool m_transformed_convex_hull_bounding_box_dirty;
public:
// Bounding box of this volume, in unscaled coordinates.
BoundingBoxf3 bounding_box;
// Color of the triangles / quads held by this volume.
float color[4];
// Color used to render this volume.
float render_color[4];
// An ID containing the object ID, volume ID and instance ID.
int composite_id;
#if !ENABLE_EXTENDED_SELECTION
// An ID for group selection. It may be the same for all meshes of all object instances, or for just a single object instance.
int select_group_id;
// An ID for group dragging. It may be the same for all meshes of all object instances, or for just a single object instance.
int drag_group_id;
#endif // !ENABLE_EXTENDED_SELECTION
// An ID containing the extruder ID (used to select color).
int extruder_id;
// Is this object selected?
bool selected;
// Whether or not this volume is active for rendering
bool is_active;
// Whether or not to use this volume when applying zoom_to_volumes()
bool zoom_to_volumes;
// Wheter or not this volume is enabled for outside print volume detection in shader.
bool shader_outside_printer_detection_enabled;
// Wheter or not this volume is outside print volume.
bool is_outside;
// Boolean: Is mouse over this object?
bool hover;
// Wheter or not this volume has been generated from a modifier
bool is_modifier;
// Wheter or not this volume has been generated from the wipe tower
bool is_wipe_tower;
// Wheter or not this volume has been generated from an extrusion path
bool is_extrusion_path;
// Interleaved triangles & normals with indexed triangles & quads.
GLIndexedVertexArray indexed_vertex_array;
// Ranges of triangle and quad indices to be rendered.
std::pair<size_t, size_t> tverts_range;
std::pair<size_t, size_t> qverts_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;
void set_render_color(float r, float g, float b, float a);
void set_render_color(const float* rgba, unsigned int size);
// Sets render color in dependence of current state
void set_render_color();
const Vec3d& get_rotation() const;
void set_rotation(const Vec3d& rotation);
#if ENABLE_EXTENDED_SELECTION
const Vec3d& get_scaling_factor() const;
#endif // ENABLE_EXTENDED_SELECTION
void set_scaling_factor(const Vec3d& scaling_factor);
#if ENABLE_MIRROR
const Vec3d& get_mirror() const;
double get_mirror(Axis axis) const;
void set_mirror(const Vec3d& mirror);
void set_mirror(Axis axis, double mirror);
#endif // ENABLE_MIRROR
const Vec3d& get_offset() const;
void set_offset(const Vec3d& offset);
void set_convex_hull(const TriangleMesh& convex_hull);
#if !ENABLE_EXTENDED_SELECTION
void set_select_group_id(const std::string& select_by);
void set_drag_group_id(const std::string& drag_by);
#endif // !ENABLE_EXTENDED_SELECTION
int object_idx() const { return this->composite_id / 1000000; }
int volume_idx() const { return (this->composite_id / 1000) % 1000; }
int instance_idx() const { return this->composite_id % 1000; }
const Transform3f& world_matrix() const;
const BoundingBoxf3& transformed_bounding_box() const;
const BoundingBoxf3& transformed_convex_hull_bounding_box() const;
bool empty() const { return this->indexed_vertex_array.empty(); }
bool indexed() const { return this->indexed_vertex_array.indexed(); }
void set_range(coordf_t low, coordf_t high);
void render() const;
void render_using_layer_height() const;
void render_VBOs(int color_id, int detection_id, int worldmatrix_id) const;
void render_legacy() const;
void finalize_geometry(bool use_VBOs) { this->indexed_vertex_array.finalize_geometry(use_VBOs); }
void release_geometry() { this->indexed_vertex_array.release_geometry(); }
/************************************************ Layer height texture ****************************************************/
std::shared_ptr<LayersTexture> layer_height_texture;
// Data to render this volume using the layer height texture
LayerHeightTextureData layer_height_texture_data;
bool has_layer_height_texture() const
{ return this->layer_height_texture.get() != nullptr; }
size_t layer_height_texture_width() const
{ return (this->layer_height_texture.get() == nullptr) ? 0 : this->layer_height_texture->width; }
size_t layer_height_texture_height() const
{ return (this->layer_height_texture.get() == nullptr) ? 0 : this->layer_height_texture->height; }
size_t layer_height_texture_cells() const
{ return (this->layer_height_texture.get() == nullptr) ? 0 : this->layer_height_texture->cells; }
void* layer_height_texture_data_ptr_level0() const {
return (layer_height_texture.get() == nullptr) ? 0 :
(void*)layer_height_texture->data.data();
}
void* layer_height_texture_data_ptr_level1() const {
return (layer_height_texture.get() == nullptr) ? 0 :
(void*)(layer_height_texture->data.data() + layer_height_texture->width * layer_height_texture->height * 4);
}
double layer_height_texture_z_to_row_id() const;
void generate_layer_height_texture(const PrintObject *print_object, bool force);
void set_layer_height_texture_data(unsigned int texture_id, unsigned int shader_id, const PrintObject* print_object, float z_cursor_relative, float edit_band_width)
{
layer_height_texture_data.texture_id = texture_id;
layer_height_texture_data.shader_id = shader_id;
layer_height_texture_data.print_object = print_object;
layer_height_texture_data.z_cursor_relative = z_cursor_relative;
layer_height_texture_data.edit_band_width = edit_band_width;
}
void reset_layer_height_texture_data() { layer_height_texture_data.reset(); }
};
#if ENABLE_EXTENDED_SELECTION
typedef std::vector<GLVolume*> GLVolumePtrs;
#endif // ENABLE_EXTENDED_SELECTION
class GLVolumeCollection
{
// min and max vertex of the print box volume
float print_box_min[3];
float print_box_max[3];
public:
#if ENABLE_EXTENDED_SELECTION
GLVolumePtrs volumes;
#else
std::vector<GLVolume*> volumes;
#endif // ENABLE_EXTENDED_SELECTION
GLVolumeCollection() {};
~GLVolumeCollection() { clear(); };
#if ENABLE_EXTENDED_SELECTION
std::vector<int> load_object(
const ModelObject *model_object,
int obj_idx,
const std::vector<int> &instance_idxs,
const std::string &color_by,
bool use_VBOs);
#else
std::vector<int> load_object(
const ModelObject *model_object,
int obj_idx,
const std::vector<int> &instance_idxs,
const std::string &color_by,
const std::string &select_by,
const std::string &drag_by,
bool use_VBOs);
#endif // ENABLE_EXTENDED_SELECTION
int load_wipe_tower_preview(
int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width);
// Render the volumes by OpenGL.
void render_VBOs() const;
void render_legacy() const;
// Finalize the initialization of the geometry & indices,
// upload the geometry and indices to OpenGL VBO objects
// and shrink the allocated data, possibly relasing it if it has been loaded into the VBOs.
void finalize_geometry(bool use_VBOs) { for (auto *v : volumes) v->finalize_geometry(use_VBOs); }
// Release the geometry data assigned to the volumes.
// If OpenGL VBOs were allocated, an OpenGL context has to be active to release them.
void release_geometry() { for (auto *v : volumes) v->release_geometry(); }
// 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_print_box(float min_x, float min_y, float min_z, float max_x, float max_y, float max_z) {
print_box_min[0] = min_x; print_box_min[1] = min_y; print_box_min[2] = min_z;
print_box_max[0] = max_x; print_box_max[1] = max_y; print_box_max[2] = max_z;
}
// returns true if all the volumes are completely contained in the print volume
// returns the containment state in the given out_state, if non-null
bool check_outside_state(const DynamicPrintConfig* config, ModelInstance::EPrintVolumeState* out_state);
void reset_outside_state();
void update_colors_by_extruder(const DynamicPrintConfig* config);
#if !ENABLE_EXTENDED_SELECTION
void set_select_by(const std::string& select_by);
void set_drag_by(const std::string& drag_by);
#endif // !ENABLE_EXTENDED_SELECTION
// 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;
private:
GLVolumeCollection(const GLVolumeCollection &other);
GLVolumeCollection& operator=(const GLVolumeCollection &);
};
class _3DScene
{
static GUI::GLCanvas3DManager s_canvas_mgr;
public:
static void init_gl();
static std::string get_gl_info(bool format_as_html, bool extensions);
static bool use_VBOs();
static bool add_canvas(wxGLCanvas* canvas);
static bool remove_canvas(wxGLCanvas* canvas);
static void remove_all_canvases();
static bool init(wxGLCanvas* canvas);
static void set_as_dirty(wxGLCanvas* canvas);
static unsigned int get_volumes_count(wxGLCanvas* canvas);
static void reset_volumes(wxGLCanvas* canvas);
#if !ENABLE_EXTENDED_SELECTION
static void deselect_volumes(wxGLCanvas* canvas);
static void select_volume(wxGLCanvas* canvas, unsigned int id);
static void update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections);
#endif // !ENABLE_EXTENDED_SELECTION
static int check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config);
static bool move_volume_up(wxGLCanvas* canvas, unsigned int id);
static bool move_volume_down(wxGLCanvas* canvas, unsigned int id);
#if ENABLE_EXTENDED_SELECTION
static GUI::GLCanvas3D* get_canvas(wxGLCanvas* canvas);
#else
static void set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections);
#endif // ENABLE_EXTENDED_SELECTION
static void set_config(wxGLCanvas* canvas, DynamicPrintConfig* config);
static void set_print(wxGLCanvas* canvas, Print* print);
static void set_model(wxGLCanvas* canvas, Model* model);
static void set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape);
static void set_auto_bed_shape(wxGLCanvas* canvas);
static BoundingBoxf3 get_volumes_bounding_box(wxGLCanvas* canvas);
static void set_axes_length(wxGLCanvas* canvas, float length);
static void set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons);
static void set_color_by(wxGLCanvas* canvas, const std::string& value);
#if !ENABLE_EXTENDED_SELECTION
static void set_select_by(wxGLCanvas* canvas, const std::string& value);
static void set_drag_by(wxGLCanvas* canvas, const std::string& value);
static std::string get_select_by(wxGLCanvas* canvas);
#endif // !ENABLE_EXTENDED_SELECTION
static bool is_layers_editing_enabled(wxGLCanvas* canvas);
static bool is_layers_editing_allowed(wxGLCanvas* canvas);
static bool is_shader_enabled(wxGLCanvas* canvas);
static bool is_reload_delayed(wxGLCanvas* canvas);
static void enable_layers_editing(wxGLCanvas* canvas, bool enable);
static void enable_warning_texture(wxGLCanvas* canvas, bool enable);
static void enable_legend_texture(wxGLCanvas* canvas, bool enable);
static void enable_picking(wxGLCanvas* canvas, bool enable);
static void enable_moving(wxGLCanvas* canvas, bool enable);
static void enable_gizmos(wxGLCanvas* canvas, bool enable);
static void enable_toolbar(wxGLCanvas* canvas, bool enable);
static void enable_shader(wxGLCanvas* canvas, bool enable);
static void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable);
static void enable_dynamic_background(wxGLCanvas* canvas, bool enable);
static void allow_multisample(wxGLCanvas* canvas, bool allow);
static void enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable);
static bool is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name);
static void zoom_to_bed(wxGLCanvas* canvas);
static void zoom_to_volumes(wxGLCanvas* canvas);
static void select_view(wxGLCanvas* canvas, const std::string& direction);
static void set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other);
static void update_volumes_colors_by_extruder(wxGLCanvas* canvas);
static void update_gizmos_data(wxGLCanvas* canvas);
static void render(wxGLCanvas* canvas);
static std::vector<double> get_current_print_zs(wxGLCanvas* canvas, bool active_only);
static void set_toolpaths_range(wxGLCanvas* canvas, double low, double high);
static std::vector<int> load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs);
static std::vector<int> load_object(wxGLCanvas* canvas, const Model* model, int obj_idx);
static int get_first_volume_id(wxGLCanvas* canvas, int obj_idx);
static int get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx);
#if ENABLE_MIRROR
#if ENABLE_EXTENDED_SELECTION
static void mirror_selection(wxGLCanvas* canvas, Axis axis);
#endif // ENABLE_EXTENDED_SELECTION
#endif // ENABLE_MIRROR
static void reload_scene(wxGLCanvas* canvas, bool force);
static void load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors);
static void load_preview(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors);
static void reset_legend_texture(wxGLCanvas* canvas);
static void thick_lines_to_verts(const Lines& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, double top_z, GLVolume& volume);
static void thick_lines_to_verts(const Lines3& lines, const std::vector<double>& widths, const std::vector<double>& heights, bool closed, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GLVolume& volume);
static void extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GLVolume& volume);
static void polyline3_to_verts(const Polyline3& polyline, double width, double height, GLVolume& volume);
static void point3_to_verts(const Vec3crd& point, double width, double height, GLVolume& volume);
};
}
#endif

View file

@ -0,0 +1,136 @@
#include "AboutDialog.hpp"
#include "../../libslic3r/Utils.hpp"
namespace Slic3r {
namespace GUI {
AboutDialogLogo::AboutDialogLogo(wxWindow* parent)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
{
this->SetBackgroundColour(*wxWHITE);
this->logo = wxBitmap(from_u8(Slic3r::var("Slic3r_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();
}
AboutDialog::AboutDialog()
: wxDialog(NULL, wxID_ANY, _(L("About Slic3r")), wxDefaultPosition, wxDefaultSize, wxCAPTION)
{
wxColour bgr_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, 20);
// logo
wxBitmap logo_bmp = wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG);
auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(logo_bmp));
hsizer->Add(logo, 1, wxEXPAND | wxTOP | wxBOTTOM, 35);
wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
#ifdef __WXMSW__
int proportion = 2;
#else
int proportion = 3;
#endif
hsizer->Add(vsizer, proportion, wxEXPAND|wxLEFT, 20);
// title
{
wxStaticText* title = new wxStaticText(this, wxID_ANY, "Slic3r Prusa Edition", wxDefaultPosition, wxDefaultSize);
wxFont title_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
title_font.SetWeight(wxFONTWEIGHT_BOLD);
title_font.SetFamily(wxFONTFAMILY_ROMAN);
title_font.SetPointSize(24);
title->SetFont(title_font);
vsizer->Add(title, 0, wxALIGN_LEFT | wxTOP, 10);
}
// version
{
auto version_string = _(L("Version"))+ " " + std::string(SLIC3R_VERSION);
wxStaticText* version = new wxStaticText(this, wxID_ANY, version_string.c_str(), wxDefaultPosition, wxDefaultSize);
wxFont version_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
#ifdef __WXMSW__
version_font.SetPointSize(9);
#else
version_font.SetPointSize(11);
#endif
version->SetFont(version_font);
vsizer->Add(version, 0, wxALIGN_LEFT | wxBOTTOM, 10);
}
// text
wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO/*NEVER*/);
{
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
const auto text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
const int fs = font.GetPointSize()-1;
int size[] = {fs,fs,fs,fs,fs,fs,fs};
html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
html->SetBorders(2);
const auto text = wxString::Format(
"<html>"
"<body bgcolor= %s link= %s>"
"<font color=%s>"
"Copyright &copy; 2016-2018 Prusa Research. <br />"
"Copyright &copy; 2011-2017 Alessandro Ranellucci. <br />"
"<a href=\"http://slic3r.org/\">Slic3r</a> is licensed under the "
"<a href=\"http://www.gnu.org/licenses/agpl-3.0.html\">GNU Affero General Public License, version 3</a>."
"<br /><br />"
"Contributions by Henrik Brix Andersen, Nicolas Dandrimont, Mark Hindess, Petr Ledvina, Joseph Lenox, Y. Sapir, Mike Sheldrake, Vojtech Bubnik and numerous others. "
"Manual by Gary Hodgson. Inspired by the RepRap community. <br />"
"Slic3r logo designed by Corey Daniels, <a href=\"http://www.famfamfam.com/lab/icons/silk/\">Silk Icon Set</a> designed by Mark James. "
"</font>"
"</body>"
"</html>", bgr_clr_str, text_clr_str, text_clr_str);
html->SetPage(text);
vsizer->Add(html, 1, wxEXPAND | wxBOTTOM, 10);
html->Bind(wxEVT_HTML_LINK_CLICKED, &AboutDialog::onLinkClicked, this);
}
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE);
this->SetEscapeId(wxID_CLOSE);
this->Bind(wxEVT_BUTTON, &AboutDialog::onCloseDialog, this, wxID_CLOSE);
vsizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
this->Bind(wxEVT_LEFT_DOWN, &AboutDialog::onCloseDialog, this);
logo->Bind(wxEVT_LEFT_DOWN, &AboutDialog::onCloseDialog, this);
SetSizer(main_sizer);
main_sizer->SetSizeHints(this);
}
void AboutDialog::onLinkClicked(wxHtmlLinkEvent &event)
{
wxLaunchDefaultBrowser(event.GetLinkInfo().GetHref());
event.Skip(false);
}
void AboutDialog::onCloseDialog(wxEvent &)
{
this->EndModal(wxID_CLOSE);
this->Close();
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,36 @@
#ifndef slic3r_GUI_AboutDialog_hpp_
#define slic3r_GUI_AboutDialog_hpp_
#include "GUI.hpp"
#include <wx/wx.h>
#include <wx/intl.h>
#include <wx/html/htmlwin.h>
namespace Slic3r {
namespace GUI {
class AboutDialogLogo : public wxPanel
{
public:
AboutDialogLogo(wxWindow* parent);
private:
wxBitmap logo;
void onRepaint(wxEvent &event);
};
class AboutDialog : public wxDialog
{
public:
AboutDialog();
private:
void onLinkClicked(wxHtmlLinkEvent &event);
void onCloseDialog(wxEvent &);
};
} // namespace GUI
} // namespace Slic3r
#endif

View file

@ -0,0 +1,266 @@
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/Utils.hpp"
#include "AppConfig.hpp"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <utility>
#include <assert.h>
#include <vector>
#include <stdexcept>
#include <boost/filesystem.hpp>
#include <boost/nowide/cenv.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/algorithm/string/predicate.hpp>
namespace Slic3r {
static const std::string VENDOR_PREFIX = "vendor:";
static const std::string MODEL_PREFIX = "model:";
static const std::string VERSION_CHECK_URL = "https://raw.githubusercontent.com/prusa3d/Slic3r-settings/master/live/Slic3rPE.version";
void AppConfig::reset()
{
m_storage.clear();
set_defaults();
};
// Override missing or keys with their defaults.
void AppConfig::set_defaults()
{
// Reset the empty fields to defaults.
if (get("autocenter").empty())
set("autocenter", "0");
// Disable background processing by default as it is not stable.
if (get("background_processing").empty())
set("background_processing", "0");
// If set, the "Controller" tab for the control of the printer over serial line and the serial port settings are hidden.
// By default, Prusa has the controller hidden.
if (get("no_controller").empty())
set("no_controller", "1");
// If set, the "- default -" selections of print/filament/printer are suppressed, if there is a valid preset available.
if (get("no_defaults").empty())
set("no_defaults", "1");
if (get("show_incompatible_presets").empty())
set("show_incompatible_presets", "0");
if (get("version_check").empty())
set("version_check", "1");
if (get("preset_update").empty())
set("preset_update", "1");
// Use OpenGL 1.1 even if OpenGL 2.0 is available. This is mainly to support some buggy Intel HD Graphics drivers.
// https://github.com/prusa3d/Slic3r/issues/233
if (get("use_legacy_opengl").empty())
set("use_legacy_opengl", "0");
if (get("remember_output_path").empty())
set("remember_output_path", "1");
// Remove legacy window positions/sizes
erase("", "main_frame_maximized");
erase("", "main_frame_pos");
erase("", "main_frame_size");
erase("", "object_settings_maximized");
erase("", "object_settings_pos");
erase("", "object_settings_size");
}
void AppConfig::load()
{
// 1) Read the complete config file into a boost::property_tree.
namespace pt = boost::property_tree;
pt::ptree tree;
boost::nowide::ifstream ifs(AppConfig::config_path());
pt::read_ini(ifs, tree);
// 2) Parse the property_tree, extract the sections and key / value pairs.
for (const auto &section : tree) {
if (section.second.empty()) {
// This may be a top level (no section) entry, or an empty section.
std::string data = section.second.data();
if (! data.empty())
// If there is a non-empty data, then it must be a top-level (without a section) config entry.
m_storage[""][section.first] = data;
} else if (boost::starts_with(section.first, VENDOR_PREFIX)) {
// This is a vendor section listing enabled model / variants
const auto vendor_name = section.first.substr(VENDOR_PREFIX.size());
auto &vendor = m_vendors[vendor_name];
for (const auto &kvp : section.second) {
if (! boost::starts_with(kvp.first, MODEL_PREFIX)) { continue; }
const auto model_name = kvp.first.substr(MODEL_PREFIX.size());
std::vector<std::string> variants;
if (! unescape_strings_cstyle(kvp.second.data(), variants)) { continue; }
for (const auto &variant : variants) {
vendor[model_name].insert(variant);
}
}
} else {
// This must be a section name. Read the entries of a section.
std::map<std::string, std::string> &storage = m_storage[section.first];
for (auto &kvp : section.second)
storage[kvp.first] = kvp.second.data();
}
}
// Figure out if datadir has legacy presets
auto ini_ver = Semver::parse(get("version"));
m_legacy_datadir = false;
if (ini_ver) {
m_orig_version = *ini_ver;
// Make 1.40.0 alphas compare well
ini_ver->set_metadata(boost::none);
ini_ver->set_prerelease(boost::none);
m_legacy_datadir = ini_ver < Semver(1, 40, 0);
}
// Override missing or keys with their defaults.
this->set_defaults();
m_dirty = false;
}
void AppConfig::save()
{
boost::nowide::ofstream c;
c.open(AppConfig::config_path(), std::ios::out | std::ios::trunc);
c << "# " << Slic3r::header_slic3r_generated() << std::endl;
// Make sure the "no" category is written first.
for (const std::pair<std::string, std::string> &kvp : m_storage[""])
c << kvp.first << " = " << kvp.second << std::endl;
// Write the other categories.
for (const auto category : m_storage) {
if (category.first.empty())
continue;
c << std::endl << "[" << category.first << "]" << std::endl;
for (const std::pair<std::string, std::string> &kvp : category.second)
c << kvp.first << " = " << kvp.second << std::endl;
}
// Write vendor sections
for (const auto &vendor : m_vendors) {
size_t size_sum = 0;
for (const auto &model : vendor.second) { size_sum += model.second.size(); }
if (size_sum == 0) { continue; }
c << std::endl << "[" << VENDOR_PREFIX << vendor.first << "]" << std::endl;
for (const auto &model : vendor.second) {
if (model.second.size() == 0) { continue; }
const std::vector<std::string> variants(model.second.begin(), model.second.end());
const auto escaped = escape_strings_cstyle(variants);
c << MODEL_PREFIX << model.first << " = " << escaped << std::endl;
}
}
c.close();
m_dirty = false;
}
bool AppConfig::get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const
{
const auto it_v = m_vendors.find(vendor);
if (it_v == m_vendors.end()) { return false; }
const auto it_m = it_v->second.find(model);
return it_m == it_v->second.end() ? false : it_m->second.find(variant) != it_m->second.end();
}
void AppConfig::set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable)
{
if (enable) {
if (get_variant(vendor, model, variant)) { return; }
m_vendors[vendor][model].insert(variant);
} else {
auto it_v = m_vendors.find(vendor);
if (it_v == m_vendors.end()) { return; }
auto it_m = it_v->second.find(model);
if (it_m == it_v->second.end()) { return; }
auto it_var = it_m->second.find(variant);
if (it_var == it_m->second.end()) { return; }
it_m->second.erase(it_var);
}
// If we got here, there was an update
m_dirty = true;
}
void AppConfig::set_vendors(const AppConfig &from)
{
m_vendors = from.m_vendors;
m_dirty = true;
}
std::string AppConfig::get_last_dir() const
{
const auto it = m_storage.find("recent");
if (it != m_storage.end()) {
{
const auto it2 = it->second.find("skein_directory");
if (it2 != it->second.end() && ! it2->second.empty())
return it2->second;
}
{
const auto it2 = it->second.find("config_directory");
if (it2 != it->second.end() && ! it2->second.empty())
return it2->second;
}
}
return std::string();
}
void AppConfig::update_config_dir(const std::string &dir)
{
this->set("recent", "config_directory", dir);
}
void AppConfig::update_skein_dir(const std::string &dir)
{
this->set("recent", "skein_directory", dir);
}
std::string AppConfig::get_last_output_dir(const std::string &alt) const
{
const auto it = m_storage.find("");
if (it != m_storage.end()) {
const auto it2 = it->second.find("last_output_path");
const auto it3 = it->second.find("remember_output_path");
if (it2 != it->second.end() && it3 != it->second.end() && ! it2->second.empty() && it3->second == "1")
return it2->second;
}
return alt;
}
void AppConfig::update_last_output_dir(const std::string &dir)
{
this->set("", "last_output_path", dir);
}
void AppConfig::reset_selections()
{
auto it = m_storage.find("presets");
if (it != m_storage.end()) {
it->second.erase("print");
it->second.erase("filament");
it->second.erase("sla_material");
it->second.erase("printer");
m_dirty = true;
}
}
std::string AppConfig::config_path()
{
return (boost::filesystem::path(Slic3r::data_dir()) / "slic3r.ini").make_preferred().string();
}
std::string AppConfig::version_check_url() const
{
auto from_settings = get("version_check_url");
return from_settings.empty() ? VERSION_CHECK_URL : from_settings;
}
bool AppConfig::exists()
{
return boost::filesystem::exists(AppConfig::config_path());
}
}; // namespace Slic3r

View file

@ -0,0 +1,140 @@
#ifndef slic3r_AppConfig_hpp_
#define slic3r_AppConfig_hpp_
#include <set>
#include <map>
#include <string>
#include "libslic3r/Config.hpp"
#include "slic3r/Utils/Semver.hpp"
namespace Slic3r {
class AppConfig
{
public:
AppConfig() :
m_dirty(false),
m_orig_version(Semver::invalid()),
m_legacy_datadir(false)
{
this->reset();
}
// Clear and reset to defaults.
void reset();
// Override missing or keys with their defaults.
void set_defaults();
// Load the slic3r.ini from a user profile directory (or a datadir, if configured).
void load();
// Store the slic3r.ini into a user profile directory (or a datadir, if configured).
void save();
// Does this config need to be saved?
bool dirty() const { return m_dirty; }
// Const accessor, it will return false if a section or a key does not exist.
bool get(const std::string &section, const std::string &key, std::string &value) const
{
value.clear();
auto it = m_storage.find(section);
if (it == m_storage.end())
return false;
auto it2 = it->second.find(key);
if (it2 == it->second.end())
return false;
value = it2->second;
return true;
}
std::string get(const std::string &section, const std::string &key) const
{ std::string value; this->get(section, key, value); return value; }
std::string get(const std::string &key) const
{ std::string value; this->get("", key, value); return value; }
void set(const std::string &section, const std::string &key, const std::string &value)
{
std::string &old = m_storage[section][key];
if (old != value) {
old = value;
m_dirty = true;
}
}
void set(const std::string &key, const std::string &value)
{ this->set("", key, value); }
bool has(const std::string &section, const std::string &key) const
{
auto it = m_storage.find(section);
if (it == m_storage.end())
return false;
auto it2 = it->second.find(key);
return it2 != it->second.end() && ! it2->second.empty();
}
bool has(const std::string &key) const
{ return this->has("", key); }
void erase(const std::string &section, const std::string &key)
{
auto it = m_storage.find(section);
if (it != m_storage.end()) {
it->second.erase(key);
}
}
void clear_section(const std::string &section)
{ m_storage[section].clear(); }
typedef std::map<std::string, std::map<std::string, std::set<std::string>>> VendorMap;
bool get_variant(const std::string &vendor, const std::string &model, const std::string &variant) const;
void set_variant(const std::string &vendor, const std::string &model, const std::string &variant, bool enable);
void set_vendors(const AppConfig &from);
void set_vendors(const VendorMap &vendors) { m_vendors = vendors; m_dirty = true; }
void set_vendors(VendorMap &&vendors) { m_vendors = std::move(vendors); m_dirty = true; }
const VendorMap& vendors() const { return m_vendors; }
// return recent/skein_directory or recent/config_directory or empty string.
std::string get_last_dir() const;
void update_config_dir(const std::string &dir);
void update_skein_dir(const std::string &dir);
std::string get_last_output_dir(const std::string &alt) const;
void update_last_output_dir(const std::string &dir);
// reset the current print / filament / printer selections, so that
// the PresetBundle::load_selections(const AppConfig &config) call will select
// the first non-default preset when called.
void reset_selections();
// Get the default config path from Slic3r::data_dir().
static std::string config_path();
// Returns true if the user's data directory comes from before Slic3r 1.40.0 (no updating)
bool legacy_datadir() const { return m_legacy_datadir; }
void set_legacy_datadir(bool value) { m_legacy_datadir = value; }
// Get the Slic3r version check url.
// This returns a hardcoded string unless it is overriden by "version_check_url" in the ini file.
std::string version_check_url() const;
// Returns the original Slic3r version found in the ini file before it was overwritten
// by the current version
Semver orig_version() const { return m_orig_version; }
// Does the config file exist?
static bool exists();
private:
// Map of section, name -> value
std::map<std::string, std::map<std::string, std::string>> m_storage;
// Map of enabled vendors / models / variants
VendorMap m_vendors;
// Has any value been modified since the config.ini has been last saved or loaded?
bool m_dirty;
// Original version found in the ini file before it was overwritten
Semver m_orig_version;
// Whether the existing version is before system profiles & configuration updating
bool m_legacy_datadir;
};
}; // namespace Slic3r
#endif /* slic3r_AppConfig_hpp_ */

View file

@ -0,0 +1,234 @@
#include "BackgroundSlicingProcess.hpp"
#include "GUI_App.hpp"
#include <wx/event.h>
#include <wx/panel.h>
#include <wx/stdpaths.h>
// Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx.
#include "../../libslic3r/Print.hpp"
#include "../../libslic3r/Utils.hpp"
#include "../../libslic3r/GCode/PostProcessor.hpp"
//#undef NDEBUG
#include <cassert>
#include <stdexcept>
#include <boost/format.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/nowide/cstdio.hpp>
namespace Slic3r {
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());
}
void BackgroundSlicingProcess::thread_proc()
{
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::string error;
try {
assert(m_print != nullptr);
m_print->process();
if (! m_print->canceled()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_sliced_id));
m_print->export_gcode(m_temp_output_path, m_gcode_preview_data);
if (! m_print->canceled() && ! this->is_step_done(bspsGCodeFinalize)) {
this->set_step_started(bspsGCodeFinalize);
if (! m_export_path.empty()) {
//FIXME localize the messages
if (copy_file(m_temp_output_path, m_export_path) != 0)
throw std::runtime_error("Copying of the temporary G-code to the output G-code failed");
m_print->set_status(95, "Running post-processing scripts");
run_post_process_scripts(m_export_path, m_print->config());
m_print->set_status(100, "G-code file exported to " + m_export_path);
} else {
m_print->set_status(100, "Slicing complete");
}
this->set_step_done(bspsGCodeFinalize);
}
}
} catch (CanceledException &ex) {
// Canceled, this is all right.
assert(m_print->canceled());
} catch (std::exception &ex) {
error = ex.what();
} catch (...) {
error = "Unknown C++ exception.";
}
lck.lock();
m_state = m_print->canceled() ? STATE_CANCELED : STATE_FINISHED;
wxCommandEvent evt(m_event_finished_id);
evt.SetString(error);
evt.SetInt(m_print->canceled() ? -1 : (error.empty() ? 1 : 0));
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
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.
}
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()
{
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 = std::thread([this]{this->thread_proc();});
// 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 std::runtime_error("Cannot start a background task, the worker thread is not idle.");
m_state = STATE_STARTED;
m_print->set_cancel_callback([this](){ this->stop(); });
lck.unlock();
m_condition.notify_one();
return true;
}
bool BackgroundSlicingProcess::stop()
{
std::unique_lock<std::mutex> lck(m_mutex);
if (m_state == STATE_INITIAL) {
// this->m_export_path.clear();
return false;
}
// assert(this->running());
if (m_state == STATE_STARTED || m_state == STATE_RUNNING) {
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([](){});
}
// this->m_export_path.clear();
return true;
}
// 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.
bool BackgroundSlicingProcess::apply_config(const DynamicPrintConfig &config)
{
this->stop();
bool invalidated = m_print->apply_config(config);
return invalidated;
}
// 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)
{
// this->stop();
Print::ApplyStatus invalidated = m_print->apply(model, config);
return invalidated;
}
// Set the output path of the G-code.
void BackgroundSlicingProcess::schedule_export(const std::string &path)
{
assert(m_export_path.empty());
if (! m_export_path.empty())
return;
// Guard against entering the export step before changing the export path.
tbb::mutex::scoped_lock lock(m_step_state_mutex);
this->invalidate_step(bspsGCodeFinalize);
m_export_path = path;
}
void BackgroundSlicingProcess::reset_export()
{
assert(! this->running());
if (! this->running()) {
m_export_path.clear();
// invalidate_step expects the mutex to be locked.
tbb::mutex::scoped_lock lock(m_step_state_mutex);
this->invalidate_step(bspsGCodeFinalize);
}
}
void BackgroundSlicingProcess::set_step_started(BackgroundSlicingProcessStep step)
{
m_step_state.set_started(step, m_step_state_mutex);
if (m_print->canceled())
throw CanceledException();
}
void BackgroundSlicingProcess::set_step_done(BackgroundSlicingProcessStep step)
{
m_step_state.set_done(step, m_step_state_mutex);
if (m_print->canceled())
throw CanceledException();
}
bool BackgroundSlicingProcess::invalidate_step(BackgroundSlicingProcessStep step)
{
bool invalidated = m_step_state.invalidate(step, m_step_state_mutex, [this](){ this->stop(); });
return invalidated;
}
bool BackgroundSlicingProcess::invalidate_all_steps()
{
return m_step_state.invalidate_all(m_step_state_mutex, [this](){ this->stop(); });
}
}; // namespace Slic3r

View file

@ -0,0 +1,117 @@
#ifndef slic3r_GUI_BackgroundSlicingProcess_hpp_
#define slic3r_GUI_BackgroundSlicingProcess_hpp_
#include <string>
#include <condition_variable>
#include <mutex>
#include <thread>
#include "Print.hpp"
namespace Slic3r {
class DynamicPrintConfig;
class GCodePreviewData;
class Model;
// 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_print(Print *print) { m_print = print; }
void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; }
// The following wxCommandEvent will be sent to the UI thread / Platter 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_sliced_event(int event_id) { m_event_sliced_id = event_id; }
// The following wxCommandEvent will be sent to the UI thread / Platter 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; }
// 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();
// 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.
bool apply_config(const DynamicPrintConfig &config);
// 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 apply(const Model &model, const DynamicPrintConfig &config);
// Set the export path of the G-code.
// Once the path is set, the G-code
void schedule_export(const std::string &path);
// 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(); }
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; }
private:
void thread_proc();
void join_background_thread();
Print *m_print = nullptr;
// Data structure, to which the G-code export writes its annotations.
GCodePreviewData *m_gcode_preview_data = 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;
// Thread, on which the background processing is executed. The thread will always be present
// and ready to execute the slicing process.
std::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;
PrintState<BackgroundSlicingProcessStep, bspsCount> m_step_state;
mutable tbb::mutex m_step_state_mutex;
void set_step_started(BackgroundSlicingProcessStep step);
void set_step_done(BackgroundSlicingProcessStep step);
bool is_step_done(BackgroundSlicingProcessStep step) const { return m_step_state.is_done(step); }
bool invalidate_step(BackgroundSlicingProcessStep step);
bool invalidate_all_steps();
// wxWidgets command ID to be sent to the platter to inform that the slicing is finished, and the G-code export will continue.
int m_event_sliced_id = 0;
// wxWidgets command ID to be sent to the platter to inform that the task finished.
int m_event_finished_id = 0;
};
}; // namespace Slic3r
#endif /* slic3r_GUI_BackgroundSlicingProcess_hpp_ */

View file

@ -0,0 +1,337 @@
#include "BedShapeDialog.hpp"
#include <wx/sizer.h>
#include <wx/statbox.h>
#include <wx/wx.h>
#include "Polygon.hpp"
#include "BoundingBox.hpp"
#include <wx/numformatter.h>
#include "Model.hpp"
#include "boost/nowide/iostream.hpp"
#include <algorithm>
namespace Slic3r {
namespace GUI {
void BedShapeDialog::build_dialog(ConfigOptionPoints* default_pt)
{
m_panel = new BedShapePanel(this);
m_panel->build_panel(default_pt);
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);
SetSizer(main_sizer);
SetMinSize(GetSize());
main_sizer->SetSizeHints(this);
// needed to actually free memory
this->Bind(wxEVT_CLOSE_WINDOW, ([this](wxCloseEvent e){
EndModal(wxID_OK);
Destroy();
}));
}
void BedShapePanel::build_panel(ConfigOptionPoints* default_pt)
{
// on_change(nullptr);
auto box = new wxStaticBox(this, wxID_ANY, _(L("Shape")));
auto sbsizer = new wxStaticBoxSizer(box, wxVERTICAL);
// shape options
m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(300, -1), wxCHB_TOP);
sbsizer->Add(m_shape_options_book);
auto optgroup = init_shape_options_page(_(L("Rectangular")));
ConfigOptionDef def;
def.type = coPoints;
def.default_value = new ConfigOptionPoints{ Vec2d(200, 200) };
def.label = L("Size");
def.tooltip = L("Size in X and Y of the rectangular plate.");
Option option(def, "rect_size");
optgroup->append_single_option_line(option);
def.type = coPoints;
def.default_value = new ConfigOptionPoints{ Vec2d(0, 0) };
def.label = L("Origin");
def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.");
option = Option(def, "rect_origin");
optgroup->append_single_option_line(option);
optgroup = init_shape_options_page(_(L("Circular")));
def.type = coFloat;
def.default_value = new ConfigOptionFloat(200);
def.sidetext = L("mm");
def.label = L("Diameter");
def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center.");
option = Option(def, "diameter");
optgroup->append_single_option_line(option);
optgroup = init_shape_options_page(_(L("Custom")));
Line line{ "", "" };
line.full_width = 1;
line.widget = [this](wxWindow* parent) {
auto btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL...")), wxDefaultPosition, wxDefaultSize);
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn);
btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e)
{
load_stl();
}));
return sizer;
};
optgroup->append_line(line);
Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent e)
{
update_shape();
}));
// right pane with preview canvas
m_canvas = new Bed_2D(this);
m_canvas->m_bed_shape = default_pt->values;
// main sizer
auto top_sizer = new wxBoxSizer(wxHORIZONTAL);
top_sizer->Add(sbsizer, 0, wxEXPAND | wxLeft | wxTOP | wxBOTTOM, 10);
if (m_canvas)
top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10) ;
SetSizerAndFit(top_sizer);
set_shape(default_pt);
update_preview();
}
#define SHAPE_RECTANGULAR 0
#define SHAPE_CIRCULAR 1
#define SHAPE_CUSTOM 2
// Called from the constructor.
// Create a panel for a rectangular / circular / custom bed shape.
ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(wxString title){
auto panel = new wxPanel(m_shape_options_book);
ConfigOptionsGroupShp optgroup;
optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Settings")));
optgroup->label_width = 100;
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;
}
// 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(ConfigOptionPoints* points)
{
auto polygon = Polygon::new_scale(points->values);
// is this a rectangle ?
if (points->size() == 4) {
auto lines = polygon.lines();
if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) {
// okay, it's a rectangle
// find origin
coordf_t x_min, x_max, y_min, y_max;
x_max = x_min = points->values[0](0);
y_max = y_min = points->values[0](1);
for (auto pt : points->values)
{
x_min = std::min(x_min, pt(0));
x_max = std::max(x_max, pt(0));
y_min = std::min(y_min, pt(1));
y_max = std::max(y_max, pt(1));
}
auto origin = new ConfigOptionPoints{ Vec2d(-x_min, -y_min) };
m_shape_options_book->SetSelection(SHAPE_RECTANGULAR);
auto optgroup = m_optgroups[SHAPE_RECTANGULAR];
optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(x_max - x_min, y_max - y_min) });//[x_max - x_min, y_max - y_min]);
optgroup->set_value("rect_origin", origin);
update_shape();
return;
}
}
// is this a circle ?
{
// Analyze the array of points.Do they reside on a circle ?
auto center = polygon.bounding_box().center();
std::vector<double> vertex_distances;
double avg_dist = 0;
for (auto pt: polygon.points)
{
double distance = (pt - center).cast<double>().norm();
vertex_distances.push_back(distance);
avg_dist += distance;
}
avg_dist /= vertex_distances.size();
bool defined_value = true;
for (auto el: vertex_distances)
{
if (abs(el - avg_dist) > 10 * SCALED_EPSILON)
defined_value = false;
break;
}
if (defined_value) {
// all vertices are equidistant to center
m_shape_options_book->SetSelection(SHAPE_CIRCULAR);
auto optgroup = m_optgroups[SHAPE_CIRCULAR];
boost::any ret = wxNumberFormatter::ToString(unscale<double>(avg_dist * 2), 0);
optgroup->set_value("diameter", ret);
update_shape();
return;
}
}
if (points->size() < 3) {
// Invalid polygon.Revert to default bed dimensions.
m_shape_options_book->SetSelection(SHAPE_RECTANGULAR);
auto optgroup = m_optgroups[SHAPE_RECTANGULAR];
optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(200, 200) });
optgroup->set_value("rect_origin", new ConfigOptionPoints{ Vec2d(0, 0) });
update_shape();
return;
}
// This is a custom bed shape, use the polygon provided.
m_shape_options_book->SetSelection(SHAPE_CUSTOM);
// Copy the polygon to the canvas, make a copy of the array.
m_canvas->m_bed_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();
if (page_idx == SHAPE_RECTANGULAR) {
Vec2d rect_size(Vec2d::Zero());
Vec2d rect_origin(Vec2d::Zero());
try{
rect_size = boost::any_cast<Vec2d>(m_optgroups[SHAPE_RECTANGULAR]->get_value("rect_size")); }
catch (const std::exception &e){
return;
}
try{
rect_origin = boost::any_cast<Vec2d>(m_optgroups[SHAPE_RECTANGULAR]->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_canvas->m_bed_shape = { Vec2d(x0, y0),
Vec2d(x1, y0),
Vec2d(x1, y1),
Vec2d(x0, y1)};
}
else if(page_idx == SHAPE_CIRCULAR) {
double diameter;
try{
diameter = boost::any_cast<double>(m_optgroups[SHAPE_CIRCULAR]->get_value("diameter"));
}
catch (const std::exception &e){
return;
}
if (diameter == 0.0) return ;
auto r = diameter / 2;
auto twopi = 2 * PI;
auto edges = 60;
std::vector<Vec2d> points;
for (size_t i = 1; i <= 60; ++i){
auto angle = i * twopi / edges;
points.push_back(Vec2d(r*cos(angle), r*sin(angle)));
}
m_canvas->m_bed_shape = points;
}
// $self->{on_change}->();
update_preview();
}
// Loads an stl file, projects it to the XY plane and calculates a polygon.
void BedShapePanel::load_stl()
{
auto dialog = new wxFileDialog(this, _(L("Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):")), "", "",
file_wildcards[FT_MODEL], wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog->ShowModal() != wxID_OK) {
dialog->Destroy();
return;
}
wxArrayString input_file;
dialog->GetPaths(input_file);
dialog->Destroy();
std::string file_name = input_file[0].ToStdString();
Model model;
try {
model = Model::read_from_file(file_name);
}
catch (std::exception &e) {
auto msg = _(L("Error! ")) + file_name + " : " + e.what() + ".";
show_error(this, msg);
exit(1);
}
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_canvas->m_bed_shape = points;
update_preview();
}
} // GUI
} // Slic3r

View file

@ -0,0 +1,56 @@
#ifndef slic3r_BedShapeDialog_hpp_
#define slic3r_BedShapeDialog_hpp_
// The bed shape dialog.
// The dialog opens from Print Settins tab->Bed Shape : Set...
#include "OptionsGroup.hpp"
#include "2DBed.hpp"
#include <wx/dialog.h>
#include <wx/choicebk.h>
namespace Slic3r {
namespace GUI {
using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>;
class BedShapePanel : public wxPanel
{
wxChoicebook* m_shape_options_book;
Bed_2D* m_canvas;
std::vector <ConfigOptionsGroupShp> m_optgroups;
public:
BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY){}
~BedShapePanel(){}
void build_panel(ConfigOptionPoints* default_pt);
ConfigOptionsGroupShp init_shape_options_page(wxString title);
void set_shape(ConfigOptionPoints* points);
void update_preview();
void update_shape();
void load_stl();
// Returns the resulting bed shape polygon. This value will be stored to the ini file.
std::vector<Vec2d> GetValue() { return m_canvas->m_bed_shape; }
};
class BedShapeDialog : public wxDialog
{
BedShapePanel* m_panel;
public:
BedShapeDialog(wxWindow* parent) : wxDialog(parent, wxID_ANY, _(L("Bed Shape")),
wxDefaultPosition, wxSize(350, 700), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER){}
~BedShapeDialog(){ }
void build_dialog(ConfigOptionPoints* default_pt);
std::vector<Vec2d> GetValue() { return m_panel->GetValue(); }
};
} // GUI
} // Slic3r
#endif /* slic3r_BedShapeDialog_hpp_ */

View file

@ -0,0 +1,174 @@
#include "BitmapCache.hpp"
#if ! defined(WIN32) && ! defined(__APPLE__)
#define BROKEN_ALPHA
#endif
#ifdef BROKEN_ALPHA
#include <wx/mstream.h>
#include <wx/rawbmp.h>
#endif /* BROKEN_ALPHA */
namespace Slic3r { namespace GUI {
void BitmapCache::clear()
{
for (std::pair<const std::string, wxBitmap*> &bitmap : m_map)
delete bitmap.second;
}
static wxBitmap wxImage_to_wxBitmap_with_alpha(wxImage &&image)
{
#ifdef BROKEN_ALPHA
wxMemoryOutputStream stream;
image.SaveFile(stream, wxBITMAP_TYPE_PNG);
wxStreamBuffer *buf = stream.GetOutputStreamBuffer();
return wxBitmap::NewFromPNGData(buf->GetBufferStart(), buf->GetBufferSize());
#else
return wxBitmap(std::move(image));
#endif
}
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, size_t width, size_t height)
{
wxBitmap *bitmap = nullptr;
auto it = m_map.find(bitmap_key);
if (it == m_map.end()) {
bitmap = new wxBitmap(width, height);
m_map[bitmap_key] = bitmap;
} else {
bitmap = it->second;
if (bitmap->GetWidth() != width || bitmap->GetHeight() != height)
bitmap->Create(width, height);
}
#ifndef BROKEN_ALPHA
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(const std::string &bitmap_key, const wxBitmap &bmp, const wxBitmap &bmp2)
{
// Copying the wxBitmaps is cheap as the bitmap's content is reference counted.
const wxBitmap bmps[2] = { bmp, bmp2 };
return this->insert(bitmap_key, bmps, bmps + 2);
}
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3)
{
// Copying the wxBitmaps is cheap as the bitmap's content is reference counted.
const wxBitmap bmps[3] = { bmp, bmp2, bmp3 };
return this->insert(bitmap_key, bmps, bmps + 3);
}
wxBitmap* BitmapCache::insert(const std::string &bitmap_key, const wxBitmap *begin, const wxBitmap *end)
{
size_t width = 0;
size_t height = 0;
for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) {
width += bmp->GetWidth();
height = std::max<size_t>(height, bmp->GetHeight());
}
#ifdef BROKEN_ALPHA
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 wxBitmap *bmp = begin; bmp != end; ++ bmp) {
if (bmp->GetWidth() > 0) {
if (bmp->GetDepth() == 32) {
wxAlphaPixelData data(*const_cast<wxBitmap*>(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(*const_cast<wxBitmap*>(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->GetWidth();
}
return this->insert(bitmap_key, wxImage_to_wxBitmap_with_alpha(std::move(image)));
#else
wxBitmap *bitmap = this->insert(bitmap_key, width, height);
wxMemoryDC memDC;
memDC.SelectObject(*bitmap);
memDC.SetBackground(*wxTRANSPARENT_BRUSH);
memDC.Clear();
size_t x = 0;
for (const wxBitmap *bmp = begin; bmp != end; ++ bmp) {
if (bmp->GetWidth() > 0)
memDC.DrawBitmap(*bmp, x, 0, true);
x += bmp->GetWidth();
}
memDC.SelectObject(wxNullBitmap);
return bitmap;
#endif
}
wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency)
{
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;
}
return wxImage_to_wxBitmap_with_alpha(std::move(image));
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,44 @@
#ifndef SLIC3R_GUI_BITMAP_CACHE_HPP
#define SLIC3R_GUI_BITMAP_CACHE_HPP
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/Config.hpp"
#include "GUI.hpp"
namespace Slic3r { namespace GUI {
class BitmapCache
{
public:
BitmapCache() {}
~BitmapCache() { clear(); }
void clear();
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); }
wxBitmap* insert(const std::string &name, size_t width, size_t height);
wxBitmap* insert(const std::string &name, const wxBitmap &bmp);
wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2);
wxBitmap* insert(const std::string &name, const wxBitmap &bmp, const wxBitmap &bmp2, const wxBitmap &bmp3);
wxBitmap* insert(const std::string &name, const std::vector<wxBitmap> &bmps) { return this->insert(name, &bmps.front(), &bmps.front() + bmps.size()); }
wxBitmap* insert(const std::string &name, const wxBitmap *begin, const wxBitmap *end);
static wxBitmap mksolid(size_t width, size_t height, unsigned char r, unsigned char g, unsigned char b, unsigned char transparency);
static wxBitmap mksolid(size_t width, size_t height, const unsigned char rgb[3]) { return mksolid(width, height, rgb[0], rgb[1], rgb[2], wxALPHA_OPAQUE); }
static wxBitmap mkclear(size_t width, size_t height) { return mksolid(width, height, 0, 0, 0, wxALPHA_TRANSPARENT); }
private:
std::map<std::string, wxBitmap*> m_map;
};
} // GUI
} // Slic3r
#endif /* SLIC3R_GUI_BITMAP_CACHE_HPP */

View file

@ -0,0 +1,200 @@
#include "slic3r/Utils/Bonjour.hpp" // On Windows, boost needs to be included before wxWidgets headers
#include "BonjourDialog.hpp"
#include <set>
#include <mutex>
#include <wx/sizer.h>
#include <wx/button.h>
#include <wx/listctrl.h>
#include <wx/stattext.h>
#include <wx/timer.h>
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/Utils/Bonjour.hpp"
namespace Slic3r {
struct BonjourReplyEvent : public wxEvent
{
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) :
wxDialog(parent, wxID_ANY, _(L("Network lookup"))),
list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxSize(800, 300))),
replies(new ReplySet),
label(new wxStaticText(this, wxID_ANY, "")),
timer(new wxTimer()),
timer_state(0)
{
wxBoxSizer *vsizer = new wxBoxSizer(wxVERTICAL);
vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 10);
list->SetSingleStyle(wxLC_SINGLE_SEL);
list->SetSingleStyle(wxLC_SORT_DESCENDING);
list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 50);
list->AppendColumn(_(L("Hostname")), wxLIST_FORMAT_LEFT, 100);
list->AppendColumn(_(L("Service name")), wxLIST_FORMAT_LEFT, 200);
list->AppendColumn(_(L("OctoPrint version")), wxLIST_FORMAT_LEFT, 50);
vsizer->Add(list, 1, wxEXPAND | wxALL, 10);
wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL);
button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, 10);
button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, 10);
// ^ 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);
}
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);
wxTimerEvent evt_dummy;
on_timer(evt_dummy);
// 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);
bonjour = std::move(Bonjour("octoprint")
.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;
}
replies->insert(std::move(e.reply));
auto selected = get_selected();
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);
list->SetItem(item, 3, reply.version);
}
for (int i = 0; i < 4; i++) {
this->list->SetColumnWidth(i, wxLIST_AUTOSIZE);
if (this->list->GetColumnWidth(i) < 100) { this->list->SetColumnWidth(i, 100); }
}
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 &)
{
const auto search_str = _(L("Searching for devices"));
if (timer_state > 0) {
const std::string dots(timer_state, '.');
label->SetLabel(wxString::Format("%s %s", search_str, dots));
timer_state = (timer_state) % 3 + 1;
} else {
label->SetLabel(wxString::Format("%s: %s", search_str, _(L("Finished"))+"."));
timer->Stop();
}
}
}

View file

@ -0,0 +1,49 @@
#ifndef slic3r_BonjourDialog_hpp_
#define slic3r_BonjourDialog_hpp_
#include <memory>
#include <wx/dialog.h>
class wxListView;
class wxStaticText;
class wxTimer;
class wxTimerEvent;
namespace Slic3r {
class Bonjour;
class BonjourReplyEvent;
class ReplySet;
class BonjourDialog: public wxDialog
{
public:
BonjourDialog(wxWindow *parent);
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;
void on_reply(BonjourReplyEvent &);
void on_timer(wxTimerEvent &);
};
}
#endif

View file

@ -0,0 +1,85 @@
#include "ButtonsDescription.hpp"
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/statbmp.h>
#include <wx/clrpicker.h>
#include "GUI.hpp"
#include "GUI_App.hpp"
namespace Slic3r {
namespace GUI {
ButtonsDescription::ButtonsDescription(wxWindow* parent, t_icon_descriptions* icon_descriptions) :
wxDialog(parent, wxID_ANY, _(L("Buttons And Text Colors Description")), wxDefaultPosition, wxDefaultSize),
m_icon_descriptions(icon_descriptions)
{
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 (auto pair : *m_icon_descriptions)
{
auto icon = new wxStaticBitmap(this, wxID_ANY, *pair.first);
grid_sizer->Add(icon, -1, wxALIGN_CENTRE_VERTICAL);
std::istringstream f(pair.second);
std::string s;
getline(f, s, ';');
auto description = new wxStaticText(this, wxID_ANY, _(s));
grid_sizer->Add(description, -1, wxALIGN_CENTRE_VERTICAL);
getline(f, s, ';');
description = new wxStaticText(this, wxID_ANY, _(s));
grid_sizer->Add(description, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
}
// Text color description
auto sys_label = new wxStaticText(this, wxID_ANY, _(L("Value is the same as the system value")));
sys_label->SetForegroundColour(wxGetApp().get_label_clr_sys());
auto sys_colour = new wxColourPickerCtrl(this, wxID_ANY, wxGetApp().get_label_clr_sys());
sys_colour->Bind(wxEVT_COLOURPICKER_CHANGED, ([sys_colour, sys_label](wxCommandEvent e)
{
sys_label->SetForegroundColour(sys_colour->GetColour());
sys_label->Refresh();
}));
size_t t= 0;
while (t < 3){
grid_sizer->Add(new wxStaticText(this, wxID_ANY, ""), -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
++t;
}
grid_sizer->Add(0, -1, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(sys_colour, -1, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(sys_label, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
auto mod_label = new wxStaticText(this, wxID_ANY, _(L("Value was changed and is not equal to the system value or the last saved preset")));
mod_label->SetForegroundColour(wxGetApp().get_label_clr_modified());
auto mod_colour = new wxColourPickerCtrl(this, wxID_ANY, wxGetApp().get_label_clr_modified());
mod_colour->Bind(wxEVT_COLOURPICKER_CHANGED, ([mod_colour, mod_label](wxCommandEvent e)
{
mod_label->SetForegroundColour(mod_colour->GetColour());
mod_label->Refresh();
}));
grid_sizer->Add(0, -1, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(mod_colour, -1, wxALIGN_CENTRE_VERTICAL);
grid_sizer->Add(mod_label, -1, wxALIGN_CENTRE_VERTICAL | wxEXPAND);
auto buttons = CreateStdDialogButtonSizer(wxOK|wxCANCEL);
main_sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
wxButton* btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
btn->Bind(wxEVT_BUTTON, [sys_colour, mod_colour, this](wxCommandEvent&) {
wxGetApp().set_label_clr_sys(sys_colour->GetColour());
wxGetApp().set_label_clr_modified(mod_colour->GetColour());
EndModal(wxID_OK);
});
SetSizer(main_sizer);
main_sizer->SetSizeHints(this);
}
} // GUI
} // Slic3r

View file

@ -0,0 +1,27 @@
#ifndef slic3r_ButtonsDescription_hpp
#define slic3r_ButtonsDescription_hpp
#include <wx/dialog.h>
#include <vector>
namespace Slic3r {
namespace GUI {
using t_icon_descriptions = std::vector<std::pair<wxBitmap*, std::string>>;
class ButtonsDescription : public wxDialog
{
t_icon_descriptions* m_icon_descriptions;
public:
ButtonsDescription(wxWindow* parent, t_icon_descriptions* icon_descriptions);
~ButtonsDescription(){}
};
} // GUI
} // Slic3r
#endif

View file

@ -0,0 +1,15 @@
#include <exception>
namespace Slic3r {
class ConfigError : public std::runtime_error {
using std::runtime_error::runtime_error;
};
namespace GUI {
class ConfigGUITypeError : public ConfigError {
using ConfigError::ConfigError;
};
}
}

View file

@ -0,0 +1,140 @@
#include "ConfigSnapshotDialog.hpp"
#include "../Config/Snapshot.hpp"
#include "../Utils/Time.hpp"
#include "../../libslic3r/Utils.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 wxString generate_html_row(const Config::Snapshot &snapshot, bool row_even, bool snapshot_active)
{
// Start by declaring a row with an alternating background color.
wxString text = "<tr bgcolor=\"";
text += snapshot_active ? "#B3FFCB" : (row_even ? "#FFFFFF" : "#D5D5D5");
text += "\">";
text += "<td>";
// Format the row header.
text += wxString("<font size=\"5\"><b>") + (snapshot_active ? _(L("Active: ")) : "") +
Utils::format_local_date_time(snapshot.time_captured) + ": " + format_reason(snapshot.reason);
if (! snapshot.comment.empty())
text += " (" + snapshot.comment + ")";
text += "</b></font><br>";
// End of row header.
text += _(L("slic3r version")) + ": " + snapshot.slic3r_version_captured.to_string() + "<br>";
text += _(L("print")) + ": " + snapshot.print + "<br>";
text += _(L("filaments")) + ": " + snapshot.filaments.front() + "<br>";
text += _(L("printer")) + ": " + snapshot.printer + "<br>";
bool 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 slic3r version")) + ": " + vc.version.min_slic3r_version.to_string();
if (vc.version.max_slic3r_version != Semver::inf())
text += ", " + _(L("max slic3r version")) + ": " + vc.version.max_slic3r_version.to_string();
text += "<br>";
for (const std::pair<std::string, std::set<std::string>> &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\">" + _(L("Incompatible with this Slic3r")) + "</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)
{
wxString text =
"<html>"
"<body bgcolor=\"#ffffff\" cellspacing=\"2\" cellpadding=\"0\" border=\"0\" link=\"#800000\">"
"<font color=\"#000000\">";
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);
}
text +=
"</table>"
"</font>"
"</body>"
"</html>";
return text;
}
ConfigSnapshotDialog::ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const wxString &on_snapshot)
: wxDialog(NULL, wxID_ANY, _(L("Configuration Snapshots")), wxDefaultPosition, wxSize(600, 500), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMAXIMIZE_BOX)
{
this->SetBackgroundColour(*wxWHITE);
wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
this->SetSizer(vsizer);
// text
wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO);
{
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
#ifdef __WXMSW__
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);
this->SetEscapeId(wxID_CLOSE);
this->Bind(wxEVT_BUTTON, &ConfigSnapshotDialog::onCloseDialog, this, wxID_CLOSE);
vsizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
}
void ConfigSnapshotDialog::onLinkClicked(wxHtmlLinkEvent &event)
{
m_snapshot_to_activate = event.GetLinkInfo().GetHref();
this->EndModal(wxID_CLOSE);
this->Close();
}
void ConfigSnapshotDialog::onCloseDialog(wxEvent &)
{
this->EndModal(wxID_CLOSE);
this->Close();
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,34 @@
#ifndef slic3r_GUI_ConfigSnapshotDialog_hpp_
#define slic3r_GUI_ConfigSnapshotDialog_hpp_
#include "GUI.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 wxDialog
{
public:
ConfigSnapshotDialog(const Config::SnapshotDB &snapshot_db, const wxString &id);
const std::string& snapshot_to_activate() const { return m_snapshot_to_activate; }
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;
};
} // namespace GUI
} // namespace Slic3r
#endif /* slic3r_GUI_ConfigSnapshotDialog_hpp_ */

View file

@ -0,0 +1,915 @@
#include "ConfigWizard_private.hpp"
#include <algorithm>
#include <utility>
#include <unordered_map>
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include <wx/settings.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/dcclient.h>
#include <wx/statbmp.h>
#include <wx/checkbox.h>
#include <wx/statline.h>
#include "libslic3r/Utils.hpp"
#include "PresetBundle.hpp"
#include "GUI.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
namespace Slic3r {
namespace GUI {
// Printer model picker GUI control
struct PrinterPickerEvent : public wxEvent
{
std::string vendor_id;
std::string model_id;
std::string variant_name;
bool enable;
PrinterPickerEvent(wxEventType eventType, int winid, std::string vendor_id, std::string model_id, std::string variant_name, bool enable) :
wxEvent(winid, eventType),
vendor_id(std::move(vendor_id)),
model_id(std::move(model_id)),
variant_name(std::move(variant_name)),
enable(enable)
{}
virtual wxEvent *Clone() const
{
return new PrinterPickerEvent(*this);
}
};
wxDEFINE_EVENT(EVT_PRINTER_PICK, PrinterPickerEvent);
PrinterPicker::PrinterPicker(wxWindow *parent, const VendorProfile &vendor, const AppConfig &appconfig_vendors) :
wxPanel(parent),
vendor_id(vendor.id),
variants_checked(0)
{
const auto &models = vendor.models;
auto *sizer = new wxBoxSizer(wxVERTICAL);
auto *printer_grid = new wxFlexGridSizer(models.size(), 0, 20);
printer_grid->SetFlexibleDirection(wxVERTICAL | wxHORIZONTAL);
sizer->Add(printer_grid);
auto namefont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
namefont.SetWeight(wxFONTWEIGHT_BOLD);
// wxGrid appends widgets by rows, but we need to construct them in columns.
// These vectors are used to hold the elements so that they can be appended in the right order.
std::vector<wxStaticText*> titles;
std::vector<wxStaticBitmap*> bitmaps;
std::vector<wxPanel*> variants_panels;
for (const auto &model : models) {
auto bitmap_file = wxString::Format("printers/%s_%s.png", vendor.id, model.id);
wxBitmap bitmap(GUI::from_u8(Slic3r::var(bitmap_file.ToStdString())), wxBITMAP_TYPE_PNG);
auto *title = new wxStaticText(this, wxID_ANY, model.name, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
title->SetFont(namefont);
title->Wrap(std::max((int)MODEL_MIN_WRAP, bitmap.GetWidth()));
titles.push_back(title);
auto *bitmap_widget = new wxStaticBitmap(this, wxID_ANY, bitmap);
bitmaps.push_back(bitmap_widget);
auto *variants_panel = new wxPanel(this);
auto *variants_sizer = new wxBoxSizer(wxVERTICAL);
variants_panel->SetSizer(variants_sizer);
const auto model_id = model.id;
bool default_variant = true; // Mark the first variant as default in the GUI
for (const auto &variant : model.variants) {
const auto label = wxString::Format("%s %s %s %s", variant.name, _(L("mm")), _(L("nozzle")),
(default_variant ? _(L("(default)")) : wxString()));
default_variant = false;
auto *cbox = new Checkbox(variants_panel, label, model_id, variant.name);
const size_t idx = cboxes.size();
cboxes.push_back(cbox);
bool enabled = appconfig_vendors.get_variant("PrusaResearch", model_id, variant.name);
variants_checked += enabled;
cbox->SetValue(enabled);
variants_sizer->Add(cbox, 0, wxBOTTOM, 3);
cbox->Bind(wxEVT_CHECKBOX, [this, idx](wxCommandEvent &event) {
if (idx >= this->cboxes.size()) { return; }
this->on_checkbox(this->cboxes[idx], event.IsChecked());
});
}
variants_panels.push_back(variants_panel);
}
for (auto title : titles) { printer_grid->Add(title, 0, wxBOTTOM, 3); }
for (auto bitmap : bitmaps) { printer_grid->Add(bitmap, 0, wxBOTTOM, 20); }
for (auto vp : variants_panels) { printer_grid->Add(vp); }
auto *all_none_sizer = new wxBoxSizer(wxHORIZONTAL);
auto *sel_all = new wxButton(this, wxID_ANY, _(L("Select all")));
auto *sel_none = new wxButton(this, wxID_ANY, _(L("Select none")));
sel_all->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(true); });
sel_none->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->select_all(false); });
all_none_sizer->AddStretchSpacer();
all_none_sizer->Add(sel_all);
all_none_sizer->Add(sel_none);
sizer->AddStretchSpacer();
sizer->Add(all_none_sizer, 0, wxEXPAND);
SetSizer(sizer);
}
void PrinterPicker::select_all(bool select)
{
for (const auto &cb : cboxes) {
if (cb->GetValue() != select) {
cb->SetValue(select);
on_checkbox(cb, select);
}
}
}
void PrinterPicker::select_one(size_t i, bool select)
{
if (i < cboxes.size() && cboxes[i]->GetValue() != select) {
cboxes[i]->SetValue(select);
on_checkbox(cboxes[i], select);
}
}
void PrinterPicker::on_checkbox(const Checkbox *cbox, bool checked)
{
variants_checked += checked ? 1 : -1;
PrinterPickerEvent evt(EVT_PRINTER_PICK, GetId(), vendor_id, cbox->model, cbox->variant, checked);
AddPendingEvent(evt);
}
// Wizard page base
ConfigWizardPage::ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname) :
wxPanel(parent->p->hscroll),
parent(parent),
shortname(std::move(shortname)),
p_prev(nullptr),
p_next(nullptr)
{
auto *sizer = new wxBoxSizer(wxVERTICAL);
auto *text = new wxStaticText(this, wxID_ANY, std::move(title), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
auto font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
font.SetWeight(wxFONTWEIGHT_BOLD);
font.SetPointSize(14);
text->SetFont(font);
sizer->Add(text, 0, wxALIGN_LEFT, 0);
sizer->AddSpacer(10);
content = new wxBoxSizer(wxVERTICAL);
sizer->Add(content, 1);
SetSizer(sizer);
this->Hide();
Bind(wxEVT_SIZE, [this](wxSizeEvent &event) {
this->Layout();
event.Skip();
});
}
ConfigWizardPage::~ConfigWizardPage() {}
ConfigWizardPage* ConfigWizardPage::chain(ConfigWizardPage *page)
{
if (p_next != nullptr) { p_next->p_prev = nullptr; }
p_next = page;
if (page != nullptr) {
if (page->p_prev != nullptr) { page->p_prev->p_next = nullptr; }
page->p_prev = this;
}
return page;
}
void ConfigWizardPage::append_text(wxString text)
{
auto *widget = new wxStaticText(this, wxID_ANY, text, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
widget->Wrap(WRAP_WIDTH);
widget->SetMinSize(wxSize(WRAP_WIDTH, -1));
append(widget);
}
void ConfigWizardPage::append_spacer(int space)
{
content->AddSpacer(space);
}
bool ConfigWizardPage::Show(bool show)
{
if (extra_buttons() != nullptr) { extra_buttons()->Show(show); }
return wxPanel::Show(show);
}
void ConfigWizardPage::enable_next(bool enable) { parent->p->enable_next(enable); }
// Wizard pages
PageWelcome::PageWelcome(ConfigWizard *parent, bool check_first_variant) :
ConfigWizardPage(parent, wxString::Format(_(L("Welcome to the Slic3r %s")), ConfigWizard::name()), _(L("Welcome"))),
printer_picker(nullptr),
others_buttons(new wxPanel(parent)),
cbox_reset(nullptr)
{
if (wizard_p()->run_reason == ConfigWizard::RR_DATA_EMPTY) {
wxString::Format(_(L("Run %s")), ConfigWizard::name());
append_text(wxString::Format(
_(L("Hello, welcome to Slic3r Prusa Edition! This %s helps you with the initial configuration; just a few settings and you will be ready to print.")),
ConfigWizard::name())
);
} else {
cbox_reset = new wxCheckBox(this, wxID_ANY, _(L("Remove user profiles - install from scratch (a snapshot will be taken beforehand)")));
append(cbox_reset);
}
const auto &vendors = wizard_p()->vendors;
const auto vendor_prusa = vendors.find("PrusaResearch");
if (vendor_prusa != vendors.cend()) {
AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors;
printer_picker = new PrinterPicker(this, vendor_prusa->second, appconfig_vendors);
if (check_first_variant) {
// Select the default (first) model/variant on the Prusa vendor
printer_picker->select_one(0, true);
}
printer_picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) {
appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable);
this->on_variant_checked();
});
append(printer_picker);
}
const size_t num_other_vendors = vendors.size() - (vendor_prusa != vendors.cend());
auto *sizer = new wxBoxSizer(wxHORIZONTAL);
auto *other_vendors = new wxButton(others_buttons, wxID_ANY, _(L("Other vendors")));
other_vendors->Enable(num_other_vendors > 0);
auto *custom_setup = new wxButton(others_buttons, wxID_ANY, _(L("Custom setup")));
sizer->Add(other_vendors);
sizer->AddSpacer(BTN_SPACING);
sizer->Add(custom_setup);
other_vendors->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->wizard_p()->on_other_vendors(); });
custom_setup->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &event) { this->wizard_p()->on_custom_setup(); });
others_buttons->SetSizer(sizer);
}
void PageWelcome::on_page_set()
{
chain(wizard_p()->page_update);
on_variant_checked();
}
void PageWelcome::on_variant_checked()
{
enable_next(printer_picker != nullptr ? printer_picker->variants_checked > 0 : false);
}
PageUpdate::PageUpdate(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Automatic updates")), _(L("Updates"))),
version_check(true),
preset_update(true)
{
const AppConfig *app_config = GUI::get_app_config();
auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
auto *box_slic3r = new wxCheckBox(this, wxID_ANY, _(L("Check for application updates")));
box_slic3r->SetValue(app_config->get("version_check") == "1");
append(box_slic3r);
append_text(_(L("If enabled, Slic3r checks for new versions of Slic3r PE online. When a new version becomes available a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done.")));
append_spacer(VERTICAL_SPACING);
auto *box_presets = new wxCheckBox(this, wxID_ANY, _(L("Update built-in Presets automatically")));
box_presets->SetValue(app_config->get("preset_update") == "1");
append(box_presets);
append_text(_(L("If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup.")));
const auto text_bold = _(L("Updates are never applied without user's consent and never overwrite user's customized settings."));
auto *label_bold = new wxStaticText(this, wxID_ANY, text_bold);
label_bold->SetFont(boldfont);
label_bold->Wrap(WRAP_WIDTH);
append(label_bold);
append_text(_(L("Additionally a backup snapshot of the whole configuration is created before an update is applied.")));
box_slic3r->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->version_check = event.IsChecked(); });
box_presets->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent &event) { this->preset_update = event.IsChecked(); });
}
PageVendors::PageVendors(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Other Vendors")), _(L("Other Vendors")))
{
append_text(_(L("Pick another vendor supported by Slic3r PE:")));
auto boldfont = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
AppConfig &appconfig_vendors = this->wizard_p()->appconfig_vendors;
wxArrayString choices_vendors;
for (const auto vendor_pair : wizard_p()->vendors) {
const auto &vendor = vendor_pair.second;
if (vendor.id == "PrusaResearch") { continue; }
auto *picker = new PrinterPicker(this, vendor, appconfig_vendors);
picker->Hide();
pickers.push_back(picker);
choices_vendors.Add(vendor.name);
picker->Bind(EVT_PRINTER_PICK, [this, &appconfig_vendors](const PrinterPickerEvent &evt) {
appconfig_vendors.set_variant(evt.vendor_id, evt.model_id, evt.variant_name, evt.enable);
this->on_variant_checked();
});
}
auto *vendor_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices_vendors);
if (choices_vendors.GetCount() > 0) {
vendor_picker->SetSelection(0);
on_vendor_pick(0);
}
vendor_picker->Bind(wxEVT_CHOICE, [this](wxCommandEvent &evt) {
this->on_vendor_pick(evt.GetInt());
});
append(vendor_picker);
for (PrinterPicker *picker : pickers) { this->append(picker); }
}
void PageVendors::on_page_set()
{
on_variant_checked();
}
void PageVendors::on_vendor_pick(size_t i)
{
for (PrinterPicker *picker : pickers) { picker->Hide(); }
if (i < pickers.size()) {
pickers[i]->Show();
wizard_p()->layout_fit();
}
}
void PageVendors::on_variant_checked()
{
size_t variants_checked = 0;
for (const PrinterPicker *picker : pickers) { variants_checked += picker->variants_checked; }
enable_next(variants_checked > 0);
}
PageFirmware::PageFirmware(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Firmware Type")), _(L("Firmware"))),
gcode_opt(*print_config_def.get("gcode_flavor")),
gcode_picker(nullptr)
{
append_text(_(L("Choose the type of firmware used by your printer.")));
append_text(gcode_opt.tooltip);
wxArrayString choices;
choices.Alloc(gcode_opt.enum_labels.size());
for (const auto &label : gcode_opt.enum_labels) {
choices.Add(label);
}
gcode_picker = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, choices);
const auto &enum_values = gcode_opt.enum_values;
auto needle = enum_values.cend();
if (gcode_opt.default_value != nullptr) {
needle = std::find(enum_values.cbegin(), enum_values.cend(), gcode_opt.default_value->serialize());
}
if (needle != enum_values.cend()) {
gcode_picker->SetSelection(needle - enum_values.cbegin());
} else {
gcode_picker->SetSelection(0);
}
append(gcode_picker);
}
void PageFirmware::apply_custom_config(DynamicPrintConfig &config)
{
auto sel = gcode_picker->GetSelection();
if (sel >= 0 && sel < gcode_opt.enum_labels.size()) {
auto *opt = new ConfigOptionEnum<GCodeFlavor>(static_cast<GCodeFlavor>(sel));
config.set_key_value("gcode_flavor", opt);
}
}
PageBedShape::PageBedShape(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Bed Shape and Size")), _(L("Bed Shape"))),
shape_panel(new BedShapePanel(this))
{
append_text(_(L("Set the shape of your printer's bed.")));
shape_panel->build_panel(wizard_p()->custom_config->option<ConfigOptionPoints>("bed_shape"));
append(shape_panel);
}
void PageBedShape::apply_custom_config(DynamicPrintConfig &config)
{
const auto points(shape_panel->GetValue());
auto *opt = new ConfigOptionPoints(points);
config.set_key_value("bed_shape", opt);
}
PageDiameters::PageDiameters(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Filament and Nozzle Diameters")), _(L("Print Diameters"))),
spin_nozzle(new wxSpinCtrlDouble(this, wxID_ANY)),
spin_filam(new wxSpinCtrlDouble(this, wxID_ANY))
{
spin_nozzle->SetDigits(2);
spin_nozzle->SetIncrement(0.1);
const auto &def_nozzle = *print_config_def.get("nozzle_diameter");
auto *default_nozzle = dynamic_cast<const ConfigOptionFloats*>(def_nozzle.default_value);
spin_nozzle->SetValue(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5);
spin_filam->SetDigits(2);
spin_filam->SetIncrement(0.25);
const auto &def_filam = *print_config_def.get("filament_diameter");
auto *default_filam = dynamic_cast<const ConfigOptionFloats*>(def_filam.default_value);
spin_filam->SetValue(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0);
append_text(_(L("Enter the diameter of your printer's hot end nozzle.")));
auto *sizer_nozzle = new wxFlexGridSizer(3, 5, 5);
auto *text_nozzle = new wxStaticText(this, wxID_ANY, _(L("Nozzle Diameter:")));
auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _(L("mm")));
sizer_nozzle->AddGrowableCol(0, 1);
sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL);
sizer_nozzle->Add(spin_nozzle);
sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL);
append(sizer_nozzle);
append_spacer(VERTICAL_SPACING);
append_text(_(L("Enter the diameter of your filament.")));
append_text(_(L("Good precision is required, so use a caliper and do multiple measurements along the filament, then compute the average.")));
auto *sizer_filam = new wxFlexGridSizer(3, 5, 5);
auto *text_filam = new wxStaticText(this, wxID_ANY, _(L("Filament Diameter:")));
auto *unit_filam = new wxStaticText(this, wxID_ANY, _(L("mm")));
sizer_filam->AddGrowableCol(0, 1);
sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL);
sizer_filam->Add(spin_filam);
sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL);
append(sizer_filam);
}
void PageDiameters::apply_custom_config(DynamicPrintConfig &config)
{
auto *opt_nozzle = new ConfigOptionFloats(1, spin_nozzle->GetValue());
config.set_key_value("nozzle_diameter", opt_nozzle);
auto *opt_filam = new ConfigOptionFloats(1, spin_filam->GetValue());
config.set_key_value("filament_diameter", opt_filam);
}
PageTemperatures::PageTemperatures(ConfigWizard *parent) :
ConfigWizardPage(parent, _(L("Extruder and Bed Temperatures")), _(L("Temperatures"))),
spin_extr(new wxSpinCtrlDouble(this, wxID_ANY)),
spin_bed(new wxSpinCtrlDouble(this, wxID_ANY))
{
spin_extr->SetIncrement(5.0);
const auto &def_extr = *print_config_def.get("temperature");
spin_extr->SetRange(def_extr.min, def_extr.max);
auto *default_extr = dynamic_cast<const ConfigOptionInts*>(def_extr.default_value);
spin_extr->SetValue(default_extr != nullptr && default_extr->size() > 0 ? default_extr->get_at(0) : 200);
spin_bed->SetIncrement(5.0);
const auto &def_bed = *print_config_def.get("bed_temperature");
spin_bed->SetRange(def_bed.min, def_bed.max);
auto *default_bed = dynamic_cast<const ConfigOptionInts*>(def_bed.default_value);
spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0);
append_text(_(L("Enter the temperature needed for extruding your filament.")));
append_text(_(L("A rule of thumb is 160 to 230 °C for PLA, and 215 to 250 °C for ABS.")));
auto *sizer_extr = new wxFlexGridSizer(3, 5, 5);
auto *text_extr = new wxStaticText(this, wxID_ANY, _(L("Extrusion Temperature:")));
auto *unit_extr = new wxStaticText(this, wxID_ANY, _(L("°C")));
sizer_extr->AddGrowableCol(0, 1);
sizer_extr->Add(text_extr, 0, wxALIGN_CENTRE_VERTICAL);
sizer_extr->Add(spin_extr);
sizer_extr->Add(unit_extr, 0, wxALIGN_CENTRE_VERTICAL);
append(sizer_extr);
append_spacer(VERTICAL_SPACING);
append_text(_(L("Enter the bed temperature needed for getting your filament to stick to your heated bed.")));
append_text(_(L("A rule of thumb is 60 °C for PLA and 110 °C for ABS. Leave zero if you have no heated bed.")));
auto *sizer_bed = new wxFlexGridSizer(3, 5, 5);
auto *text_bed = new wxStaticText(this, wxID_ANY, _(L("Bed Temperature:")));
auto *unit_bed = new wxStaticText(this, wxID_ANY, _(L("°C")));
sizer_bed->AddGrowableCol(0, 1);
sizer_bed->Add(text_bed, 0, wxALIGN_CENTRE_VERTICAL);
sizer_bed->Add(spin_bed);
sizer_bed->Add(unit_bed, 0, wxALIGN_CENTRE_VERTICAL);
append(sizer_bed);
}
void PageTemperatures::apply_custom_config(DynamicPrintConfig &config)
{
auto *opt_extr = new ConfigOptionInts(1, spin_extr->GetValue());
config.set_key_value("temperature", opt_extr);
auto *opt_extr1st = new ConfigOptionInts(1, spin_extr->GetValue());
config.set_key_value("first_layer_temperature", opt_extr1st);
auto *opt_bed = new ConfigOptionInts(1, spin_bed->GetValue());
config.set_key_value("bed_temperature", opt_bed);
auto *opt_bed1st = new ConfigOptionInts(1, spin_bed->GetValue());
config.set_key_value("first_layer_bed_temperature", opt_bed1st);
}
// Index
ConfigWizardIndex::ConfigWizardIndex(wxWindow *parent) :
wxPanel(parent),
bg(GUI::from_u8(Slic3r::var("Slic3r_192px_transparent.png")), wxBITMAP_TYPE_PNG),
bullet_black(GUI::from_u8(Slic3r::var("bullet_black.png")), wxBITMAP_TYPE_PNG),
bullet_blue(GUI::from_u8(Slic3r::var("bullet_blue.png")), wxBITMAP_TYPE_PNG),
bullet_white(GUI::from_u8(Slic3r::var("bullet_white.png")), wxBITMAP_TYPE_PNG)
{
SetMinSize(bg.GetSize());
wxClientDC dc(this);
text_height = dc.GetCharHeight();
// Add logo bitmap.
// This could be done in on_paint() along with the index labels, but I've found it tricky
// to get the bitmap rendered well on all platforms with transparent background.
// In some cases it didn't work at all. And so wxStaticBitmap is used here instead,
// because it has all the platform quirks figured out.
auto *sizer = new wxBoxSizer(wxVERTICAL);
auto *logo = new wxStaticBitmap(this, wxID_ANY, bg);
sizer->AddStretchSpacer();
sizer->Add(logo);
SetSizer(sizer);
Bind(wxEVT_PAINT, &ConfigWizardIndex::on_paint, this);
}
void ConfigWizardIndex::load_items(ConfigWizardPage *firstpage)
{
items.clear();
item_active = items.cend();
for (auto *page = firstpage; page != nullptr; page = page->page_next()) {
items.emplace_back(page->shortname);
}
Refresh();
}
void ConfigWizardIndex::set_active(ConfigWizardPage *page)
{
item_active = std::find(items.cbegin(), items.cend(), page->shortname);
Refresh();
}
void ConfigWizardIndex::on_paint(wxPaintEvent & evt)
{
enum {
MARGIN = 10,
SPACING = 5,
};
const auto size = GetClientSize();
if (size.GetHeight() == 0 || size.GetWidth() == 0) { return; }
wxPaintDC dc(this);
const auto bullet_w = bullet_black.GetSize().GetWidth();
const auto bullet_h = bullet_black.GetSize().GetHeight();
const int yoff_icon = bullet_h < text_height ? (text_height - bullet_h) / 2 : 0;
const int yoff_text = bullet_h > text_height ? (bullet_h - text_height) / 2 : 0;
const int yinc = std::max(bullet_h, text_height) + SPACING;
unsigned y = 0;
for (auto it = items.cbegin(); it != items.cend(); ++it) {
if (it < item_active) { dc.DrawBitmap(bullet_black, MARGIN, y + yoff_icon, false); }
if (it == item_active) { dc.DrawBitmap(bullet_blue, MARGIN, y + yoff_icon, false); }
if (it > item_active) { dc.DrawBitmap(bullet_white, MARGIN, y + yoff_icon, false); }
dc.DrawText(*it, MARGIN + bullet_w + SPACING, y + yoff_text);
y += yinc;
}
}
// priv
static const std::unordered_map<std::string, std::pair<std::string, std::string>> legacy_preset_map {{
{ "Original Prusa i3 MK2.ini", std::make_pair("MK2S", "0.4") },
{ "Original Prusa i3 MK2 MM Single Mode.ini", std::make_pair("MK2SMM", "0.4") },
{ "Original Prusa i3 MK2 MM Single Mode 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") },
{ "Original Prusa i3 MK2 MultiMaterial.ini", std::make_pair("MK2SMM", "0.4") },
{ "Original Prusa i3 MK2 MultiMaterial 0.6 nozzle.ini", std::make_pair("MK2SMM", "0.6") },
{ "Original Prusa i3 MK2 0.25 nozzle.ini", std::make_pair("MK2S", "0.25") },
{ "Original Prusa i3 MK2 0.6 nozzle.ini", std::make_pair("MK2S", "0.6") },
{ "Original Prusa i3 MK3.ini", std::make_pair("MK3", "0.4") },
}};
void ConfigWizard::priv::load_vendors()
{
const auto vendor_dir = fs::path(Slic3r::data_dir()) / "vendor";
const auto rsrc_vendor_dir = fs::path(resources_dir()) / "profiles";
// Load vendors from the "vendors" directory in datadir
for (fs::directory_iterator it(vendor_dir); it != fs::directory_iterator(); ++it) {
if (it->path().extension() == ".ini") {
try {
auto vp = VendorProfile::from_ini(it->path());
vendors[vp.id] = std::move(vp);
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % it->path() % e.what();
}
}
}
// Additionally load up vendors from the application resources directory, but only those not seen in the datadir
for (fs::directory_iterator it(rsrc_vendor_dir); it != fs::directory_iterator(); ++it) {
if (it->path().extension() == ".ini") {
const auto id = it->path().stem().string();
if (vendors.find(id) == vendors.end()) {
try {
auto vp = VendorProfile::from_ini(it->path());
vendors_rsrc[vp.id] = it->path().filename().string();
vendors[vp.id] = std::move(vp);
}
catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << boost::format("Error loading vendor bundle %1%: %2%") % it->path() % e.what();
}
}
}
}
// Load up the set of vendors / models / variants the user has had enabled up till now
const AppConfig *app_config = GUI::get_app_config();
if (! app_config->legacy_datadir()) {
appconfig_vendors.set_vendors(*app_config);
} else {
// In case of legacy datadir, try to guess the preference based on the printer preset files that are present
const auto printer_dir = fs::path(Slic3r::data_dir()) / "printer";
for (fs::directory_iterator it(printer_dir); it != fs::directory_iterator(); ++it) {
auto needle = legacy_preset_map.find(it->path().filename().string());
if (needle == legacy_preset_map.end()) { continue; }
const auto &model = needle->second.first;
const auto &variant = needle->second.second;
appconfig_vendors.set_variant("PrusaResearch", model, variant, true);
}
}
}
void ConfigWizard::priv::index_refresh()
{
index->load_items(page_welcome);
}
void ConfigWizard::priv::add_page(ConfigWizardPage *page)
{
hscroll_sizer->Add(page, 0, wxEXPAND);
auto *extra_buttons = page->extra_buttons();
if (extra_buttons != nullptr) {
btnsizer->Prepend(extra_buttons, 0);
}
}
void ConfigWizard::priv::set_page(ConfigWizardPage *page)
{
if (page == nullptr) { return; }
if (page_current != nullptr) { page_current->Hide(); }
page_current = page;
enable_next(true);
page->on_page_set();
index->load_items(page_welcome);
index->set_active(page);
page->Show();
btn_prev->Enable(page->page_prev() != nullptr);
btn_next->Show(page->page_next() != nullptr);
btn_finish->Show(page->page_next() == nullptr);
layout_fit();
}
void ConfigWizard::priv::layout_fit()
{
q->Layout();
q->Fit();
}
void ConfigWizard::priv::enable_next(bool enable)
{
btn_next->Enable(enable);
btn_finish->Enable(enable);
}
void ConfigWizard::priv::on_other_vendors()
{
page_welcome
->chain(page_vendors)
->chain(page_update);
set_page(page_vendors);
}
void ConfigWizard::priv::on_custom_setup()
{
page_welcome->chain(page_firmware);
page_temps->chain(page_update);
set_page(page_firmware);
}
void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
{
const bool is_custom_setup = page_welcome->page_next() == page_firmware;
if (! is_custom_setup) {
const auto enabled_vendors = appconfig_vendors.vendors();
// Install bundles from resources if needed:
std::vector<std::string> install_bundles;
for (const auto &vendor_rsrc : vendors_rsrc) {
const auto vendor = enabled_vendors.find(vendor_rsrc.first);
if (vendor == enabled_vendors.end()) { continue; }
size_t size_sum = 0;
for (const auto &model : vendor->second) { size_sum += model.second.size(); }
if (size_sum == 0) { continue; }
// This vendor needs to be installed
install_bundles.emplace_back(vendor_rsrc.second);
}
// Decide whether to create snapshot based on run_reason and the reset profile checkbox
bool snapshot = true;
switch (run_reason) {
case ConfigWizard::RR_DATA_EMPTY: snapshot = false; break;
case ConfigWizard::RR_DATA_LEGACY: snapshot = true; break;
case ConfigWizard::RR_DATA_INCOMPAT: snapshot = false; break; // In this case snapshot is done by PresetUpdater with the appropriate reason
case ConfigWizard::RR_USER: snapshot = page_welcome->reset_user_profile(); break;
}
if (install_bundles.size() > 0) {
// Install bundles from resources.
updater->install_bundles_rsrc(std::move(install_bundles), snapshot);
} else {
BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources";
}
if (page_welcome->reset_user_profile()) {
BOOST_LOG_TRIVIAL(info) << "Resetting user profiles...";
preset_bundle->reset(true);
}
app_config->set_vendors(appconfig_vendors);
app_config->set("version_check", page_update->version_check ? "1" : "0");
app_config->set("preset_update", page_update->preset_update ? "1" : "0");
app_config->reset_selections();
preset_bundle->load_presets(*app_config);
} else {
for (ConfigWizardPage *page = page_firmware; page != nullptr; page = page->page_next()) {
page->apply_custom_config(*custom_config);
}
preset_bundle->load_config("My Settings", *custom_config);
}
// Update the selections from the compatibilty.
preset_bundle->export_selections(*app_config);
}
// Public
ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) :
wxDialog(parent, wxID_ANY, name(), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
p(new priv(this))
{
p->run_reason = reason;
p->load_vendors();
p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({
"gcode_flavor", "bed_shape", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature",
}));
p->index = new ConfigWizardIndex(this);
auto *vsizer = new wxBoxSizer(wxVERTICAL);
auto *topsizer = new wxBoxSizer(wxHORIZONTAL);
auto *hline = new wxStaticLine(this);
p->btnsizer = new wxBoxSizer(wxHORIZONTAL);
// Initially we _do not_ SetScrollRate in order to figure out the overall width of the Wizard without scrolling.
// Later, we compare that to the size of the current screen and set minimum width based on that (see below).
p->hscroll = new wxScrolledWindow(this);
p->hscroll_sizer = new wxBoxSizer(wxHORIZONTAL);
p->hscroll->SetSizer(p->hscroll_sizer);
topsizer->Add(p->index, 0, wxEXPAND);
topsizer->AddSpacer(INDEX_MARGIN);
topsizer->Add(p->hscroll, 1, wxEXPAND);
p->btn_prev = new wxButton(this, wxID_NONE, _(L("< &Back")));
p->btn_next = new wxButton(this, wxID_NONE, _(L("&Next >")));
p->btn_finish = new wxButton(this, wxID_APPLY, _(L("&Finish")));
p->btn_cancel = new wxButton(this, wxID_CANCEL);
p->btnsizer->AddStretchSpacer();
p->btnsizer->Add(p->btn_prev, 0, wxLEFT, BTN_SPACING);
p->btnsizer->Add(p->btn_next, 0, wxLEFT, BTN_SPACING);
p->btnsizer->Add(p->btn_finish, 0, wxLEFT, BTN_SPACING);
p->btnsizer->Add(p->btn_cancel, 0, wxLEFT, BTN_SPACING);
p->add_page(p->page_welcome = new PageWelcome(this, reason == RR_DATA_EMPTY || reason == RR_DATA_LEGACY));
p->add_page(p->page_update = new PageUpdate(this));
p->add_page(p->page_vendors = new PageVendors(this));
p->add_page(p->page_firmware = new PageFirmware(this));
p->add_page(p->page_bed = new PageBedShape(this));
p->add_page(p->page_diams = new PageDiameters(this));
p->add_page(p->page_temps = new PageTemperatures(this));
p->index_refresh();
p->page_welcome->chain(p->page_update);
p->page_firmware
->chain(p->page_bed)
->chain(p->page_diams)
->chain(p->page_temps);
vsizer->Add(topsizer, 1, wxEXPAND | wxALL, DIALOG_MARGIN);
vsizer->Add(hline, 0, wxEXPAND);
vsizer->Add(p->btnsizer, 0, wxEXPAND | wxALL, DIALOG_MARGIN);
p->set_page(p->page_welcome);
SetSizer(vsizer);
SetSizerAndFit(vsizer);
// We can now enable scrolling on hscroll
p->hscroll->SetScrollRate(30, 30);
// Compare current ("ideal") wizard size with the size of the current screen.
// If the screen is smaller, resize wizrad to match, which will enable scrollbars.
auto wizard_size = GetSize();
unsigned width, height;
if (GUI::get_current_screen_size(this, width, height)) {
wizard_size.SetWidth(std::min(wizard_size.GetWidth(), (int)(width - 2 * DIALOG_MARGIN)));
wizard_size.SetHeight(std::min(wizard_size.GetHeight(), (int)(height - 2 * DIALOG_MARGIN)));
SetMinSize(wizard_size);
}
Fit();
p->btn_prev->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_prev(); });
p->btn_next->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->p->go_next(); });
p->btn_finish->Bind(wxEVT_BUTTON, [this](const wxCommandEvent &evt) { this->EndModal(wxID_OK); });
}
ConfigWizard::~ConfigWizard() {}
bool ConfigWizard::run(PresetBundle *preset_bundle, const PresetUpdater *updater)
{
BOOST_LOG_TRIVIAL(info) << "Running ConfigWizard, reason: " << p->run_reason;
if (ShowModal() == wxID_OK) {
auto *app_config = GUI::get_app_config();
p->apply_config(app_config, preset_bundle, updater);
app_config->set_legacy_datadir(false);
BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied";
return true;
} else {
BOOST_LOG_TRIVIAL(info) << "ConfigWizard cancelled";
return false;
}
}
const wxString& ConfigWizard::name()
{
// A different naming convention is used for the Wizard on Windows vs. OSX & GTK.
#if WIN32
static const wxString config_wizard_name = L("Configuration Wizard");
#else
static const wxString config_wizard_name = L("Configuration Assistant");
#endif
return config_wizard_name;
}
}
}

View file

@ -0,0 +1,50 @@
#ifndef slic3r_ConfigWizard_hpp_
#define slic3r_ConfigWizard_hpp_
#include <memory>
#include <wx/dialog.h>
namespace Slic3r {
class PresetBundle;
class PresetUpdater;
namespace GUI {
class ConfigWizard: public wxDialog
{
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
};
ConfigWizard(wxWindow *parent, RunReason run_reason);
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(PresetBundle *preset_bundle, const PresetUpdater *updater);
static const wxString& name();
private:
struct priv;
std::unique_ptr<priv> p;
friend class ConfigWizardPage;
};
}
}
#endif

View file

@ -0,0 +1,241 @@
#ifndef slic3r_ConfigWizard_private_hpp_
#define slic3r_ConfigWizard_private_hpp_
#include "ConfigWizard.hpp"
#include <vector>
#include <set>
#include <unordered_map>
#include <boost/filesystem.hpp>
#include <wx/sizer.h>
#include <wx/panel.h>
#include <wx/button.h>
#include <wx/choice.h>
#include <wx/spinctrl.h>
#include "libslic3r/PrintConfig.hpp"
#include "slic3r/Utils/PresetUpdater.hpp"
#include "AppConfig.hpp"
#include "Preset.hpp"
#include "BedShapeDialog.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,
};
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;
unsigned variants_checked;
PrinterPicker(wxWindow *parent, const VendorProfile &vendor, const AppConfig &appconfig_vendors);
void select_all(bool select);
void select_one(size_t i, bool select);
void on_checkbox(const Checkbox *cbox, bool checked);
};
struct ConfigWizardPage: wxPanel
{
ConfigWizard *parent;
const wxString shortname;
wxBoxSizer *content;
ConfigWizardPage(ConfigWizard *parent, wxString title, wxString shortname);
virtual ~ConfigWizardPage();
ConfigWizardPage* page_prev() const { return p_prev; }
ConfigWizardPage* page_next() const { return p_next; }
ConfigWizardPage* chain(ConfigWizardPage *page);
template<class T>
void append(T *thing, int proportion = 0, int flag = wxEXPAND|wxTOP|wxBOTTOM, int border = 10)
{
content->Add(thing, proportion, flag, border);
}
void append_text(wxString text);
void append_spacer(int space);
ConfigWizard::priv *wizard_p() const { return parent->p.get(); }
virtual bool Show(bool show = true);
virtual bool Hide() { return Show(false); }
virtual wxPanel* extra_buttons() { return nullptr; }
virtual void on_page_set() {}
virtual void apply_custom_config(DynamicPrintConfig &config) {}
void enable_next(bool enable);
private:
ConfigWizardPage *p_prev;
ConfigWizardPage *p_next;
};
struct PageWelcome: ConfigWizardPage
{
PrinterPicker *printer_picker;
wxPanel *others_buttons;
wxCheckBox *cbox_reset;
PageWelcome(ConfigWizard *parent, bool check_first_variant);
virtual wxPanel* extra_buttons() { return others_buttons; }
virtual void on_page_set();
bool reset_user_profile() const { return cbox_reset != nullptr ? cbox_reset->GetValue() : false; }
void on_variant_checked();
};
struct PageUpdate: ConfigWizardPage
{
bool version_check;
bool preset_update;
PageUpdate(ConfigWizard *parent);
};
struct PageVendors: ConfigWizardPage
{
std::vector<PrinterPicker*> pickers;
PageVendors(ConfigWizard *parent);
virtual void on_page_set();
void on_vendor_pick(size_t i);
void on_variant_checked();
};
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 PageDiameters: ConfigWizardPage
{
wxSpinCtrlDouble *spin_nozzle;
wxSpinCtrlDouble *spin_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);
};
class ConfigWizardIndex: public wxPanel
{
public:
ConfigWizardIndex(wxWindow *parent);
void load_items(ConfigWizardPage *firstpage);
void set_active(ConfigWizardPage *page);
private:
const wxBitmap bg;
const wxBitmap bullet_black;
const wxBitmap bullet_blue;
const wxBitmap bullet_white;
int text_height;
std::vector<wxString> items;
std::vector<wxString>::const_iterator item_active;
void on_paint(wxPaintEvent &evt);
};
struct ConfigWizard::priv
{
ConfigWizard *q;
ConfigWizard::RunReason run_reason;
AppConfig appconfig_vendors;
std::unordered_map<std::string, VendorProfile> vendors;
std::unordered_map<std::string, std::string> vendors_rsrc;
std::unique_ptr<DynamicPrintConfig> custom_config;
wxScrolledWindow *hscroll = nullptr;
wxBoxSizer *hscroll_sizer = nullptr;
wxBoxSizer *btnsizer = nullptr;
ConfigWizardPage *page_current = nullptr;
ConfigWizardIndex *index = nullptr;
wxButton *btn_prev = nullptr;
wxButton *btn_next = nullptr;
wxButton *btn_finish = nullptr;
wxButton *btn_cancel = nullptr;
PageWelcome *page_welcome = nullptr;
PageUpdate *page_update = nullptr;
PageVendors *page_vendors = nullptr;
PageFirmware *page_firmware = nullptr;
PageBedShape *page_bed = nullptr;
PageDiameters *page_diams = nullptr;
PageTemperatures *page_temps = nullptr;
priv(ConfigWizard *q) : q(q) {}
void load_vendors();
void add_page(ConfigWizardPage *page);
void index_refresh();
void set_page(ConfigWizardPage *page);
void layout_fit();
void go_prev() { if (page_current != nullptr) { set_page(page_current->page_prev()); } }
void go_next() { if (page_current != nullptr) { set_page(page_current->page_next()); } }
void enable_next(bool enable);
void on_other_vendors();
void on_custom_setup();
void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
};
}
}
#endif

66
src/slic3r/GUI/Event.hpp Normal file
View file

@ -0,0 +1,66 @@
#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 ArrayEvent<T, 1> : public wxEvent
{
T data;
ArrayEvent(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 ArrayEvent<T, 1>(GetEventType(), data, GetEventObject());
}
};
template <class T> using Event = ArrayEvent<T, 1>;
}
}
#endif // slic3r_Events_hpp_

791
src/slic3r/GUI/Field.cpp Normal file
View file

@ -0,0 +1,791 @@
#include "GUI.hpp"//"slic3r_gui.hpp"
#include "Field.hpp"
//#include <wx/event.h>
#include <regex>
#include <wx/numformatter.h>
#include <wx/tooltip.h>
#include "PrintConfig.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include "GUI_App.hpp"
namespace Slic3r { namespace GUI {
wxString double_to_string(double const value)
{
if (value - int(value) == 0)
return wxString::Format(_T("%i"), int(value));
else {
int precision = 4;
for (size_t p = 1; p < 4; p++)
{
double cur_val = pow(10, p)*value;
if (cur_val - int(cur_val) == 0) {
precision = p;
break;
}
}
return wxNumberFormatter::ToString(value, precision, wxNumberFormatter::Style_None);
}
}
void Field::PostInitialize(){
auto color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
m_Undo_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition,wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER);
m_Undo_to_sys_btn = new MyButton(m_parent, wxID_ANY, "", wxDefaultPosition,wxDefaultSize, wxBU_EXACTFIT | wxNO_BORDER);
if (wxMSW) {
m_Undo_btn->SetBackgroundColour(color);
m_Undo_to_sys_btn->SetBackgroundColour(color);
}
m_Undo_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_initial_value(); }));
m_Undo_to_sys_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent){ on_back_to_sys_value(); }));
//set default bitmap
wxBitmap bmp;
bmp.LoadFile(from_u8(var("bullet_white.png")), wxBITMAP_TYPE_PNG);
set_undo_bitmap(&bmp);
set_undo_to_sys_bitmap(&bmp);
switch (m_opt.type)
{
case coPercents:
case coFloats:
case coStrings:
case coBools:
case coInts: {
auto tag_pos = m_opt_id.find("#");
if (tag_pos != std::string::npos)
m_opt_idx = stoi(m_opt_id.substr(tag_pos + 1, m_opt_id.size()));
break;
}
default:
break;
}
BUILD();
}
void Field::on_kill_focus(wxEvent& event) {
// Without this, there will be nasty focus bugs on Windows.
// Also, docs for wxEvent::Skip() say "In general, it is recommended to skip all
// non-command events to allow the default handling to take place."
event.Skip();
// call the registered function if it is available
if (m_on_kill_focus!=nullptr)
m_on_kill_focus();
}
void Field::on_change_field()
{
// std::cerr << "calling Field::_on_change \n";
if (m_on_change != nullptr && !m_disable_change_event)
m_on_change(m_opt_id, get_value());
}
void Field::on_back_to_initial_value(){
if (m_back_to_initial_value != nullptr && m_is_modified_value)
m_back_to_initial_value(m_opt_id);
}
void Field::on_back_to_sys_value(){
if (m_back_to_sys_value != nullptr && m_is_nonsys_value)
m_back_to_sys_value(m_opt_id);
}
wxString Field::get_tooltip_text(const wxString& default_string)
{
wxString tooltip_text("");
wxString tooltip = _(m_opt.tooltip);
if (tooltip.length() > 0)
tooltip_text = tooltip + "\n" + _(L("default value")) + "\t: " +
(boost::iends_with(m_opt_id, "_gcode") ? "\n" : "") + default_string +
(boost::iends_with(m_opt_id, "_gcode") ? "" : "\n") +
_(L("parameter name")) + "\t: " + m_opt_id;
return tooltip_text;
}
bool Field::is_matched(const std::string& string, const std::string& pattern)
{
std::regex regex_pattern(pattern, std::regex_constants::icase); // use ::icase to make the matching case insensitive like /i in perl
return std::regex_match(string, regex_pattern);
}
void Field::get_value_by_opt_type(wxString& str)
{
switch (m_opt.type){
case coInt:
m_value = wxAtoi(str);
break;
case coPercent:
case coPercents:
case coFloats:
case coFloat:{
if (m_opt.type == coPercent && str.Last() == '%')
str.RemoveLast();
else if (str.Last() == '%') {
wxString label = m_Label->GetLabel();
if (label.Last() == '\n') label.RemoveLast();
while (label.Last() == ' ') label.RemoveLast();
if (label.Last() == ':') label.RemoveLast();
show_error(m_parent, wxString::Format(_(L("%s doesn't support percentage")), label));
set_value(double_to_string(m_opt.min), true);
m_value = double(m_opt.min);
break;
}
double val;
if(!str.ToCDouble(&val))
{
show_error(m_parent, _(L("Input value contains incorrect symbol(s).\nUse, please, only digits")));
set_value(double_to_string(val), true);
}
if (m_opt.min > val || val > m_opt.max)
{
show_error(m_parent, _(L("Input value is out of range")));
if (m_opt.min > val) val = m_opt.min;
if (val > m_opt.max) val = m_opt.max;
set_value(double_to_string(val), true);
}
m_value = val;
break; }
case coString:
case coStrings:
case coFloatOrPercent:
m_value = str.ToStdString();
break;
default:
break;
}
}
void TextCtrl::BUILD() {
auto size = wxSize(wxDefaultSize);
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
wxString text_value = wxString("");
switch (m_opt.type) {
case coFloatOrPercent:
{
text_value = double_to_string(m_opt.default_value->getFloat());
if (static_cast<const ConfigOptionFloatOrPercent*>(m_opt.default_value)->percent)
text_value += "%";
break;
}
case coPercent:
{
text_value = wxString::Format(_T("%i"), int(m_opt.default_value->getFloat()));
text_value += "%";
break;
}
case coPercents:
case coFloats:
case coFloat:
{
double val = m_opt.type == coFloats ?
static_cast<const ConfigOptionFloats*>(m_opt.default_value)->get_at(m_opt_idx) :
m_opt.type == coFloat ?
m_opt.default_value->getFloat() :
static_cast<const ConfigOptionPercents*>(m_opt.default_value)->get_at(m_opt_idx);
text_value = double_to_string(val);
break;
}
case coString:
text_value = static_cast<const ConfigOptionString*>(m_opt.default_value)->value;
break;
case coStrings:
{
const ConfigOptionStrings *vec = static_cast<const ConfigOptionStrings*>(m_opt.default_value);
if (vec == nullptr || vec->empty()) break; //for the case of empty default value
text_value = vec->get_at(m_opt_idx);
break;
}
default:
break;
}
auto temp = new wxTextCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size, (m_opt.multiline ? wxTE_MULTILINE : 0));
temp->SetToolTip(get_tooltip_text(text_value));
temp->Bind(wxEVT_LEFT_DOWN, ([temp](wxEvent& event)
{
//! to allow the default handling
event.Skip();
//! eliminating the g-code pop up text description
bool flag = false;
#ifdef __WXGTK__
// I have no idea why, but on GTK flag works in other way
flag = true;
#endif // __WXGTK__
temp->GetToolTip()->Enable(flag);
}), temp->GetId());
#if !defined(__WXGTK__)
temp->Bind(wxEVT_KILL_FOCUS, ([this, temp](wxEvent& e)
{
e.Skip();// on_kill_focus(e);
temp->GetToolTip()->Enable(true);
}), temp->GetId());
#endif // __WXGTK__
temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent& evt)
{
#ifdef __WXGTK__
if (bChangedValueEvent)
#endif //__WXGTK__
on_change_field();
}), temp->GetId());
#ifdef __WXGTK__
// to correct value updating on GTK we should:
// call on_change_field() on wxEVT_KEY_UP instead of wxEVT_TEXT
// and prevent value updating on wxEVT_KEY_DOWN
temp->Bind(wxEVT_KEY_DOWN, &TextCtrl::change_field_value, this);
temp->Bind(wxEVT_KEY_UP, &TextCtrl::change_field_value, this);
#endif //__WXGTK__
// select all text using Ctrl+A
temp->Bind(wxEVT_CHAR, ([temp](wxKeyEvent& event)
{
if (wxGetKeyState(wxKeyCode('A')) && wxGetKeyState(WXK_CONTROL))
temp->SetSelection(-1, -1); //select all
event.Skip();
}));
// recast as a wxWindow to fit the calling convention
window = dynamic_cast<wxWindow*>(temp);
}
boost::any& TextCtrl::get_value()
{
wxString ret_str = static_cast<wxTextCtrl*>(window)->GetValue();
get_value_by_opt_type(ret_str);
return m_value;
}
void TextCtrl::enable() { dynamic_cast<wxTextCtrl*>(window)->Enable(); dynamic_cast<wxTextCtrl*>(window)->SetEditable(true); }
void TextCtrl::disable() { dynamic_cast<wxTextCtrl*>(window)->Disable(); dynamic_cast<wxTextCtrl*>(window)->SetEditable(false); }
#ifdef __WXGTK__
void TextCtrl::change_field_value(wxEvent& event)
{
if (bChangedValueEvent = event.GetEventType()==wxEVT_KEY_UP)
on_change_field();
event.Skip();
};
#endif //__WXGTK__
void CheckBox::BUILD() {
auto size = wxSize(wxDefaultSize);
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
bool check_value = m_opt.type == coBool ?
m_opt.default_value->getBool() : m_opt.type == coBools ?
static_cast<const ConfigOptionBools*>(m_opt.default_value)->get_at(m_opt_idx) :
false;
auto temp = new wxCheckBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size);
temp->SetValue(check_value);
if (m_opt.readonly) temp->Disable();
temp->Bind(wxEVT_CHECKBOX, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId());
temp->SetToolTip(get_tooltip_text(check_value ? "true" : "false"));
// recast as a wxWindow to fit the calling convention
window = dynamic_cast<wxWindow*>(temp);
}
boost::any& CheckBox::get_value()
{
// boost::any m_value;
bool value = dynamic_cast<wxCheckBox*>(window)->GetValue();
if (m_opt.type == coBool)
m_value = static_cast<bool>(value);
else
m_value = static_cast<unsigned char>(value);
return m_value;
}
int undef_spin_val = -9999; //! Probably, It's not necessary
void SpinCtrl::BUILD() {
auto size = wxSize(wxDefaultSize);
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
wxString text_value = wxString("");
int default_value = 0;
switch (m_opt.type) {
case coInt:
default_value = m_opt.default_value->getInt();
text_value = wxString::Format(_T("%i"), default_value);
break;
case coInts:
{
const ConfigOptionInts *vec = static_cast<const ConfigOptionInts*>(m_opt.default_value);
if (vec == nullptr || vec->empty()) break;
for (size_t id = 0; id < vec->size(); ++id)
{
default_value = vec->get_at(id);
text_value += wxString::Format(_T("%i"), default_value);
}
break;
}
default:
break;
}
const int min_val = m_opt.min == INT_MIN ? 0: m_opt.min;
const int max_val = m_opt.max < 2147483647 ? m_opt.max : 2147483647;
auto temp = new wxSpinCtrl(m_parent, wxID_ANY, text_value, wxDefaultPosition, size,
0, min_val, max_val, default_value);
// temp->Bind(wxEVT_SPINCTRL, ([this](wxCommandEvent e) { tmp_value = undef_spin_val; on_change_field(); }), temp->GetId());
// temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) { tmp_value = undef_spin_val; on_kill_focus(e); }), temp->GetId());
temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e)
{
// # On OSX / Cocoa, wxSpinCtrl::GetValue() doesn't return the new value
// # when it was changed from the text control, so the on_change callback
// # gets the old one, and on_kill_focus resets the control to the old value.
// # As a workaround, we get the new value from $event->GetString and store
// # here temporarily so that we can return it from $self->get_value
std::string value = e.GetString().utf8_str().data();
if (is_matched(value, "^\\d+$"))
tmp_value = std::stoi(value);
on_change_field();
// # We don't reset tmp_value here because _on_change might put callbacks
// # in the CallAfter queue, and we want the tmp value to be available from
// # them as well.
}), temp->GetId());
temp->SetToolTip(get_tooltip_text(text_value));
// recast as a wxWindow to fit the calling convention
window = dynamic_cast<wxWindow*>(temp);
}
void Choice::BUILD() {
auto size = wxSize(wxDefaultSize);
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
wxComboBox* temp;
if (!m_opt.gui_type.empty() && m_opt.gui_type.compare("select_open") != 0)
temp = new wxComboBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size);
else
temp = new wxComboBox(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, NULL, wxCB_READONLY);
// recast as a wxWindow to fit the calling convention
window = dynamic_cast<wxWindow*>(temp);
if (m_opt.enum_labels.empty() && m_opt.enum_values.empty()){
}
else{
for (auto el : m_opt.enum_labels.empty() ? m_opt.enum_values : m_opt.enum_labels){
const wxString& str = _(el);//m_opt_id == "support" ? _(el) : el;
temp->Append(str);
}
set_selection();
}
temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId());
temp->Bind(wxEVT_COMBOBOX, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId());
temp->SetToolTip(get_tooltip_text(temp->GetValue()));
}
void Choice::set_selection()
{
wxString text_value = wxString("");
switch (m_opt.type){
case coFloat:
case coPercent: {
double val = m_opt.default_value->getFloat();
text_value = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 1);
size_t idx = 0;
for (auto el : m_opt.enum_values)
{
if (el.compare(text_value) == 0)
break;
++idx;
}
// if (m_opt.type == coPercent) text_value += "%";
idx == m_opt.enum_values.size() ?
dynamic_cast<wxComboBox*>(window)->SetValue(text_value) :
dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
break;
}
case coEnum:{
int id_value = static_cast<const ConfigOptionEnum<SeamPosition>*>(m_opt.default_value)->value; //!!
dynamic_cast<wxComboBox*>(window)->SetSelection(id_value);
break;
}
case coInt:{
int val = m_opt.default_value->getInt(); //!!
text_value = wxString::Format(_T("%i"), int(val));
size_t idx = 0;
for (auto el : m_opt.enum_values)
{
if (el.compare(text_value) == 0)
break;
++idx;
}
idx == m_opt.enum_values.size() ?
dynamic_cast<wxComboBox*>(window)->SetValue(text_value) :
dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
break;
}
case coStrings:{
text_value = static_cast<const ConfigOptionStrings*>(m_opt.default_value)->get_at(m_opt_idx);
size_t idx = 0;
for (auto el : m_opt.enum_values)
{
if (el.compare(text_value) == 0)
break;
++idx;
}
idx == m_opt.enum_values.size() ?
dynamic_cast<wxComboBox*>(window)->SetValue(text_value) :
dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
break;
}
}
}
void Choice::set_value(const std::string& value, bool change_event) //! Redundant?
{
m_disable_change_event = !change_event;
size_t idx=0;
for (auto el : m_opt.enum_values)
{
if (el.compare(value) == 0)
break;
++idx;
}
idx == m_opt.enum_values.size() ?
dynamic_cast<wxComboBox*>(window)->SetValue(value) :
dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
m_disable_change_event = false;
}
void Choice::set_value(const boost::any& value, bool change_event)
{
m_disable_change_event = !change_event;
switch (m_opt.type){
case coInt:
case coFloat:
case coPercent:
case coString:
case coStrings:{
wxString text_value;
if (m_opt.type == coInt)
text_value = wxString::Format(_T("%i"), int(boost::any_cast<int>(value)));
else
text_value = boost::any_cast<wxString>(value);
auto idx = 0;
for (auto el : m_opt.enum_values)
{
if (el.compare(text_value) == 0)
break;
++idx;
}
idx == m_opt.enum_values.size() ?
dynamic_cast<wxComboBox*>(window)->SetValue(text_value) :
dynamic_cast<wxComboBox*>(window)->SetSelection(idx);
break;
}
case coEnum:{
int val = boost::any_cast<int>(value);
if (m_opt_id.compare("external_fill_pattern") == 0)
{
if (!m_opt.enum_values.empty()){
std::string key;
t_config_enum_values map_names = ConfigOptionEnum<InfillPattern>::get_enum_values();
for (auto it : map_names) {
if (val == it.second) {
key = it.first;
break;
}
}
size_t idx = 0;
for (auto el : m_opt.enum_values)
{
if (el.compare(key) == 0)
break;
++idx;
}
val = idx == m_opt.enum_values.size() ? 0 : idx;
}
else
val = 0;
}
dynamic_cast<wxComboBox*>(window)->SetSelection(val);
break;
}
default:
break;
}
m_disable_change_event = false;
}
//! it's needed for _update_serial_ports()
void Choice::set_values(const std::vector<std::string>& values)
{
if (values.empty())
return;
m_disable_change_event = true;
// # it looks that Clear() also clears the text field in recent wxWidgets versions,
// # but we want to preserve it
auto ww = dynamic_cast<wxComboBox*>(window);
auto value = ww->GetValue();
ww->Clear();
ww->Append("");
for (auto el : values)
ww->Append(wxString(el));
ww->SetValue(value);
m_disable_change_event = false;
}
boost::any& Choice::get_value()
{
// boost::any m_value;
wxString ret_str = static_cast<wxComboBox*>(window)->GetValue();
// options from right panel
std::vector <std::string> right_panel_options{ "support", "scale_unit" };
for (auto rp_option: right_panel_options)
if (m_opt_id == rp_option)
return m_value = boost::any(ret_str);
if (m_opt.type != coEnum)
/*m_value = */get_value_by_opt_type(ret_str);
else
{
int ret_enum = static_cast<wxComboBox*>(window)->GetSelection();
if (m_opt_id.compare("external_fill_pattern") == 0)
{
if (!m_opt.enum_values.empty()){
std::string key = m_opt.enum_values[ret_enum];
t_config_enum_values map_names = ConfigOptionEnum<InfillPattern>::get_enum_values();
int value = map_names.at(key);
m_value = static_cast<InfillPattern>(value);
}
else
m_value = static_cast<InfillPattern>(0);
}
if (m_opt_id.compare("fill_pattern") == 0)
m_value = static_cast<InfillPattern>(ret_enum);
else if (m_opt_id.compare("gcode_flavor") == 0)
m_value = static_cast<GCodeFlavor>(ret_enum);
else if (m_opt_id.compare("support_material_pattern") == 0)
m_value = static_cast<SupportMaterialPattern>(ret_enum);
else if (m_opt_id.compare("seam_position") == 0)
m_value = static_cast<SeamPosition>(ret_enum);
else if (m_opt_id.compare("host_type") == 0)
m_value = static_cast<PrintHostType>(ret_enum);
}
return m_value;
}
void ColourPicker::BUILD()
{
auto size = wxSize(wxDefaultSize);
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
// Validate the color
wxString clr_str(static_cast<const ConfigOptionStrings*>(m_opt.default_value)->get_at(m_opt_idx));
wxColour clr(clr_str);
if (! clr.IsOk()) {
clr = wxTransparentColour;
}
auto temp = new wxColourPickerCtrl(m_parent, wxID_ANY, clr, wxDefaultPosition, size);
// // recast as a wxWindow to fit the calling convention
window = dynamic_cast<wxWindow*>(temp);
temp->Bind(wxEVT_COLOURPICKER_CHANGED, ([this](wxCommandEvent e) { on_change_field(); }), temp->GetId());
temp->SetToolTip(get_tooltip_text(clr_str));
}
boost::any& ColourPicker::get_value(){
// boost::any m_value;
auto colour = static_cast<wxColourPickerCtrl*>(window)->GetColour();
auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), colour.Red(), colour.Green(), colour.Blue());
m_value = clr_str.ToStdString();
return m_value;
}
void PointCtrl::BUILD()
{
auto size = wxSize(wxDefaultSize);
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
auto temp = new wxBoxSizer(wxHORIZONTAL);
// $self->wxSizer($sizer);
//
wxSize field_size(40, -1);
auto default_pt = static_cast<const ConfigOptionPoints*>(m_opt.default_value)->values.at(0);
double val = default_pt(0);
wxString X = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None);
val = default_pt(1);
wxString Y = val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None);
x_textctrl = new wxTextCtrl(m_parent, wxID_ANY, X, wxDefaultPosition, field_size);
y_textctrl = new wxTextCtrl(m_parent, wxID_ANY, Y, wxDefaultPosition, field_size);
temp->Add(new wxStaticText(m_parent, wxID_ANY, "x : "), 0, wxALIGN_CENTER_VERTICAL, 0);
temp->Add(x_textctrl);
temp->Add(new wxStaticText(m_parent, wxID_ANY, " y : "), 0, wxALIGN_CENTER_VERTICAL, 0);
temp->Add(y_textctrl);
x_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), x_textctrl->GetId());
y_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) { on_change_field(); }), y_textctrl->GetId());
// // recast as a wxWindow to fit the calling convention
sizer = dynamic_cast<wxSizer*>(temp);
x_textctrl->SetToolTip(get_tooltip_text(X+", "+Y));
y_textctrl->SetToolTip(get_tooltip_text(X+", "+Y));
}
void PointCtrl::set_value(const Vec2d& value, bool change_event)
{
m_disable_change_event = !change_event;
double val = value(0);
x_textctrl->SetValue(val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None));
val = value(1);
y_textctrl->SetValue(val - int(val) == 0 ? wxString::Format(_T("%i"), int(val)) : wxNumberFormatter::ToString(val, 2, wxNumberFormatter::Style_None));
m_disable_change_event = false;
}
void PointCtrl::set_value(const boost::any& value, bool change_event)
{
Vec2d pt(Vec2d::Zero());
const Vec2d *ptf = boost::any_cast<Vec2d>(&value);
if (!ptf)
{
ConfigOptionPoints* pts = boost::any_cast<ConfigOptionPoints*>(value);
pt = pts->values.at(0);
}
else
pt = *ptf;
set_value(pt, change_event);
}
boost::any& PointCtrl::get_value()
{
double x, y;
x_textctrl->GetValue().ToDouble(&x);
y_textctrl->GetValue().ToDouble(&y);
return m_value = Vec2d(x, y);
}
void StaticText::BUILD()
{
auto size = wxSize(wxDefaultSize);
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
wxString legend(static_cast<const ConfigOptionString*>(m_opt.default_value)->value);
auto temp = new wxStaticText(m_parent, wxID_ANY, legend, wxDefaultPosition, size);
temp->SetFont(wxGetApp().bold_font());
// // recast as a wxWindow to fit the calling convention
window = dynamic_cast<wxWindow*>(temp);
temp->SetToolTip(get_tooltip_text(legend));
}
void SliderCtrl::BUILD()
{
auto size = wxSize(wxDefaultSize);
if (m_opt.height >= 0) size.SetHeight(m_opt.height);
if (m_opt.width >= 0) size.SetWidth(m_opt.width);
auto temp = new wxBoxSizer(wxHORIZONTAL);
auto def_val = static_cast<const ConfigOptionInt*>(m_opt.default_value)->value;
auto min = m_opt.min == INT_MIN ? 0 : m_opt.min;
auto max = m_opt.max == INT_MAX ? 100 : m_opt.max;
m_slider = new wxSlider(m_parent, wxID_ANY, def_val * m_scale,
min * m_scale, max * m_scale,
wxDefaultPosition, size);
wxSize field_size(40, -1);
m_textctrl = new wxTextCtrl(m_parent, wxID_ANY, wxString::Format("%d", m_slider->GetValue()/m_scale),
wxDefaultPosition, field_size);
temp->Add(m_slider, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL, 0);
temp->Add(m_textctrl, 0, wxALIGN_CENTER_VERTICAL, 0);
m_slider->Bind(wxEVT_SLIDER, ([this](wxCommandEvent e) {
if (!m_disable_change_event){
int val = boost::any_cast<int>(get_value());
m_textctrl->SetLabel(wxString::Format("%d", val));
on_change_field();
}
}), m_slider->GetId());
m_textctrl->Bind(wxEVT_TEXT, ([this](wxCommandEvent e) {
std::string value = e.GetString().utf8_str().data();
if (is_matched(value, "^-?\\d+(\\.\\d*)?$")){
m_disable_change_event = true;
m_slider->SetValue(stoi(value)*m_scale);
m_disable_change_event = false;
on_change_field();
}
}), m_textctrl->GetId());
m_sizer = dynamic_cast<wxSizer*>(temp);
}
void SliderCtrl::set_value(const boost::any& value, bool change_event)
{
m_disable_change_event = !change_event;
m_slider->SetValue(boost::any_cast<int>(value)*m_scale);
int val = boost::any_cast<int>(get_value());
m_textctrl->SetLabel(wxString::Format("%d", val));
m_disable_change_event = false;
}
boost::any& SliderCtrl::get_value()
{
// int ret_val;
// x_textctrl->GetValue().ToDouble(&val);
return m_value = int(m_slider->GetValue()/m_scale);
}
} // GUI
} // Slic3r

469
src/slic3r/GUI/Field.hpp Normal file
View file

@ -0,0 +1,469 @@
#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 <functional>
#include <boost/any.hpp>
#include <wx/spinctrl.h>
#include <wx/clrpicker.h>
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/Config.hpp"
//#include "slic3r_gui.hpp"
#include "GUI.hpp"
#include "Utils.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()>;
using t_change = std::function<void(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);
class MyButton : public wxButton
{
bool hidden = false; // never show button if it's hidden ones
public:
MyButton() {}
MyButton(wxWindow* parent, wxWindowID id, const wxString& label = wxEmptyString,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize, long style = 0,
const wxValidator& validator = wxDefaultValidator,
const wxString& name = wxTextCtrlNameStr)
{
this->Create(parent, id, label, pos, size, style, validator, name);
}
// overridden from wxWindow base class
virtual bool
AcceptsFocusFromKeyboard() const { return false; }
void set_as_hidden() {
Hide();
hidden = true;
}
virtual bool Show(bool show = true) override {
return wxButton::Show(hidden ? false : show);
}
};
class Field {
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(wxEvent& event);
/// Call the attached on_change method.
void on_change_field();
/// 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;
/// 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;
/// 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);
// set icon to "UndoToSystemValue" button according to an inheritance of preset
// void set_nonsys_btn_icon(const wxBitmap& icon);
Field(const ConfigOptionDef& opt, const t_config_option_key& id) : m_opt(opt), m_opt_id(id) {};
Field(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : m_parent(parent), m_opt(opt), m_opt_id(id) {};
/// 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);
/// 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;
}
bool set_undo_bitmap(const wxBitmap *bmp) {
if (m_undo_bitmap != bmp) {
m_undo_bitmap = bmp;
m_Undo_btn->SetBitmap(*bmp);
return true;
}
return false;
}
bool set_undo_to_sys_bitmap(const wxBitmap *bmp) {
if (m_undo_to_sys_bitmap != bmp) {
m_undo_to_sys_bitmap = bmp;
m_Undo_to_sys_btn->SetBitmap(*bmp);
return true;
}
return false;
}
bool set_label_colour(const wxColour *clr) {
if (m_Label == nullptr) return false;
if (m_label_color != clr) {
m_label_color = clr;
m_Label->SetForegroundColour(*clr);
m_Label->Refresh(true);
}
return false;
}
bool set_label_colour_force(const wxColour *clr) {
if (m_Label == nullptr) return false;
m_Label->SetForegroundColour(*clr);
m_Label->Refresh(true);
return false;
}
bool set_undo_tooltip(const wxString *tip) {
if (m_undo_tooltip != tip) {
m_undo_tooltip = tip;
m_Undo_btn->SetToolTip(*tip);
return true;
}
return false;
}
bool set_undo_to_sys_tooltip(const wxString *tip) {
if (m_undo_to_sys_tooltip != tip) {
m_undo_to_sys_tooltip = tip;
m_Undo_to_sys_btn->SetToolTip(*tip);
return true;
}
return false;
}
void set_side_text_ptr(wxStaticText* side_text) {
m_side_text = side_text;
}
protected:
MyButton* m_Undo_btn = nullptr;
// Bitmap and Tooltip text for m_Undo_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
const wxBitmap* m_undo_bitmap = nullptr;
const wxString* m_undo_tooltip = nullptr;
MyButton* m_Undo_to_sys_btn = nullptr;
// Bitmap and Tooltip text for m_Undo_to_sys_btn. The wxButton will be updated only if the new wxBitmap pointer differs from the currently rendered one.
const wxBitmap* m_undo_to_sys_bitmap = nullptr;
const wxString* m_undo_to_sys_tooltip = nullptr;
wxStaticText* m_Label = nullptr;
// Color for Label. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one.
const wxColour* m_label_color = nullptr;
wxStaticText* m_side_text = nullptr;
// current value
boost::any m_value;
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();
wxWindow* window {nullptr};
virtual 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;
}
virtual void set_value(const boost::any& value, bool change_event = false) {
m_disable_change_event = !change_event;
dynamic_cast<wxTextCtrl*>(window)->SetValue(boost::any_cast<wxString>(value));
m_disable_change_event = false;
}
boost::any& get_value() override;
virtual void enable();
virtual void disable();
virtual wxWindow* getWindow() { return window; }
};
class CheckBox : public Field {
using Field::Field;
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) {
m_disable_change_event = !change_event;
dynamic_cast<wxCheckBox*>(window)->SetValue(boost::any_cast<bool>(value));
m_disable_change_event = false;
}
boost::any& get_value() 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;
public:
SpinCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id), tmp_value(-9999) {}
SpinCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id), tmp_value(-9999) {}
~SpinCtrl() {}
int tmp_value;
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<wxSpinCtrl*>(window)->SetValue(value);
m_disable_change_event = false;
}
void set_value(const boost::any& value, bool change_event = false) {
m_disable_change_event = !change_event;
tmp_value = boost::any_cast<int>(value);
dynamic_cast<wxSpinCtrl*>(window)->SetValue(tmp_value);
m_disable_change_event = false;
}
boost::any& get_value() override {
// return boost::any(tmp_value);
return m_value = tmp_value;
}
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;
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);
void set_values(const std::vector<std::string> &values);
boost::any& get_value() override;
void enable() override { dynamic_cast<wxComboBox*>(window)->Enable(); };
void disable() override{ dynamic_cast<wxComboBox*>(window)->Disable(); };
wxWindow* getWindow() override { return window; }
};
class ColourPicker : public Field {
using Field::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) {
m_disable_change_event = !change_event;
dynamic_cast<wxColourPickerCtrl*>(window)->SetColour(boost::any_cast<wxString>(value));
m_disable_change_event = false;
}
boost::any& get_value() 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;
void set_value(const Vec2d& value, bool change_event = false);
void set_value(const boost::any& value, bool change_event = false);
boost::any& get_value() override;
void enable() override {
x_textctrl->Enable();
y_textctrl->Enable(); }
void disable() override{
x_textctrl->Disable();
y_textctrl->Disable(); }
wxSizer* getSizer() override { return sizer; }
};
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(value);
m_disable_change_event = false;
}
void set_value(const boost::any& value, bool change_event = false) {
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 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);
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 */

View file

@ -0,0 +1,847 @@
#include <numeric>
#include <algorithm>
#include <thread>
#include <condition_variable>
#include <stdexcept>
#include <boost/format.hpp>
#include <boost/asio.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/log/trivial.hpp>
#include <boost/optional.hpp>
#include "libslic3r/Utils.hpp"
#include "avrdude/avrdude-slic3r.hpp"
#include "GUI.hpp"
#include "MsgDialog.hpp"
#include "../Utils/HexFile.hpp"
#include "../Utils/Serial.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>
#include "GUI_App.hpp"
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,
};
// 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);
// 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 *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;
// Async modal dialog during flashing
std::mutex mutex;
int modal_response;
std::condition_variable response_cv;
// Data
std::vector<SerialPortInfo> ports;
optional<SerialPortInfo> port;
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;
std::string avrdude_config;
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),
avrdude_config((fs::path(::Slic3r::resources_dir()) / "avrdude" / "avrdude.conf").string()),
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 enable_port_picker(bool enable);
void load_hex_file(const wxString &path);
void queue_status(wxString message);
void queue_error(const wxString &message);
bool ask_model_id_mismatch(const std::string &printer_model);
bool check_model_id();
void wait_for_mmu_bootloader(unsigned retries);
void mmu_reboot(const SerialPortInfo &port);
void lookup_port_mmu();
void prepare_common();
void prepare_mk2();
void prepare_mk3();
void prepare_mm_control();
void perform_upload();
void user_cancel();
void on_avrdude(const wxCommandEvent &evt);
void on_async_dialog(const wxCommandEvent &evt);
void ensure_joined();
};
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);
}
}
}
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)
{
auto text_color = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
port_picker->Enable();
btn_rescan->Enable();
hex_picker->Enable();
btn_close->Enable();
btn_flash->SetLabel(btn_flash_label_ready);
txt_status->SetForegroundColour(text_color);
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::enable_port_picker(bool enable)
{
port_picker->Show(enable);
btn_rescan->Show(enable);
port_autodetect->Show(! enable);
q->Layout();
fit_no_shrink();
}
void FirmwareDialog::priv::load_hex_file(const wxString &path)
{
hex_file = HexFile(path.wx_str());
enable_port_picker(hex_file.device != HexFile::DEV_MM_CONTROL);
}
void FirmwareDialog::priv::queue_status(wxString message)
{
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
evt->SetExtraLong(AE_STATUS);
evt->SetString(std::move(message));
wxQueueEvent(this->q, evt);
}
void FirmwareDialog::priv::queue_error(const wxString &message)
{
auto evt = new wxCommandEvent(EVT_AVRDUDE, this->q->GetId());
evt->SetExtraLong(AE_STATUS);
evt->SetString(wxString::Format(_(L("Flashing failed: %s")), message));
wxQueueEvent(this->q, evt); avrdude->cancel();
}
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::wait_for_mmu_bootloader(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_MMU_BOOT;
}), ports.end());
if (ports.size() == 1) {
port = ports[0];
return;
} else if (ports.size() > 1) {
BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found";
queue_error(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing.")));
return;
}
}
}
void FirmwareDialog::priv::mmu_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::lookup_port_mmu()
{
static const auto msg_not_found =
"The Multi Material Control device was not found.\n"
"If the device is connected, please press the Reset button next to the USB connector ...";
BOOST_LOG_TRIVIAL(info) << "Flashing MMU 2.0, looking for VID/PID 0x2c99/3 or 0x2c99/4 ...";
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_MMU_BOOT &&
port.id_product != USB_PID_MMU_APP;
}), ports.end());
if (ports.size() == 0) {
BOOST_LOG_TRIVIAL(info) << "MMU 2.0 device not found, asking the user to press Reset and waiting for the device to show up ...";
queue_status(_(L(msg_not_found)));
wait_for_mmu_bootloader(30);
} else if (ports.size() > 1) {
BOOST_LOG_TRIVIAL(error) << "Several VID/PID 0x2c99/3 devices found";
queue_error(_(L("Multiple Original Prusa i3 MMU 2.0 devices found. Please only connect one at a time for flashing.")));
} else {
if (ports[0].id_product == USB_PID_MMU_APP) {
// The device needs to be rebooted into the bootloader mode
BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/4 at `%1%`, rebooting the device ...") % ports[0].port;
mmu_reboot(ports[0]);
wait_for_mmu_bootloader(10);
if (! port) {
// The device in bootloader mode was not found, inform the user and wait some more...
BOOST_LOG_TRIVIAL(info) << "MMU 2.0 bootloader device not found after reboot, asking the user to press Reset and waiting for the device to show up ...";
queue_status(_(L(msg_not_found)));
wait_for_mmu_bootloader(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 Prusa'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) << "Invoking 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));
}
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 Prusa'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) << "Invoking avrdude for external flash flashing, 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));
}
void FirmwareDialog::priv::prepare_mm_control()
{
port = boost::none;
lookup_port_mmu();
if (! port) {
queue_error(_(L("The device could not have been found")));
return;
}
BOOST_LOG_TRIVIAL(info) << boost::format("Found VID/PID 0x2c99/3 at `%1%`, flashing ...") % 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) << "Invoking 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));
}
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
int selection = port_picker->GetSelection();
if (selection != wxNOT_FOUND) {
port = this->ports[selection];
// Verify whether the combo box list selection equals to the combo box edit value.
if (wxString::FromUTF8(port->friendly_name.data()) != port_picker->GetValue()) {
return;
}
}
const bool extra_verbose = false; // For debugging
flashing_start(hex_file.device == HexFile::DEV_MK3 ? 2 : 1);
// Init the avrdude object
AvrDude avrdude(avrdude_config);
// 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_mm_control();
break;
default:
this->prepare_mk2();
break;
}
} catch (const std::exception &ex) {
queue_error(wxString::Format(_(L("Error accessing port at %s: %s")), port->port, ex.what()));
}
})
.on_message(std::move([q, extra_verbose](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);
evt->SetExtraLong(AE_MESSAGE);
evt->SetString(std::move(wxmsg));
wxQueueEvent(q, evt);
}))
.on_progress(std::move([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(std::move([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);
{
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();
}
// Public
FirmwareDialog::FirmwareDialog(wxWindow *parent) :
wxDialog(parent, wxID_ANY, _(L("Firmware flasher")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER),
p(new priv(this))
{
enum {
DIALOG_MARGIN = 15,
SPACING = 10,
MIN_WIDTH = 600,
MIN_HEIGHT = 200,
MIN_HEIGHT_EXPANDED = 500,
};
wxFont status_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
status_font.MakeBold();
wxFont mono_font(wxFontInfo().Family(wxFONTFAMILY_TELETYPE));
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,
"Hex files (*.hex)|*.hex|All files|*.*");
auto *label_port_picker = new wxStaticText(panel, wxID_ANY, _(L("Serial port:")));
p->port_picker = new wxComboBox(panel, wxID_ANY);
p->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->port_autodetect, 1, wxEXPAND);
p->enable_port_picker(true);
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: avrdude 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);
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);
auto *topsizer = new wxBoxSizer(wxVERTICAL);
topsizer->Add(panel, 1, wxEXPAND | wxALL, DIALOG_MARGIN);
SetMinSize(wxSize(MIN_WIDTH, MIN_HEIGHT));
SetSizerAndFit(topsizer);
const auto size = GetSize();
SetSize(std::max(size.GetWidth(), static_cast<int>(MIN_WIDTH)), std::max(size.GetHeight(), static_cast<int>(MIN_HEIGHT)));
Layout();
// Bind events
p->hex_picker->Bind(wxEVT_FILEPICKER_CHANGED, [this](wxFileDirPickerEvent& evt) {
if (wxFileExists(evt.GetPath())) {
this->p->load_hex_file(evt.GetPath());
this->p->btn_flash->Enable();
}
});
p->spoiler->Bind(wxEVT_COLLAPSIBLEPANE_CHANGED, [this](wxCollapsiblePaneEvent &evt) {
if (evt.GetCollapsed()) {
this->SetMinSize(wxSize(MIN_WIDTH, 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(MIN_WIDTH, MIN_HEIGHT_EXPANDED));
}
this->Layout();
this->p->fit_no_shrink();
});
p->btn_close->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { this->Close(); });
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,
_(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 {
evt.Skip();
}
});
p->find_serial_ports();
}
FirmwareDialog::~FirmwareDialog()
{
// Needed bacuse of forward defs
}
void FirmwareDialog::run(wxWindow *parent)
{
FirmwareDialog dialog(parent);
dialog.ShowModal();
}
}

View file

@ -0,0 +1,31 @@
#ifndef slic3r_FirmwareDialog_hpp_
#define slic3r_FirmwareDialog_hpp_
#include <memory>
#include <wx/dialog.h>
namespace Slic3r {
class FirmwareDialog: public wxDialog
{
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);
private:
struct priv;
std::unique_ptr<priv> p;
};
}
#endif

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,691 @@
#include "GLCanvas3DManager.hpp"
#include "../../slic3r/GUI/GUI.hpp"
#include "../../slic3r/GUI/AppConfig.hpp"
#include "../../slic3r/GUI/GLCanvas3D.hpp"
#include <GL/glew.h>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <wx/glcanvas.h>
#include <wx/timer.h>
#include <vector>
#include <string>
#include <iostream>
namespace Slic3r {
namespace GUI {
GLCanvas3DManager::GLInfo::GLInfo()
: version("")
, glsl_version("")
, vendor("")
, renderer("")
{
}
void GLCanvas3DManager::GLInfo::detect()
{
const char* data = (const char*)::glGetString(GL_VERSION);
if (data != nullptr)
version = data;
data = (const char*)::glGetString(GL_SHADING_LANGUAGE_VERSION);
if (data != nullptr)
glsl_version = data;
data = (const char*)::glGetString(GL_VENDOR);
if (data != nullptr)
vendor = data;
data = (const char*)::glGetString(GL_RENDERER);
if (data != nullptr)
renderer = data;
}
bool GLCanvas3DManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const
{
std::vector<std::string> tokens;
boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on);
if (tokens.empty())
return false;
std::vector<std::string> numbers;
boost::split(numbers, tokens[0], boost::is_any_of("."), boost::token_compress_on);
unsigned int gl_major = 0;
unsigned int gl_minor = 0;
if (numbers.size() > 0)
gl_major = ::atoi(numbers[0].c_str());
if (numbers.size() > 1)
gl_minor = ::atoi(numbers[1].c_str());
if (gl_major < major)
return false;
else if (gl_major > major)
return true;
else
return gl_minor >= minor;
}
std::string GLCanvas3DManager::GLInfo::to_string(bool format_as_html, bool extensions) const
{
std::stringstream out;
std::string h2_start = format_as_html ? "<b>" : "";
std::string h2_end = format_as_html ? "</b>" : "";
std::string b_start = format_as_html ? "<b>" : "";
std::string b_end = format_as_html ? "</b>" : "";
std::string line_end = format_as_html ? "<br>" : "\n";
out << h2_start << "OpenGL installation" << h2_end << line_end;
out << b_start << "GL version: " << b_end << (version.empty() ? "N/A" : version) << line_end;
out << b_start << "Vendor: " << b_end << (vendor.empty() ? "N/A" : vendor) << line_end;
out << b_start << "Renderer: " << b_end << (renderer.empty() ? "N/A" : renderer) << line_end;
out << b_start << "GLSL version: " << b_end << (glsl_version.empty() ? "N/A" : glsl_version) << line_end;
if (extensions)
{
std::vector<std::string> extensions_list;
std::string extensions_str = (const char*)::glGetString(GL_EXTENSIONS);
boost::split(extensions_list, extensions_str, boost::is_any_of(" "), boost::token_compress_off);
if (!extensions_list.empty())
{
out << h2_start << "Installed extensions:" << h2_end << line_end;
std::sort(extensions_list.begin(), extensions_list.end());
for (const std::string& ext : extensions_list)
{
out << ext << line_end;
}
}
}
return out.str();
}
GLCanvas3DManager::EMultisampleState GLCanvas3DManager::s_multisample = GLCanvas3DManager::MS_Unknown;
GLCanvas3DManager::GLCanvas3DManager()
#if ENABLE_USE_UNIQUE_GLCONTEXT
: m_context(nullptr)
, m_current(nullptr)
#else
: m_current(nullptr)
#endif // ENABLE_USE_UNIQUE_GLCONTEXT
, m_gl_initialized(false)
, m_use_legacy_opengl(false)
, m_use_VBOs(false)
{
}
#if ENABLE_USE_UNIQUE_GLCONTEXT
GLCanvas3DManager::~GLCanvas3DManager()
{
if (m_context != nullptr)
{
delete m_context;
m_context = nullptr;
}
}
#endif // ENABLE_USE_UNIQUE_GLCONTEXT
bool GLCanvas3DManager::add(wxGLCanvas* canvas)
{
if (canvas == nullptr)
return false;
if (_get_canvas(canvas) != m_canvases.end())
return false;
GLCanvas3D* canvas3D = new GLCanvas3D(canvas);
if (canvas3D == nullptr)
return false;
canvas3D->bind_event_handlers();
#if ENABLE_USE_UNIQUE_GLCONTEXT
if (m_context == nullptr)
{
m_context = new wxGLContext(canvas);
if (m_context == nullptr)
return false;
}
canvas3D->set_context(m_context);
#endif // ENABLE_USE_UNIQUE_GLCONTEXT
m_canvases.insert(CanvasesMap::value_type(canvas, canvas3D));
return true;
}
bool GLCanvas3DManager::remove(wxGLCanvas* canvas)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it == m_canvases.end())
return false;
it->second->unbind_event_handlers();
delete it->second;
m_canvases.erase(it);
return true;
}
void GLCanvas3DManager::remove_all()
{
for (CanvasesMap::value_type& item : m_canvases)
{
item.second->unbind_event_handlers();
delete item.second;
}
m_canvases.clear();
}
unsigned int GLCanvas3DManager::count() const
{
return (unsigned int)m_canvases.size();
}
void GLCanvas3DManager::init_gl()
{
if (!m_gl_initialized)
{
glewInit();
m_gl_info.detect();
const AppConfig* config = GUI::get_app_config();
m_use_legacy_opengl = (config == nullptr) || (config->get("use_legacy_opengl") == "1");
m_use_VBOs = !m_use_legacy_opengl && m_gl_info.is_version_greater_or_equal_to(2, 0);
m_gl_initialized = true;
}
}
std::string GLCanvas3DManager::get_gl_info(bool format_as_html, bool extensions) const
{
return m_gl_info.to_string(format_as_html, extensions);
}
bool GLCanvas3DManager::use_VBOs() const
{
return m_use_VBOs;
}
bool GLCanvas3DManager::init(wxGLCanvas* canvas)
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
return (it->second != nullptr) ? _init(*it->second) : false;
else
return false;
}
void GLCanvas3DManager::set_as_dirty(wxGLCanvas* canvas)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_as_dirty();
}
unsigned int GLCanvas3DManager::get_volumes_count(wxGLCanvas* canvas) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->get_volumes_count() : 0;
}
void GLCanvas3DManager::reset_volumes(wxGLCanvas* canvas)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->reset_volumes();
}
#if !ENABLE_EXTENDED_SELECTION
void GLCanvas3DManager::deselect_volumes(wxGLCanvas* canvas)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->deselect_volumes();
}
void GLCanvas3DManager::select_volume(wxGLCanvas* canvas, unsigned int id)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->select_volume(id);
}
void GLCanvas3DManager::update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->update_volumes_selection(selections);
}
#endif // !ENABLE_EXTENDED_SELECTION
int GLCanvas3DManager::check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->check_volumes_outside_state(config) : false;
}
bool GLCanvas3DManager::move_volume_up(wxGLCanvas* canvas, unsigned int id)
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->move_volume_up(id) : false;
}
bool GLCanvas3DManager::move_volume_down(wxGLCanvas* canvas, unsigned int id)
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->move_volume_down(id) : false;
}
#if ENABLE_EXTENDED_SELECTION
GLCanvas3D* GLCanvas3DManager::get_canvas(wxGLCanvas* canvas)
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second : nullptr;
}
#else
void GLCanvas3DManager::set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_objects_selections(selections);
}
#endif // ENABLE_EXTENDED_SELECTION
void GLCanvas3DManager::set_config(wxGLCanvas* canvas, DynamicPrintConfig* config)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_config(config);
}
void GLCanvas3DManager::set_print(wxGLCanvas* canvas, Print* print)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_print(print);
}
void GLCanvas3DManager::set_model(wxGLCanvas* canvas, Model* model)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_model(model);
}
void GLCanvas3DManager::set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_bed_shape(shape);
}
void GLCanvas3DManager::set_auto_bed_shape(wxGLCanvas* canvas)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_auto_bed_shape();
}
BoundingBoxf3 GLCanvas3DManager::get_volumes_bounding_box(wxGLCanvas* canvas)
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->volumes_bounding_box() : BoundingBoxf3();
}
void GLCanvas3DManager::set_axes_length(wxGLCanvas* canvas, float length)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_axes_length(length);
}
void GLCanvas3DManager::set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_cutting_plane(z, polygons);
}
void GLCanvas3DManager::set_color_by(wxGLCanvas* canvas, const std::string& value)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_color_by(value);
}
#if !ENABLE_EXTENDED_SELECTION
void GLCanvas3DManager::set_select_by(wxGLCanvas* canvas, const std::string& value)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_select_by(value);
}
void GLCanvas3DManager::set_drag_by(wxGLCanvas* canvas, const std::string& value)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_drag_by(value);
}
std::string GLCanvas3DManager::get_select_by(wxGLCanvas* canvas) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->get_select_by() : "";
}
#endif // !ENABLE_EXTENDED_SELECTION
bool GLCanvas3DManager::is_layers_editing_enabled(wxGLCanvas* canvas) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->is_layers_editing_enabled() : false;
}
bool GLCanvas3DManager::is_layers_editing_allowed(wxGLCanvas* canvas) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->is_layers_editing_allowed() : false;
}
bool GLCanvas3DManager::is_shader_enabled(wxGLCanvas* canvas) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->is_shader_enabled() : false;
}
bool GLCanvas3DManager::is_reload_delayed(wxGLCanvas* canvas) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->is_reload_delayed() : false;
}
void GLCanvas3DManager::enable_layers_editing(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_layers_editing(enable);
}
void GLCanvas3DManager::enable_warning_texture(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_warning_texture(enable);
}
void GLCanvas3DManager::enable_legend_texture(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_legend_texture(enable);
}
void GLCanvas3DManager::enable_picking(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_picking(enable);
}
void GLCanvas3DManager::enable_moving(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_moving(enable);
}
void GLCanvas3DManager::enable_gizmos(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_gizmos(enable);
}
void GLCanvas3DManager::enable_toolbar(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_toolbar(enable);
}
void GLCanvas3DManager::enable_shader(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_shader(enable);
}
void GLCanvas3DManager::enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_force_zoom_to_bed(enable);
}
void GLCanvas3DManager::enable_dynamic_background(wxGLCanvas* canvas, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_dynamic_background(enable);
}
void GLCanvas3DManager::allow_multisample(wxGLCanvas* canvas, bool allow)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->allow_multisample(allow);
}
void GLCanvas3DManager::enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->enable_toolbar_item(name, enable);
}
bool GLCanvas3DManager::is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->is_toolbar_item_pressed(name) : false;
}
void GLCanvas3DManager::zoom_to_bed(wxGLCanvas* canvas)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->zoom_to_bed();
}
void GLCanvas3DManager::zoom_to_volumes(wxGLCanvas* canvas)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->zoom_to_volumes();
}
void GLCanvas3DManager::select_view(wxGLCanvas* canvas, const std::string& direction)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->select_view(direction);
}
void GLCanvas3DManager::set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
{
CanvasesMap::iterator other_it = _get_canvas(other);
if (other_it != m_canvases.end())
it->second->set_viewport_from_scene(*other_it->second);
}
}
void GLCanvas3DManager::update_volumes_colors_by_extruder(wxGLCanvas* canvas)
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->update_volumes_colors_by_extruder();
}
void GLCanvas3DManager::update_gizmos_data(wxGLCanvas* canvas)
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->update_gizmos_data();
}
void GLCanvas3DManager::render(wxGLCanvas* canvas) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->render();
}
std::vector<double> GLCanvas3DManager::get_current_print_zs(wxGLCanvas* canvas, bool active_only) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->get_current_print_zs(active_only) : std::vector<double>();
}
void GLCanvas3DManager::set_toolpaths_range(wxGLCanvas* canvas, double low, double high)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->set_toolpaths_range(low, high);
}
std::vector<int> GLCanvas3DManager::load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs)
{
if (model_object == nullptr)
return std::vector<int>();
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->load_object(*model_object, obj_idx, instance_idxs) : std::vector<int>();
}
std::vector<int> GLCanvas3DManager::load_object(wxGLCanvas* canvas, const Model* model, int obj_idx)
{
if (model == nullptr)
return std::vector<int>();
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->load_object(*model, obj_idx) : std::vector<int>();
}
int GLCanvas3DManager::get_first_volume_id(wxGLCanvas* canvas, int obj_idx) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->get_first_volume_id(obj_idx) : -1;
}
int GLCanvas3DManager::get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx) const
{
CanvasesMap::const_iterator it = _get_canvas(canvas);
return (it != m_canvases.end()) ? it->second->get_in_object_volume_id(scene_vol_idx) : -1;
}
#if ENABLE_MIRROR
#if ENABLE_EXTENDED_SELECTION
void GLCanvas3DManager::mirror_selection(wxGLCanvas* canvas, Axis axis)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->mirror_selection(axis);
}
#endif // ENABLE_EXTENDED_SELECTION
#endif // ENABLE_MIRROR
void GLCanvas3DManager::reload_scene(wxGLCanvas* canvas, bool force)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->reload_scene(force);
}
void GLCanvas3DManager::load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors)
{
if (preview_data == nullptr)
return;
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->load_gcode_preview(*preview_data, str_tool_colors);
}
void GLCanvas3DManager::load_preview(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->load_preview(str_tool_colors);
}
void GLCanvas3DManager::reset_legend_texture(wxGLCanvas* canvas)
{
CanvasesMap::iterator it = _get_canvas(canvas);
if (it != m_canvases.end())
it->second->reset_legend_texture();
}
wxGLCanvas* GLCanvas3DManager::create_wxglcanvas(wxWindow *parent)
{
int attribList[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 24, WX_GL_SAMPLE_BUFFERS, GL_TRUE, WX_GL_SAMPLES, 4, 0 };
if (s_multisample == MS_Unknown)
{
_detect_multisample(attribList);
// debug output
std::cout << "Multisample " << (can_multisample() ? "enabled" : "disabled") << std::endl;
}
if (! can_multisample()) {
attribList[4] = 0;
}
return new wxGLCanvas(parent, wxID_ANY, attribList);
}
GLCanvas3DManager::CanvasesMap::iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas)
{
return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas);
}
GLCanvas3DManager::CanvasesMap::const_iterator GLCanvas3DManager::_get_canvas(wxGLCanvas* canvas) const
{
return (canvas == nullptr) ? m_canvases.end() : m_canvases.find(canvas);
}
bool GLCanvas3DManager::_init(GLCanvas3D& canvas)
{
if (!m_gl_initialized)
init_gl();
return canvas.init(m_use_VBOs, m_use_legacy_opengl);
}
void GLCanvas3DManager::_detect_multisample(int* attribList)
{
int wxVersion = wxMAJOR_VERSION * 10000 + wxMINOR_VERSION * 100 + wxRELEASE_NUMBER;
const AppConfig* app_config = GUI::get_app_config();
bool enable_multisample = app_config != nullptr
&& app_config->get("use_legacy_opengl") != "1"
&& wxVersion >= 30003;
s_multisample = (enable_multisample && wxGLCanvas::IsDisplaySupported(attribList)) ? MS_Enabled : MS_Disabled;
// Alternative method: it was working on previous version of wxWidgets but not with the latest, at least on Windows
// s_multisample = enable_multisample && wxGLCanvas::IsExtensionSupported("WGL_ARB_multisample");
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,193 @@
#ifndef slic3r_GLCanvas3DManager_hpp_
#define slic3r_GLCanvas3DManager_hpp_
#include "../../libslic3r/BoundingBox.hpp"
#include <map>
#include <vector>
class wxWindow;
class wxGLCanvas;
class wxGLContext;
namespace Slic3r {
class DynamicPrintConfig;
class Print;
class Model;
class ExPolygon;
typedef std::vector<ExPolygon> ExPolygons;
class ModelObject;
class PrintObject;
class GCodePreviewData;
namespace GUI {
class GLCanvas3D;
class GLCanvas3DManager
{
struct GLInfo
{
std::string version;
std::string glsl_version;
std::string vendor;
std::string renderer;
GLInfo();
void detect();
bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const;
std::string to_string(bool format_as_html, bool extensions) const;
};
enum EMultisampleState : unsigned char
{
MS_Unknown,
MS_Enabled,
MS_Disabled
};
typedef std::map<wxGLCanvas*, GLCanvas3D*> CanvasesMap;
CanvasesMap m_canvases;
#if ENABLE_USE_UNIQUE_GLCONTEXT
wxGLContext* m_context;
#endif // ENABLE_USE_UNIQUE_GLCONTEXT
wxGLCanvas* m_current;
GLInfo m_gl_info;
bool m_gl_initialized;
bool m_use_legacy_opengl;
bool m_use_VBOs;
static EMultisampleState s_multisample;
public:
GLCanvas3DManager();
#if ENABLE_USE_UNIQUE_GLCONTEXT
~GLCanvas3DManager();
#endif // ENABLE_USE_UNIQUE_GLCONTEXT
bool add(wxGLCanvas* canvas);
bool remove(wxGLCanvas* canvas);
void remove_all();
unsigned int count() const;
void init_gl();
std::string get_gl_info(bool format_as_html, bool extensions) const;
bool use_VBOs() const;
bool layer_editing_allowed() const;
bool init(wxGLCanvas* canvas);
void set_as_dirty(wxGLCanvas* canvas);
unsigned int get_volumes_count(wxGLCanvas* canvas) const;
void reset_volumes(wxGLCanvas* canvas);
#if !ENABLE_EXTENDED_SELECTION
void deselect_volumes(wxGLCanvas* canvas);
void select_volume(wxGLCanvas* canvas, unsigned int id);
void update_volumes_selection(wxGLCanvas* canvas, const std::vector<int>& selections);
#endif // !ENABLE_EXTENDED_SELECTION
int check_volumes_outside_state(wxGLCanvas* canvas, const DynamicPrintConfig* config) const;
bool move_volume_up(wxGLCanvas* canvas, unsigned int id);
bool move_volume_down(wxGLCanvas* canvas, unsigned int id);
#if ENABLE_EXTENDED_SELECTION
GLCanvas3D* get_canvas(wxGLCanvas* canvas);
#else
void set_objects_selections(wxGLCanvas* canvas, const std::vector<int>& selections);
#endif // ENABLE_EXTENDED_SELECTION
void set_config(wxGLCanvas* canvas, DynamicPrintConfig* config);
void set_print(wxGLCanvas* canvas, Print* print);
void set_model(wxGLCanvas* canvas, Model* model);
void set_bed_shape(wxGLCanvas* canvas, const Pointfs& shape);
void set_auto_bed_shape(wxGLCanvas* canvas);
BoundingBoxf3 get_volumes_bounding_box(wxGLCanvas* canvas);
void set_axes_length(wxGLCanvas* canvas, float length);
void set_cutting_plane(wxGLCanvas* canvas, float z, const ExPolygons& polygons);
void set_color_by(wxGLCanvas* canvas, const std::string& value);
#if !ENABLE_EXTENDED_SELECTION
void set_select_by(wxGLCanvas* canvas, const std::string& value);
void set_drag_by(wxGLCanvas* canvas, const std::string& value);
std::string get_select_by(wxGLCanvas* canvas) const;
#endif // !ENABLE_EXTENDED_SELECTION
bool is_layers_editing_enabled(wxGLCanvas* canvas) const;
bool is_layers_editing_allowed(wxGLCanvas* canvas) const;
bool is_shader_enabled(wxGLCanvas* canvas) const;
bool is_reload_delayed(wxGLCanvas* canvas) const;
void enable_layers_editing(wxGLCanvas* canvas, bool enable);
void enable_warning_texture(wxGLCanvas* canvas, bool enable);
void enable_legend_texture(wxGLCanvas* canvas, bool enable);
void enable_picking(wxGLCanvas* canvas, bool enable);
void enable_moving(wxGLCanvas* canvas, bool enable);
void enable_gizmos(wxGLCanvas* canvas, bool enable);
void enable_toolbar(wxGLCanvas* canvas, bool enable);
void enable_shader(wxGLCanvas* canvas, bool enable);
void enable_force_zoom_to_bed(wxGLCanvas* canvas, bool enable);
void enable_dynamic_background(wxGLCanvas* canvas, bool enable);
void allow_multisample(wxGLCanvas* canvas, bool allow);
void enable_toolbar_item(wxGLCanvas* canvas, const std::string& name, bool enable);
bool is_toolbar_item_pressed(wxGLCanvas* canvas, const std::string& name) const;
void zoom_to_bed(wxGLCanvas* canvas);
void zoom_to_volumes(wxGLCanvas* canvas);
void select_view(wxGLCanvas* canvas, const std::string& direction);
void set_viewport_from_scene(wxGLCanvas* canvas, wxGLCanvas* other);
void update_volumes_colors_by_extruder(wxGLCanvas* canvas);
void update_gizmos_data(wxGLCanvas* canvas);
void render(wxGLCanvas* canvas) const;
std::vector<double> get_current_print_zs(wxGLCanvas* canvas, bool active_only) const;
void set_toolpaths_range(wxGLCanvas* canvas, double low, double high);
std::vector<int> load_object(wxGLCanvas* canvas, const ModelObject* model_object, int obj_idx, std::vector<int> instance_idxs);
std::vector<int> load_object(wxGLCanvas* canvas, const Model* model, int obj_idx);
int get_first_volume_id(wxGLCanvas* canvas, int obj_idx) const;
int get_in_object_volume_id(wxGLCanvas* canvas, int scene_vol_idx) const;
#if ENABLE_MIRROR
#if ENABLE_EXTENDED_SELECTION
void mirror_selection(wxGLCanvas* canvas, Axis axis);
#endif // ENABLE_EXTENDED_SELECTION
#endif // ENABLE_MIRROR
void reload_scene(wxGLCanvas* canvas, bool force);
void load_gcode_preview(wxGLCanvas* canvas, const GCodePreviewData* preview_data, const std::vector<std::string>& str_tool_colors);
void load_preview(wxGLCanvas* canvas, const std::vector<std::string>& str_tool_colors);
void reset_legend_texture(wxGLCanvas* canvas);
static bool can_multisample() { return s_multisample == MS_Enabled; }
static wxGLCanvas* create_wxglcanvas(wxWindow *parent);
private:
CanvasesMap::iterator _get_canvas(wxGLCanvas* canvas);
CanvasesMap::const_iterator _get_canvas(wxGLCanvas* canvas) const;
bool _init(GLCanvas3D& canvas);
static void _detect_multisample(int* attribList);
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLCanvas3DManager_hpp_

2145
src/slic3r/GUI/GLGizmo.cpp Normal file

File diff suppressed because it is too large Load diff

554
src/slic3r/GUI/GLGizmo.hpp Normal file
View file

@ -0,0 +1,554 @@
#ifndef slic3r_GLGizmo_hpp_
#define slic3r_GLGizmo_hpp_
#include "../../slic3r/GUI/GLTexture.hpp"
#if ENABLE_EXTENDED_SELECTION
#include "../../slic3r/GUI/GLCanvas3D.hpp"
#endif // ENABLE_EXTENDED_SELECTION
#include "../../libslic3r/Point.hpp"
#include "../../libslic3r/BoundingBox.hpp"
#include <vector>
namespace Slic3r {
class BoundingBoxf3;
class Linef3;
class ModelObject;
namespace GUI {
class GLCanvas3D;
class GLGizmoBase
{
protected:
struct Grabber
{
static const float SizeFactor;
static const float MinHalfSize;
static const float DraggingScaleFactor;
Vec3d center;
Vec3d angles;
float color[3];
bool enabled;
bool dragging;
Grabber();
void render(bool hover, float size) const;
void render_for_picking(float size) const { render(size, color, false); }
private:
void render(float size, const float* render_color, bool use_lighting) const;
void render_face(float half_size) const;
};
public:
enum EState
{
Off,
Hover,
On,
Num_States
};
protected:
GLCanvas3D& m_parent;
int m_group_id;
EState m_state;
// textures are assumed to be square and all with the same size in pixels, no internal check is done
GLTexture m_textures[Num_States];
int m_hover_id;
bool m_dragging;
float m_base_color[3];
float m_drag_color[3];
float m_highlight_color[3];
mutable std::vector<Grabber> m_grabbers;
public:
explicit GLGizmoBase(GLCanvas3D& parent);
virtual ~GLGizmoBase() {}
bool init() { return on_init(); }
std::string get_name() const { return on_get_name(); }
int get_group_id() const { return m_group_id; }
void set_group_id(int id) { m_group_id = id; }
EState get_state() const { return m_state; }
void set_state(EState state) { m_state = state; on_set_state(); }
#if ENABLE_EXTENDED_SELECTION
bool is_activable(const GLCanvas3D::Selection& selection) const { return on_is_activable(selection); }
#endif // ENABLE_EXTENDED_SELECTION
unsigned int get_texture_id() const { return m_textures[m_state].get_id(); }
int get_textures_size() const { return m_textures[Off].get_width(); }
int get_hover_id() const { return m_hover_id; }
void set_hover_id(int id);
void set_highlight_color(const float* color);
void enable_grabber(unsigned int id);
void disable_grabber(unsigned int id);
#if ENABLE_EXTENDED_SELECTION
void start_dragging(const GLCanvas3D::Selection& selection);
#else
void start_dragging(const BoundingBoxf3& box);
#endif // ENABLE_EXTENDED_SELECTION
void stop_dragging();
bool is_dragging() const { return m_dragging; }
void update(const Linef3& mouse_ray, const Point* mouse_pos);
#if ENABLE_GIZMOS_RESET
void process_double_click() { on_process_double_click(); }
#endif // ENABLE_GIZMOS_RESET
#if ENABLE_EXTENDED_SELECTION
void render(const GLCanvas3D::Selection& selection) const { on_render(selection); }
void render_for_picking(const GLCanvas3D::Selection& selection) const { on_render_for_picking(selection); }
#else
void render(const BoundingBoxf3& box) const { on_render(box); }
void render_for_picking(const BoundingBoxf3& box) const { on_render_for_picking(box); }
#endif // ENABLE_EXTENDED_SELECTION
protected:
virtual bool on_init() = 0;
virtual std::string on_get_name() const = 0;
virtual void on_set_state() {}
virtual void on_set_hover_id() {}
#if ENABLE_EXTENDED_SELECTION
virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return true; }
#endif // ENABLE_EXTENDED_SELECTION
virtual void on_enable_grabber(unsigned int id) {}
virtual void on_disable_grabber(unsigned int id) {}
#if ENABLE_EXTENDED_SELECTION
virtual void on_start_dragging(const GLCanvas3D::Selection& selection) {}
#else
virtual void on_start_dragging(const BoundingBoxf3& box) {}
#endif // ENABLE_EXTENDED_SELECTION
virtual void on_stop_dragging() {}
virtual void on_update(const Linef3& mouse_ray, const Point* mouse_pos) = 0;
#if ENABLE_GIZMOS_RESET
virtual void on_process_double_click() {}
#endif // ENABLE_GIZMOS_RESET
#if ENABLE_EXTENDED_SELECTION
virtual void on_render(const GLCanvas3D::Selection& selection) const = 0;
virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const = 0;
#else
virtual void on_render(const BoundingBoxf3& box) const = 0;
virtual void on_render_for_picking(const BoundingBoxf3& box) const = 0;
#endif // ENABLE_EXTENDED_SELECTION
float picking_color_component(unsigned int id) const;
void render_grabbers(const BoundingBoxf3& box) const;
void render_grabbers_for_picking(const BoundingBoxf3& box) const;
void set_tooltip(const std::string& tooltip) const;
std::string format(float value, unsigned int decimals) const;
};
class GLGizmoRotate : public GLGizmoBase
{
static const float Offset;
static const unsigned int CircleResolution;
static const unsigned int AngleResolution;
static const unsigned int ScaleStepsCount;
static const float ScaleStepRad;
static const unsigned int ScaleLongEvery;
static const float ScaleLongTooth;
static const unsigned int SnapRegionsCount;
static const float GrabberOffset;
public:
enum Axis : unsigned char
{
X,
Y,
Z
};
private:
Axis m_axis;
double m_angle;
mutable Vec3d m_center;
mutable float m_radius;
mutable float m_snap_coarse_in_radius;
mutable float m_snap_coarse_out_radius;
mutable float m_snap_fine_in_radius;
mutable float m_snap_fine_out_radius;
public:
GLGizmoRotate(GLCanvas3D& parent, Axis axis);
double get_angle() const { return m_angle; }
void set_angle(double angle);
protected:
virtual bool on_init();
virtual std::string on_get_name() const { return ""; }
#if ENABLE_EXTENDED_SELECTION
virtual void on_start_dragging(const GLCanvas3D::Selection& selection);
#else
virtual void on_start_dragging(const BoundingBoxf3& box);
#endif // ENABLE_EXTENDED_SELECTION
virtual void on_update(const Linef3& mouse_ray, const Point* mouse_pos);
#if ENABLE_GIZMOS_RESET
virtual void on_process_double_click() { m_angle = 0.0; }
#endif // ENABLE_GIZMOS_RESET
#if ENABLE_EXTENDED_SELECTION
virtual void on_render(const GLCanvas3D::Selection& selection) const;
virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const;
#else
virtual void on_render(const BoundingBoxf3& box) const;
virtual void on_render_for_picking(const BoundingBoxf3& box) const;
#endif // ENABLE_EXTENDED_SELECTION
private:
void render_circle() const;
void render_scale() const;
void render_snap_radii() const;
void render_reference_radius() const;
void render_angle() const;
void render_grabber(const BoundingBoxf3& box) const;
void transform_to_local() const;
// returns the intersection of the mouse ray with the plane perpendicular to the gizmo axis, in local coordinate
Vec3d mouse_position_in_local_plane(const Linef3& mouse_ray) const;
};
class GLGizmoRotate3D : public GLGizmoBase
{
std::vector<GLGizmoRotate> m_gizmos;
public:
explicit GLGizmoRotate3D(GLCanvas3D& parent);
Vec3d get_rotation() const { return Vec3d(m_gizmos[X].get_angle(), m_gizmos[Y].get_angle(), m_gizmos[Z].get_angle()); }
void set_rotation(const Vec3d& rotation) { m_gizmos[X].set_angle(rotation(0)); m_gizmos[Y].set_angle(rotation(1)); m_gizmos[Z].set_angle(rotation(2)); }
protected:
virtual bool on_init();
virtual std::string on_get_name() const;
virtual void on_set_state()
{
for (GLGizmoRotate& g : m_gizmos)
{
g.set_state(m_state);
}
}
virtual void on_set_hover_id()
{
for (unsigned int i = 0; i < 3; ++i)
{
m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1);
}
}
#if ENABLE_EXTENDED_SELECTION
virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return !selection.is_wipe_tower(); }
#endif // ENABLE_EXTENDED_SELECTION
virtual void on_enable_grabber(unsigned int id)
{
if ((0 <= id) && (id < 3))
m_gizmos[id].enable_grabber(0);
}
virtual void on_disable_grabber(unsigned int id)
{
if ((0 <= id) && (id < 3))
m_gizmos[id].disable_grabber(0);
}
#if ENABLE_EXTENDED_SELECTION
virtual void on_start_dragging(const GLCanvas3D::Selection& selection);
#else
virtual void on_start_dragging(const BoundingBoxf3& box);
#endif // ENABLE_EXTENDED_SELECTION
virtual void on_stop_dragging();
virtual void on_update(const Linef3& mouse_ray, const Point* mouse_pos)
{
for (GLGizmoRotate& g : m_gizmos)
{
g.update(mouse_ray, mouse_pos);
}
}
#if ENABLE_GIZMOS_RESET
virtual void on_process_double_click()
{
if (m_hover_id != -1)
m_gizmos[m_hover_id].process_double_click();
}
#endif // ENABLE_GIZMOS_RESET
#if ENABLE_EXTENDED_SELECTION
virtual void on_render(const GLCanvas3D::Selection& selection) const;
virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const
{
for (const GLGizmoRotate& g : m_gizmos)
{
g.render_for_picking(selection);
}
}
#else
virtual void on_render(const BoundingBoxf3& box) const;
virtual void on_render_for_picking(const BoundingBoxf3& box) const
{
for (const GLGizmoRotate& g : m_gizmos)
{
g.render_for_picking(box);
}
}
#endif // ENABLE_EXTENDED_SELECTION
};
class GLGizmoScale3D : public GLGizmoBase
{
static const float Offset;
#if !ENABLE_EXTENDED_SELECTION
static const Vec3d OffsetVec;
#endif // !ENABLE_EXTENDED_SELECTION
mutable BoundingBoxf3 m_box;
Vec3d m_scale;
Vec3d m_starting_scale;
Vec3d m_starting_drag_position;
BoundingBoxf3 m_starting_box;
public:
explicit GLGizmoScale3D(GLCanvas3D& parent);
const Vec3d& get_scale() const { return m_scale; }
#if ENABLE_EXTENDED_SELECTION
void set_scale(const Vec3d& scale) { m_starting_scale = scale; m_scale = scale; }
#else
void set_scale(const Vec3d& scale) { m_starting_scale = scale; }
#endif // ENABLE_EXTENDED_SELECTION
protected:
virtual bool on_init();
virtual std::string on_get_name() const;
#if ENABLE_EXTENDED_SELECTION
virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return !selection.is_wipe_tower(); }
#endif // ENABLE_EXTENDED_SELECTION
#if ENABLE_EXTENDED_SELECTION
virtual void on_start_dragging(const GLCanvas3D::Selection& selection);
#else
virtual void on_start_dragging(const BoundingBoxf3& box);
virtual void on_stop_dragging() { m_show_starting_box = false; }
#endif // ENABLE_EXTENDED_SELECTION
virtual void on_update(const Linef3& mouse_ray, const Point* mouse_pos);
#if ENABLE_GIZMOS_RESET
virtual void on_process_double_click();
#endif // ENABLE_GIZMOS_RESET
#if ENABLE_EXTENDED_SELECTION
virtual void on_render(const GLCanvas3D::Selection& selection) const;
virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const;
#else
virtual void on_render(const BoundingBoxf3& box) const;
virtual void on_render_for_picking(const BoundingBoxf3& box) const;
#endif // ENABLE_EXTENDED_SELECTION
private:
void render_grabbers_connection(unsigned int id_1, unsigned int id_2) const;
void do_scale_x(const Linef3& mouse_ray);
void do_scale_y(const Linef3& mouse_ray);
void do_scale_z(const Linef3& mouse_ray);
void do_scale_uniform(const Linef3& mouse_ray);
#if ENABLE_EXTENDED_SELECTION
double calc_ratio(const Linef3& mouse_ray) const;
#else
double calc_ratio(unsigned int preferred_plane_id, const Linef3& mouse_ray, const Vec3d& center) const;
#endif // ENABLE_EXTENDED_SELECTION
};
class GLGizmoMove3D : public GLGizmoBase
{
static const double Offset;
#if ENABLE_EXTENDED_SELECTION
Vec3d m_displacement;
#else
Vec3d m_position;
#endif // ENABLE_EXTENDED_SELECTION
Vec3d m_starting_drag_position;
Vec3d m_starting_box_center;
Vec3d m_starting_box_bottom_center;
public:
explicit GLGizmoMove3D(GLCanvas3D& parent);
#if ENABLE_EXTENDED_SELECTION
const Vec3d& get_displacement() const { return m_displacement; }
#else
const Vec3d& get_position() const { return m_position; }
void set_position(const Vec3d& position) { m_position = position; }
#endif // ENABLE_EXTENDED_SELECTION
protected:
virtual bool on_init();
virtual std::string on_get_name() const;
#if ENABLE_EXTENDED_SELECTION
virtual void on_start_dragging(const GLCanvas3D::Selection& selection);
virtual void on_stop_dragging();
#else
virtual void on_start_dragging(const BoundingBoxf3& box);
#endif // ENABLE_EXTENDED_SELECTION
virtual void on_update(const Linef3& mouse_ray, const Point* mouse_pos);
#if ENABLE_EXTENDED_SELECTION
virtual void on_render(const GLCanvas3D::Selection& selection) const;
virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const;
#else
virtual void on_render(const BoundingBoxf3& box) const;
virtual void on_render_for_picking(const BoundingBoxf3& box) const;
#endif // ENABLE_EXTENDED_SELECTION
private:
#if ENABLE_EXTENDED_SELECTION
double calc_projection(const Linef3& mouse_ray) const;
#else
double calc_projection(Axis axis, unsigned int preferred_plane_id, const Linef3& mouse_ray) const;
#endif // ENABLE_EXTENDED_SELECTION
};
class GLGizmoFlatten : public GLGizmoBase
{
// This gizmo does not use grabbers. The m_hover_id relates to polygon managed by the class itself.
private:
mutable Vec3d m_normal;
struct PlaneData {
std::vector<Vec3d> vertices;
Vec3d normal;
float area;
};
struct SourceDataSummary {
std::vector<BoundingBoxf3> bounding_boxes; // bounding boxes of convex hulls of individual volumes
Vec3d mesh_first_point;
};
// This holds information to decide whether recalculation is necessary:
SourceDataSummary m_source_data;
std::vector<PlaneData> m_planes;
#if !ENABLE_EXTENDED_SELECTION
struct InstanceData
{
Transform3d matrix;
InstanceData(const Transform3d& matrix) : matrix(matrix) {}
};
std::vector<InstanceData> m_instances;
#endif // !ENABLE_EXTENDED_SELECTION
mutable Vec3d m_starting_center;
const ModelObject* m_model_object = nullptr;
void update_planes();
bool is_plane_update_necessary() const;
public:
explicit GLGizmoFlatten(GLCanvas3D& parent);
void set_flattening_data(const ModelObject* model_object);
Vec3d get_flattening_rotation() const;
protected:
virtual bool on_init();
virtual std::string on_get_name() const;
#if ENABLE_EXTENDED_SELECTION
virtual bool on_is_activable(const GLCanvas3D::Selection& selection) const { return selection.is_single_full_instance(); }
#endif // ENABLE_EXTENDED_SELECTION
#if ENABLE_EXTENDED_SELECTION
virtual void on_start_dragging(const GLCanvas3D::Selection& selection);
#else
virtual void on_start_dragging(const BoundingBoxf3& box);
#endif // ENABLE_EXTENDED_SELECTION
virtual void on_update(const Linef3& mouse_ray, const Point* mouse_pos) {}
#if ENABLE_EXTENDED_SELECTION
virtual void on_render(const GLCanvas3D::Selection& selection) const;
virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const;
#else
virtual void on_render(const BoundingBoxf3& box) const;
virtual void on_render_for_picking(const BoundingBoxf3& box) const;
#endif // ENABLE_EXTENDED_SELECTION
virtual void on_set_state()
{
if (m_state == On && is_plane_update_necessary())
update_planes();
}
};
class GLGizmoSlaSupports : public GLGizmoBase
{
private:
ModelObject* m_model_object = nullptr;
Transform3d m_model_object_matrix;
Vec3f unproject_on_mesh(const Vec2d& mouse_pos);
Eigen::MatrixXf m_V; // vertices
Eigen::MatrixXi m_F; // facets indices
struct SourceDataSummary {
BoundingBoxf3 bounding_box;
#if !ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM
Vec3d scaling_factor;
Vec3d rotation;
Vec3d offset;
#else
Transform3d matrix;
#endif
Vec3d mesh_first_point;
};
// This holds information to decide whether recalculation is necessary:
SourceDataSummary m_source_data;
mutable Vec3d m_starting_center;
public:
explicit GLGizmoSlaSupports(GLCanvas3D& parent);
void set_model_object_ptr(ModelObject* model_object);
void clicked_on_object(const Vec2d& mouse_position);
void delete_current_grabber(bool delete_all);
private:
bool on_init();
void on_update(const Linef3& mouse_ray, const Point* mouse_pos);
#if ENABLE_EXTENDED_SELECTION
virtual void on_render(const GLCanvas3D::Selection& selection) const;
virtual void on_render_for_picking(const GLCanvas3D::Selection& selection) const;
#else
virtual void on_render(const BoundingBoxf3& box) const;
virtual void on_render_for_picking(const BoundingBoxf3& box) const;
#endif // ENABLE_EXTENDED_SELECTION
void render_grabbers(bool picking = false) const;
void render_tooltip_texture() const;
bool is_mesh_update_necessary() const;
void update_mesh();
mutable GLTexture m_tooltip_texture;
mutable GLTexture m_reset_texture;
protected:
void on_set_state() override {
if (m_state == On && is_mesh_update_necessary())
update_mesh();
}
std::string on_get_name() const override;
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmo_hpp_

256
src/slic3r/GUI/GLShader.cpp Normal file
View file

@ -0,0 +1,256 @@
#include <GL/glew.h>
#include "GLShader.hpp"
#include "../../libslic3r/Utils.hpp"
#include <boost/nowide/fstream.hpp>
#include <string>
#include <utility>
#include <assert.h>
namespace Slic3r {
GLShader::~GLShader()
{
assert(fragment_program_id == 0);
assert(vertex_program_id == 0);
assert(shader_program_id == 0);
}
// A safe wrapper around glGetString to report a "N/A" string in case glGetString returns nullptr.
inline std::string gl_get_string_safe(GLenum param)
{
const char *value = (const char*)glGetString(param);
return std::string(value ? value : "N/A");
}
bool GLShader::load_from_text(const char *fragment_shader, const char *vertex_shader)
{
std::string gl_version = gl_get_string_safe(GL_VERSION);
int major = atoi(gl_version.c_str());
//int minor = atoi(gl_version.c_str() + gl_version.find('.') + 1);
if (major < 2) {
// Cannot create a shader object on OpenGL 1.x.
// Form an error message.
std::string gl_vendor = gl_get_string_safe(GL_VENDOR);
std::string gl_renderer = gl_get_string_safe(GL_RENDERER);
std::string glsl_version = gl_get_string_safe(GL_SHADING_LANGUAGE_VERSION);
last_error = "Your computer does not support OpenGL shaders.\n";
#ifdef _WIN32
if (gl_vendor == "Microsoft Corporation" && gl_renderer == "GDI Generic") {
last_error = "Windows is using a software OpenGL renderer.\n"
"You are either connected over remote desktop,\n"
"or a hardware acceleration is not available.\n";
}
#endif
last_error += "GL version: " + gl_version + "\n";
last_error += "vendor: " + gl_vendor + "\n";
last_error += "renderer: " + gl_renderer + "\n";
last_error += "GLSL version: " + glsl_version + "\n";
return false;
}
if (fragment_shader != nullptr) {
this->fragment_program_id = glCreateShader(GL_FRAGMENT_SHADER);
if (this->fragment_program_id == 0) {
last_error = "glCreateShader(GL_FRAGMENT_SHADER) failed.";
return false;
}
GLint len = (GLint)strlen(fragment_shader);
glShaderSource(this->fragment_program_id, 1, &fragment_shader, &len);
glCompileShader(this->fragment_program_id);
GLint params;
glGetShaderiv(this->fragment_program_id, GL_COMPILE_STATUS, &params);
if (params == GL_FALSE) {
// Compilation failed. Get the log.
glGetShaderiv(this->fragment_program_id, GL_INFO_LOG_LENGTH, &params);
std::vector<char> msg(params);
glGetShaderInfoLog(this->fragment_program_id, params, &params, msg.data());
this->last_error = std::string("Fragment shader compilation failed:\n") + msg.data();
this->release();
return false;
}
}
if (vertex_shader != nullptr) {
this->vertex_program_id = glCreateShader(GL_VERTEX_SHADER);
if (this->vertex_program_id == 0) {
last_error = "glCreateShader(GL_VERTEX_SHADER) failed.";
this->release();
return false;
}
GLint len = (GLint)strlen(vertex_shader);
glShaderSource(this->vertex_program_id, 1, &vertex_shader, &len);
glCompileShader(this->vertex_program_id);
GLint params;
glGetShaderiv(this->vertex_program_id, GL_COMPILE_STATUS, &params);
if (params == GL_FALSE) {
// Compilation failed. Get the log.
glGetShaderiv(this->vertex_program_id, GL_INFO_LOG_LENGTH, &params);
std::vector<char> msg(params);
glGetShaderInfoLog(this->vertex_program_id, params, &params, msg.data());
this->last_error = std::string("Vertex shader compilation failed:\n") + msg.data();
this->release();
return false;
}
}
// Link shaders
this->shader_program_id = glCreateProgram();
if (this->shader_program_id == 0) {
last_error = "glCreateProgram() failed.";
this->release();
return false;
}
if (this->fragment_program_id)
glAttachShader(this->shader_program_id, this->fragment_program_id);
if (this->vertex_program_id)
glAttachShader(this->shader_program_id, this->vertex_program_id);
glLinkProgram(this->shader_program_id);
GLint params;
glGetProgramiv(this->shader_program_id, GL_LINK_STATUS, &params);
if (params == GL_FALSE) {
// Linking failed. Get the log.
glGetProgramiv(this->vertex_program_id, GL_INFO_LOG_LENGTH, &params);
std::vector<char> msg(params);
glGetProgramInfoLog(this->vertex_program_id, params, &params, msg.data());
this->last_error = std::string("Shader linking failed:\n") + msg.data();
this->release();
return false;
}
last_error.clear();
return true;
}
bool GLShader::load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename)
{
const std::string& path = resources_dir() + "/shaders/";
boost::nowide::ifstream vs(path + std::string(vertex_shader_filename), boost::nowide::ifstream::binary);
if (!vs.good())
return false;
vs.seekg(0, vs.end);
int file_length = (int)vs.tellg();
vs.seekg(0, vs.beg);
std::string vertex_shader(file_length, '\0');
vs.read(const_cast<char*>(vertex_shader.data()), file_length);
if (!vs.good())
return false;
vs.close();
boost::nowide::ifstream fs(path + std::string(fragment_shader_filename), boost::nowide::ifstream::binary);
if (!fs.good())
return false;
fs.seekg(0, fs.end);
file_length = (int)fs.tellg();
fs.seekg(0, fs.beg);
std::string fragment_shader(file_length, '\0');
fs.read(const_cast<char*>(fragment_shader.data()), file_length);
if (!fs.good())
return false;
fs.close();
return load_from_text(fragment_shader.c_str(), vertex_shader.c_str());
}
void GLShader::release()
{
if (this->shader_program_id) {
if (this->vertex_program_id)
glDetachShader(this->shader_program_id, this->vertex_program_id);
if (this->fragment_program_id)
glDetachShader(this->shader_program_id, this->fragment_program_id);
glDeleteProgram(this->shader_program_id);
this->shader_program_id = 0;
}
if (this->vertex_program_id) {
glDeleteShader(this->vertex_program_id);
this->vertex_program_id = 0;
}
if (this->fragment_program_id) {
glDeleteShader(this->fragment_program_id);
this->fragment_program_id = 0;
}
}
void GLShader::enable() const
{
glUseProgram(this->shader_program_id);
}
void GLShader::disable() const
{
glUseProgram(0);
}
// Return shader vertex attribute ID
int GLShader::get_attrib_location(const char *name) const
{
return this->shader_program_id ? glGetAttribLocation(this->shader_program_id, name) : -1;
}
// Return shader uniform variable ID
int GLShader::get_uniform_location(const char *name) const
{
return this->shader_program_id ? glGetUniformLocation(this->shader_program_id, name) : -1;
}
bool GLShader::set_uniform(const char *name, float value) const
{
int id = this->get_uniform_location(name);
if (id >= 0) {
glUniform1fARB(id, value);
return true;
}
return false;
}
bool GLShader::set_uniform(const char* name, const float* matrix) const
{
int id = get_uniform_location(name);
if (id >= 0)
{
::glUniformMatrix4fv(id, 1, GL_FALSE, (const GLfloat*)matrix);
return true;
}
return false;
}
/*
# Set shader vector
sub SetVector
{
my($self,$var,@values) = @_;
my $id = $self->Map($var);
return 'Unable to map $var' if (!defined($id));
my $count = scalar(@values);
eval('glUniform'.$count.'fARB($id,@values)');
return '';
}
# Set shader 4x4 matrix
sub SetMatrix
{
my($self,$var,$oga) = @_;
my $id = $self->Map($var);
return 'Unable to map $var' if (!defined($id));
glUniformMatrix4fvARB_c($id,1,0,$oga->ptr());
return '';
}
*/
} // namespace Slic3r

View file

@ -0,0 +1,41 @@
#ifndef slic3r_GLShader_hpp_
#define slic3r_GLShader_hpp_
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/Point.hpp"
namespace Slic3r {
class GLShader
{
public:
GLShader() :
fragment_program_id(0),
vertex_program_id(0),
shader_program_id(0)
{}
~GLShader();
bool load_from_text(const char *fragment_shader, const char *vertex_shader);
bool load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename);
void release();
int get_attrib_location(const char *name) const;
int get_uniform_location(const char *name) const;
bool set_uniform(const char *name, float value) const;
bool set_uniform(const char* name, const float* matrix) const;
void enable() const;
void disable() const;
unsigned int fragment_program_id;
unsigned int vertex_program_id;
unsigned int shader_program_id;
std::string last_error;
};
}
#endif /* slic3r_GLShader_hpp_ */

View file

@ -0,0 +1,199 @@
#include "GLTexture.hpp"
#include <GL/glew.h>
#include <wx/image.h>
#include <boost/filesystem.hpp>
#include <vector>
#include <algorithm>
namespace Slic3r {
namespace GUI {
GLTexture::Quad_UVs GLTexture::FullTextureUVs = { { 0.0f, 1.0f }, { 1.0f, 1.0f }, { 1.0f, 0.0f }, { 0.0f, 0.0f } };
GLTexture::GLTexture()
: m_id(0)
, m_width(0)
, m_height(0)
, m_source("")
{
}
GLTexture::~GLTexture()
{
reset();
}
bool GLTexture::load_from_file(const std::string& filename, bool generate_mipmaps)
{
reset();
if (!boost::filesystem::exists(filename))
return false;
// Load a PNG with an alpha channel.
wxImage image;
if (!image.LoadFile(filename, wxBITMAP_TYPE_PNG))
{
reset();
return false;
}
m_width = image.GetWidth();
m_height = image.GetHeight();
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
::glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
::glGenTextures(1, &m_id);
::glBindTexture(GL_TEXTURE_2D, m_id);
::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data());
if (generate_mipmaps)
{
// we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards
unsigned int levels_count = _generate_mipmaps(image);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1 + levels_count);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
}
else
{
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
}
::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
::glBindTexture(GL_TEXTURE_2D, 0);
m_source = filename;
return true;
}
void GLTexture::reset()
{
if (m_id != 0)
::glDeleteTextures(1, &m_id);
m_id = 0;
m_width = 0;
m_height = 0;
m_source = "";
}
unsigned int GLTexture::get_id() const
{
return m_id;
}
int GLTexture::get_width() const
{
return m_width;
}
int GLTexture::get_height() const
{
return m_height;
}
const std::string& GLTexture::get_source() const
{
return m_source;
}
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)
{
::glEnable(GL_BLEND);
::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
::glEnable(GL_TEXTURE_2D);
::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
::glBindTexture(GL_TEXTURE_2D, (GLuint)tex_id);
::glBegin(GL_QUADS);
::glTexCoord2f(uvs.left_bottom.u, uvs.left_bottom.v); ::glVertex2f(left, bottom);
::glTexCoord2f(uvs.right_bottom.u, uvs.right_bottom.v); ::glVertex2f(right, bottom);
::glTexCoord2f(uvs.right_top.u, uvs.right_top.v); ::glVertex2f(right, top);
::glTexCoord2f(uvs.left_top.u, uvs.left_top.v); ::glVertex2f(left, top);
::glEnd();
::glBindTexture(GL_TEXTURE_2D, 0);
::glDisable(GL_TEXTURE_2D);
::glDisable(GL_BLEND);
}
unsigned int GLTexture::_generate_mipmaps(wxImage& image)
{
int w = image.GetWidth();
int h = image.GetHeight();
GLint level = 0;
std::vector<unsigned char> data(w * h * 4, 0);
while ((w > 1) || (h > 1))
{
++level;
w = std::max(w / 2, 1);
h = std::max(h / 2, 1);
int n_pixels = w * h;
image = image.ResampleBicubic(w, h);
unsigned char* img_rgb = image.GetData();
unsigned char* img_alpha = image.GetAlpha();
data.resize(n_pixels * 4);
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;
}
::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)w, (GLsizei)h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data());
}
return (unsigned int)level;
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,60 @@
#ifndef slic3r_GLTexture_hpp_
#define slic3r_GLTexture_hpp_
#include <string>
class wxImage;
namespace Slic3r {
namespace GUI {
class GLTexture
{
public:
struct UV
{
float u;
float v;
};
struct Quad_UVs
{
UV left_bottom;
UV right_bottom;
UV right_top;
UV left_top;
};
static Quad_UVs FullTextureUVs;
protected:
unsigned int m_id;
int m_width;
int m_height;
std::string m_source;
public:
GLTexture();
virtual ~GLTexture();
bool load_from_file(const std::string& filename, bool generate_mipmaps);
void reset();
unsigned int get_id() const;
int get_width() const;
int get_height() const;
const std::string& get_source() const;
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);
protected:
unsigned int _generate_mipmaps(wxImage& image);
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLTexture_hpp_

View file

@ -0,0 +1,750 @@
#include "../../libslic3r/Point.hpp"
#include "GLToolbar.hpp"
#include "../../libslic3r/libslic3r.h"
#include "../../slic3r/GUI/GLCanvas3D.hpp"
#include <GL/glew.h>
#include <wx/event.h>
#include <wx/bitmap.h>
#include <wx/dcmemory.h>
#include <wx/settings.h>
#include <wx/glcanvas.h>
namespace Slic3r {
namespace GUI {
wxDEFINE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_FEWER, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_SPLIT_VOLUMES, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_CUT, SimpleEvent);
#if !ENABLE_EXTENDED_SELECTION
wxDEFINE_EVENT(EVT_GLTOOLBAR_SETTINGS, SimpleEvent);
#endif // !ENABLE_EXTENDED_SELECTION
wxDEFINE_EVENT(EVT_GLTOOLBAR_LAYERSEDITING, SimpleEvent);
#if !ENABLE_EXTENDED_SELECTION
wxDEFINE_EVENT(EVT_GLTOOLBAR_SELECTBYPARTS, SimpleEvent);
#endif // !ENABLE_EXTENDED_SELECTION
GLToolbarItem::Data::Data()
: name("")
, tooltip("")
, sprite_id(-1)
, is_toggable(false)
{
}
GLToolbarItem::GLToolbarItem(GLToolbarItem::EType type, const GLToolbarItem::Data& data)
: m_type(type)
, m_state(Disabled)
, m_data(data)
{
}
GLToolbarItem::EState GLToolbarItem::get_state() const
{
return m_state;
}
void GLToolbarItem::set_state(GLToolbarItem::EState state)
{
m_state = state;
}
const std::string& GLToolbarItem::get_name() const
{
return m_data.name;
}
const std::string& GLToolbarItem::get_tooltip() const
{
return m_data.tooltip;
}
void GLToolbarItem::do_action(wxEvtHandler *target)
{
wxPostEvent(target, SimpleEvent(m_data.action_event));
}
bool GLToolbarItem::is_enabled() const
{
return m_state != Disabled;
}
bool GLToolbarItem::is_hovered() const
{
return (m_state == Hover) || (m_state == HoverPressed);
}
bool GLToolbarItem::is_pressed() const
{
return (m_state == Pressed) || (m_state == HoverPressed);
}
bool GLToolbarItem::is_toggable() const
{
return m_data.is_toggable;
}
bool GLToolbarItem::is_separator() const
{
return m_type == Separator;
}
void GLToolbarItem::render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const
{
GLTexture::render_sub_texture(tex_id, left, right, bottom, top, get_uvs(texture_size, border_size, icon_size, gap_size));
}
GLTexture::Quad_UVs GLToolbarItem::get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const
{
GLTexture::Quad_UVs uvs;
float inv_texture_size = (texture_size != 0) ? 1.0f / (float)texture_size : 0.0f;
float scaled_icon_size = (float)icon_size * inv_texture_size;
float scaled_border_size = (float)border_size * inv_texture_size;
float scaled_gap_size = (float)gap_size * inv_texture_size;
float stride = scaled_icon_size + scaled_gap_size;
float left = scaled_border_size + (float)m_state * stride;
float right = left + scaled_icon_size;
float top = scaled_border_size + (float)m_data.sprite_id * stride;
float bottom = top + scaled_icon_size;
uvs.left_top = { left, top };
uvs.left_bottom = { left, bottom };
uvs.right_bottom = { right, bottom };
uvs.right_top = { right, top };
return uvs;
}
GLToolbar::ItemsIconsTexture::ItemsIconsTexture()
: items_icon_size(0)
, items_icon_border_size(0)
, items_icon_gap_size(0)
{
}
GLToolbar::Layout::Layout()
: type(Horizontal)
, top(0.0f)
, left(0.0f)
, separator_size(0.0f)
, gap_size(0.0f)
{
}
GLToolbar::GLToolbar(GLCanvas3D& parent)
: m_parent(parent)
, m_enabled(false)
{
}
GLToolbar::~GLToolbar()
{
for (GLToolbarItem* item : m_items)
{
delete item;
}
}
bool GLToolbar::init(const std::string& icons_texture_filename, unsigned int items_icon_size, unsigned int items_icon_border_size, unsigned int items_icon_gap_size)
{
std::string path = resources_dir() + "/icons/";
bool res = !icons_texture_filename.empty() && m_icons_texture.texture.load_from_file(path + icons_texture_filename, false);
if (res)
{
m_icons_texture.items_icon_size = items_icon_size;
m_icons_texture.items_icon_border_size = items_icon_border_size;
m_icons_texture.items_icon_gap_size = items_icon_gap_size;
}
return res;
}
GLToolbar::Layout::Type GLToolbar::get_layout_type() const
{
return m_layout.type;
}
void GLToolbar::set_layout_type(GLToolbar::Layout::Type type)
{
m_layout.type = type;
}
void GLToolbar::set_position(float top, float left)
{
m_layout.top = top;
m_layout.left = left;
}
void GLToolbar::set_separator_size(float size)
{
m_layout.separator_size = size;
}
void GLToolbar::set_gap_size(float size)
{
m_layout.gap_size = size;
}
bool GLToolbar::is_enabled() const
{
return m_enabled;
}
void GLToolbar::set_enabled(bool enable)
{
m_enabled = true;
}
bool GLToolbar::add_item(const GLToolbarItem::Data& data)
{
GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Action, data);
if (item == nullptr)
return false;
m_items.push_back(item);
return true;
}
bool GLToolbar::add_separator()
{
GLToolbarItem::Data data;
GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Separator, data);
if (item == nullptr)
return false;
m_items.push_back(item);
return true;
}
float GLToolbar::get_width() const
{
switch (m_layout.type)
{
default:
case Layout::Horizontal:
{
return get_width_horizontal();
}
case Layout::Vertical:
{
return get_width_vertical();
}
}
}
float GLToolbar::get_height() const
{
switch (m_layout.type)
{
default:
case Layout::Horizontal:
{
return get_height_horizontal();
}
case Layout::Vertical:
{
return get_height_vertical();
}
}
}
void GLToolbar::enable_item(const std::string& name)
{
for (GLToolbarItem* item : m_items)
{
if ((item->get_name() == name) && (item->get_state() == GLToolbarItem::Disabled))
{
item->set_state(GLToolbarItem::Normal);
return;
}
}
}
void GLToolbar::disable_item(const std::string& name)
{
for (GLToolbarItem* item : m_items)
{
if (item->get_name() == name)
{
item->set_state(GLToolbarItem::Disabled);
return;
}
}
}
bool GLToolbar::is_item_pressed(const std::string& name) const
{
for (GLToolbarItem* item : m_items)
{
if (item->get_name() == name)
return item->is_pressed();
}
return false;
}
void GLToolbar::update_hover_state(const Vec2d& mouse_pos)
{
if (!m_enabled)
return;
switch (m_layout.type)
{
default:
case Layout::Horizontal:
{
update_hover_state_horizontal(mouse_pos);
break;
}
case Layout::Vertical:
{
update_hover_state_vertical(mouse_pos);
break;
}
}
}
int GLToolbar::contains_mouse(const Vec2d& mouse_pos) const
{
if (!m_enabled)
return -1;
switch (m_layout.type)
{
default:
case Layout::Horizontal:
{
return contains_mouse_horizontal(mouse_pos);
}
case Layout::Vertical:
{
return contains_mouse_vertical(mouse_pos);
}
}
}
void GLToolbar::do_action(unsigned int item_id)
{
if (item_id < (unsigned int)m_items.size())
{
GLToolbarItem* item = m_items[item_id];
if ((item != nullptr) && !item->is_separator() && item->is_hovered())
{
if (item->is_toggable())
{
GLToolbarItem::EState state = item->get_state();
if (state == GLToolbarItem::Hover)
item->set_state(GLToolbarItem::HoverPressed);
else if (state == GLToolbarItem::HoverPressed)
item->set_state(GLToolbarItem::Hover);
m_parent.render();
item->do_action(m_parent.get_wxglcanvas());
}
else
{
item->set_state(GLToolbarItem::HoverPressed);
m_parent.render();
item->do_action(m_parent.get_wxglcanvas());
if (item->get_state() != GLToolbarItem::Disabled)
{
// the item may get disabled during the action, if not, set it back to hover state
item->set_state(GLToolbarItem::Hover);
m_parent.render();
}
}
}
}
}
void GLToolbar::render() const
{
if (!m_enabled || m_items.empty())
return;
::glDisable(GL_DEPTH_TEST);
::glPushMatrix();
::glLoadIdentity();
switch (m_layout.type)
{
default:
case Layout::Horizontal:
{
render_horizontal();
break;
}
case Layout::Vertical:
{
render_vertical();
break;
}
}
::glPopMatrix();
}
float GLToolbar::get_width_horizontal() const
{
return get_main_size();
}
float GLToolbar::get_width_vertical() const
{
return m_icons_texture.items_icon_size;
}
float GLToolbar::get_height_horizontal() const
{
return m_icons_texture.items_icon_size;
}
float GLToolbar::get_height_vertical() const
{
return get_main_size();
}
float GLToolbar::get_main_size() const
{
float size = 0.0f;
for (unsigned int i = 0; i < (unsigned int)m_items.size(); ++i)
{
if (m_items[i]->is_separator())
size += m_layout.separator_size;
else
size += (float)m_icons_texture.items_icon_size;
}
if (m_items.size() > 1)
size += ((float)m_items.size() - 1.0f) * m_layout.gap_size;
return size;
}
void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos)
{
float zoom = m_parent.get_camera_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
Size cnv_size = m_parent.get_canvas_size();
Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom);
float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom;
float scaled_separator_size = m_layout.separator_size * inv_zoom;
float scaled_gap_size = m_layout.gap_size * inv_zoom;
float separator_stride = scaled_separator_size + scaled_gap_size;
float icon_stride = scaled_icons_size + scaled_gap_size;
float left = m_layout.left;
float top = m_layout.top;
std::string tooltip = "";
for (GLToolbarItem* item : m_items)
{
if (item->is_separator())
left += separator_stride;
else
{
float right = left + scaled_icons_size;
float bottom = top - scaled_icons_size;
GLToolbarItem::EState state = item->get_state();
bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top);
switch (state)
{
case GLToolbarItem::Normal:
{
if (inside)
item->set_state(GLToolbarItem::Hover);
break;
}
case GLToolbarItem::Hover:
{
if (inside)
tooltip = item->get_tooltip();
else
item->set_state(GLToolbarItem::Normal);
break;
}
case GLToolbarItem::Pressed:
{
if (inside)
item->set_state(GLToolbarItem::HoverPressed);
break;
}
case GLToolbarItem::HoverPressed:
{
if (inside)
tooltip = item->get_tooltip();
else
item->set_state(GLToolbarItem::Pressed);
break;
}
default:
case GLToolbarItem::Disabled:
{
break;
}
}
left += icon_stride;
}
}
if (!tooltip.empty())
m_parent.set_tooltip(tooltip);
}
void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos)
{
float zoom = m_parent.get_camera_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
Size cnv_size = m_parent.get_canvas_size();
Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom);
float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom;
float scaled_separator_size = m_layout.separator_size * inv_zoom;
float scaled_gap_size = m_layout.gap_size * inv_zoom;
float separator_stride = scaled_separator_size + scaled_gap_size;
float icon_stride = scaled_icons_size + scaled_gap_size;
float left = m_layout.left;
float top = m_layout.top;
std::string tooltip = "";
for (GLToolbarItem* item : m_items)
{
if (item->is_separator())
top -= separator_stride;
else
{
float right = left + scaled_icons_size;
float bottom = top - scaled_icons_size;
GLToolbarItem::EState state = item->get_state();
bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top);
switch (state)
{
case GLToolbarItem::Normal:
{
if (inside)
item->set_state(GLToolbarItem::Hover);
break;
}
case GLToolbarItem::Hover:
{
if (inside)
tooltip = item->get_tooltip();
else
item->set_state(GLToolbarItem::Normal);
break;
}
case GLToolbarItem::Pressed:
{
if (inside)
item->set_state(GLToolbarItem::HoverPressed);
break;
}
case GLToolbarItem::HoverPressed:
{
if (inside)
tooltip = item->get_tooltip();
else
item->set_state(GLToolbarItem::Pressed);
break;
}
default:
case GLToolbarItem::Disabled:
{
break;
}
}
top -= icon_stride;
}
}
m_parent.set_tooltip(tooltip);
}
int GLToolbar::contains_mouse_horizontal(const Vec2d& mouse_pos) const
{
float zoom = m_parent.get_camera_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
Size cnv_size = m_parent.get_canvas_size();
Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom);
float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom;
float scaled_separator_size = m_layout.separator_size * inv_zoom;
float scaled_gap_size = m_layout.gap_size * inv_zoom;
float separator_stride = scaled_separator_size + scaled_gap_size;
float icon_stride = scaled_icons_size + scaled_gap_size;
float left = m_layout.left;
float top = m_layout.top;
int id = -1;
for (GLToolbarItem* item : m_items)
{
++id;
if (item->is_separator())
left += separator_stride;
else
{
float right = left + scaled_icons_size;
float bottom = top - scaled_icons_size;
if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top))
return id;
left += icon_stride;
}
}
return -1;
}
int GLToolbar::contains_mouse_vertical(const Vec2d& mouse_pos) const
{
float zoom = m_parent.get_camera_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
Size cnv_size = m_parent.get_canvas_size();
Vec2d scaled_mouse_pos((mouse_pos(0) - 0.5 * (double)cnv_size.get_width()) * inv_zoom, (0.5 * (double)cnv_size.get_height() - mouse_pos(1)) * inv_zoom);
float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom;
float scaled_separator_size = m_layout.separator_size * inv_zoom;
float scaled_gap_size = m_layout.gap_size * inv_zoom;
float separator_stride = scaled_separator_size + scaled_gap_size;
float icon_stride = scaled_icons_size + scaled_gap_size;
float left = m_layout.left;
float top = m_layout.top;
int id = -1;
for (GLToolbarItem* item : m_items)
{
++id;
if (item->is_separator())
top -= separator_stride;
else
{
float right = left + scaled_icons_size;
float bottom = top - scaled_icons_size;
if ((left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top))
return id;
top -= icon_stride;
}
}
return -1;
}
void GLToolbar::render_horizontal() const
{
unsigned int tex_id = m_icons_texture.texture.get_id();
int tex_size = m_icons_texture.texture.get_width();
if ((tex_id == 0) || (tex_size <= 0))
return;
float zoom = m_parent.get_camera_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom;
float scaled_separator_size = m_layout.separator_size * inv_zoom;
float scaled_gap_size = m_layout.gap_size * inv_zoom;
float separator_stride = scaled_separator_size + scaled_gap_size;
float icon_stride = scaled_icons_size + scaled_gap_size;
float left = m_layout.left;
float top = m_layout.top;
// renders icons
for (const GLToolbarItem* item : m_items)
{
if (item->is_separator())
left += separator_stride;
else
{
item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.items_icon_border_size, m_icons_texture.items_icon_size, m_icons_texture.items_icon_gap_size);
left += icon_stride;
}
}
}
void GLToolbar::render_vertical() const
{
unsigned int tex_id = m_icons_texture.texture.get_id();
int tex_size = m_icons_texture.texture.get_width();
if ((tex_id == 0) || (tex_size <= 0))
return;
float zoom = m_parent.get_camera_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
float scaled_icons_size = (float)m_icons_texture.items_icon_size * inv_zoom;
float scaled_separator_size = m_layout.separator_size * inv_zoom;
float scaled_gap_size = m_layout.gap_size * inv_zoom;
float separator_stride = scaled_separator_size + scaled_gap_size;
float icon_stride = scaled_icons_size + scaled_gap_size;
float left = m_layout.left;
float top = m_layout.top;
// renders icons
for (const GLToolbarItem* item : m_items)
{
if (item->is_separator())
top -= separator_stride;
else
{
item->render(tex_id, left, left + scaled_icons_size, top - scaled_icons_size, top, (unsigned int)tex_size, m_icons_texture.items_icon_border_size, m_icons_texture.items_icon_size, m_icons_texture.items_icon_gap_size);
top -= icon_stride;
}
}
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,197 @@
#ifndef slic3r_GLToolbar_hpp_
#define slic3r_GLToolbar_hpp_
#include <functional>
#include <string>
#include <vector>
#include "GLTexture.hpp"
#include "Event.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_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_CUT, SimpleEvent);
#if !ENABLE_EXTENDED_SELECTION
wxDECLARE_EVENT(EVT_GLTOOLBAR_SETTINGS, SimpleEvent);
#endif // !ENABLE_EXTENDED_SELECTION
wxDECLARE_EVENT(EVT_GLTOOLBAR_LAYERSEDITING, SimpleEvent);
#if !ENABLE_EXTENDED_SELECTION
wxDECLARE_EVENT(EVT_GLTOOLBAR_SELECTBYPARTS, SimpleEvent);
#endif // !ENABLE_EXTENDED_SELECTION
class GLToolbarItem
{
public:
enum EType : unsigned char
{
Action,
Separator,
Num_Types
};
enum EState : unsigned char
{
Normal,
Pressed,
Disabled,
Hover,
HoverPressed,
Num_States
};
struct Data
{
std::string name;
std::string tooltip;
unsigned int sprite_id;
bool is_toggable;
wxEventType action_event;
Data();
};
private:
EType m_type;
EState m_state;
Data m_data;
public:
GLToolbarItem(EType type, const Data& data);
EState get_state() const;
void set_state(EState state);
const std::string& get_name() const;
const std::string& get_tooltip() const;
void do_action(wxEvtHandler *target);
bool is_enabled() const;
bool is_hovered() const;
bool is_pressed() const;
bool is_toggable() const;
bool is_separator() const;
void render(unsigned int tex_id, float left, float right, float bottom, float top, unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const;
private:
GLTexture::Quad_UVs get_uvs(unsigned int texture_size, unsigned int border_size, unsigned int icon_size, unsigned int gap_size) const;
};
class GLToolbar
{
public:
// items icon textures are assumed to be square and all with the same size in pixels, no internal check is done
// icons are layed-out into the texture starting from the top-left corner in the same order as enum GLToolbarItem::EState
// from left to right
struct ItemsIconsTexture
{
GLTexture texture;
// size of the square icons, in pixels
unsigned int items_icon_size;
// distance from the border, in pixels
unsigned int items_icon_border_size;
// distance between two adjacent icons (to avoid filtering artifacts), in pixels
unsigned int items_icon_gap_size;
ItemsIconsTexture();
};
struct Layout
{
enum Type : unsigned char
{
Horizontal,
Vertical,
Num_Types
};
Type type;
float top;
float left;
float separator_size;
float gap_size;
Layout();
};
private:
typedef std::vector<GLToolbarItem*> ItemsList;
GLCanvas3D& m_parent;
bool m_enabled;
ItemsIconsTexture m_icons_texture;
Layout m_layout;
ItemsList m_items;
public:
explicit GLToolbar(GLCanvas3D& parent);
~GLToolbar();
bool init(const std::string& icons_texture_filename, unsigned int items_icon_size, unsigned int items_icon_border_size, unsigned int items_icon_gap_size);
Layout::Type get_layout_type() const;
void set_layout_type(Layout::Type type);
void set_position(float top, float left);
void set_separator_size(float size);
void set_gap_size(float size);
bool is_enabled() const;
void set_enabled(bool enable);
bool add_item(const GLToolbarItem::Data& data);
bool add_separator();
float get_width() const;
float get_height() const;
void enable_item(const std::string& name);
void disable_item(const std::string& name);
bool is_item_pressed(const std::string& name) const;
void update_hover_state(const Vec2d& mouse_pos);
// returns the id of the item under the given mouse position or -1 if none
int contains_mouse(const Vec2d& mouse_pos) const;
void do_action(unsigned int item_id);
void render() const;
private:
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 update_hover_state_horizontal(const Vec2d& mouse_pos);
void update_hover_state_vertical(const Vec2d& mouse_pos);
int contains_mouse_horizontal(const Vec2d& mouse_pos) const;
int contains_mouse_vertical(const Vec2d& mouse_pos) const;
void render_horizontal() const;
void render_vertical() const;
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLToolbar_hpp_

471
src/slic3r/GUI/GUI.cpp Normal file
View file

@ -0,0 +1,471 @@
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "../AppController.hpp"
#include "WipeTowerDialog.hpp"
#include <assert.h>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#if __APPLE__
#import <IOKit/pwr_mgt/IOPMLib.h>
#elif _WIN32
#include <Windows.h>
// Undefine min/max macros incompatible with the standard library
// For example, std::numeric_limits<std::streamsize>::max()
// produces some weird errors
#ifdef min
#undef min
#endif
#ifdef max
#undef max
#endif
#include "boost/nowide/convert.hpp"
#endif
#include <wx/display.h>
#include "wxExtensions.hpp"
#include "GUI_Preview.hpp"
#include "GUI_PreviewIface.hpp"
#include "AboutDialog.hpp"
#include "AppConfig.hpp"
#include "ConfigWizard.hpp"
#include "PresetBundle.hpp"
#include "UpdateDialogs.hpp"
#include "../../libslic3r/Utils.hpp"
#include "../../libslic3r/Print.hpp"
namespace Slic3r { namespace GUI {
#if __APPLE__
IOPMAssertionID assertionID;
#endif
void disable_screensaver()
{
#if __APPLE__
CFStringRef reasonForActivity = CFSTR("Slic3r");
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__
IOReturn success = IOPMAssertionRelease(assertionID);
#elif _WIN32
SetThreadExecutionState(ES_CONTINUOUS);
#endif
}
bool debugged()
{
#ifdef _WIN32
return IsDebuggerPresent();
#else
return false;
#endif /* _WIN32 */
}
void break_to_debugger()
{
#ifdef _WIN32
if (IsDebuggerPresent())
DebugBreak();
#endif /* _WIN32 */
}
PreviewIface* g_preview = nullptr;
bool config_wizard_startup(bool app_config_exists)
{
if (!app_config_exists || wxGetApp().preset_bundle->printers.size() <= 1) {
config_wizard(ConfigWizard::RR_DATA_EMPTY);
return true;
} else if (get_app_config()->legacy_datadir()) {
// Looks like user has legacy pre-vendorbundle data directory,
// explain what this is and run the wizard
MsgDataLegacy dlg;
dlg.ShowModal();
config_wizard(ConfigWizard::RR_DATA_LEGACY);
return true;
}
return false;
}
void config_wizard(int reason)
{
// Exit wizard if there are unsaved changes and the user cancels the action.
if (! wxGetApp().check_unsaved_changes())
return;
try {
ConfigWizard wizard(nullptr, static_cast<ConfigWizard::RunReason>(reason));
wizard.run(wxGetApp().preset_bundle, wxGetApp().preset_updater);
}
catch (const std::exception &e) {
show_error(nullptr, e.what());
}
// Load the currently selected preset into the GUI, update the preset selection box.
wxGetApp().load_current_presets();
}
PreviewIface* create_preview_iface(wxNotebook* parent, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data)
{
if (g_preview == nullptr)
{
Preview* panel = new Preview(parent, config, print, gcode_preview_data);
g_preview = new PreviewIface(panel);
}
return g_preview;
}
// 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{
switch (config.def()->get(opt_key)->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 = stod(str);
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 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.compare("compatible_printers") == 0) {
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);
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.
std::vector<std::string> values;
boost::split(values, str, boost::is_any_of(";"));
if (values.size() == 1 && values[0] == "")
break;
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{ (bool)boost::any_cast<unsigned char>(value) };
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:{
if (opt_key.compare("external_fill_pattern") == 0 ||
opt_key.compare("fill_pattern") == 0)
config.set_key_value(opt_key, new ConfigOptionEnum<InfillPattern>(boost::any_cast<InfillPattern>(value)));
else if (opt_key.compare("gcode_flavor") == 0)
config.set_key_value(opt_key, new ConfigOptionEnum<GCodeFlavor>(boost::any_cast<GCodeFlavor>(value)));
else if (opt_key.compare("support_material_pattern") == 0)
config.set_key_value(opt_key, new ConfigOptionEnum<SupportMaterialPattern>(boost::any_cast<SupportMaterialPattern>(value)));
else if (opt_key.compare("seam_position") == 0)
config.set_key_value(opt_key, new ConfigOptionEnum<SeamPosition>(boost::any_cast<SeamPosition>(value)));
else if (opt_key.compare("host_type") == 0)
config.set_key_value(opt_key, new ConfigOptionEnum<PrintHostType>(boost::any_cast<PrintHostType>(value)));
}
break;
case coPoints:{
if (opt_key.compare("bed_shape") == 0){
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)
{
int i = 0;//no reason, just experiment
}
}
void show_error(wxWindow* parent, const wxString& message) {
ErrorDialog msg(parent, message);
msg.ShowModal();
}
void show_error_id(int id, const std::string& message) {
auto *parent = id != 0 ? wxWindow::FindWindowById(id) : nullptr;
show_error(parent, wxString::FromUTF8(message.data()));
}
void show_info(wxWindow* parent, const wxString& message, const wxString& title){
wxMessageDialog msg_wingow(parent, message, title.empty() ? _(L("Notice")) : title, wxOK | wxICON_INFORMATION);
msg_wingow.ShowModal();
}
void warning_catcher(wxWindow* parent, const wxString& message){
if (message == "GLUquadricObjPtr | " + _(L("Attempt to free unreferenced scalar")) )
return;
wxMessageDialog msg(parent, message, _(L("Warning")), wxOK | wxICON_WARNING);
msg.ShowModal();
}
void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value)
{
if (comboCtrl == nullptr)
return;
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();
comboCtrl->EnablePopupAnimation(false);
comboCtrl->SetPopupControl(popup);
popup->SetStringValue(from_u8(text));
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);
for (const std::string& item : items_str)
{
popup->Append(from_u8(item));
}
for (unsigned int i = 0; i < popup->GetCount(); ++i)
{
popup->Check(i, initial_value);
}
}
}
int combochecklist_get_flags(wxComboCtrl* comboCtrl)
{
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;
}
AppConfig* get_app_config()
{
return wxGetApp().app_config;
}
wxString L_str(const std::string &str)
{
//! Explicitly specify that the source string is already in UTF-8 encoding
return wxGetTranslation(wxString(str.c_str(), wxConvUTF8));
}
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());
}
bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height)
{
const auto idx = wxDisplay::GetFromWindow(window);
if (idx == wxNOT_FOUND) {
return false;
}
wxDisplay display(idx);
const auto disp_size = display.GetClientArea();
width = disp_size.GetWidth();
height = disp_size.GetHeight();
return true;
}
void save_window_size(wxTopLevelWindow *window, const std::string &name)
{
const wxSize size = window->GetSize();
const wxPoint pos = window->GetPosition();
const auto maximized = window->IsMaximized() ? "1" : "0";
get_app_config()->set((boost::format("window_%1%_size") % name).str(), (boost::format("%1%;%2%") % size.GetWidth() % size.GetHeight()).str());
get_app_config()->set((boost::format("window_%1%_maximized") % name).str(), maximized);
}
void restore_window_size(wxTopLevelWindow *window, const std::string &name)
{
// XXX: This still doesn't behave nicely in some situations (mostly on Linux).
// The problem is that it's hard to obtain window position with respect to screen geometry reliably
// from wxWidgets. Sometimes wxWidgets claim a window is located on a different screen than on which
// it's actually visible. I suspect this has something to do with window initialization (maybe we
// restore window geometry too early), but haven't yet found a workaround.
const auto display_idx = wxDisplay::GetFromWindow(window);
if (display_idx == wxNOT_FOUND) { return; }
const auto display = wxDisplay(display_idx).GetClientArea();
std::vector<std::string> pair;
try {
const auto key_size = (boost::format("window_%1%_size") % name).str();
if (get_app_config()->has(key_size)) {
if (unescape_strings_cstyle(get_app_config()->get(key_size), pair) && pair.size() == 2) {
auto width = boost::lexical_cast<int>(pair[0]);
auto height = boost::lexical_cast<int>(pair[1]);
window->SetSize(width, height);
}
}
} catch(const boost::bad_lexical_cast &) {}
// Maximizing should be the last thing to do.
// This ensure the size and position are sane when the user un-maximizes the window.
const auto key_maximized = (boost::format("window_%1%_maximized") % name).str();
if (get_app_config()->get(key_maximized) == "1") {
window->Maximize(true);
}
}
void about()
{
AboutDialog dlg;
dlg.ShowModal();
dlg.Destroy();
}
void desktop_open_datadir_folder()
{
// 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.
const auto path = data_dir();
#ifdef _WIN32
const auto widepath = wxString::FromUTF8(path.data());
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
}
namespace {
AppControllerPtr g_appctl;
}
AppControllerPtr get_appctl()
{
return g_appctl;
}
void set_cli_appctl()
{
g_appctl = std::make_shared<AppControllerCli>();
}
void set_gui_appctl()
{
g_appctl = std::make_shared<AppControllerGui>();
}
} }

118
src/slic3r/GUI/GUI.hpp Normal file
View file

@ -0,0 +1,118 @@
#ifndef slic3r_GUI_hpp_
#define slic3r_GUI_hpp_
#include "Config.hpp"
#include "callback.hpp"
#include <wx/intl.h>
class wxWindow;
class wxMenuBar;
class wxNotebook;
class wxComboCtrl;
class wxFileDialog;
class wxTopLevelWindow;
namespace Slic3r {
class AppConfig;
class DynamicPrintConfig;
class PreviewIface;
class Print;
class GCodePreviewData;
class AppControllerBase;
using AppControllerPtr = std::shared_ptr<AppControllerBase>;
#define _(s) Slic3r::GUI::I18N::translate((s))
namespace GUI { namespace I18N {
inline wxString translate(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)); }
inline wxString translate(const wchar_t *s) { return wxGetTranslation(s); }
inline wxString translate(const std::string &s) { return wxGetTranslation(wxString(s.c_str(), wxConvUTF8)); }
inline wxString translate(const std::wstring &s) { return wxGetTranslation(s.c_str()); }
} }
// !!! If you needed to translate some wxString,
// !!! please use _(L(string))
// !!! _() - is a standard wxWidgets macro to translate
// !!! L() is used only for marking localizable string
// !!! It will be used in "xgettext" to create a Locating Message Catalog.
#define L(s) s
//! macro used to localization, return wxScopedCharBuffer
//! With wxConvUTF8 explicitly specify that the source string is already in UTF-8 encoding
#define _CHB(s) wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str()
// Minimal buffer length for translated string (char buf[MIN_BUF_LENGTH_FOR_L])
#define MIN_BUF_LENGTH_FOR_L 512
namespace GUI {
void disable_screensaver();
void enable_screensaver();
bool debugged();
void break_to_debugger();
AppConfig* get_app_config();
AppControllerPtr get_appctl();
void set_cli_appctl();
void set_gui_appctl();
extern void add_menus(wxMenuBar *menu, int event_preferences_changed, int event_language_change);
// Checks if configuration wizard needs to run, calls config_wizard if so.
// Returns whether the Wizard ran.
extern bool config_wizard_startup(bool app_config_exists);
// Opens the configuration wizard, returns true if wizard is finished & accepted.
// The run_reason argument is actually ConfigWizard::RunReason, but int is used here because of Perl.
extern void config_wizard(int run_reason);
PreviewIface* create_preview_iface(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data);
// 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);
void show_error(wxWindow* parent, const wxString& message);
void show_error_id(int id, const std::string& message); // For Perl
void show_info(wxWindow* parent, const wxString& message, const wxString& title);
void warning_catcher(wxWindow* parent, const wxString& message);
// Creates a wxCheckListBoxComboPopup inside the given wxComboCtrl, filled with the given text and items.
// Items are all initialized to the given value.
// Items must be separated by '|', for example "Item1|Item2|Item3", and so on.
void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value);
// Returns the current state of the items listed in the wxCheckListBoxComboPopup contained in the given wxComboCtrl,
// encoded inside an int.
int combochecklist_get_flags(wxComboCtrl* comboCtrl);
// Return translated std::string as a wxString
wxString L_str(const std::string &str);
// Return wxString from std::string in UTF8
wxString from_u8(const std::string &str);
// Return std::string in UTF8 from wxString
std::string into_u8(const wxString &str);
// Callback to trigger a configuration update timer on the Plater.
static PerlCallback g_on_request_update_callback;
// Returns the dimensions of the screen on which the main frame is displayed
bool get_current_screen_size(wxWindow *window, unsigned &width, unsigned &height);
// Save window size and maximized status into AppConfig
void save_window_size(wxTopLevelWindow *window, const std::string &name);
// Restore the above
void restore_window_size(wxTopLevelWindow *window, const std::string &name);
// 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();
} // namespace GUI
} // namespace Slic3r
#endif

706
src/slic3r/GUI/GUI_App.cpp Normal file
View file

@ -0,0 +1,706 @@
#include "GUI_App.hpp"
#include "GUI_ObjectList.hpp"
#include "GUI_ObjectManipulation.hpp"
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/filesystem.hpp>
#include <wx/stdpaths.h>
#include <wx/imagpng.h>
#include <wx/display.h>
#include <wx/menu.h>
#include <wx/menuitem.h>
#include <wx/filedlg.h>
#include <wx/dir.h>
#include "Utils.hpp"
#include "GUI.hpp"
#include "GUI_Utils.hpp"
#include "AppConfig.hpp"
#include "PresetBundle.hpp"
#include "3DScene.hpp"
#include "Model.hpp"
#include "../Utils/PresetUpdater.hpp"
#include "ConfigWizard_private.hpp"
#include "slic3r/Config/Snapshot.hpp"
#include "ConfigSnapshotDialog.hpp"
#include "FirmwareDialog.hpp"
#include "Preferences.hpp"
#include "Tab.hpp"
#include <I18N.hpp>
#include <wx/wupdlock.h>
#include "SysInfoDialog.hpp"
namespace Slic3r {
namespace GUI {
const wxString file_wildcards[FT_SIZE] = {
/* FT_STL */ "STL files (*.stl)|*.stl;*.STL",
/* FT_OBJ */ "OBJ files (*.obj)|*.obj;*.OBJ",
/* FT_AMF */ "AMF files (*.amf)|*.zip.amf;*.amf;*.AMF;*.xml;*.XML",
/* FT_3MF */ "3MF files (*.3mf)|*.3mf;*.3MF;",
/* FT_PRUSA */ "Prusa Control files (*.prusa)|*.prusa;*.PRUSA",
/* FT_GCODE */ "G-code files (*.gcode, *.gco, *.g, *.ngc)|*.gcode;*.GCODE;*.gco;*.GCO;*.g;*.G;*.ngc;*.NGC",
/* FT_MODEL */ "Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA",
/* FT_INI */ "INI files *.ini|*.ini;*.INI",
/* FT_SVG */ "SVG files *.svg|*.svg;*.SVG",
};
static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); }
IMPLEMENT_APP(GUI_App)
bool GUI_App::OnInit()
{
SetAppName("Slic3rPE");
SetAppDisplayName("Slic3r Prusa Edition");
// Slic3r::debugf "wxWidgets version %s, Wx version %s\n", wxVERSION_STRING, wxVERSION;
// Set the Slic3r data directory at the Slic3r XS module.
// Unix: ~/ .Slic3r
// Windows : "C:\Users\username\AppData\Roaming\Slic3r" or "C:\Documents and Settings\username\Application Data\Slic3r"
// Mac : "~/Library/Application Support/Slic3r"
if (data_dir().empty())
set_data_dir(wxStandardPaths::Get().GetUserDataDir().ToUTF8().data());
app_config = new AppConfig();
preset_bundle = new PresetBundle();
// just checking for existence of Slic3r::data_dir is not enough : it may be an empty directory
// supplied as argument to --datadir; in that case we should still run the wizard
// eval{
preset_bundle->setup_directories();
// };
// if ($@) {
// warn $@ . "\n";
// fatal_error(undef, $@);
// }
app_conf_exists = app_config->exists();
// load settings
if (app_conf_exists) app_config->load();
app_config->set("version", SLIC3R_VERSION);
app_config->save();
preset_updater = new PresetUpdater();
load_language();
// Suppress the '- default -' presets.
preset_bundle->set_default_suppressed(app_config->get("no_defaults").empty() ? false : true);
// eval{
preset_bundle->load_presets(*app_config);
// };
// if ($@) {
// warn $@ . "\n";
// show_error(undef, $@);
// }
// Let the libslic3r know the callback, which will translate messages on demand.
Slic3r::I18N::set_translate_callback(libslic3r_translate_callback);
// initialize label colors and fonts
init_label_colours();
init_fonts();
// application frame
std::cerr << "Creating main frame..." << std::endl;
// wxImage::FindHandlerType(wxBITMAP_TYPE_PNG) ||
wxImage::AddHandler(new wxPNGHandler());
mainframe = new MainFrame(no_plater, false);
sidebar().obj_list()->init_objects(); // propagate model objects to object list
update_mode();
SetTopWindow(mainframe);
// This makes CallAfter() work
Bind(wxEVT_IDLE, [this](wxIdleEvent& event)
{
std::function<void()> cur_cb{ nullptr };
// try to get the mutex. If we can't, just skip this idle event and get the next one.
if (!callback_register.try_lock()) return;
// pop callback
if (m_cb.size() != 0){
cur_cb = m_cb.top();
m_cb.pop();
}
// unlock mutex
this->callback_register.unlock();
try { // call the function if it's not nullptr;
if (cur_cb != nullptr) cur_cb();
}
catch (std::exception& e) {
std::cerr << "Exception thrown: " << e.what() << std::endl;
}
if (app_config->dirty())
app_config->save();
});
// On OS X the UI tends to freeze in weird ways if modal dialogs(config wizard, update notifications, ...)
// are shown before or in the same event callback with the main frame creation.
// Therefore we schedule them for later using CallAfter.
CallAfter([this](){
// eval{
if (!preset_updater->config_update())
mainframe->Close();
// };
// if ($@) {
// show_error(undef, $@);
// mainframe->Close();
// }
});
CallAfter([this](){
if (!config_wizard_startup(app_conf_exists)) {
// Only notify if there was not wizard so as not to bother too much ...
preset_updater->slic3r_update_notify();
}
preset_updater->sync(preset_bundle);
});
mainframe->Show(true);
return true;
}
unsigned GUI_App::get_colour_approx_luma(const wxColour &colour)
{
double r = colour.Red();
double g = colour.Green();
double b = colour.Blue();
return std::round(std::sqrt(
r * r * .241 +
g * g * .691 +
b * b * .068
));
}
void GUI_App::init_label_colours()
{
auto luma = get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
if (luma >= 128) {
m_color_label_modified = wxColour(252, 77, 1);
m_color_label_sys = wxColour(26, 132, 57);
}
else {
m_color_label_modified = wxColour(253, 111, 40);
m_color_label_sys = wxColour(115, 220, 103);
}
m_color_label_default = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
}
void GUI_App::update_label_colours_from_appconfig()
{
if (app_config->has("label_clr_sys")){
auto str = app_config->get("label_clr_sys");
if (str != "")
m_color_label_sys = wxColour(str);
}
if (app_config->has("label_clr_modified")){
auto str = app_config->get("label_clr_modified");
if (str != "")
m_color_label_modified = wxColour(str);
}
}
void GUI_App::init_fonts()
{
m_small_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
m_bold_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Bold();
#ifdef __WXMAC__
m_small_font.SetPointSize(11);
m_bold_font.SetPointSize(13);
#endif /*__WXMAC__*/
}
void GUI_App::set_label_clr_modified(const wxColour& clr) {
m_color_label_modified = clr;
auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue());
std::string str = clr_str.ToStdString();
app_config->set("label_clr_modified", str);
app_config->save();
}
void GUI_App::set_label_clr_sys(const wxColour& clr) {
m_color_label_sys = clr;
auto clr_str = wxString::Format(wxT("#%02X%02X%02X"), clr.Red(), clr.Green(), clr.Blue());
std::string str = clr_str.ToStdString();
app_config->set("label_clr_sys", str);
app_config->save();
}
void GUI_App::recreate_GUI()
{
std::cerr << "recreate_GUI" << std::endl;
auto topwindow = GetTopWindow();
mainframe = new MainFrame(no_plater,false);
sidebar().obj_list()->init_objects(); // propagate model objects to object list
update_mode();
if (topwindow) {
SetTopWindow(mainframe);
topwindow->Destroy();
}
// On OSX the UI was not initialized correctly if the wizard was called
// before the UI was up and running.
CallAfter([](){
// Run the config wizard, don't offer the "reset user profile" checkbox.
config_wizard_startup(true);
});
}
void GUI_App::system_info()
{
SysInfoDialog dlg;
dlg.ShowModal();
dlg.Destroy();
}
// static method accepting a wxWindow object as first parameter
bool GUI_App::catch_error(std::function<void()> cb,
// wxMessageDialog* message_dialog,
const std::string& err /*= ""*/){
if (!err.empty()) {
if (cb)
cb();
// if (message_dialog)
// message_dialog->(err, "Error", wxOK | wxICON_ERROR);
show_error(/*this*/nullptr, err);
return true;
}
return false;
}
// static method accepting a wxWindow object as first parameter
void fatal_error(wxWindow* parent){
show_error(parent, "");
// exit 1; // #ys_FIXME
}
// Called after the Preferences dialog is closed and the program settings are saved.
// Update the UI based on the current preferences.
void GUI_App::update_ui_from_settings(){
mainframe->update_ui_from_settings();
}
void GUI_App::open_model(wxWindow *parent, wxArrayString& input_files)
{
auto dialog = new wxFileDialog(parent ? parent : GetTopWindow(),
_(L("Choose one or more files (STL/OBJ/AMF/3MF/PRUSA):")),
app_config->get_last_dir(), "",
file_wildcards[FT_MODEL], wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
if (dialog->ShowModal() != wxID_OK) {
dialog->Destroy();
return;
}
dialog->GetPaths(input_files);
dialog->Destroy();
}
void GUI_App::CallAfter(std::function<void()> cb)
{
// set mutex
callback_register.lock();
// push function onto stack
m_cb.emplace(cb);
// unset mutex
callback_register.unlock();
}
void GUI_App::window_pos_save(wxTopLevelWindow* window, const std::string &name)
{
if (name.empty()) { return; }
const auto config_key = (boost::format("window_%1%") % name).str();
WindowMetrics metrics = WindowMetrics::from_window(window);
app_config->set(config_key, metrics.serialize());
app_config->save();
}
void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &name)
{
if (name.empty()) { return; }
const auto config_key = (boost::format("window_%1%") % name).str();
if (! app_config->has(config_key)) { return; }
auto metrics = WindowMetrics::deserialize(app_config->get(config_key));
if (! metrics) { return; }
window->SetSize(metrics->get_rect());
window->Maximize(metrics->get_maximized());
}
void GUI_App::window_pos_sanitize(wxTopLevelWindow* window)
{
const auto display_idx = wxDisplay::GetFromWindow(window);
if (display_idx == wxNOT_FOUND) { return; }
const auto display = wxDisplay(display_idx).GetClientArea();
auto metrics = WindowMetrics::from_window(window);
metrics.sanitize_for_display(display);
if (window->GetScreenRect() != metrics.get_rect()) {
window->SetSize(metrics.get_rect());
}
}
// select language from the list of installed languages
bool GUI_App::select_language( wxArrayString & names,
wxArrayLong & identifiers)
{
wxCHECK_MSG(names.Count() == identifiers.Count(), false,
_(L("Array of language names and identifiers should have the same size.")));
int init_selection = 0;
long current_language = m_wxLocale ? m_wxLocale->GetLanguage() : wxLANGUAGE_UNKNOWN;
for (auto lang : identifiers){
if (lang == current_language)
break;
++init_selection;
}
if (init_selection == identifiers.size())
init_selection = 0;
long index = wxGetSingleChoiceIndex(_(L("Select the language")), _(L("Language")),
names, init_selection);
if (index != -1)
{
m_wxLocale = new wxLocale;
m_wxLocale->Init(identifiers[index]);
m_wxLocale->AddCatalogLookupPathPrefix(localization_dir());
m_wxLocale->AddCatalog(GetAppName());
wxSetlocale(LC_NUMERIC, "C");
Preset::update_suffix_modified();
return true;
}
return false;
}
// load language saved at application config
bool GUI_App::load_language()
{
wxString language = wxEmptyString;
if (app_config->has("translation_language"))
language = app_config->get("translation_language");
if (language.IsEmpty())
return false;
wxArrayString names;
wxArrayLong identifiers;
get_installed_languages(names, identifiers);
for (size_t i = 0; i < identifiers.Count(); i++)
{
if (wxLocale::GetLanguageCanonicalName(identifiers[i]) == language)
{
m_wxLocale = new wxLocale;
m_wxLocale->Init(identifiers[i]);
m_wxLocale->AddCatalogLookupPathPrefix(localization_dir());
m_wxLocale->AddCatalog(GetAppName());
wxSetlocale(LC_NUMERIC, "C");
Preset::update_suffix_modified();
return true;
}
}
return false;
}
// save language at application config
void GUI_App::save_language()
{
wxString language = wxEmptyString;
if (m_wxLocale)
language = m_wxLocale->GetCanonicalName();
app_config->set("translation_language", language.ToStdString());
app_config->save();
}
// get list of installed languages
void GUI_App::get_installed_languages(wxArrayString & names, wxArrayLong & identifiers)
{
names.Clear();
identifiers.Clear();
wxDir dir(localization_dir());
wxString filename;
const wxLanguageInfo * langinfo;
wxString name = wxLocale::GetLanguageName(wxLANGUAGE_DEFAULT);
if (!name.IsEmpty())
{
names.Add(_(L("Default")));
identifiers.Add(wxLANGUAGE_DEFAULT);
}
for (bool cont = dir.GetFirst(&filename, wxEmptyString, wxDIR_DIRS);
cont; cont = dir.GetNext(&filename))
{
langinfo = wxLocale::FindLanguageInfo(filename);
if (langinfo != NULL)
{
auto full_file_name = dir.GetName() + wxFileName::GetPathSeparator() +
filename + wxFileName::GetPathSeparator() +
GetAppName() + wxT(".mo");
if (wxFileExists(full_file_name))
{
names.Add(langinfo->Description);
identifiers.Add(langinfo->Language);
}
}
}
}
Tab* GUI_App::get_tab(Preset::Type type)
{
for (Tab* tab: tabs_list)
if (tab->type() == type)
return tab;
return nullptr;
}
ConfigMenuIDs GUI_App::get_view_mode()
{
if (!app_config->has("view_mode"))
return ConfigMenuModeSimple;
const auto mode = app_config->get("view_mode");
return mode == "expert" ? ConfigMenuModeExpert :
mode == "simple" ? ConfigMenuModeSimple : ConfigMenuModeAdvanced;
}
// Update view mode according to selected menu
void GUI_App::update_mode()
{
wxWindowUpdateLocker noUpdates(mainframe->m_plater);
ConfigMenuIDs mode = wxGetApp().get_view_mode();
obj_list()->get_sizer()->Show(mode == ConfigMenuModeExpert);
sidebar().show_info_sizers(mode == ConfigMenuModeExpert);
sidebar().show_buttons(mode == ConfigMenuModeExpert);
obj_manipul()->show_object_name(mode == ConfigMenuModeSimple);
obj_list()->update_manipulation_sizer(mode == ConfigMenuModeSimple);
sidebar().Layout();
mainframe->m_plater->Layout();
ConfigOptionMode opt_mode = mode == ConfigMenuModeSimple ? comSimple :
mode == ConfigMenuModeExpert ? comExpert : comAdvanced;
for (auto tab : tabs_list)
tab->update_visibility(opt_mode);
}
void GUI_App::add_config_menu(wxMenuBar *menu)
{
auto local_menu = new wxMenu();
wxWindowID config_id_base = wxWindow::NewControlId((int)ConfigMenuCnt);
const auto config_wizard_name = _(ConfigWizard::name().wx_str());
const auto config_wizard_tooltip = wxString::Format(_(L("Run %s")), config_wizard_name);
// Cmd+, is standard on OS X - what about other operating systems?
local_menu->Append(config_id_base + ConfigMenuWizard, config_wizard_name + dots, config_wizard_tooltip);
local_menu->Append(config_id_base + ConfigMenuSnapshots, _(L("Configuration Snapshots")) + dots, _(L("Inspect / activate configuration snapshots")));
local_menu->Append(config_id_base + ConfigMenuTakeSnapshot, _(L("Take Configuration Snapshot")), _(L("Capture a configuration snapshot")));
// local_menu->Append(config_id_base + ConfigMenuUpdate, _(L("Check for updates")), _(L("Check for configuration updates")));
local_menu->AppendSeparator();
local_menu->Append(config_id_base + ConfigMenuPreferences, _(L("Preferences")) + dots + "\tCtrl+,", _(L("Application preferences")));
local_menu->AppendSeparator();
auto mode_menu = new wxMenu();
mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeSimple, _(L("&Simple")), _(L("Simple View Mode")));
mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeAdvanced, _(L("&Advanced")), _(L("Advanced View Mode")));
mode_menu->AppendRadioItem(config_id_base + ConfigMenuModeExpert, _(L("&Expert")), _(L("Expert View Mode")));
mode_menu->Check(config_id_base + get_view_mode(), true);
local_menu->AppendSubMenu(mode_menu, _(L("&Mode")), _(L("Slic3r View Mode")));
local_menu->AppendSeparator();
local_menu->Append(config_id_base + ConfigMenuLanguage, _(L("Change Application Language")));
local_menu->AppendSeparator();
local_menu->Append(config_id_base + ConfigMenuFlashFirmware, _(L("Flash printer firmware")), _(L("Upload a firmware image into an Arduino based printer")));
// TODO: for when we're able to flash dictionaries
// local_menu->Append(config_id_base + FirmwareMenuDict, _(L("Flash language file")), _(L("Upload a language dictionary file into a Prusa printer")));
local_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent &event){
switch (event.GetId() - config_id_base) {
case ConfigMenuWizard:
config_wizard(ConfigWizard::RR_USER);
break;
case ConfigMenuTakeSnapshot:
// Take a configuration snapshot.
if (check_unsaved_changes()) {
wxTextEntryDialog dlg(nullptr, _(L("Taking configuration snapshot")), _(L("Snapshot name")));
if (dlg.ShowModal() == wxID_OK)
app_config->set("on_snapshot",
Slic3r::GUI::Config::SnapshotDB::singleton().take_snapshot(
*app_config, Slic3r::GUI::Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()).id);
}
break;
case ConfigMenuSnapshots:
if (check_unsaved_changes()) {
std::string on_snapshot;
if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config))
on_snapshot = app_config->get("on_snapshot");
ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot);
dlg.ShowModal();
if (!dlg.snapshot_to_activate().empty()) {
if (!Config::SnapshotDB::singleton().is_on_snapshot(*app_config))
Config::SnapshotDB::singleton().take_snapshot(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK);
app_config->set("on_snapshot",
Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id);
preset_bundle->load_presets(*app_config);
// Load the currently selected preset into the GUI, update the preset selection box.
load_current_presets();
}
}
break;
case ConfigMenuPreferences:
{
PreferencesDialog dlg(mainframe);
dlg.ShowModal();
break;
}
case ConfigMenuLanguage:
{
wxArrayString names;
wxArrayLong identifiers;
get_installed_languages(names, identifiers);
if (select_language(names, identifiers)) {
save_language();
show_info(mainframe->m_tabpanel, _(L("Application will be restarted")), _(L("Attention!")));
_3DScene::remove_all_canvases();// remove all canvas before recreate GUI
recreate_GUI();
}
break;
}
case ConfigMenuFlashFirmware:
FirmwareDialog::run(mainframe);
break;
default:
break;
}
});
mode_menu->Bind(wxEVT_MENU, [this, config_id_base](wxEvent& event) {
int id_mode = event.GetId() - config_id_base;
std::string mode = id_mode == ConfigMenuModeExpert ? "expert" :
id_mode == ConfigMenuModeSimple ? "simple" : "advanced";
app_config->set("view_mode", mode);
app_config->save();
update_mode();
});
menu->Append(local_menu, _(L("&Configuration")));
}
// This is called when closing the application, when loading a config file or when starting the config wizard
// to notify the user whether he is aware that some preset changes will be lost.
bool GUI_App::check_unsaved_changes()
{
std::string dirty;
for (Tab *tab : tabs_list)
if (tab->current_preset_is_dirty())
if (dirty.empty())
dirty = tab->name();
else
dirty += std::string(", ") + tab->name();
if (dirty.empty())
// No changes, the application may close or reload presets.
return true;
// Ask the user.
auto dialog = new wxMessageDialog(mainframe,
_(L("You have unsaved changes ")) + dirty + _(L(". Discard changes and continue anyway?")),
_(L("Unsaved Presets")),
wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT);
return dialog->ShowModal() == wxID_YES;
}
bool GUI_App::checked_tab(Tab* tab)
{
bool ret = true;
if (find(tabs_list.begin(), tabs_list.end(), tab) == tabs_list.end())
ret = false;
return ret;
}
void GUI_App::delete_tab_from_list(Tab* tab)
{
std::vector<Tab *>::iterator itr = find(tabs_list.begin(), tabs_list.end(), tab);
if (itr != tabs_list.end())
tabs_list.erase(itr);
}
// Update UI / Tabs to reflect changes in the currently loaded presets
void GUI_App::load_current_presets()
{
for (Tab *tab : tabs_list) {
tab->load_current_preset();
}
}
Sidebar& GUI_App::sidebar()
{
return plater_->sidebar();
}
ObjectManipulation* GUI_App::obj_manipul()
{
return sidebar().obj_manipul();
}
ObjectList* GUI_App::obj_list()
{
return sidebar().obj_list();
}
Plater* GUI_App::plater()
{
return plater_;
}
wxGLCanvas* GUI_App::canvas3D()
{
return plater_->canvas3D();
}
ModelObjectPtrs* GUI_App::model_objects()
{
return &plater_->model().objects;
}
wxNotebook* GUI_App::tab_panel() const
{
return mainframe->m_tabpanel;
}
// static method accepting a wxWindow object as first parameter
// void warning_catcher{
// my($self, $message_dialog) = @_;
// return sub{
// my $message = shift;
// return if $message = ~/ GLUquadricObjPtr | Attempt to free unreferenced scalar / ;
// my @params = ($message, 'Warning', wxOK | wxICON_WARNING);
// $message_dialog
// ? $message_dialog->(@params)
// : Wx::MessageDialog->new($self, @params)->ShowModal;
// };
// }
// Do we need this function???
// void GUI_App::notify(message){
// auto frame = GetTopWindow();
// // try harder to attract user attention on OS X
// if (!frame->IsActive())
// frame->RequestUserAttention(defined(__WXOSX__/*&Wx::wxMAC */)? wxUSER_ATTENTION_ERROR : wxUSER_ATTENTION_INFO);
//
// // There used to be notifier using a Growl application for OSX, but Growl is dead.
// // The notifier also supported the Linux X D - bus notifications, but that support was broken.
// //TODO use wxNotificationMessage ?
// }
} // GUI
} //Slic3r

154
src/slic3r/GUI/GUI_App.hpp Normal file
View file

@ -0,0 +1,154 @@
#ifndef slic3r_GUI_App_hpp_
#define slic3r_GUI_App_hpp_
#include <string>
#include "PrintConfig.hpp"
#include "MainFrame.hpp"
#include <wx/app.h>
#include <wx/colour.h>
#include <wx/font.h>
#include <mutex>
#include <stack>
class wxMenuItem;
class wxMenuBar;
class wxTopLevelWindow;
class wxNotebook;
namespace Slic3r {
class AppConfig;
class PresetBundle;
class PresetUpdater;
class ModelObject;
namespace GUI
{
enum FileType
{
FT_STL,
FT_OBJ,
FT_AMF,
FT_3MF,
FT_PRUSA,
FT_GCODE,
FT_MODEL,
FT_INI,
FT_SVG,
FT_SIZE,
};
extern const wxString file_wildcards[FT_SIZE];
enum ConfigMenuIDs {
ConfigMenuWizard,
ConfigMenuSnapshots,
ConfigMenuTakeSnapshot,
ConfigMenuUpdate,
ConfigMenuPreferences,
ConfigMenuModeSimple,
ConfigMenuModeAdvanced,
ConfigMenuModeExpert,
ConfigMenuLanguage,
ConfigMenuFlashFirmware,
ConfigMenuCnt,
};
class Tab;
static wxString dots("", wxConvUTF8);
class GUI_App : public wxApp
{
bool no_plater{ false };
bool app_conf_exists{ false };
// Lock to guard the callback stack
std::mutex callback_register;
// callbacks registered to run during idle event.
std::stack<std::function<void()>> m_cb{};
wxColour m_color_label_modified;
wxColour m_color_label_sys;
wxColour m_color_label_default;
wxFont m_small_font;
wxFont m_bold_font;
wxLocale* m_wxLocale{ nullptr };
public:
bool OnInit() override;
GUI_App() : wxApp() {}
unsigned get_colour_approx_luma(const wxColour &colour);
void init_label_colours();
void update_label_colours_from_appconfig();
void init_fonts();
void set_label_clr_modified(const wxColour& clr);
void set_label_clr_sys(const wxColour& clr);
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 wxFont& small_font() { return m_small_font; }
const wxFont& bold_font() { return m_bold_font; }
void recreate_GUI();
void system_info();
void open_model(wxWindow *parent, wxArrayString& input_files);
static bool catch_error(std::function<void()> cb,
// wxMessageDialog* message_dialog,
const std::string& err);
// void notify(/*message*/);
void update_ui_from_settings();
void CallAfter(std::function<void()> cb);
void window_pos_save(wxTopLevelWindow* window, const std::string &name);
void window_pos_restore(wxTopLevelWindow* window, const std::string &name);
void window_pos_sanitize(wxTopLevelWindow* window);
bool select_language(wxArrayString & names, wxArrayLong & identifiers);
bool load_language();
void save_language();
void get_installed_languages(wxArrayString & names, wxArrayLong & identifiers);
Tab* get_tab(Preset::Type type);
ConfigMenuIDs get_view_mode();
void update_mode();
void add_config_menu(wxMenuBar *menu);
bool check_unsaved_changes();
bool checked_tab(Tab* tab);
void delete_tab_from_list(Tab* tab);
void load_current_presets();
Sidebar& sidebar();
ObjectManipulation* obj_manipul();
ObjectList* obj_list();
Plater* plater();
wxGLCanvas* canvas3D();
std::vector<ModelObject*> *model_objects();
AppConfig* app_config{ nullptr };
PresetBundle* preset_bundle{ nullptr };
PresetUpdater* preset_updater{ nullptr };
MainFrame* mainframe{ nullptr };
Plater* plater_{ nullptr };
wxNotebook* tab_panel() const ;
std::vector<Tab *> tabs_list;
};
DECLARE_APP(GUI_App)
} // GUI
} //Slic3r
#endif // slic3r_GUI_App_hpp_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,146 @@
#ifndef slic3r_GUI_ObjectList_hpp_
#define slic3r_GUI_ObjectList_hpp_
#include <wx/bitmap.h>
#include <wx/dataview.h>
#include <map>
#include <vector>
class wxBoxSizer;
class PrusaObjectDataViewModel;
namespace Slic3r {
class ConfigOptionsGroup;
class DynamicPrintConfig;
class ModelObject;
class ModelVolume;
namespace GUI {
class ObjectList : public wxDataViewCtrl
{
wxBoxSizer *m_sizer {nullptr};
DynamicPrintConfig *m_default_config {nullptr};
wxBitmap m_bmp_modifiermesh;
wxBitmap m_bmp_solidmesh;
wxBitmap m_bmp_manifold_warning;
wxBitmap m_bmp_cog;
wxBitmap m_bmp_split;
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
#ifdef __WXOSX__
wxString m_selected_extruder = "";
#endif //__WXOSX__
bool m_parts_changed = false;
bool m_part_settings_changed = false;
public:
ObjectList(wxWindow* parent);
~ObjectList();
std::map<std::string, wxBitmap> CATEGORY_ICON;
PrusaObjectDataViewModel *m_objects_model{ nullptr };
DynamicPrintConfig *m_config {nullptr};
std::vector<ModelObject*> *m_objects{ nullptr };
void create_objects_ctrl();
wxDataViewColumn* create_objects_list_extruder_column(int extruders_count);
void update_objects_list_extruder_column(int extruders_count);
// show/hide "Extruder" column for Objects List
void set_extruder_column_hidden(bool hide);
// update extruder in current config
void update_extruder_in_config(const wxString& selection);
void init_icons();
void set_tooltip_for_item(const wxPoint& pt);
void selection_changed();
void context_menu();
void show_context_menu();
void key_event(wxKeyEvent& event);
void item_value_change(wxDataViewEvent& event);
void on_begin_drag(wxDataViewEvent &event);
void on_drop_possible(wxDataViewEvent &event);
void on_drop(wxDataViewEvent &event);
void get_settings_choice(wxMenu *menu, int id, bool is_part);
void menu_item_add_generic(wxMenuItem* &menu, int id);
wxMenuItem* menu_item_split(wxMenu* menu, int id);
wxMenuItem* menu_item_settings(wxMenu* menu, int id, const bool is_part);
wxMenu* create_add_part_popupmenu();
wxMenu* create_part_settings_popupmenu();
wxMenu* create_add_settings_popupmenu(bool is_part);
void load_subobject(bool is_modifier = false, bool is_lambda = false);
void load_part(ModelObject* model_object, wxArrayString& part_names, const bool is_modifier);
void load_lambda(ModelObject* model_object, wxArrayString& part_names, const bool is_modifier);
void load_lambda(const std::string& type_name);
void del_subobject_item(wxDataViewItem& item);
void del_settings_from_config();
void del_instances_from_object(const int obj_idx);
bool del_subobject_from_object(const int obj_idx, const int idx, const int type);
void split(const bool split_part);
bool get_volume_by_item(const bool split_part, const wxDataViewItem& item, ModelVolume*& volume);
bool is_splittable_object(const bool split_part);
wxPoint get_mouse_position_in_control();
wxBoxSizer* get_sizer(){return m_sizer;}
int get_selected_obj_idx() const;
bool is_parts_changed() const { return m_parts_changed; }
bool is_part_settings_changed() const{ return m_part_settings_changed; }
void parts_changed(int obj_idx);
void part_selection_changed();
void update_manipulation_sizer(const bool is_simple_mode);
// Add object to the list
void add_object_to_list(size_t obj_idx);
// 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);
// 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 current object in the list on c++ side
void select_current_object(int idx);
// Select current volume in the list on c++ side
void select_current_volume(int idx, int vol_idx);
// Remove objects/sub-object from the list
void remove();
void init_objects();
bool multiple_selection() const ;
void update_selections();
void update_selections_on_canvas();
void select_item(const wxDataViewItem& item);
void select_items(const wxDataViewItemArray& sels);
void select_all();
// correct current selections to avoid of the possible conflicts
void fix_multiselection_conflicts();
};
}}
#endif //slic3r_GUI_ObjectList_hpp_

View file

@ -0,0 +1,468 @@
#include "GUI_ObjectManipulation.hpp"
#include "GUI_ObjectList.hpp"
#include "OptionsGroup.hpp"
#include "wxExtensions.hpp"
#include "PresetBundle.hpp"
#include "Model.hpp"
#include "Geometry.hpp"
#include <boost/algorithm/string.hpp>
namespace Slic3r
{
namespace GUI
{
OG_Settings::OG_Settings(wxWindow* parent, const bool staticbox)
{
wxString title = staticbox ? " " : ""; // temporary workaround - #ys_FIXME
m_og = std::make_shared<ConfigOptionsGroup>(parent, title);
}
wxSizer* OG_Settings::get_sizer()
{
return m_og->sizer;
}
ObjectManipulation::ObjectManipulation(wxWindow* parent):
OG_Settings(parent, true)
{
m_og->set_name(_(L("Object Manipulation")));
m_og->label_width = 100;
m_og->set_grid_vgap(5);
m_og->m_on_change = [this](t_config_option_key opt_key, boost::any value){
if (opt_key == "scale_unit"){
const wxString& selection = boost::any_cast<wxString>(value);
std::vector<std::string> axes{ "x", "y", "z" };
for (auto axis : axes) {
std::string key = "scale_" + axis;
m_og->set_side_text(key, selection);
}
m_is_percent_scale = selection == _("%");
update_scale_values();
}
};
ConfigOptionDef def;
// Objects(sub-objects) name
def.label = L("Name");
// def.type = coString;
def.gui_type = "legend";
def.tooltip = L("Object name");
def.full_width = true;
def.default_value = new ConfigOptionString{ " " };
m_og->append_single_option_line(Option(def, "object_name"));
// Legend for object modification
auto line = Line{ "", "" };
def.label = "";
def.type = coString;
def.width = 55;
std::vector<std::string> axes{ "x", "y", "z" };
for (const auto axis : axes) {
const auto label = boost::algorithm::to_upper_copy(axis);
def.default_value = new ConfigOptionString{ " " + label };
Option option = Option(def, axis + "_axis_legend");
line.append_option(option);
}
m_og->append_line(line);
auto add_og_to_object_settings = [](const std::string& option_name, const std::string& sidetext)
{
int def_value = 0;
Line line = { _(option_name), "" };
if (option_name == "Scale") {
line.near_label_widget = [](wxWindow* parent) {
auto btn = new PrusaLockButton(parent, wxID_ANY);
btn->Bind(wxEVT_BUTTON, [btn](wxCommandEvent &event){
event.Skip();
wxTheApp->CallAfter([btn]() {
wxGetApp().obj_manipul()->set_uniform_scaling(btn->IsLocked());
});
});
return btn;
};
}
ConfigOptionDef def;
def.type = coInt;
def.default_value = new ConfigOptionInt(def_value);
def.width = 55;
if (option_name == "Rotation")
def.min = -360;
const std::string lower_name = boost::algorithm::to_lower_copy(option_name);
std::vector<std::string> axes{ "x", "y", "z" };
for (auto axis : axes) {
if (axis == "z" && option_name != "Scale")
def.sidetext = sidetext;
Option option = Option(def, lower_name + "_" + axis);
option.opt.full_width = true;
line.append_option(option);
}
if (option_name == "Scale")
{
def.width = 45;
def.type = coStrings;
def.gui_type = "select_open";
def.enum_labels.push_back(L("%"));
def.enum_labels.push_back(L("mm"));
def.default_value = new ConfigOptionStrings{ "mm" };
const Option option = Option(def, lower_name + "_unit");
line.append_option(option);
}
return line;
};
// Settings table
m_og->append_line(add_og_to_object_settings(L("Position"), L("mm")));
m_og->append_line(add_og_to_object_settings(L("Rotation"), "°"));
m_og->append_line(add_og_to_object_settings(L("Scale"), "mm"));
def.label = L("Place on bed");
def.type = coBool;
def.tooltip = L("Automatic placing of models on printing bed in Y axis");
def.gui_type = "";
def.sidetext = "";
def.default_value = new ConfigOptionBool{ false };
m_og->append_single_option_line(Option(def, "place_on_bed"));
m_settings_list_sizer = new wxBoxSizer(wxVERTICAL);
m_og->sizer->Add(m_settings_list_sizer, 1, wxEXPAND | wxLEFT, 5);
m_og->disable();
}
int ObjectManipulation::ol_selection()
{
return wxGetApp().obj_list()->get_selected_obj_idx();
}
void ObjectManipulation::update_settings_list()
{
#ifdef __WXGTK__
auto parent = m_og->get_parent();
#else
auto parent = m_og->parent();
#endif /* __WXGTK__ */
// There is a bug related to Ubuntu overlay scrollbars, see https://github.com/prusa3d/Slic3r/issues/898 and https://github.com/prusa3d/Slic3r/issues/952.
// The issue apparently manifests when Show()ing a window with overlay scrollbars while the UI is frozen. For this reason,
// we will Thaw the UI prematurely on Linux. This means destroing the no_updates object prematurely.
#ifdef __linux__
std::unique_ptr<wxWindowUpdateLocker> no_updates(new wxWindowUpdateLocker(parent));
#else
wxWindowUpdateLocker noUpdates(parent);
#endif
m_settings_list_sizer->Clear(true);
bool show_manipulations = true;
auto objects_ctrl = wxGetApp().obj_list();
auto objects_model = wxGetApp().obj_list()->m_objects_model;
auto config = wxGetApp().obj_list()->m_config;
const auto item = objects_ctrl->GetSelection();
if (!objects_ctrl->multiple_selection() &&
config && objects_model->IsSettingsItem(item))
{
auto extra_column = [config](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 wxBitmapButton(parent, wxID_ANY, wxBitmap(from_u8(var("colorchange_delete_on.png")), wxBITMAP_TYPE_PNG),
wxDefaultPosition, wxDefaultSize, wxBORDER_NONE);
#ifdef __WXMSW__
btn->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
#endif // __WXMSW__
btn->Bind(wxEVT_BUTTON, [opt_key, config](wxEvent &event){
config->erase(opt_key);
wxTheApp->CallAfter([]() { wxGetApp().obj_manipul()->update_settings_list(); });
});
return btn;
};
std::map<std::string, std::vector<std::string>> cat_options;
auto opt_keys = config->keys();
m_og_settings.resize(0);
std::vector<std::string> categories;
if (!(opt_keys.size() == 1 && opt_keys[0] == "extruder"))// return;
{
auto extruders_cnt = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA ? 1 :
wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloats>("nozzle_diameter")->values.size();
for (auto& opt_key : opt_keys) {
auto category = config->def()->get(opt_key)->category;
if (category.empty() ||
(category == "Extruders" && extruders_cnt == 1)) continue;
std::vector< std::string > new_category;
auto& cat_opt = cat_options.find(category) == cat_options.end() ? new_category : cat_options.at(category);
cat_opt.push_back(opt_key);
if (cat_opt.size() == 1)
cat_options[category] = cat_opt;
}
for (auto& cat : cat_options) {
if (cat.second.size() == 1 && cat.second[0] == "extruder")
continue;
auto optgroup = std::make_shared<ConfigOptionsGroup>(parent, cat.first, config, false, extra_column);
optgroup->label_width = 150;
optgroup->sidetext_width = 70;
for (auto& opt : cat.second)
{
if (opt == "extruder")
continue;
Option option = optgroup->get_option(opt);
option.opt.width = 70;
optgroup->append_single_option_line(option);
}
optgroup->reload_config();
m_settings_list_sizer->Add(optgroup->sizer, 0, wxEXPAND | wxALL, 0);
m_og_settings.push_back(optgroup);
categories.push_back(cat.first);
}
}
if (m_og_settings.empty()) {
objects_ctrl->Select(objects_model->Delete(item));
wxGetApp().obj_list()->part_selection_changed();
}
else {
if (!categories.empty())
objects_model->UpdateSettingsDigest(item, categories);
show_manipulations = false;
}
}
show_manipulation_og(show_manipulations);
wxGetApp().sidebar().show_info_sizers(show_manipulations && item && objects_model->GetParent(item) == wxDataViewItem(0));
#ifdef __linux__
no_updates.reset(nullptr);
#endif
parent->Layout();
parent->GetParent()->Layout();
}
#if ENABLE_EXTENDED_SELECTION
void ObjectManipulation::update_settings_value(const GLCanvas3D::Selection& selection)
{
if (selection.is_single_full_object())
{
if (wxGetApp().mainframe->m_plater->model().objects[selection.get_object_idx()]->instances.size() == 1)
{
// all volumes in the selection belongs to the same instance, any of them contains the needed data, so we take the first
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
update_position_value(volume->get_offset());
update_rotation_value(volume->get_rotation());
update_scale_value(volume->get_scaling_factor());
m_og->enable();
}
else
reset_settings_value();
}
else if (selection.is_single_full_instance())
{
// all volumes in the selection belongs to the same instance, any of them contains the needed data, so we take the first
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
update_position_value(volume->get_offset());
update_rotation_value(volume->get_rotation());
update_scale_value(volume->get_scaling_factor());
m_og->enable();
}
else if (selection.is_wipe_tower())
{
// the selection contains a single volume
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
update_position_value(volume->get_offset());
update_rotation_value(volume->get_rotation());
update_scale_value(volume->get_scaling_factor());
m_og->enable();
}
else if (selection.is_modifier())
{
// the selection contains a single volume
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
update_position_value(volume->get_offset());
update_rotation_value(volume->get_rotation());
update_scale_value(volume->get_scaling_factor());
m_og->enable();
}
else
reset_settings_value();
}
void ObjectManipulation::reset_settings_value()
{
reset_position_value();
reset_rotation_value();
reset_scale_value();
m_og->disable();
}
void ObjectManipulation::reset_position_value()
{
m_og->set_value("position_x", 0);
m_og->set_value("position_y", 0);
m_og->set_value("position_z", 0);
}
void ObjectManipulation::reset_rotation_value()
{
m_og->set_value("rotation_x", 0);
m_og->set_value("rotation_y", 0);
m_og->set_value("rotation_z", 0);
}
void ObjectManipulation::reset_scale_value()
{
m_is_percent_scale = true;
m_og->set_value("scale_unit", _("%"));
m_og->set_value("scale_x", 100);
m_og->set_value("scale_y", 100);
m_og->set_value("scale_z", 100);
}
#endif // ENABLE_EXTENDED_SELECTION
void ObjectManipulation::update_values()
{
int selection = ol_selection();
if (selection < 0 || wxGetApp().mainframe->m_plater->model().objects.size() <= selection) {
m_og->set_value("position_x", 0);
m_og->set_value("position_y", 0);
m_og->set_value("position_z", 0);
m_og->set_value("scale_x", 0);
m_og->set_value("scale_y", 0);
m_og->set_value("scale_z", 0);
m_og->set_value("rotation_x", 0);
m_og->set_value("rotation_y", 0);
m_og->set_value("rotation_z", 0);
m_og->disable();
return;
}
m_is_percent_scale = boost::any_cast<wxString>(m_og->get_value("scale_unit")) == _("%");
update_position_values();
update_scale_values();
update_rotation_values();
m_og->enable();
}
void ObjectManipulation::update_scale_values()
{
int selection = ol_selection();
ModelObjectPtrs& objects = wxGetApp().mainframe->m_plater->model().objects;
auto instance = objects[selection]->instances.front();
auto size = objects[selection]->instance_bounding_box(0).size();
if (m_is_percent_scale) {
m_og->set_value("scale_x", int(instance->get_scaling_factor(X) * 100));
m_og->set_value("scale_y", int(instance->get_scaling_factor(Y) * 100));
m_og->set_value("scale_z", int(instance->get_scaling_factor(Z) * 100));
}
else {
m_og->set_value("scale_x", int(instance->get_scaling_factor(X) * size(0) + 0.5));
m_og->set_value("scale_y", int(instance->get_scaling_factor(Y) * size(1) + 0.5));
m_og->set_value("scale_z", int(instance->get_scaling_factor(Z) * size(2) + 0.5));
}
}
void ObjectManipulation::update_position_values()
{
auto instance = wxGetApp().mainframe->m_plater->model().objects[ol_selection()]->instances.front();
m_og->set_value("position_x", int(instance->get_offset(X)));
m_og->set_value("position_y", int(instance->get_offset(Y)));
m_og->set_value("position_z", int(instance->get_offset(Z)));
}
void ObjectManipulation::update_position_value(const Vec3d& position)
{
m_og->set_value("position_x", int(position(0)));
m_og->set_value("position_y", int(position(1)));
m_og->set_value("position_z", int(position(2)));
}
void ObjectManipulation::update_scale_value(const Vec3d& scaling_factor)
{
// this is temporary
// to be able to update the values as size
// we need to store somewhere the original size
// or have it passed as parameter
if (!m_is_percent_scale)
m_og->set_value("scale_unit", _("%"));
auto scale = scaling_factor * 100.0;
m_og->set_value("scale_x", int(scale(0)));
m_og->set_value("scale_y", int(scale(1)));
m_og->set_value("scale_z", int(scale(2)));
}
void ObjectManipulation::update_rotation_values()
{
update_rotation_value(wxGetApp().mainframe->m_plater->model().objects[ol_selection()]->instances.front()->get_rotation());
}
void ObjectManipulation::update_rotation_value(double angle, Axis axis)
{
std::string axis_str;
switch (axis) {
case X: {
axis_str = "rotation_x";
break; }
case Y: {
axis_str = "rotation_y";
break; }
case Z: {
axis_str = "rotation_z";
break; }
}
m_og->set_value(axis_str, round_nearest(int(Geometry::rad2deg(angle)), 0));
}
void ObjectManipulation::update_rotation_value(const Vec3d& rotation)
{
m_og->set_value("rotation_x", int(round_nearest(Geometry::rad2deg(rotation(0)), 0)));
m_og->set_value("rotation_y", int(round_nearest(Geometry::rad2deg(rotation(1)), 0)));
m_og->set_value("rotation_z", int(round_nearest(Geometry::rad2deg(rotation(2)), 0)));
}
void ObjectManipulation::show_object_name(bool show)
{
wxGridSizer* grid_sizer = m_og->get_grid_sizer();
grid_sizer->Show(static_cast<size_t>(0), show);
grid_sizer->Show(static_cast<size_t>(1), show);
}
void ObjectManipulation::show_manipulation_og(const bool show)
{
wxGridSizer* grid_sizer = m_og->get_grid_sizer();
if (show == grid_sizer->IsShown(2))
return;
for (size_t id = 2; id < 12; id++)
grid_sizer->Show(id, show);
}
} //namespace GUI
} //namespace Slic3r

View file

@ -0,0 +1,79 @@
#ifndef slic3r_GUI_ObjectManipulation_hpp_
#define slic3r_GUI_ObjectManipulation_hpp_
#include <memory>
#include <wx/panel.h>
#include "Preset.hpp"
#if ENABLE_EXTENDED_SELECTION
#include "GLCanvas3D.hpp"
#endif // ENABLE_EXTENDED_SELECTION
class wxBoxSizer;
namespace Slic3r {
namespace GUI {
class ConfigOptionsGroup;
class OG_Settings
{
protected:
std::shared_ptr<ConfigOptionsGroup> m_og;
public:
OG_Settings(wxWindow* parent, const bool staticbox);
~OG_Settings() {}
wxSizer* get_sizer();
ConfigOptionsGroup* get_og() { return m_og.get(); }
};
class ObjectManipulation : public OG_Settings
{
bool m_is_percent_scale = false; // true -> percentage scale unit
// false -> uniform scale unit
bool m_is_uniform_scale = false; // It indicates if scale is uniform
// 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;
public:
ObjectManipulation(wxWindow* parent);
~ObjectManipulation() {}
int ol_selection();
void update_settings_list();
#if ENABLE_EXTENDED_SELECTION
void update_settings_value(const GLCanvas3D::Selection& selection);
void reset_settings_value();
void reset_position_value();
void reset_rotation_value();
void reset_scale_value();
#endif // ENABLE_EXTENDED_SELECTION
void update_values();
// update position values displacements or "gizmos"
void update_position_values();
void update_position_value(const Vec3d& position);
// update scale values after scale unit changing or "gizmos"
void update_scale_values();
void update_scale_value(const Vec3d& scaling_factor);
// update rotation values object selection changing
void update_rotation_values();
// update rotation value after "gizmos"
void update_rotation_value(double angle, Axis axis);
void update_rotation_value(const Vec3d& rotation);
void set_uniform_scaling(const bool uniform_scale) { m_is_uniform_scale = uniform_scale; }
void show_object_name(bool show);
void show_manipulation_og(const bool show);
};
}}
#endif // slic3r_GUI_ObjectManipulation_hpp_

View file

@ -0,0 +1,572 @@
#include "../../libslic3r/libslic3r.h"
#include "GUI_Preview.hpp"
#include "GUI.hpp"
#include "AppConfig.hpp"
#include "3DScene.hpp"
#include "GLCanvas3DManager.hpp"
#include "../../libslic3r/GCode/PreviewData.hpp"
#include "PresetBundle.hpp"
#include "wxExtensions.hpp"
#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/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"
namespace Slic3r {
namespace GUI {
Preview::Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data)
: m_canvas(nullptr)
, m_double_slider_sizer(nullptr)
, m_label_view_type(nullptr)
, m_choice_view_type(nullptr)
, m_label_show_features(nullptr)
, m_combochecklist_features(nullptr)
, m_checkbox_travel(nullptr)
, m_checkbox_retractions(nullptr)
, m_checkbox_unretractions(nullptr)
, m_checkbox_shells(nullptr)
, m_config(config)
, m_print(print)
, m_gcode_preview_data(gcode_preview_data)
, m_number_extruders(1)
, m_preferred_color_mode("feature")
, m_loaded(false)
, m_enabled(false)
, m_force_sliders_full_range(false)
{
if (init(notebook, config, print, gcode_preview_data))
{
notebook->AddPage(this, _(L("Preview")));
show_hide_ui_elements("none");
load_print();
}
}
bool Preview::init(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data)
{
if ((notebook == nullptr) || (config == nullptr) || (print == nullptr) || (gcode_preview_data == nullptr))
return false;
// creates this panel add append it to the given notebook as a new page
if (!Create(notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize))
return false;
m_canvas = GLCanvas3DManager::create_wxglcanvas(this);
_3DScene::add_canvas(m_canvas);
_3DScene::allow_multisample(m_canvas, GLCanvas3DManager::can_multisample());
_3DScene::enable_shader(m_canvas, true);
_3DScene::set_config(m_canvas, m_config);
_3DScene::set_print(m_canvas, m_print);
_3DScene::enable_legend_texture(m_canvas, true);
_3DScene::enable_dynamic_background(m_canvas, true);
m_double_slider_sizer = new wxBoxSizer(wxHORIZONTAL);
create_double_slider();
m_label_view_type = new wxStaticText(this, wxID_ANY, _(L("View")));
m_choice_view_type = new wxChoice(this, wxID_ANY);
m_choice_view_type->Append(_(L("Feature type")));
m_choice_view_type->Append(_(L("Height")));
m_choice_view_type->Append(_(L("Width")));
m_choice_view_type->Append(_(L("Speed")));
m_choice_view_type->Append(_(L("Volumetric flow rate")));
m_choice_view_type->Append(_(L("Tool")));
m_choice_view_type->SetSelection(0);
m_label_show_features = new wxStaticText(this, wxID_ANY, _(L("Show")));
m_combochecklist_features = new wxComboCtrl();
m_combochecklist_features->Create(this, wxID_ANY, _(L("Feature types")), wxDefaultPosition, wxSize(200, -1), wxCB_READONLY);
std::string feature_text = GUI::into_u8(_(L("Feature types")));
std::string feature_items = GUI::into_u8(
_(L("Perimeter")) + "|" +
_(L("External perimeter")) + "|" +
_(L("Overhang perimeter")) + "|" +
_(L("Internal infill")) + "|" +
_(L("Solid infill")) + "|" +
_(L("Top solid infill")) + "|" +
_(L("Bridge infill")) + "|" +
_(L("Gap fill")) + "|" +
_(L("Skirt")) + "|" +
_(L("Support material")) + "|" +
_(L("Support material interface")) + "|" +
_(L("Wipe tower")) + "|" +
_(L("Custom"))
);
Slic3r::GUI::create_combochecklist(m_combochecklist_features, feature_text, feature_items, true);
m_checkbox_travel = new wxCheckBox(this, wxID_ANY, _(L("Travel")));
m_checkbox_retractions = new wxCheckBox(this, wxID_ANY, _(L("Retractions")));
m_checkbox_unretractions = new wxCheckBox(this, wxID_ANY, _(L("Unretractions")));
m_checkbox_shells = new wxCheckBox(this, wxID_ANY, _(L("Shells")));
wxBoxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL);
top_sizer->Add(m_canvas, 1, wxALL | wxEXPAND, 0);
top_sizer->Add(m_double_slider_sizer, 0, wxEXPAND, 0);
wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL);
bottom_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5);
bottom_sizer->Add(m_choice_view_type, 0, wxEXPAND | wxALL, 5);
bottom_sizer->AddSpacer(10);
bottom_sizer->Add(m_label_show_features, 0, wxALIGN_CENTER_VERTICAL, 5);
bottom_sizer->Add(m_combochecklist_features, 0, wxEXPAND | wxALL, 5);
bottom_sizer->AddSpacer(20);
bottom_sizer->Add(m_checkbox_travel, 0, wxEXPAND | wxALL, 5);
bottom_sizer->AddSpacer(10);
bottom_sizer->Add(m_checkbox_retractions, 0, wxEXPAND | wxALL, 5);
bottom_sizer->AddSpacer(10);
bottom_sizer->Add(m_checkbox_unretractions, 0, wxEXPAND | wxALL, 5);
bottom_sizer->AddSpacer(10);
bottom_sizer->Add(m_checkbox_shells, 0, wxEXPAND | wxALL, 5);
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(top_sizer, 1, wxALL | wxEXPAND, 0);
main_sizer->Add(bottom_sizer, 0, wxALL | wxEXPAND, 0);
SetSizer(main_sizer);
SetMinSize(GetSize());
GetSizer()->SetSizeHints(this);
bind_event_handlers();
// sets colors for gcode preview extrusion roles
std::vector<std::string> extrusion_roles_colors = {
"Perimeter", "FFFF66",
"External perimeter", "FFA500",
"Overhang perimeter", "0000FF",
"Internal infill", "B1302A",
"Solid infill", "D732D7",
"Top solid infill", "FF1A1A",
"Bridge infill", "9999FF",
"Gap fill", "FFFFFF",
"Skirt", "845321",
"Support material", "00FF00",
"Support material interface", "008000",
"Wipe tower", "B3E3AB",
"Custom", "28CC94"
};
m_gcode_preview_data->set_extrusion_paths_colors(extrusion_roles_colors);
return true;
}
Preview::~Preview()
{
unbind_event_handlers();
if (m_canvas != nullptr)
{
_3DScene::remove_canvas(m_canvas);
delete m_canvas;
}
}
void Preview::set_number_extruders(unsigned int number_extruders)
{
if (m_number_extruders != number_extruders)
{
m_number_extruders = number_extruders;
int type = 0; // color by a feature type
if (number_extruders > 1)
{
int tool_idx = m_choice_view_type->FindString(_(L("Tool")));
int type = (number_extruders > 1) ? tool_idx /* color by a tool number */ : 0; // color by a feature type
m_choice_view_type->SetSelection(type);
if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types))
m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type;
m_preferred_color_mode = (type == tool_idx) ? "tool_or_feature" : "feature";
}
}
}
void Preview::reset_gcode_preview_data()
{
m_gcode_preview_data->reset();
if (m_canvas != nullptr)
_3DScene::reset_legend_texture(m_canvas);
}
void Preview::set_canvas_as_dirty()
{
if (m_canvas != nullptr)
_3DScene::set_as_dirty(m_canvas);
}
void Preview::set_enabled(bool enabled)
{
m_enabled = enabled;
}
void Preview::set_bed_shape(const Pointfs& shape)
{
if (m_canvas != nullptr)
_3DScene::set_bed_shape(m_canvas, shape);
}
void Preview::select_view(const std::string& direction)
{
if (m_canvas != nullptr)
_3DScene::select_view(m_canvas, direction);
}
void Preview::set_viewport_from_scene(wxGLCanvas* canvas)
{
if ((m_canvas != nullptr) && (canvas != nullptr))
_3DScene::set_viewport_from_scene(m_canvas, canvas);
}
void Preview::set_viewport_into_scene(wxGLCanvas* canvas)
{
if ((m_canvas != nullptr) && (canvas != nullptr))
_3DScene::set_viewport_from_scene(canvas, m_canvas);
}
void Preview::set_drop_target(wxDropTarget* target)
{
if (target != nullptr)
SetDropTarget(target);
}
void Preview::load_print()
{
if (m_loaded)
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)
unsigned int n_layers = 0;
if (m_print->is_step_done(posSlice))
{
std::set<float> zs;
for (const PrintObject* print_object : m_print->objects())
{
const LayerPtrs& layers = print_object->layers();
const SupportLayerPtrs& support_layers = print_object->support_layers();
for (const Layer* layer : layers)
{
zs.insert(layer->print_z);
}
for (const SupportLayer* layer : support_layers)
{
zs.insert(layer->print_z);
}
}
n_layers = (unsigned int)zs.size();
}
if (n_layers == 0)
{
reset_sliders();
if (m_canvas != nullptr)
{
_3DScene::reset_legend_texture(m_canvas);
m_canvas->Refresh();
}
return;
}
if (m_preferred_color_mode == "tool_or_feature")
{
// It is left to Slic3r to decide whether the print shall be colored by the tool or by the feature.
// Color by feature if it is a single extruder print.
unsigned int number_extruders = (unsigned int)m_print->extruders().size();
int tool_idx = m_choice_view_type->FindString(_(L("Tool")));
int type = (number_extruders > 1) ? tool_idx /* color by a tool number */ : 0; // color by a feature type
m_choice_view_type->SetSelection(type);
if ((0 <= type) && (type < (int)GCodePreviewData::Extrusion::Num_View_Types))
m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type;
// If the->SetSelection changed the following line, revert it to "decide yourself".
m_preferred_color_mode = "tool_or_feature";
}
// Collect colors per extruder.
std::vector<std::string> colors;
if (!m_gcode_preview_data->empty() || (m_gcode_preview_data->extrusion.view_type == GCodePreviewData::Extrusion::Tool))
{
const ConfigOptionStrings* extruders_opt = dynamic_cast<const ConfigOptionStrings*>(m_config->option("extruder_colour"));
const ConfigOptionStrings* filamemts_opt = dynamic_cast<const ConfigOptionStrings*>(m_config->option("filament_colour"));
unsigned int colors_count = std::max((unsigned int)extruders_opt->values.size(), (unsigned int)filamemts_opt->values.size());
unsigned char rgb[3];
for (unsigned int i = 0; i < colors_count; ++i)
{
std::string color = m_config->opt_string("extruder_colour", i);
if (!PresetBundle::parse_color(color, rgb))
{
color = m_config->opt_string("filament_colour", i);
if (!PresetBundle::parse_color(color, rgb))
color = "#FFFFFF";
}
colors.push_back(color);
}
}
if (IsShown() && (m_canvas != nullptr))
{
// used to set the sliders to the extremes of the current zs range
m_force_sliders_full_range = false;
if (m_gcode_preview_data->empty())
{
// load skirt and brim
_3DScene::load_preview(m_canvas, colors);
show_hide_ui_elements("simple");
}
else
{
m_force_sliders_full_range = (_3DScene::get_volumes_count(m_canvas) == 0);
_3DScene::load_gcode_preview(m_canvas, m_gcode_preview_data, colors);
show_hide_ui_elements("full");
// recalculates zs and update sliders accordingly
n_layers = (unsigned int)_3DScene::get_current_print_zs(m_canvas, true).size();
if (n_layers == 0)
{
// all layers filtered out
reset_sliders();
m_canvas->Refresh();
}
}
if (n_layers > 0)
update_sliders();
m_loaded = true;
}
}
void Preview::reload_print(bool force)
{
_3DScene::reset_volumes(m_canvas);
m_loaded = false;
if (!IsShown() && !force)
return;
load_print();
}
void Preview::refresh_print()
{
m_loaded = false;
if (!IsShown())
return;
load_print();
}
void Preview::bind_event_handlers()
{
this->Bind(wxEVT_SIZE, &Preview::on_size, this);
m_choice_view_type->Bind(wxEVT_CHOICE, &Preview::on_choice_view_type, this);
m_combochecklist_features->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this);
m_checkbox_travel->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this);
m_checkbox_retractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this);
m_checkbox_unretractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_unretractions, this);
m_checkbox_shells->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_shells, this);
}
void Preview::unbind_event_handlers()
{
this->Unbind(wxEVT_SIZE, &Preview::on_size, this);
m_choice_view_type->Unbind(wxEVT_CHOICE, &Preview::on_choice_view_type, this);
m_combochecklist_features->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this);
m_checkbox_travel->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this);
m_checkbox_retractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this);
m_checkbox_unretractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_unretractions, this);
m_checkbox_shells->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_shells, this);
}
void Preview::show_hide_ui_elements(const std::string& what)
{
bool enable = (what == "full");
m_label_show_features->Enable(enable);
m_combochecklist_features->Enable(enable);
m_checkbox_travel->Enable(enable);
m_checkbox_retractions->Enable(enable);
m_checkbox_unretractions->Enable(enable);
m_checkbox_shells->Enable(enable);
enable = (what != "none");
m_label_view_type->Enable(enable);
m_choice_view_type->Enable(enable);
}
void Preview::reset_sliders()
{
m_enabled = false;
reset_double_slider();
m_double_slider_sizer->Hide((size_t)0);
}
void Preview::update_sliders()
{
m_enabled = true;
update_double_slider(m_force_sliders_full_range);
m_double_slider_sizer->Show((size_t)0);
Layout();
}
void Preview::on_size(wxSizeEvent& evt)
{
evt.Skip();
Refresh();
}
void Preview::on_choice_view_type(wxCommandEvent& evt)
{
m_preferred_color_mode = (m_choice_view_type->GetStringSelection() == L("Tool")) ? "tool" : "feature";
int selection = m_choice_view_type->GetCurrentSelection();
if ((0 <= selection) && (selection < (int)GCodePreviewData::Extrusion::Num_View_Types))
m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)selection;
reload_print();
}
void Preview::on_combochecklist_features(wxCommandEvent& evt)
{
int flags = Slic3r::GUI::combochecklist_get_flags(m_combochecklist_features);
m_gcode_preview_data->extrusion.role_flags = (unsigned int)flags;
refresh_print();
}
void Preview::on_checkbox_travel(wxCommandEvent& evt)
{
m_gcode_preview_data->travel.is_visible = m_checkbox_travel->IsChecked();
refresh_print();
}
void Preview::on_checkbox_retractions(wxCommandEvent& evt)
{
m_gcode_preview_data->retraction.is_visible = m_checkbox_retractions->IsChecked();
refresh_print();
}
void Preview::on_checkbox_unretractions(wxCommandEvent& evt)
{
m_gcode_preview_data->unretraction.is_visible = m_checkbox_unretractions->IsChecked();
refresh_print();
}
void Preview::on_checkbox_shells(wxCommandEvent& evt)
{
m_gcode_preview_data->shell.is_visible = m_checkbox_shells->IsChecked();
refresh_print();
}
void Preview::create_double_slider()
{
m_slider = new PrusaDoubleSlider(this, wxID_ANY, 0, 0, 0, 100);
m_double_slider_sizer->Add(m_slider, 0, wxEXPAND, 0);
// sizer, m_canvas
m_canvas->Bind(wxEVT_KEY_DOWN, &Preview::update_double_slider_from_canvas, this);
m_slider->Bind(wxEVT_SCROLL_CHANGED, [this](wxEvent& event) {
_3DScene::set_toolpaths_range(m_canvas, m_slider->GetLowerValueD() - 1e-6, m_slider->GetHigherValueD() + 1e-6);
if (IsShown())
m_canvas->Refresh();
});
}
void Preview::update_double_slider(bool force_sliders_full_range)
{
std::vector<std::pair<int, double>> values;
std::vector<double> layers_z = _3DScene::get_current_print_zs(m_canvas, true);
fill_slider_values(values, layers_z);
const double z_low = m_slider->GetLowerValueD();
const double z_high = m_slider->GetHigherValueD();
m_slider->SetMaxValue(layers_z.size() - 1);
m_slider->SetSliderValues(values);
set_double_slider_thumbs(force_sliders_full_range, layers_z, z_low, z_high);
}
void Preview::fill_slider_values(std::vector<std::pair<int, double>> &values,
const std::vector<double> &layers_z)
{
std::vector<double> layers_all_z = _3DScene::get_current_print_zs(m_canvas, false);
if (layers_all_z.size() == layers_z.size())
for (int i = 0; i < layers_z.size(); i++)
values.push_back(std::pair<int, double>(i + 1, layers_z[i]));
else if (layers_all_z.size() > layers_z.size()) {
int cur_id = 0;
for (int i = 0; i < layers_z.size(); i++)
for (int j = cur_id; j < layers_all_z.size(); j++)
if (layers_z[i] - 1e-6 < layers_all_z[j] && layers_all_z[j] < layers_z[i] + 1e-6) {
values.push_back(std::pair<int, double>(j + 1, layers_z[i]));
cur_id = j;
break;
}
}
}
void Preview::set_double_slider_thumbs(const bool force_sliders_full_range,
const std::vector<double> &layers_z,
const double z_low,
const double z_high)
{
// Force slider full range only when slider is created.
// Support selected diapason on the all next steps
if (/*force_sliders_full_range*/z_high == 0.0) {
m_slider->SetLowerValue(0);
m_slider->SetHigherValue(layers_z.size() - 1);
return;
}
for (int i = layers_z.size() - 1; i >= 0; i--)
if (z_low >= layers_z[i]) {
m_slider->SetLowerValue(i);
break;
}
for (int i = layers_z.size() - 1; i >= 0; i--)
if (z_high >= layers_z[i]) {
m_slider->SetHigherValue(i);
break;
}
}
void Preview::reset_double_slider()
{
m_slider->SetHigherValue(0);
m_slider->SetLowerValue(0);
}
void Preview::update_double_slider_from_canvas(wxKeyEvent& event)
{
if (event.HasModifiers()) {
event.Skip();
return;
}
const auto key = event.GetKeyCode();
if (key == 'U' || key == 'D') {
const int new_pos = key == 'U' ? m_slider->GetHigherValue() + 1 : m_slider->GetHigherValue() - 1;
m_slider->SetHigherValue(new_pos);
if (event.ShiftDown()) m_slider->SetLowerValue(m_slider->GetHigherValue());
}
else if (key == 'S')
m_slider->ChangeOneLayerLock();
else
event.Skip();
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,108 @@
#ifndef slic3r_GUI_Preview_hpp_
#define slic3r_GUI_Preview_hpp_
#include <wx/panel.h>
#include "../../libslic3r/Point.hpp"
#include <string>
class wxNotebook;
class wxGLCanvas;
class wxBoxSizer;
class wxStaticText;
class wxChoice;
class wxComboCtrl;
class wxCheckBox;
class PrusaDoubleSlider;
namespace Slic3r {
class DynamicPrintConfig;
class Print;
class GCodePreviewData;
namespace GUI {
class Preview : public wxPanel
{
wxGLCanvas* m_canvas;
wxBoxSizer* m_double_slider_sizer;
wxStaticText* m_label_view_type;
wxChoice* m_choice_view_type;
wxStaticText* m_label_show_features;
wxComboCtrl* m_combochecklist_features;
wxCheckBox* m_checkbox_travel;
wxCheckBox* m_checkbox_retractions;
wxCheckBox* m_checkbox_unretractions;
wxCheckBox* m_checkbox_shells;
DynamicPrintConfig* m_config;
Print* m_print;
GCodePreviewData* m_gcode_preview_data;
unsigned int m_number_extruders;
std::string m_preferred_color_mode;
bool m_loaded;
bool m_enabled;
bool m_force_sliders_full_range;
PrusaDoubleSlider* m_slider {nullptr};
public:
Preview(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data);
virtual ~Preview();
wxGLCanvas* get_wxglcanvas() { return m_canvas; }
void set_number_extruders(unsigned int number_extruders);
void reset_gcode_preview_data();
void set_canvas_as_dirty();
void set_enabled(bool enabled);
void set_bed_shape(const Pointfs& shape);
void select_view(const std::string& direction);
void set_viewport_from_scene(wxGLCanvas* canvas);
void set_viewport_into_scene(wxGLCanvas* canvas);
void set_drop_target(wxDropTarget* target);
void load_print();
void reload_print(bool force = false);
void refresh_print();
private:
bool init(wxNotebook* notebook, DynamicPrintConfig* config, Print* print, GCodePreviewData* gcode_preview_data);
void bind_event_handlers();
void unbind_event_handlers();
void show_hide_ui_elements(const std::string& what);
void reset_sliders();
void update_sliders();
void on_size(wxSizeEvent& evt);
void on_choice_view_type(wxCommandEvent& evt);
void on_combochecklist_features(wxCommandEvent& evt);
void on_checkbox_travel(wxCommandEvent& evt);
void on_checkbox_retractions(wxCommandEvent& evt);
void on_checkbox_unretractions(wxCommandEvent& evt);
void on_checkbox_shells(wxCommandEvent& evt);
// Create/Update/Reset double slider on 3dPreview
void create_double_slider();
void update_double_slider(bool force_sliders_full_range);
void fill_slider_values(std::vector<std::pair<int, double>> &values,
const std::vector<double> &layers_z);
void set_double_slider_thumbs( const bool force_sliders_full_range,
const std::vector<double> &layers_z,
const double z_low,
const double z_high);
void reset_double_slider();
// update DoubleSlider after keyDown in canvas
void update_double_slider_from_canvas(wxKeyEvent& event);
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GUI_Preview_hpp_

View file

@ -0,0 +1,57 @@
#include "../../libslic3r/libslic3r.h"
#include "GUI_PreviewIface.hpp"
#include "GUI_Preview.hpp"
namespace Slic3r {
void PreviewIface::set_number_extruders(unsigned int number_extruders)
{
m_preview->set_number_extruders(number_extruders);
}
void PreviewIface::reset_gcode_preview_data()
{
m_preview->reset_gcode_preview_data();
}
void PreviewIface::reload_print(bool force)
{
m_preview->reload_print(force);
}
void PreviewIface::set_canvas_as_dirty()
{
m_preview->set_canvas_as_dirty();
}
void PreviewIface::set_enabled(bool enabled)
{
m_preview->set_enabled(enabled);
}
void PreviewIface::set_bed_shape(const Pointfs& shape)
{
m_preview->set_bed_shape(shape);
}
void PreviewIface::select_view(const std::string& direction)
{
m_preview->select_view(direction);
}
void PreviewIface::set_viewport_from_scene(wxGLCanvas* canvas)
{
m_preview->set_viewport_from_scene(canvas);
}
void PreviewIface::set_viewport_into_scene(wxGLCanvas* canvas)
{
m_preview->set_viewport_into_scene(canvas);
}
void PreviewIface::set_drop_target(wxDropTarget* target)
{
m_preview->set_drop_target(target);
}
} // namespace Slic3r

View file

@ -0,0 +1,36 @@
#ifndef slic3r_GUI_PreviewIface_hpp_
#define slic3r_GUI_PreviewIface_hpp_
#include "../../libslic3r/Point.hpp"
class wxGLCanvas;
class wxDropTarget;
namespace Slic3r {
namespace GUI {
class Preview;
} // namespace GUI
class PreviewIface
{
GUI::Preview* m_preview;
public:
explicit PreviewIface(GUI::Preview* preview) : m_preview(preview) {}
void set_number_extruders(unsigned int number_extruders);
void reset_gcode_preview_data();
void reload_print(bool force = false);
void set_canvas_as_dirty();
void set_enabled(bool enabled);
void set_bed_shape(const Pointfs& shape);
void select_view(const std::string& direction);
void set_viewport_from_scene(wxGLCanvas* canvas);
void set_viewport_into_scene(wxGLCanvas* canvas);
void set_drop_target(wxDropTarget* target);
};
} // namespace Slic3r
#endif // slic3r_GUI_PreviewIface_hpp_

View file

@ -0,0 +1,127 @@
#include "GUI_Utils.hpp"
#include <algorithm>
#include <boost/lexical_cast.hpp>
#include <boost/format.hpp>
#include <wx/toplevel.h>
#include <wx/sizer.h>
#include <wx/checkbox.h>
#include "libslic3r/Config.hpp"
namespace Slic3r {
namespace GUI {
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];
return res;
}
void WindowMetrics::sanitize_for_display(const wxRect &screen_rect)
{
rect = rect.Intersect(screen_rect);
}
std::string WindowMetrics::serialize()
{
return (boost::format("%1%; %2%; %3%; %4%; %5%")
% rect.GetX()
% rect.GetY()
% rect.GetWidth()
% rect.GetHeight()
% static_cast<int>(maximized)
).str();
}
}
}

View file

@ -0,0 +1,74 @@
#ifndef slic3r_GUI_Utils_hpp_
#define slic3r_GUI_Utils_hpp_
#include <functional>
#include <string>
#include <boost/optional.hpp>
#include <wx/filedlg.h>
#include <wx/gdicmn.h>
#include <wx/panel.h>
class wxCheckBox;
class wxTopLevelWindow;
class wxRect;
namespace Slic3r {
namespace GUI {
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);
wxRect get_rect() const { return rect; }
bool get_maximized() const { return maximized; }
void sanitize_for_display(const wxRect &screen_rect);
std::string serialize();
};
}}
#endif

View file

@ -0,0 +1,198 @@
#include "LambdaObjectDialog.hpp"
#include <wx/window.h>
#include <wx/button.h>
#include "OptionsGroup.hpp"
namespace Slic3r
{
namespace GUI
{
LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent,
const wxString type_name):
m_type_name(type_name)
{
Create(parent, wxID_ANY, _(L("Lambda Object")),
parent->GetScreenPosition(), wxDefaultSize,
wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER);
// instead of double dim[3] = { 1.0, 1.0, 1.0 };
object_parameters.dim[0] = 1.0;
object_parameters.dim[1] = 1.0;
object_parameters.dim[2] = 1.0;
sizer = new wxBoxSizer(wxVERTICAL);
// modificator options
if (m_type_name == wxEmptyString) {
m_modificator_options_book = new wxChoicebook( this, wxID_ANY, wxDefaultPosition,
wxDefaultSize, wxCHB_TOP);
sizer->Add(m_modificator_options_book, 1, wxEXPAND | wxALL, 10);
}
else {
m_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize);
sizer->Add(m_panel, 1, wxEXPAND | wxALL, 10);
}
ConfigOptionDef def;
def.width = 70;
auto optgroup = init_modificator_options_page(_(L("Box")));
if (optgroup){
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
int opt_id = opt_key == "l" ? 0 :
opt_key == "w" ? 1 :
opt_key == "h" ? 2 : -1;
if (opt_id < 0) return;
object_parameters.dim[opt_id] = boost::any_cast<double>(value);
};
def.type = coFloat;
def.default_value = new ConfigOptionFloat{ 1.0 };
def.label = L("L");
Option option(def, "l");
optgroup->append_single_option_line(option);
def.label = L("W");
option = Option(def, "w");
optgroup->append_single_option_line(option);
def.label = L("H");
option = Option(def, "h");
optgroup->append_single_option_line(option);
}
optgroup = init_modificator_options_page(_(L("Cylinder")));
if (optgroup){
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
int val = boost::any_cast<int>(value);
if (opt_key == "cyl_r")
object_parameters.cyl_r = val;
else if (opt_key == "cyl_h")
object_parameters.cyl_h = val;
else return;
};
def.type = coInt;
def.default_value = new ConfigOptionInt{ 1 };
def.label = L("Radius");
auto option = Option(def, "cyl_r");
optgroup->append_single_option_line(option);
def.label = L("Height");
option = Option(def, "cyl_h");
optgroup->append_single_option_line(option);
}
optgroup = init_modificator_options_page(_(L("Sphere")));
if (optgroup){
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
if (opt_key == "sph_rho")
object_parameters.sph_rho = boost::any_cast<double>(value);
else return;
};
def.type = coFloat;
def.default_value = new ConfigOptionFloat{ 1.0 };
def.label = L("Rho");
auto option = Option(def, "sph_rho");
optgroup->append_single_option_line(option);
}
optgroup = init_modificator_options_page(_(L("Slab")));
if (optgroup){
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
double val = boost::any_cast<double>(value);
if (opt_key == "slab_z")
object_parameters.slab_z = val;
else if (opt_key == "slab_h")
object_parameters.slab_h = val;
else return;
};
def.type = coFloat;
def.default_value = new ConfigOptionFloat{ 1.0 };
def.label = L("H");
auto option = Option(def, "slab_h");
optgroup->append_single_option_line(option);
def.label = L("Initial Z");
option = Option(def, "slab_z");
optgroup->append_single_option_line(option);
}
Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent e)
{
auto page_idx = m_modificator_options_book->GetSelection();
if (page_idx < 0) return;
switch (page_idx)
{
case 0:
object_parameters.type = LambdaTypeBox;
break;
case 1:
object_parameters.type = LambdaTypeCylinder;
break;
case 2:
object_parameters.type = LambdaTypeSphere;
break;
case 3:
object_parameters.type = LambdaTypeSlab;
break;
default:
break;
}
}));
const auto button_sizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
wxButton* btn_OK = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
btn_OK->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
// validate user input
if (!CanClose())return;
EndModal(wxID_OK);
Destroy();
});
wxButton* btn_CANCEL = static_cast<wxButton*>(FindWindowById(wxID_CANCEL, this));
btn_CANCEL->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) {
// validate user input
if (!CanClose())return;
EndModal(wxID_CANCEL);
Destroy();
});
sizer->Add(button_sizer, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
SetSizer(sizer);
sizer->Fit(this);
sizer->SetSizeHints(this);
}
// Called from the constructor.
// Create a panel for a rectangular / circular / custom bed shape.
ConfigOptionsGroupShp LambdaObjectDialog::init_modificator_options_page(const wxString& title){
if (!m_type_name.IsEmpty() && m_type_name != title)
return nullptr;
auto panel = m_type_name.IsEmpty() ? new wxPanel(m_modificator_options_book) : m_panel;
ConfigOptionsGroupShp optgroup;
optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Add")) + " " +title + " " +dots);
optgroup->label_width = 100;
m_optgroups.push_back(optgroup);
if (m_type_name.IsEmpty()) {
panel->SetSizerAndFit(optgroup->sizer);
m_modificator_options_book->AddPage(panel, title);
}
else
panel->SetSizer(optgroup->sizer);
return optgroup;
}
} //namespace GUI
} //namespace Slic3r

View file

@ -0,0 +1,58 @@
#ifndef slic3r_LambdaObjectDialog_hpp_
#define slic3r_LambdaObjectDialog_hpp_
#include "GUI.hpp"
#include <wx/dialog.h>
#include <wx/sizer.h>
#include <wx/choicebk.h>
class wxPanel;
namespace Slic3r
{
namespace GUI
{
enum LambdaTypeIDs{
LambdaTypeBox,
LambdaTypeCylinder,
LambdaTypeSphere,
LambdaTypeSlab
};
struct OBJECT_PARAMETERS
{
LambdaTypeIDs type = LambdaTypeBox;
double dim[3];// = { 1.0, 1.0, 1.0 };
int cyl_r = 1;
int cyl_h = 1;
double sph_rho = 1.0;
double slab_h = 1.0;
double slab_z = 0.0;
};
class ConfigOptionsGroup;
using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>;
class LambdaObjectDialog : public wxDialog
{
wxChoicebook* m_modificator_options_book = nullptr;
std::vector <ConfigOptionsGroupShp> m_optgroups;
wxString m_type_name;
wxPanel* m_panel = nullptr;
public:
LambdaObjectDialog(wxWindow* parent,
const wxString type_name = wxEmptyString);
~LambdaObjectDialog(){}
bool CanClose() { return true; } // ???
OBJECT_PARAMETERS& ObjectParameters(){ return object_parameters; }
ConfigOptionsGroupShp init_modificator_options_page(const wxString& title);
// Note whether the window was already closed, so a pending update is not executed.
bool m_already_closed = false;
OBJECT_PARAMETERS object_parameters;
wxBoxSizer* sizer = nullptr;
};
} //namespace GUI
} //namespace Slic3r
#endif //slic3r_LambdaObjectDialog_hpp_

View file

@ -0,0 +1,777 @@
#include "MainFrame.hpp"
#include <wx/panel.h>
#include <wx/notebook.h>
#include <wx/icon.h>
#include <wx/sizer.h>
#include <wx/menu.h>
#include <wx/progdlg.h>
#include <wx/tooltip.h>
#include <wx/debug.h>
#include "Tab.hpp"
#include "PresetBundle.hpp"
#include "../AppController.hpp"
#include "ProgressStatusBar.hpp"
#include "3DScene.hpp"
#include "Print.hpp"
#include "Polygon.hpp"
#include "AppConfig.hpp"
#include "wxExtensions.hpp"
#include <fstream>
#include "GUI_App.hpp"
namespace Slic3r {
namespace GUI {
MainFrame::MainFrame(const bool no_plater, const bool loaded) :
wxFrame(NULL, wxID_ANY, SLIC3R_BUILD, wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE),
m_no_plater(no_plater),
m_loaded(loaded)
{
m_appController = new Slic3r::AppController();
// Load the icon either from the exe, or from the ico file.
#if _WIN32
{
TCHAR szExeFileName[MAX_PATH];
GetModuleFileName(nullptr, szExeFileName, MAX_PATH);
SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO));
}
#else
SetIcon(wxIcon(Slic3r::var("Slic3r_128px.png"), wxBITMAP_TYPE_PNG));
#endif // _WIN32
// initialize tabpanel and menubar
init_tabpanel();
init_menubar();
// set default tooltip timer in msec
// SetAutoPop supposedly accepts long integers but some bug doesn't allow for larger values
// (SetAutoPop is not available on GTK.)
wxToolTip::SetAutoPop(32767);
// initialize status bar
m_statusbar = new ProgressStatusBar(this);
m_statusbar->embed(this);
m_statusbar->set_status_text(_(L("Version ")) +
SLIC3R_VERSION +
_(L(" - Remember to check for updates at http://github.com/prusa3d/slic3r/releases")));
m_appController->set_model(&m_plater->model());
m_appController->set_print(&m_plater->print());
GUI::set_gui_appctl();
// Make the global status bar and its progress indicator available in C++
m_appController->set_global_progress_indicator(m_statusbar);
m_loaded = true;
// initialize layout
auto sizer = new wxBoxSizer(wxVERTICAL);
if (m_tabpanel)
sizer->Add(m_tabpanel, 1, wxEXPAND);
sizer->SetSizeHints(this);
SetSizer(sizer);
Fit();
SetMinSize(wxSize(760, 490));
SetSize(GetMinSize());
Layout();
// declare events
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event){
if (event.CanVeto() && !wxGetApp().check_unsaved_changes()) {
event.Veto();
return;
}
// save window size
wxGetApp().window_pos_save(this, "mainframe");
// Save the slic3r.ini.Usually the ini file is saved from "on idle" callback,
// but in rare cases it may not have been called yet.
wxGetApp().app_config->save();
// if (m_plater)
// m_plater->print = undef;
_3DScene::remove_all_canvases();
// Slic3r::GUI::deregister_on_request_update_callback();
// propagate event
event.Skip();
});
// NB: Restoring the window position is done in a two-phase manner here,
// first the saved position is restored as-is and validation is done after the window is shown
// and initial round of events is complete, because on some platforms that is the only way
// to get an accurate window position & size.
wxGetApp().window_pos_restore(this, "mainframe");
Bind(wxEVT_SHOW, [this](wxShowEvent&) {
CallAfter([this]() {
wxGetApp().window_pos_sanitize(this);
});
});
update_ui_from_settings();
return;
}
void MainFrame::init_tabpanel()
{
m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL);
m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxEvent&){
auto panel = m_tabpanel->GetCurrentPage();
// panel->OnActivate(); if panel->can('OnActivate');
if (panel == nullptr)
return;
for (auto& tab_name : { "print", "filament", "printer" }) {
if (tab_name == panel->GetName()) {
// On GTK, the wxEVT_NOTEBOOK_PAGE_CHANGED event is triggered
// before the MainFrame is fully set up.
auto it = m_options_tabs.find(tab_name);
assert(it != m_options_tabs.end());
if (it != m_options_tabs.end())
it->second->OnActivate();
}
}
});
if (!m_no_plater) {
m_plater = new Slic3r::GUI::Plater(m_tabpanel, this);
wxGetApp().plater_ = m_plater;
m_tabpanel->AddPage(m_plater, _(L("Plater")));
}
// The following event is emited by Tab implementation on config value change.
Bind(EVT_TAB_VALUE_CHANGED, &MainFrame::on_value_changed, this);
// The following event is emited by Tab on preset selection,
// or when the preset's "modified" status changes.
Bind(EVT_TAB_PRESETS_CHANGED, &MainFrame::on_presets_changed, this);
create_preset_tabs();
std::vector<std::string> tab_names = { "print", "filament", "sla_material", "printer" };
for (auto tab_name : tab_names)
m_options_tabs[tab_name] = get_preset_tab(tab_name.c_str());
if (m_plater) {
// load initial config
auto full_config = wxGetApp().preset_bundle->full_config();
m_plater->on_config_change(full_config);
// Show a correct number of filament fields.
// nozzle_diameter is undefined when SLA printer is selected
if (full_config.has("nozzle_diameter")){
m_plater->on_extruders_change(full_config.option<ConfigOptionFloats>("nozzle_diameter")->values.size());
}
}
}
std::vector<PresetTab> preset_tabs = {
{ "print", nullptr, ptFFF },
{ "filament", nullptr, ptFFF },
{ "sla_material", nullptr, ptSLA }
};
std::vector<PresetTab>& MainFrame::get_preset_tabs() {
return preset_tabs;
}
Tab* MainFrame::get_tab(const std::string& name)
{
std::vector<PresetTab>::iterator it = std::find_if(preset_tabs.begin(), preset_tabs.end(),
[name](PresetTab& tab){ return name == tab.name; });
return it != preset_tabs.end() ? it->panel : nullptr;
}
Tab* MainFrame::get_preset_tab(const std::string& name)
{
Tab* tab = get_tab(name);
if (tab) return tab;
for (size_t i = 0; i < m_tabpanel->GetPageCount(); ++i) {
tab = dynamic_cast<Tab*>(m_tabpanel->GetPage(i));
if (!tab)
continue;
if (tab->name() == name) {
return tab;
}
}
return nullptr;
}
void MainFrame::create_preset_tabs()
{
wxGetApp().update_label_colours_from_appconfig();
add_created_tab(new TabPrint(m_tabpanel));
add_created_tab(new TabFilament(m_tabpanel));
add_created_tab(new TabSLAMaterial(m_tabpanel));
add_created_tab(new TabPrinter(m_tabpanel));
}
void MainFrame::add_created_tab(Tab* panel)
{
panel->create_preset_tab();
const wxString& tab_name = panel->GetName();
bool add_panel = true;
auto it = std::find_if(preset_tabs.begin(), preset_tabs.end(),
[tab_name](PresetTab& tab){return tab.name == tab_name; });
if (it != preset_tabs.end()) {
it->panel = panel;
add_panel = it->technology == wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
}
if (add_panel)
m_tabpanel->AddPage(panel, panel->title());
}
void MainFrame::init_menubar()
{
// File menu
wxMenu* fileMenu = new wxMenu;
{
append_menu_item(fileMenu, wxID_ANY, _(L("Open STL/OBJ/AMF/3MF…\tCtrl+O")), _(L("Open a model")),
[this](wxCommandEvent&) { if (m_plater) m_plater->add(); }, "brick_add.png");
append_menu_item(fileMenu, wxID_ANY, _(L("&Load Config…\tCtrl+L")), _(L("Load exported configuration file")),
[this](wxCommandEvent&){ load_config_file(); }, "plugin_add.png");
append_menu_item(fileMenu, wxID_ANY, _(L("&Export Config…\tCtrl+E")), _(L("Export current configuration to file")),
[this](wxCommandEvent&){ export_config(); }, "plugin_go.png");
append_menu_item(fileMenu, wxID_ANY, _(L("&Load Config Bundle…")), _(L("Load presets from a bundle")),
[this](wxCommandEvent&){ load_configbundle(); }, "lorry_add.png");
append_menu_item(fileMenu, wxID_ANY, _(L("&Export Config Bundle…")), _(L("Export all presets to file")),
[this](wxCommandEvent&){ export_configbundle(); }, "lorry_go.png");
fileMenu->AppendSeparator();
wxMenuItem* repeat = nullptr;
append_menu_item(fileMenu, wxID_ANY, _(L("Q&uick Slice…\tCtrl+U")), _(L("Slice a file into a G-code")),
[this, repeat](wxCommandEvent&){
wxTheApp->CallAfter([this, repeat](){
quick_slice();
repeat->Enable(is_last_input_file());
}); }, "cog_go.png");
append_menu_item(fileMenu, wxID_ANY, _(L("Quick Slice and Save &As…\tCtrl+Alt+U")), _(L("Slice a file into a G-code, save as")),
[this, repeat](wxCommandEvent&){
wxTheApp->CallAfter([this, repeat](){
quick_slice(qsSaveAs);
repeat->Enable(is_last_input_file());
}); }, "cog_go.png");
repeat = append_menu_item(fileMenu, wxID_ANY, _(L("&Repeat Last Quick Slice\tCtrl+Shift+U")), _(L("Repeat last quick slice")),
[this](wxCommandEvent&){
wxTheApp->CallAfter([this](){
quick_slice(qsReslice);
}); }, "cog_go.png");
repeat->Enable(0);
fileMenu->AppendSeparator();
append_menu_item(fileMenu, wxID_ANY, _(L("Slice to SV&G…\tCtrl+G")), _(L("Slice file to a multi-layer SVG")),
[this](wxCommandEvent&){ quick_slice(qsSaveAs | qsExportSVG); }, "shape_handles.png");
append_menu_item(fileMenu, wxID_ANY, _(L("Slice to PNG…")), _(L("Slice file to a set of PNG files")),
[this](wxCommandEvent&){ slice_to_png(); /*$self->quick_slice(save_as = > 0, export_png = > 1);*/ }, "shape_handles.png");
m_menu_item_reslice_now = append_menu_item(fileMenu, wxID_ANY, _(L("(&Re)Slice Now\tCtrl+S")), _(L("Start new slicing process")),
[this](wxCommandEvent&){ reslice_now(); }, "shape_handles.png");
fileMenu->AppendSeparator();
append_menu_item(fileMenu, wxID_ANY, _(L("Repair STL file…")), _(L("Automatically repair an STL file")),
[this](wxCommandEvent&){ repair_stl(); }, "wrench.png");
fileMenu->AppendSeparator();
append_menu_item(fileMenu, wxID_EXIT, _(L("&Quit")), _(L("Quit Slic3r")),
[this](wxCommandEvent&){ Close(false); } );
}
// Plater menu
if (m_plater) {
m_plater_menu = new wxMenu();
append_menu_item(m_plater_menu, wxID_ANY, _(L("Export G-code...")), _(L("Export current plate as G-code")),
[this](wxCommandEvent&){ m_plater->export_gcode(); }, "cog_go.png");
append_menu_item(m_plater_menu, wxID_ANY, _(L("Export plate as STL...")), _(L("Export current plate as STL")),
[this](wxCommandEvent&){ m_plater->export_stl(); }, "brick_go.png");
append_menu_item(m_plater_menu, wxID_ANY, _(L("Export plate as AMF...")), _(L("Export current plate as AMF")),
[this](wxCommandEvent&){ m_plater->export_amf(); }, "brick_go.png");
append_menu_item(m_plater_menu, wxID_ANY, _(L("Export plate as 3MF...")), _(L("Export current plate as 3MF")),
[this](wxCommandEvent&){ m_plater->export_3mf(); }, "brick_go.png");
}
// Window menu
auto windowMenu = new wxMenu();
{
size_t tab_offset = 0;
if (m_plater) {
append_menu_item(windowMenu, wxID_ANY, L("Select &Plater Tab\tCtrl+1"), L("Show the plater"),
[this](wxCommandEvent&){ select_tab(0); }, "application_view_tile.png");
tab_offset += 1;
}
if (tab_offset > 0) {
windowMenu->AppendSeparator();
}
append_menu_item(windowMenu, wxID_ANY, L("Select P&rint Settings Tab\tCtrl+2"), L("Show the print settings"),
[this, tab_offset](wxCommandEvent&){ select_tab(tab_offset + 0); }, "cog.png");
append_menu_item(windowMenu, wxID_ANY, L("Select &Filament Settings Tab\tCtrl+3"), L("Show the filament settings"),
[this, tab_offset](wxCommandEvent&){ select_tab(tab_offset + 1); }, "spool.png");
append_menu_item(windowMenu, wxID_ANY, L("Select Print&er Settings Tab\tCtrl+4"), L("Show the printer settings"),
[this, tab_offset](wxCommandEvent&){ select_tab(tab_offset + 2); }, "printer_empty.png");
}
// View menu
if (m_plater) {
m_viewMenu = new wxMenu();
// \xA0 is a non-breaing space. It is entered here to spoil the automatic accelerators,
// as the simple numeric accelerators spoil all numeric data entry.
// The camera control accelerators are captured by GLCanvas3D::on_char().
append_menu_item(m_viewMenu, wxID_ANY, _(L("Iso")) + "\t\xA0" + "0", _(L("Iso View")), [this](wxCommandEvent&){ select_view("iso"); });
m_viewMenu->AppendSeparator();
append_menu_item(m_viewMenu, wxID_ANY, _(L("Top")) + "\t\xA0" + "1", _(L("Top View")), [this](wxCommandEvent&){ select_view("top"); });
append_menu_item(m_viewMenu, wxID_ANY, _(L("Bottom")) + "\t\xA0" + "2", _(L("Bottom View")), [this](wxCommandEvent&){ select_view("bottom"); });
append_menu_item(m_viewMenu, wxID_ANY, _(L("Front")) + "\t\xA0" + "3", _(L("Front View")), [this](wxCommandEvent&){ select_view("front"); });
append_menu_item(m_viewMenu, wxID_ANY, _(L("Rear")) + "\t\xA0" + "4", _(L("Rear View")), [this](wxCommandEvent&){ select_view("rear"); });
append_menu_item(m_viewMenu, wxID_ANY, _(L("Left")) + "\t\xA0" + "5", _(L("Left View")), [this](wxCommandEvent&){ select_view("left"); });
append_menu_item(m_viewMenu, wxID_ANY, _(L("Right")) + "\t\xA0" + "6", _(L("Right View")), [this](wxCommandEvent&){ select_view("right"); });
}
// Help menu
auto helpMenu = new wxMenu();
{
append_menu_item(helpMenu, wxID_ANY, _(L("Prusa 3D Drivers")), _(L("Open the Prusa3D drivers download page in your browser")),
[this](wxCommandEvent&){ wxLaunchDefaultBrowser("http://www.prusa3d.com/drivers/"); });
append_menu_item(helpMenu, wxID_ANY, _(L("Prusa Edition Releases")), _(L("Open the Prusa Edition releases page in your browser")),
[this](wxCommandEvent&){ wxLaunchDefaultBrowser("http://github.com/prusa3d/slic3r/releases"); });
//# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{
//# wxTheApp->check_version(1);
//# });
//# $versioncheck->Enable(wxTheApp->have_version_check);
append_menu_item(helpMenu, wxID_ANY, _(L("Slic3r &Website")), _(L("Open the Slic3r website in your browser")),
[this](wxCommandEvent&){ wxLaunchDefaultBrowser("http://slic3r.org/"); });
append_menu_item(helpMenu, wxID_ANY, _(L("Slic3r &Manual")), _(L("Open the Slic3r manual in your browser")),
[this](wxCommandEvent&){ wxLaunchDefaultBrowser("http://manual.slic3r.org/"); });
helpMenu->AppendSeparator();
append_menu_item(helpMenu, wxID_ANY, _(L("System Info")), _(L("Show system information")),
[this](wxCommandEvent&){ wxGetApp().system_info(); });
append_menu_item(helpMenu, wxID_ANY, _(L("Show &Configuration Folder")), _(L("Show user configuration folder (datadir)")),
[this](wxCommandEvent&){ Slic3r::GUI::desktop_open_datadir_folder(); });
append_menu_item(helpMenu, wxID_ANY, _(L("Report an Issue")), _(L("Report an issue on the Slic3r Prusa Edition")),
[this](wxCommandEvent&){ wxLaunchDefaultBrowser("http://github.com/prusa3d/slic3r/issues/new"); });
append_menu_item(helpMenu, wxID_ANY, _(L("&About Slic3r")), _(L("Show about dialog")),
[this](wxCommandEvent&){ Slic3r::GUI::about(); });
}
// menubar
// assign menubar to frame after appending items, otherwise special items
// will not be handled correctly
{
auto menubar = new wxMenuBar();
menubar->Append(fileMenu, L("&File"));
if (m_plater_menu) menubar->Append(m_plater_menu, L("&Plater")) ;
menubar->Append(windowMenu, L("&Window"));
if (m_viewMenu) menubar->Append(m_viewMenu, L("&View"));
// Add additional menus from C++
wxGetApp().add_config_menu(menubar);
menubar->Append(helpMenu, L("&Help"));
SetMenuBar(menubar);
}
}
void MainFrame::slice_to_png(){
// m_plater->stop_background_process();
// m_plater->async_apply_config();
m_appController->print_ctl()->slice_to_png();
}
// To perform the "Quck Slice", "Quick Slice and Save As", "Repeat last Quick Slice" and "Slice to SVG".
void MainFrame::quick_slice(const int qs){
// my $progress_dialog;
wxString input_file;
// eval
// {
// validate configuration
auto config = wxGetApp().preset_bundle->full_config();
config.validate();
// select input file
if (!(qs & qsReslice)) {
auto dlg = new wxFileDialog(this, _(L("Choose a file to slice (STL/OBJ/AMF/3MF/PRUSA):")),
wxGetApp().app_config->get_last_dir(), "",
file_wildcards[FT_MODEL], wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dlg->ShowModal() != wxID_OK) {
dlg->Destroy();
return;
}
input_file = dlg->GetPath();
dlg->Destroy();
if (!(qs & qsExportSVG))
m_qs_last_input_file = input_file;
}
else {
if (m_qs_last_input_file.IsEmpty()) {
auto dlg = new wxMessageDialog(this, _(L("No previously sliced file.")),
_(L("Error")), wxICON_ERROR | wxOK);
dlg->ShowModal();
return;
}
if (std::ifstream(m_qs_last_input_file.char_str())) {
auto dlg = new wxMessageDialog(this, _(L("Previously sliced file ("))+m_qs_last_input_file+_(L(") not found.")),
_(L("File Not Found")), wxICON_ERROR | wxOK);
dlg->ShowModal();
return;
}
input_file = m_qs_last_input_file;
}
auto input_file_basename = get_base_name(input_file);
wxGetApp().app_config->update_skein_dir(get_dir_name(input_file));
auto bed_shape = Slic3r::Polygon::new_scale(config.option<ConfigOptionPoints>("bed_shape")->values);
// auto print_center = Slic3r::Pointf->new_unscale(bed_shape.bounding_box().center());
//
// auto sprint = new Slic3r::Print::Simple(
// print_center = > print_center,
// status_cb = > [](int percent, const wxString& msg){
// m_progress_dialog->Update(percent, msg+"…");
// });
// keep model around
auto model = Slic3r::Model::read_from_file(input_file.ToStdString());
// sprint->apply_config(config);
// sprint->set_model(model);
// Copy the names of active presets into the placeholder parser.
// wxGetApp().preset_bundle->export_selections(sprint->placeholder_parser);
// select output file
wxString output_file;
if (qs & qsReslice) {
if (!m_qs_last_output_file.IsEmpty())
output_file = m_qs_last_output_file;
}
else if (qs & qsSaveAs) {
// The following line may die if the output_filename_format template substitution fails.
// output_file = sprint->output_filepath;
// if (export_svg)
// output_file = ~s / \.[gG][cC][oO][dD][eE]$ / .svg /;
auto dlg = new wxFileDialog(this, _(L("Save ")) + (qs & qsExportSVG ? _(L("SVG")) : _(L("G-code"))) + _(L(" file as:")),
wxGetApp().app_config->get_last_output_dir(get_dir_name(output_file)), get_base_name(input_file),
qs & qsExportSVG ? file_wildcards[FT_SVG] : file_wildcards[FT_GCODE],
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (dlg->ShowModal() != wxID_OK) {
dlg->Destroy();
return;
}
output_file = dlg->GetPath();
dlg->Destroy();
if (!(qs & qsExportSVG))
m_qs_last_output_file = output_file;
wxGetApp().app_config->update_last_output_dir(get_dir_name(output_file));
}
else if (qs & qsExportPNG) {
// output_file = sprint->output_filepath;
// output_file = ~s / \.[gG][cC][oO][dD][eE]$ / .zip / ;
auto dlg = new wxFileDialog(this, _(L("Save zip file as:")),
wxGetApp().app_config->get_last_output_dir(get_dir_name(output_file)),
get_base_name(output_file), "*.zip", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (dlg->ShowModal() != wxID_OK) {
dlg->Destroy();
return;
}
output_file = dlg->GetPath();
dlg->Destroy();
}
// show processbar dialog
m_progress_dialog = new wxProgressDialog(_(L("Slicing…")), _(L("Processing ")) + input_file_basename + "",
100, this, 4);
m_progress_dialog->Pulse();
{
// my @warnings = ();
// local $SIG{ __WARN__ } = sub{ push @warnings, $_[0] };
// sprint->output_file(output_file);
// if (export_svg) {
// sprint->export_svg();
// }
// else if(export_png) {
// sprint->export_png();
// }
// else {
// sprint->export_gcode();
// }
// sprint->status_cb(undef);
// Slic3r::GUI::warning_catcher($self)->($_) for @warnings;
}
m_progress_dialog->Destroy();
m_progress_dialog = nullptr;
auto message = input_file_basename + _(L(" was successfully sliced."));
// wxTheApp->notify(message);
wxMessageDialog(this, message, _(L("Slicing Done!")), wxOK | wxICON_INFORMATION).ShowModal();
// };
// Slic3r::GUI::catch_error(this, [](){ if (m_progress_dialog) m_progress_dialog->Destroy(); });
}
void MainFrame::reslice_now()
{
if (m_plater)
m_plater->reslice();
}
void MainFrame::repair_stl()
{
wxString input_file;
{
auto dlg = new wxFileDialog(this, _(L("Select the STL file to repair:")),
wxGetApp().app_config->get_last_dir(), "",
file_wildcards[FT_STL], wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dlg->ShowModal() != wxID_OK) {
dlg->Destroy();
return;
}
input_file = dlg->GetPath();
dlg->Destroy();
}
auto output_file = input_file;
{
// output_file = ~s / \.[sS][tT][lL]$ / _fixed.obj / ;
auto dlg = new wxFileDialog( this, L("Save OBJ file (less prone to coordinate errors than STL) as:"),
get_dir_name(output_file), get_base_name(output_file),
file_wildcards[FT_OBJ], wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (dlg->ShowModal() != wxID_OK) {
dlg->Destroy();
return /*undef*/;
}
output_file = dlg->GetPath();
dlg->Destroy();
}
auto tmesh = new Slic3r::TriangleMesh();
tmesh->ReadSTLFile(input_file.char_str());
tmesh->repair();
tmesh->WriteOBJFile(output_file.char_str());
Slic3r::GUI::show_info(this, L("Your file was repaired."), L("Repair"));
}
void MainFrame::export_config()
{
// Generate a cummulative configuration for the selected print, filaments and printer.
auto config = wxGetApp().preset_bundle->full_config();
// Validate the cummulative configuration.
auto valid = config.validate();
if (!valid.empty()) {
// Slic3r::GUI::catch_error(this);
return;
}
// Ask user for the file name for the config file.
auto dlg = new wxFileDialog(this, _(L("Save configuration as:")),
!m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(),
!m_last_config.IsEmpty() ? get_base_name(m_last_config) : "config.ini",
file_wildcards[FT_INI], wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
wxString file;
if (dlg->ShowModal() == wxID_OK)
file = dlg->GetPath();
dlg->Destroy();
if (!file.IsEmpty()) {
wxGetApp().app_config->update_config_dir(get_dir_name(file));
m_last_config = file;
config.save(file.ToStdString());
}
}
// Load a config file containing a Print, Filament & Printer preset.
void MainFrame::load_config_file(wxString file/* = wxEmptyString*/)
{
if (file.IsEmpty()) {
if (!wxGetApp().check_unsaved_changes())
return;
auto dlg = new wxFileDialog(this, _(L("Select configuration to load:")),
!m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(),
"config.ini", "INI files (*.ini, *.gcode)|*.ini;*.INI;*.gcode;*.g", wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dlg->ShowModal() != wxID_OK)
return;
file = dlg->GetPath();
dlg->Destroy();
}
try {
wxGetApp().preset_bundle->load_config_file(file.ToStdString());
} catch (std::exception & /* ex */) {
// Dont proceed further if the config file cannot be loaded.
// if (Slic3r::GUI::catch_error(this))
// return;
}
for (auto tab : m_options_tabs )
tab.second->load_current_preset();
wxGetApp().app_config->update_config_dir(get_dir_name(file));
m_last_config = file;
}
void MainFrame::export_configbundle()
{
if (!wxGetApp().check_unsaved_changes())
return;
// validate current configuration in case it's dirty
auto valid = wxGetApp().preset_bundle->full_config().validate();
if (!valid.empty()) {
// Slic3r::GUI::catch_error(this);
return;
}
// Ask user for a file name.
auto dlg = new wxFileDialog(this, _(L("Save presets bundle as:")),
!m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(),
"Slic3r_config_bundle.ini",
file_wildcards[FT_INI], wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
wxString file;
if (dlg->ShowModal() == wxID_OK)
file = dlg->GetPath();
dlg->Destroy();
if (!file.IsEmpty()) {
// Export the config bundle.
wxGetApp().app_config->update_config_dir(get_dir_name(file));
try {
wxGetApp().preset_bundle->export_configbundle(file.ToStdString());
} catch (std::exception & /* ex */) {
// Slic3r::GUI::catch_error(this);
}
}
}
// Loading a config bundle with an external file name used to be used
// to auto - install a config bundle on a fresh user account,
// but that behavior was not documented and likely buggy.
void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool reset_user_profile*/)
{
if (!wxGetApp().check_unsaved_changes())
return;
if (file.IsEmpty()) {
auto dlg = new wxFileDialog(this, _(L("Select configuration to load:")),
!m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(),
"config.ini", file_wildcards[FT_INI], wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dlg->ShowModal() != wxID_OK)
return;
file = dlg->GetPath();
dlg->Destroy();
}
wxGetApp().app_config->update_config_dir(get_dir_name(file));
auto presets_imported = 0;
try {
presets_imported = wxGetApp().preset_bundle->load_configbundle(file.ToStdString());
} catch (std::exception & /* ex */) {
// Slic3r::GUI::catch_error(this) and return;
}
// Load the currently selected preset into the GUI, update the preset selection box.
for (auto tab : m_options_tabs)
tab.second->load_current_preset();
const auto message = wxString::Format(_(L("%d presets successfully imported.")), presets_imported);
Slic3r::GUI::show_info(this, message, "Info");
}
// Load a provied DynamicConfig into the Print / Filament / Printer tabs, thus modifying the active preset.
// Also update the platter with the new presets.
void MainFrame::load_config(const DynamicPrintConfig& config)
{
for (auto tab : m_options_tabs)
tab.second->load_config(config);
if (m_plater)
m_plater->on_config_change(config);
}
void MainFrame::select_tab(size_t tab) const
{
m_tabpanel->SetSelection(tab);
}
// Set a camera direction, zoom to all objects.
void MainFrame::select_view(const std::string& direction)
{
if (m_plater)
m_plater->select_view(direction);
}
void MainFrame::on_presets_changed(SimpleEvent &event)
{
auto *tab = dynamic_cast<Tab*>(event.GetEventObject());
wxASSERT(tab != nullptr);
if (tab == nullptr) {
return;
}
// Update preset combo boxes(Print settings, Filament, Material, Printer) from their respective tabs.
auto presets = tab->get_presets();
if (m_plater != nullptr && presets != nullptr) {
auto reload_dependent_tabs = tab->get_dependent_tabs();
// FIXME: The preset type really should be a property of Tab instead
Slic3r::Preset::Type preset_type = tab->type();
if (preset_type == Slic3r::Preset::TYPE_INVALID){
wxASSERT(false);
return;
}
m_plater->sidebar().update_presets(preset_type);
if (preset_type == Slic3r::Preset::TYPE_PRINTER) {
// Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors.
// XXX: Do this in a more C++ way
for (const auto tab_name_other : { "print", "filament", "sla_material" }) {
Tab* cur_tab = m_options_tabs[tab_name_other];
// If the printer tells us that the print or filament preset has been switched or invalidated,
// refresh the print or filament tab page.Otherwise just refresh the combo box.
if (reload_dependent_tabs.empty() ||
find(reload_dependent_tabs.begin(), reload_dependent_tabs.end(), tab_name_other) ==
reload_dependent_tabs.end() )
cur_tab->update_tab_ui();
else
cur_tab->load_current_preset();
}
}
m_plater->on_config_change(*tab->get_config());
}
}
void MainFrame::on_value_changed(wxCommandEvent& event)
{
auto *tab = dynamic_cast<Tab*>(event.GetEventObject());
wxASSERT(tab != nullptr);
if (tab == nullptr)
return;
auto opt_key = event.GetString();
if (m_plater) {
m_plater->on_config_change(*tab->get_config()); // propagate config change events to the plater
if (opt_key == "extruders_count"){
auto value = event.GetInt();
m_plater->on_extruders_change(value);
}
}
// Don't save while loading for the first time.
if (m_loaded) {
AppConfig &cfg = *wxGetApp().app_config;
if (cfg.get("autosave") == "1")
cfg.save();
}
}
// Called after the Preferences dialog is closed and the program settings are saved.
// Update the UI based on the current preferences.
void MainFrame::update_ui_from_settings()
{
m_menu_item_reslice_now->Enable(wxGetApp().app_config->get("background_processing") == "1");
// if (m_plater) m_plater->update_ui_from_settings();
std::vector<std::string> tab_names = { "print", "filament", "printer" };
for (auto tab_name: tab_names)
m_options_tabs[tab_name]->update_ui_from_settings();
}
std::string MainFrame::get_base_name(const wxString full_name) const
{
return boost::filesystem::path(full_name).filename().string();
}
std::string MainFrame::get_dir_name(const wxString full_name) const
{
return boost::filesystem::path(full_name).parent_path().string();
}
} // GUI
} // Slic3r

View file

@ -0,0 +1,113 @@
#ifndef slic3r_MainFrame_hpp_
#define slic3r_MainFrame_hpp_
#include "PrintConfig.hpp"
#include <wx/frame.h>
#include <string>
#include <map>
#include "Plater.hpp"
#include "Event.hpp"
class wxMenuBar;
class wxNotebook;
class wxPanel;
class wxMenu;
class wxProgressDialog;
namespace Slic3r {
class ProgressStatusBar;
class AppController;
// #define _(s) Slic3r::GUI::I18N::translate((s))
namespace GUI
{
class Tab;
enum QuickSlice
{
qsUndef = 0,
qsReslice = 1,
qsSaveAs = 2,
qsExportSVG = 4,
qsExportPNG = 8
};
struct PresetTab {
std::string name;
Tab* panel;
PrinterTechnology technology;
};
class MainFrame : public wxFrame
{
bool m_no_plater;
bool m_loaded;
int m_lang_ch_event;
int m_preferences_event;
wxString m_qs_last_input_file = wxEmptyString;
wxString m_qs_last_output_file = wxEmptyString;
wxString m_last_config = wxEmptyString;
AppController* m_appController { nullptr };
std::map<std::string, Tab*> m_options_tabs;
wxMenuItem* m_menu_item_reslice_now { nullptr };
wxMenu* m_plater_menu { nullptr };
wxMenu* m_viewMenu{ nullptr };
std::string get_base_name(const wxString full_name) const ;
std::string get_dir_name(const wxString full_name) const ;
void on_presets_changed(SimpleEvent&);
void on_value_changed(wxCommandEvent&);
Tab* get_tab(const std::string& name);
public:
MainFrame() {}
MainFrame(const bool no_plater, const bool loaded);
~MainFrame() {}
void init_tabpanel();
const std::map<std::string, Tab*>& options_tabs() const { return m_options_tabs; }
Tab* get_preset_tab(const std::string& name);
void create_preset_tabs();
void add_created_tab(Tab* panel);
void init_menubar();
void update_ui_from_settings();
bool is_loaded() const { return m_loaded; }
bool is_last_input_file() const { return !m_qs_last_input_file.IsEmpty(); }
void slice_to_png();
void quick_slice(const int qs = qsUndef);
void reslice_now();
void repair_stl();
void export_config();
void load_config_file(wxString file = wxEmptyString);
void export_configbundle();
void load_configbundle(wxString file = wxEmptyString);
void load_config(const DynamicPrintConfig& config);
void select_tab(size_t tab) const;
void select_view(const std::string& direction);
AppController* app_controller() { return m_appController; }
std::vector<PresetTab>& get_preset_tabs();
Plater* m_plater { nullptr };
wxNotebook* m_tabpanel { nullptr };
wxProgressDialog* m_progress_dialog { nullptr };
ProgressStatusBar* m_statusbar { nullptr };
};
} // GUI
} //Slic3r
#endif // slic3r_MainFrame_hpp_

View file

@ -0,0 +1,88 @@
#include "MsgDialog.hpp"
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/button.h>
#include <wx/statbmp.h>
#include <wx/scrolwin.h>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "GUI.hpp"
#include "ConfigWizard.hpp"
namespace Slic3r {
namespace GUI {
MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id) :
MsgDialog(parent, title, headline, wxBitmap(from_u8(Slic3r::var("Slic3r_192px.png")), wxBITMAP_TYPE_PNG), button_id)
{}
MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id) :
wxDialog(parent, wxID_ANY, title),
boldfont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)),
content_sizer(new wxBoxSizer(wxVERTICAL)),
btn_sizer(new wxBoxSizer(wxHORIZONTAL))
{
boldfont.SetWeight(wxFONTWEIGHT_BOLD);
auto *topsizer = new wxBoxSizer(wxHORIZONTAL);
auto *rightsizer = new wxBoxSizer(wxVERTICAL);
auto *headtext = new wxStaticText(this, wxID_ANY, headline);
headtext->SetFont(boldfont);
headtext->Wrap(CONTENT_WIDTH);
rightsizer->Add(headtext);
rightsizer->AddSpacer(VERT_SPACING);
rightsizer->Add(content_sizer, 1, wxEXPAND);
if (button_id != wxID_NONE) {
auto *button = new wxButton(this, button_id);
button->SetFocus();
btn_sizer->Add(button);
}
rightsizer->Add(btn_sizer, 0, wxALIGN_CENTRE_HORIZONTAL);
auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(bitmap));
topsizer->Add(logo, 0, wxALL, BORDER);
topsizer->Add(rightsizer, 1, wxALL | wxEXPAND, BORDER);
SetSizerAndFit(topsizer);
}
MsgDialog::~MsgDialog() {}
// ErrorDialog
ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg) :
MsgDialog(parent, _(L("Slic3r error")), _(L("Slic3r has encountered an error")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")), wxBITMAP_TYPE_PNG))
{
auto *panel = new wxScrolledWindow(this);
auto *p_sizer = new wxBoxSizer(wxVERTICAL);
panel->SetSizer(p_sizer);
auto *text = new wxStaticText(panel, wxID_ANY, msg);
text->Wrap(CONTENT_WIDTH);
p_sizer->Add(text, 1, wxEXPAND);
panel->SetMinSize(wxSize(CONTENT_WIDTH, 0));
panel->SetScrollRate(0, 5);
content_sizer->Add(panel, 1, wxEXPAND);
SetMaxSize(wxSize(-1, CONTENT_MAX_HEIGHT));
Fit();
}
ErrorDialog::~ErrorDialog() {}
}
}

View file

@ -0,0 +1,67 @@
#ifndef slic3r_MsgDialog_hpp_
#define slic3r_MsgDialog_hpp_
#include <string>
#include <unordered_map>
#include <wx/dialog.h>
#include <wx/font.h>
#include <wx/bitmap.h>
#include "slic3r/Utils/Semver.hpp"
class wxBoxSizer;
class wxCheckBox;
namespace Slic3r {
namespace GUI {
// A message / query dialog with a bitmap on the left and any content on the right
// with buttons underneath.
struct MsgDialog : wxDialog
{
MsgDialog(MsgDialog &&) = delete;
MsgDialog(const MsgDialog &) = delete;
MsgDialog &operator=(MsgDialog &&) = delete;
MsgDialog &operator=(const MsgDialog &) = delete;
virtual ~MsgDialog();
// TODO: refactor with CreateStdDialogButtonSizer usage
protected:
enum {
CONTENT_WIDTH = 500,
CONTENT_MAX_HEIGHT = 600,
BORDER = 30,
VERT_SPACING = 15,
HORIZ_SPACING = 5,
};
// button_id is an id of a button that can be added by default, use wxID_NONE to disable
MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxWindowID button_id = wxID_OK);
MsgDialog(wxWindow *parent, const wxString &title, const wxString &headline, wxBitmap bitmap, wxWindowID button_id = wxID_OK);
wxFont boldfont;
wxBoxSizer *content_sizer;
wxBoxSizer *btn_sizer;
};
// Generic error dialog, used for displaying exceptions
struct ErrorDialog : MsgDialog
{
ErrorDialog(wxWindow *parent, const wxString &msg);
ErrorDialog(ErrorDialog &&) = delete;
ErrorDialog(const ErrorDialog &) = delete;
ErrorDialog &operator=(ErrorDialog &&) = delete;
ErrorDialog &operator=(const ErrorDialog &) = delete;
virtual ~ErrorDialog();
};
}
}
#endif

View file

@ -0,0 +1,562 @@
#include "OptionsGroup.hpp"
#include "ConfigExceptions.hpp"
#include <utility>
#include <wx/numformatter.h>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include "Utils.hpp"
namespace Slic3r { namespace GUI {
const t_field& OptionsGroup::build_field(const Option& opt, wxStaticText* label/* = nullptr*/) {
return build_field(opt.opt_id, opt.opt, label);
}
const t_field& OptionsGroup::build_field(const t_config_option_key& id, wxStaticText* label/* = nullptr*/) {
const ConfigOptionDef& opt = m_options.at(id).opt;
return build_field(id, opt, label);
}
const t_field& OptionsGroup::build_field(const t_config_option_key& id, const ConfigOptionDef& opt, wxStaticText* label/* = nullptr*/) {
// Check the gui_type field first, fall through
// is the normal type.
if (opt.gui_type.compare("select") == 0) {
} else if (opt.gui_type.compare("select_open") == 0) {
m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(parent(), opt, id)));
} else if (opt.gui_type.compare("color") == 0) {
m_fields.emplace(id, STDMOVE(ColourPicker::Create<ColourPicker>(parent(), opt, id)));
} else if (opt.gui_type.compare("f_enum_open") == 0 ||
opt.gui_type.compare("i_enum_open") == 0 ||
opt.gui_type.compare("i_enum_closed") == 0) {
m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(parent(), opt, id)));
} else if (opt.gui_type.compare("slider") == 0) {
m_fields.emplace(id, STDMOVE(SliderCtrl::Create<SliderCtrl>(parent(), opt, id)));
} else if (opt.gui_type.compare("i_spin") == 0) { // Spinctrl
} else if (opt.gui_type.compare("legend") == 0) { // StaticText
m_fields.emplace(id, STDMOVE(StaticText::Create<StaticText>(parent(), opt, id)));
} else {
switch (opt.type) {
case coFloatOrPercent:
case coFloat:
case coFloats:
case coPercent:
case coPercents:
case coString:
case coStrings:
m_fields.emplace(id, STDMOVE(TextCtrl::Create<TextCtrl>(parent(), opt, id)));
break;
case coBool:
case coBools:
m_fields.emplace(id, STDMOVE(CheckBox::Create<CheckBox>(parent(), opt, id)));
break;
case coInt:
case coInts:
m_fields.emplace(id, STDMOVE(SpinCtrl::Create<SpinCtrl>(parent(), opt, id)));
break;
case coEnum:
m_fields.emplace(id, STDMOVE(Choice::Create<Choice>(parent(), opt, id)));
break;
case coPoints:
m_fields.emplace(id, STDMOVE(PointCtrl::Create<PointCtrl>(parent(), opt, id)));
break;
case coNone: break;
default:
throw /*//!ConfigGUITypeError("")*/std::logic_error("This control doesn't exist till now"); break;
}
}
// Grab a reference to fields for convenience
const t_field& field = m_fields[id];
field->m_on_change = [this](std::string opt_id, boost::any value){
//! This function will be called from Field.
//! Call OptionGroup._on_change(...)
if (!m_disabled)
this->on_change_OG(opt_id, value);
};
field->m_on_kill_focus = [this](){
//! This function will be called from Field.
if (!m_disabled)
this->on_kill_focus();
};
field->m_parent = parent();
//! Label to change background color, when option is modified
field->m_Label = label;
field->m_back_to_initial_value = [this](std::string opt_id){
if (!m_disabled)
this->back_to_initial_value(opt_id);
};
field->m_back_to_sys_value = [this](std::string opt_id){
if (!this->m_disabled)
this->back_to_sys_value(opt_id);
};
// assign function objects for callbacks, etc.
return field;
}
void OptionsGroup::add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field)
{
if (!m_show_modified_btns) {
field->m_Undo_btn->set_as_hidden();
field->m_Undo_to_sys_btn->set_as_hidden();
return;
}
sizer->Add(field->m_Undo_to_sys_btn, 0, wxALIGN_CENTER_VERTICAL);
sizer->Add(field->m_Undo_btn, 0, wxALIGN_CENTER_VERTICAL);
}
void OptionsGroup::append_line(const Line& line, wxStaticText** colored_Label/* = nullptr*/) {
//! if (line.sizer != nullptr || (line.widget != nullptr && line.full_width > 0)){
if ( (line.sizer != nullptr || line.widget != nullptr) && line.full_width){
if (line.sizer != nullptr) {
sizer->Add(line.sizer, 0, wxEXPAND | wxALL, wxOSX ? 0 : 15);
return;
}
if (line.widget != nullptr) {
sizer->Add(line.widget(m_parent), 0, wxEXPAND | wxALL, wxOSX ? 0 : 15);
return;
}
}
auto option_set = line.get_options();
for (auto opt : option_set)
m_options.emplace(opt.opt_id, opt);
// add mode value for current line to m_options_mode
if (!option_set.empty())
m_options_mode.push_back(option_set[0].opt.mode);
// if we have a single option with no label, no sidetext just add it directly to sizer
if (option_set.size() == 1 && label_width == 0 && option_set.front().opt.full_width &&
option_set.front().opt.sidetext.size() == 0 && option_set.front().side_widget == nullptr &&
line.get_extra_widgets().size() == 0) {
wxSizer* tmp_sizer;
#ifdef __WXGTK__
tmp_sizer = new wxBoxSizer(wxVERTICAL);
m_panel->SetSizer(tmp_sizer);
m_panel->Layout();
#else
tmp_sizer = sizer;
#endif /* __WXGTK__ */
const auto& option = option_set.front();
const auto& field = build_field(option);
auto btn_sizer = new wxBoxSizer(wxHORIZONTAL);
add_undo_buttuns_to_sizer(btn_sizer, field);
tmp_sizer->Add(btn_sizer, 0, wxEXPAND | wxALL, 0);
if (is_window_field(field))
tmp_sizer->Add(field->getWindow(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5);
if (is_sizer_field(field))
tmp_sizer->Add(field->getSizer(), 0, wxEXPAND | wxALL, wxOSX ? 0 : 5);
return;
}
auto grid_sizer = m_grid_sizer;
#ifdef __WXGTK__
m_panel->SetSizer(m_grid_sizer);
m_panel->Layout();
#endif /* __WXGTK__ */
// if we have an extra column, build it
if (extra_column)
grid_sizer->Add(extra_column(parent(), line), 0, wxALIGN_CENTER_VERTICAL|wxRIGHT, 3);
// Build a label if we have it
wxStaticText* label=nullptr;
if (label_width != 0) {
long label_style = staticbox ? 0 : wxALIGN_RIGHT;
#ifdef __WXGTK__
// workaround for correct text align of the StaticBox on Linux
// flags wxALIGN_RIGHT and wxALIGN_CENTRE don't work when Ellipsize flags are _not_ given.
// Text is properly aligned only when Ellipsize is checked.
label_style |= staticbox ? 0 : wxST_ELLIPSIZE_END;
#endif /* __WXGTK__ */
label = new wxStaticText(parent(), wxID_ANY, line.label + (line.label.IsEmpty() ? "" : ":"),
wxDefaultPosition, wxSize(label_width, -1), label_style);
label->SetFont(label_font);
label->Wrap(label_width); // avoid a Linux/GTK bug
if (!line.near_label_widget)
grid_sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, 5);
else {
// If we're here, we have some widget near the label
// so we need a horizontal sizer to arrange these things
auto sizer = new wxBoxSizer(wxHORIZONTAL);
grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM | wxTOP | wxLEFT), staticbox ? 0 : 1);
sizer->Add(line.near_label_widget(parent()), 0, wxRIGHT, 7);
sizer->Add(label, 0, (staticbox ? 0 : wxALIGN_RIGHT | wxRIGHT) | wxALIGN_CENTER_VERTICAL, 5);
}
if (line.label_tooltip.compare("") != 0)
label->SetToolTip(line.label_tooltip);
}
// If there's a widget, build it and add the result to the sizer.
if (line.widget != nullptr) {
auto wgt = line.widget(parent());
// If widget doesn't have label, don't use border
grid_sizer->Add(wgt, 0, wxEXPAND | wxBOTTOM | wxTOP, (wxOSX || line.label.IsEmpty()) ? 0 : 5);
if (colored_Label != nullptr) *colored_Label = label;
return;
}
// If we're here, we have more than one option or a single option with sidetext
// so we need a horizontal sizer to arrange these things
auto sizer = new wxBoxSizer(wxHORIZONTAL);
grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM | wxTOP | wxLEFT), staticbox ? 0 : 1);
// If we have a single option with no sidetext just add it directly to the grid sizer
if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 &&
option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) {
const auto& option = option_set.front();
const auto& field = build_field(option, label);
add_undo_buttuns_to_sizer(sizer, field);
if (is_window_field(field))
sizer->Add(field->getWindow(), option.opt.full_width ? 1 : 0, //(option.opt.full_width ? wxEXPAND : 0) |
wxBOTTOM | wxTOP | (option.opt.full_width ? wxEXPAND : wxALIGN_CENTER_VERTICAL), (wxOSX || !staticbox) ? 0 : 2);
if (is_sizer_field(field))
sizer->Add(field->getSizer(), 1, /*(*/option.opt.full_width ? wxEXPAND : /*0) |*/ wxALIGN_CENTER_VERTICAL, 0);
return;
}
for (auto opt : option_set) {
ConfigOptionDef option = opt.opt;
wxSizer* sizer_tmp = sizer;
// add label if any
if (option.label != "") {
wxString str_label = _(option.label);
//! To correct translation by context have to use wxGETTEXT_IN_CONTEXT macro from wxWidget 3.1.1
// wxString str_label = (option.label == "Top" || option.label == "Bottom") ?
// wxGETTEXT_IN_CONTEXT("Layers", wxString(option.label.c_str()):
// L_str(option.label);
label = new wxStaticText(parent(), wxID_ANY, str_label + ":", wxDefaultPosition, wxDefaultSize);
label->SetFont(label_font);
sizer_tmp->Add(label, 0, /*wxALIGN_RIGHT |*/ wxALIGN_CENTER_VERTICAL, 0);
}
// add field
const Option& opt_ref = opt;
auto& field = build_field(opt_ref, label);
add_undo_buttuns_to_sizer(sizer_tmp, field);
is_sizer_field(field) ?
sizer_tmp->Add(field->getSizer(), 0, wxALIGN_CENTER_VERTICAL, 0) :
sizer_tmp->Add(field->getWindow(), 0, wxALIGN_CENTER_VERTICAL, 0);
// add sidetext if any
if (option.sidetext != "") {
auto sidetext = new wxStaticText( parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition,
wxSize(sidetext_width, -1)/*wxDefaultSize*/, wxALIGN_LEFT);
sidetext->SetFont(sidetext_font);
sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4);
field->set_side_text_ptr(sidetext);
}
// add side widget if any
if (opt.side_widget != nullptr) {
sizer_tmp->Add(opt.side_widget(parent())/*!.target<wxWindow>()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 1); //! requires verification
}
if (opt.opt_id != option_set.back().opt_id) //! istead of (opt != option_set.back())
{
sizer_tmp->AddSpacer(6);
}
}
// add extra sizers if any
for (auto extra_widget : line.get_extra_widgets()) {
sizer->Add(extra_widget(parent())/*!.target<wxWindow>()*/, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4); //! requires verification
}
}
Line OptionsGroup::create_single_option_line(const Option& option) const {
Line retval{ _(option.opt.label), _(option.opt.tooltip) };
Option tmp(option);
tmp.opt.label = std::string("");
retval.append_option(tmp);
return retval;
}
void OptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value) {
if (m_on_change != nullptr)
m_on_change(opt_id, value);
}
Option ConfigOptionsGroup::get_option(const std::string& opt_key, int opt_index /*= -1*/)
{
if (!m_config->has(opt_key)) {
std::cerr << "No " << opt_key << " in ConfigOptionsGroup config.\n";
}
std::string opt_id = opt_index == -1 ? opt_key : opt_key + "#" + std::to_string(opt_index);
std::pair<std::string, int> pair(opt_key, opt_index);
m_opt_map.emplace(opt_id, pair);
return Option(*m_config->def()->get(opt_key), opt_id);
}
void ConfigOptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value)
{
if (!m_opt_map.empty())
{
auto it = m_opt_map.find(opt_id);
if (it == m_opt_map.end())
{
OptionsGroup::on_change_OG(opt_id, value);
return;
}
auto itOption = it->second;
std::string opt_key = itOption.first;
int opt_index = itOption.second;
auto option = m_options.at(opt_id).opt;
// get value
//! auto field_value = get_value(opt_id);
if (option.gui_flags.compare("serialized")==0) {
if (opt_index != -1){
// die "Can't set serialized option indexed value" ;
}
change_opt_value(*m_config, opt_key, value);
}
else {
if (opt_index == -1) {
// change_opt_value(*m_config, opt_key, field_value);
//!? why field_value?? in this case changed value will be lose! No?
change_opt_value(*m_config, opt_key, value);
}
else {
change_opt_value(*m_config, opt_key, value, opt_index);
// auto value = m_config->get($opt_key);
// $value->[$opt_index] = $field_value;
// $self->config->set($opt_key, $value);
}
}
}
OptionsGroup::on_change_OG(opt_id, value); //!? Why doing this
}
void ConfigOptionsGroup::back_to_initial_value(const std::string& opt_key)
{
if (m_get_initial_config == nullptr)
return;
back_to_config_value(m_get_initial_config(), opt_key);
}
void ConfigOptionsGroup::back_to_sys_value(const std::string& opt_key)
{
if (m_get_sys_config == nullptr)
return;
if (!have_sys_config())
return;
back_to_config_value(m_get_sys_config(), opt_key);
}
void ConfigOptionsGroup::back_to_config_value(const DynamicPrintConfig& config, const std::string& opt_key)
{
boost::any value;
if (opt_key == "extruders_count"){
auto *nozzle_diameter = dynamic_cast<const ConfigOptionFloats*>(config.option("nozzle_diameter"));
value = int(nozzle_diameter->values.size());
}
else if (m_opt_map.find(opt_key) != m_opt_map.end())
{
auto opt_id = m_opt_map.find(opt_key)->first;
std::string opt_short_key = m_opt_map.at(opt_id).first;
int opt_index = m_opt_map.at(opt_id).second;
value = get_config_value(config, opt_short_key, opt_index);
}
else{
value = get_config_value(config, opt_key);
change_opt_value(*m_config, opt_key, value);
return;
}
set_value(opt_key, value);
on_change_OG(opt_key, get_value(opt_key));
}
void ConfigOptionsGroup::reload_config(){
for (t_opt_map::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) {
auto opt_id = it->first;
std::string opt_key = m_opt_map.at(opt_id).first;
int opt_index = m_opt_map.at(opt_id).second;
auto option = m_options.at(opt_id).opt;
set_value(opt_id, config_value(opt_key, opt_index, option.gui_flags.compare("serialized") == 0 ));
}
}
bool ConfigOptionsGroup::update_visibility(ConfigOptionMode mode) {
if (m_options_mode.empty())
return true;
if (m_grid_sizer->GetEffectiveRowsCount() != m_options_mode.size() &&
m_options_mode.size() == 1)
return m_options_mode[0] <= mode;
sizer->ShowItems(true);
#ifdef __WXGTK__
m_panel->Show(true);
m_grid_sizer->Show(true);
#endif /* __WXGTK__ */
int coef = 0;
int hidden_row_cnt = 0;
const int cols = m_grid_sizer->GetCols();
for (auto opt_mode : m_options_mode) {
const bool show = opt_mode <= mode;
if (!show) {
hidden_row_cnt++;
for (int i = 0; i < cols; ++i)
m_grid_sizer->Show(coef + i, show);
}
coef+= cols;
}
if (hidden_row_cnt == m_options_mode.size()) {
sizer->ShowItems(false);
return false;
}
return true;
}
boost::any ConfigOptionsGroup::config_value(const std::string& opt_key, int opt_index, bool deserialize){
if (deserialize) {
// Want to edit a vector value(currently only multi - strings) in a single edit box.
// Aggregate the strings the old way.
// Currently used for the post_process config value only.
if (opt_index != -1)
throw std::out_of_range("Can't deserialize option indexed value");
// return join(';', m_config->get(opt_key)});
return get_config_value(*m_config, opt_key);
}
else {
// return opt_index == -1 ? m_config->get(opt_key) : m_config->get_at(opt_key, opt_index);
return get_config_value(*m_config, opt_key, opt_index);
}
}
boost::any ConfigOptionsGroup::get_config_value(const DynamicPrintConfig& config, const std::string& opt_key, int opt_index /*= -1*/)
{
size_t idx = opt_index == -1 ? 0 : opt_index;
boost::any ret;
wxString text_value = wxString("");
const ConfigOptionDef* opt = config.def()->get(opt_key);
switch (opt->type){
case coFloatOrPercent:{
const auto &value = *config.option<ConfigOptionFloatOrPercent>(opt_key);
if (value.percent)
{
text_value = wxString::Format(_T("%i"), int(value.value));
text_value += "%";
}
else
text_value = double_to_string(value.value);
ret = text_value;
break;
}
case coPercent:{
double val = config.option<ConfigOptionPercent>(opt_key)->value;
text_value = wxString::Format(_T("%i"), int(val));
ret = text_value;// += "%";
}
break;
case coPercents:
case coFloats:
case coFloat:{
double val = opt->type == coFloats ?
config.opt_float(opt_key, idx) :
opt->type == coFloat ? config.opt_float(opt_key) :
config.option<ConfigOptionPercents>(opt_key)->get_at(idx);
ret = double_to_string(val);
}
break;
case coString:
ret = static_cast<wxString>(config.opt_string(opt_key));
break;
case coStrings:
if (opt_key.compare("compatible_printers") == 0){
ret = config.option<ConfigOptionStrings>(opt_key)->values;
break;
}
if (config.option<ConfigOptionStrings>(opt_key)->values.empty())
ret = text_value;
else if (opt->gui_flags.compare("serialized") == 0){
std::vector<std::string> values = config.option<ConfigOptionStrings>(opt_key)->values;
if (!values.empty() && values[0].compare("") != 0)
for (auto el : values)
text_value += el + ";";
ret = text_value;
}
else
ret = static_cast<wxString>(config.opt_string(opt_key, static_cast<unsigned int>(idx)));
break;
case coBool:
ret = config.opt_bool(opt_key);
break;
case coBools:
ret = config.opt_bool(opt_key, idx);
break;
case coInt:
ret = config.opt_int(opt_key);
break;
case coInts:
ret = config.opt_int(opt_key, idx);
break;
case coEnum:{
if (opt_key.compare("external_fill_pattern") == 0 ||
opt_key.compare("fill_pattern") == 0 ){
ret = static_cast<int>(config.option<ConfigOptionEnum<InfillPattern>>(opt_key)->value);
}
else if (opt_key.compare("gcode_flavor") == 0 ){
ret = static_cast<int>(config.option<ConfigOptionEnum<GCodeFlavor>>(opt_key)->value);
}
else if (opt_key.compare("support_material_pattern") == 0){
ret = static_cast<int>(config.option<ConfigOptionEnum<SupportMaterialPattern>>(opt_key)->value);
}
else if (opt_key.compare("seam_position") == 0){
ret = static_cast<int>(config.option<ConfigOptionEnum<SeamPosition>>(opt_key)->value);
}
else if (opt_key.compare("host_type") == 0){
ret = static_cast<int>(config.option<ConfigOptionEnum<PrintHostType>>(opt_key)->value);
}
}
break;
case coPoints:
if (opt_key.compare("bed_shape") == 0)
ret = config.option<ConfigOptionPoints>(opt_key)->values;
else
ret = config.option<ConfigOptionPoints>(opt_key)->get_at(idx);
break;
case coNone:
default:
break;
}
return ret;
}
Field* ConfigOptionsGroup::get_fieldc(const t_config_option_key& opt_key, int opt_index){
Field* field = get_field(opt_key);
if (field != nullptr)
return field;
std::string opt_id = "";
for (t_opt_map::iterator it = m_opt_map.begin(); it != m_opt_map.end(); ++it) {
if (opt_key == m_opt_map.at(it->first).first && opt_index == m_opt_map.at(it->first).second){
opt_id = it->first;
break;
}
}
return opt_id.empty() ? nullptr : get_field(opt_id);
}
void ogStaticText::SetText(const wxString& value, bool wrap/* = true*/)
{
SetLabel(value);
if (wrap) Wrap(400);
GetParent()->Layout();
}
} // GUI
} // Slic3r

View file

@ -0,0 +1,269 @@
#ifndef slic3r_OptionsGroup_hpp_
#define slic3r_OptionsGroup_hpp_
#include <wx/wx.h>
#include <wx/stattext.h>
#include <wx/settings.h>
//#include <wx/window.h>
#include <map>
#include <functional>
#include "libslic3r/Config.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/libslic3r.h"
#include "Field.hpp"
#include "GUI_App.hpp"
// Translate the ifdef
#ifdef __WXOSX__
#define wxOSX true
#else
#define wxOSX false
#endif
#define BORDER(a, b) ((wxOSX ? a : b))
namespace Slic3r { namespace GUI {
/// Widget type describes a function object that returns a wxWindow (our widget) and accepts a wxWidget (parent window).
using widget_t = std::function<wxSizer*(wxWindow*)>;//!std::function<wxWindow*(wxWindow*)>;
//auto default_label_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_3DLIGHT); //GetSystemColour
//auto modified_label_clr = *new wxColour(254, 189, 101);
/// Wraps a ConfigOptionDef and adds function object for creating a side_widget.
struct Option {
ConfigOptionDef opt { ConfigOptionDef() };
t_config_option_key opt_id;//! {""};
widget_t side_widget {nullptr};
bool readonly {false};
Option(const ConfigOptionDef& _opt, t_config_option_key id) :
opt(_opt), opt_id(id) {}
};
using t_option = std::unique_ptr<Option>; //!
/// Represents option lines
class Line {
public:
wxString label {wxString("")};
wxString label_tooltip {wxString("")};
size_t full_width {0};
wxSizer* sizer {nullptr};
widget_t widget {nullptr};
std::function<wxWindow*(wxWindow*)> near_label_widget{ nullptr };
void append_option(const Option& option) {
m_options.push_back(option);
}
void append_widget(const widget_t widget) {
m_extra_widgets.push_back(widget);
}
Line(wxString label, wxString tooltip) :
label(label), label_tooltip(tooltip) {}
const std::vector<widget_t>& get_extra_widgets() const {return m_extra_widgets;}
const std::vector<Option>& get_options() const { return m_options; }
private:
std::vector<Option> m_options;//! {std::vector<Option>()};
std::vector<widget_t> m_extra_widgets;//! {std::vector<widget_t>()};
};
using column_t = std::function<wxWindow*(wxWindow* parent, const Line&)>;//std::function<wxSizer*(const Line&)>;
using t_optionfield_map = std::map<t_config_option_key, t_field>;
using t_opt_map = std::map< std::string, std::pair<std::string, int> >;
class OptionsGroup {
wxStaticBox* stb;
public:
const bool staticbox {true};
const wxString title {wxString("")};
size_t label_width {200};
wxSizer* sizer {nullptr};
column_t extra_column {nullptr};
t_change m_on_change {nullptr};
std::function<DynamicPrintConfig()> m_get_initial_config{ nullptr };
std::function<DynamicPrintConfig()> m_get_sys_config{ nullptr };
std::function<bool()> have_sys_config{ nullptr };
wxFont sidetext_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) };
wxFont label_font {wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT) };
int sidetext_width{ -1 };
/// Returns a copy of the pointer of the parent wxWindow.
/// Accessor function is because users are not allowed to change the parent
/// but defining it as const means a lot of const_casts to deal with wx functions.
inline wxWindow* parent() const {
#ifdef __WXGTK__
return m_panel;
#else
return m_parent;
#endif /* __WXGTK__ */
}
#ifdef __WXGTK__
wxWindow* get_parent() const {
return m_parent;
}
#endif /* __WXGTK__ */
void append_line(const Line& line, wxStaticText** colored_Label = nullptr);
Line create_single_option_line(const Option& option) const;
void append_single_option_line(const Option& option) { append_line(create_single_option_line(option)); }
// return a non-owning pointer reference
inline Field* get_field(const t_config_option_key& id) const{
if (m_fields.find(id) == m_fields.end()) return nullptr;
return m_fields.at(id).get();
}
bool set_value(const t_config_option_key& id, const boost::any& value, bool change_event = false) {
if (m_fields.find(id) == m_fields.end()) return false;
m_fields.at(id)->set_value(value, change_event);
return true;
}
boost::any get_value(const t_config_option_key& id) {
boost::any out;
if (m_fields.find(id) == m_fields.end()) ;
else
out = m_fields.at(id)->get_value();
return out;
}
bool set_side_text(const t_config_option_key& opt_key, const wxString& side_text) {
if (m_fields.find(opt_key) == m_fields.end()) return false;
auto st = m_fields.at(opt_key)->m_side_text;
if (!st) return false;
st->SetLabel(side_text);
return true;
}
void set_name(const wxString& new_name) {
stb->SetLabel(new_name);
}
inline void enable() { for (auto& field : m_fields) field.second->enable(); }
inline void disable() { for (auto& field : m_fields) field.second->disable(); }
void set_grid_vgap(int gap) { m_grid_sizer->SetVGap(gap); }
void set_show_modified_btns_val(bool show) {
m_show_modified_btns = show;
}
OptionsGroup( wxWindow* _parent, const wxString& title, bool is_tab_opt = false,
column_t extra_clmn = nullptr) :
m_parent(_parent), title(title),
m_show_modified_btns(is_tab_opt),
staticbox(title!=""), extra_column(extra_clmn){
if (staticbox) {
stb = new wxStaticBox(_parent, wxID_ANY, title);
stb->SetFont(wxGetApp().bold_font());
}
sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL));
auto num_columns = 1U;
if (label_width != 0) num_columns++;
if (extra_column != nullptr) num_columns++;
m_grid_sizer = new wxFlexGridSizer(0, num_columns, 1,0);
static_cast<wxFlexGridSizer*>(m_grid_sizer)->SetFlexibleDirection(wxBOTH/*wxHORIZONTAL*/);
static_cast<wxFlexGridSizer*>(m_grid_sizer)->AddGrowableCol(label_width == 0 ? 0 : !extra_column ? 1 : 2 );
#ifdef __WXGTK__
m_panel = new wxPanel( _parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
sizer->Fit(m_panel);
sizer->Add(m_panel, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5);
#else
sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5);
#endif /* __WXGTK__ */
}
wxGridSizer* get_grid_sizer(){ return m_grid_sizer; }
protected:
std::map<t_config_option_key, Option> m_options;
wxWindow* m_parent {nullptr};
std::vector<ConfigOptionMode> m_options_mode;
/// Field list, contains unique_ptrs of the derived type.
/// using types that need to know what it is beyond the public interface
/// need to cast based on the related ConfigOptionDef.
t_optionfield_map m_fields;
bool m_disabled {false};
wxGridSizer* m_grid_sizer {nullptr};
// "true" if option is created in preset tabs
bool m_show_modified_btns{ false };
// This panel is needed for correct showing of the ToolTips for Button, StaticText and CheckBox
// Tooltips on GTK doesn't work inside wxStaticBoxSizer unless you insert a panel
// inside it before you insert the other controls.
#ifdef __WXGTK__
wxPanel* m_panel {nullptr};
#endif /* __WXGTK__ */
/// Generate a wxSizer or wxWindow from a configuration option
/// Precondition: opt resolves to a known ConfigOption
/// Postcondition: fields contains a wx gui object.
const t_field& build_field(const t_config_option_key& id, const ConfigOptionDef& opt, wxStaticText* label = nullptr);
const t_field& build_field(const t_config_option_key& id, wxStaticText* label = nullptr);
const t_field& build_field(const Option& opt, wxStaticText* label = nullptr);
void add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field);
virtual void on_kill_focus (){};
virtual void on_change_OG(const t_config_option_key& opt_id, const boost::any& value);
virtual void back_to_initial_value(const std::string& opt_key){}
virtual void back_to_sys_value(const std::string& opt_key){}
};
class ConfigOptionsGroup: public OptionsGroup {
public:
ConfigOptionsGroup( wxWindow* parent, const wxString& title, DynamicPrintConfig* _config = nullptr,
bool is_tab_opt = false, column_t extra_clmn = nullptr) :
OptionsGroup(parent, title, is_tab_opt, extra_clmn), m_config(_config) {}
/// reference to libslic3r config, non-owning pointer (?).
DynamicPrintConfig* m_config {nullptr};
bool m_full_labels {0};
t_opt_map m_opt_map;
void set_config(DynamicPrintConfig* config){ m_config = config; }
Option get_option(const std::string& opt_key, int opt_index = -1);
Line create_single_option_line(const std::string& title, int idx = -1) /*const*/{
Option option = get_option(title, idx);
return OptionsGroup::create_single_option_line(option);
}
void append_single_option_line(const Option& option) {
OptionsGroup::append_single_option_line(option);
}
void append_single_option_line(const std::string title, int idx = -1)
{
Option option = get_option(title, idx);
append_single_option_line(option);
}
void on_change_OG(const t_config_option_key& opt_id, const boost::any& value) override;
void back_to_initial_value(const std::string& opt_key) override;
void back_to_sys_value(const std::string& opt_key) override;
void back_to_config_value(const DynamicPrintConfig& config, const std::string& opt_key);
void on_kill_focus() override{ reload_config();}
void reload_config();
// return value shows visibility : false => all options are hidden
bool update_visibility(ConfigOptionMode mode);
boost::any config_value(const std::string& opt_key, int opt_index, bool deserialize);
// return option value from config
boost::any get_config_value(const DynamicPrintConfig& config, const std::string& opt_key, int opt_index = -1);
Field* get_fieldc(const t_config_option_key& opt_key, int opt_index);
};
// Static text shown among the options.
class ogStaticText :public wxStaticText{
public:
ogStaticText() {}
ogStaticText(wxWindow* parent, const char *text) : wxStaticText(parent, wxID_ANY, text, wxDefaultPosition, wxDefaultSize){}
~ogStaticText(){}
void SetText(const wxString& value, bool wrap = true);
};
}}
#endif /* slic3r_OptionsGroup_hpp_ */

2577
src/slic3r/GUI/Plater.cpp Normal file

File diff suppressed because it is too large Load diff

140
src/slic3r/GUI/Plater.hpp Normal file
View file

@ -0,0 +1,140 @@
#ifndef slic3r_Plater_hpp_
#define slic3r_Plater_hpp_
#include <memory>
#include <vector>
#include <boost/filesystem/path.hpp>
#include <wx/panel.h>
#include <wx/bmpcbox.h>
#include "Preset.hpp"
class wxButton;
class wxBoxSizer;
class wxGLCanvas;
namespace Slic3r {
class Model;
class Print;
namespace GUI {
class MainFrame;
class ConfigOptionsGroup;
class ObjectManipulation;
class ObjectList;
using t_optgroups = std::vector <std::shared_ptr<ConfigOptionsGroup>>;
class Plater;
class PresetComboBox : public wxBitmapComboBox
{
public:
PresetComboBox(wxWindow *parent, Preset::Type preset_type);
~PresetComboBox();
void set_label_marker(int item);
void set_extruder_idx(const int extr_idx) { extruder_idx = extr_idx; }
int get_extruder_idx() const { return extruder_idx; }
private:
typedef std::size_t Marker;
enum { LABEL_ITEM_MARKER = 0x4d };
Preset::Type preset_type;
int last_selected;
int extruder_idx = -1;
};
enum ButtonAction
{
baUndef,
baReslice,
baExportGcode,
baSendGcode
};
class Sidebar : public wxPanel
{
public:
Sidebar(Plater *parent);
Sidebar(Sidebar &&) = delete;
Sidebar(const Sidebar &) = delete;
Sidebar &operator=(Sidebar &&) = delete;
Sidebar &operator=(const Sidebar &) = delete;
~Sidebar();
void init_filament_combo(PresetComboBox **combo, const int extr_idx);
void remove_unused_filament_combos(const int current_extruder_count);
void update_presets(Slic3r::Preset::Type preset_type);
ObjectManipulation* obj_manipul();
ObjectList* obj_list();
ConfigOptionsGroup* og_freq_chng_params();
wxButton* get_wiping_dialog_button();
void update_objects_list_extruder_column(int extruders_count);
void show_info_sizers(const bool show);
void show_buttons(const bool show);
void show_button(ButtonAction but_action, bool show);
void enable_buttons(bool enable);
bool is_multifilament();
std::vector<PresetComboBox*>& combos_filament();
private:
struct priv;
std::unique_ptr<priv> p;
};
class Plater: public wxPanel
{
public:
Plater(wxWindow *parent, MainFrame *main_frame);
Plater(Plater &&) = delete;
Plater(const Plater &) = delete;
Plater &operator=(Plater &&) = delete;
Plater &operator=(const Plater &) = delete;
~Plater();
Sidebar& sidebar();
Model& model();
Print& print();
void add();
void load_files(const std::vector<boost::filesystem::path> &input_files);
void update(bool force_autocenter = false);
void select_view(const std::string& direction);
void remove(size_t obj_idx);
void remove_selected();
void increase_instances(size_t num = 1);
void decrease_instances(size_t num = 1);
void set_number_of_copies(/*size_t num*/);
// Note: empty path means "use the default"
void export_gcode(boost::filesystem::path output_path = boost::filesystem::path());
void export_stl();
void export_amf();
void export_3mf();
void reslice();
void changed_object_settings(int obj_idx);
void send_gcode();
void on_extruders_change(int extruders_count);
void on_config_change(const DynamicPrintConfig &config);
wxGLCanvas* canvas3D();
private:
struct priv;
std::unique_ptr<priv> p;
};
}}
#endif

View file

@ -0,0 +1,130 @@
#include "Preferences.hpp"
#include "AppConfig.hpp"
#include "OptionsGroup.hpp"
namespace Slic3r {
namespace GUI {
PreferencesDialog::PreferencesDialog(wxWindow* parent) :
wxDialog(parent, wxID_ANY, _(L("Preferences")), wxDefaultPosition, wxDefaultSize) {
build();
}
void PreferencesDialog::build()
{
auto app_config = get_app_config();
m_optgroup = std::make_shared<ConfigOptionsGroup>(this, _(L("General")));
m_optgroup->label_width = 400;
m_optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value){
m_values[opt_key] = boost::any_cast<bool>(value) ? "1" : "0";
};
// TODO
// $optgroup->append_single_option_line(Slic3r::GUI::OptionsGroup::Option->new(
// opt_id = > 'version_check',
// type = > 'bool',
// label = > 'Check for updates',
// tooltip = > 'If this is enabled, Slic3r will check for updates daily and display a reminder if a newer version is available.',
// default = > $app_config->get("version_check") // 1,
// readonly = > !wxTheApp->have_version_check,
// ));
ConfigOptionDef def;
def.label = L("Remember output directory");
def.type = coBool;
def.tooltip = L("If this is enabled, Slic3r will prompt the last output directory "
"instead of the one containing the input files.");
def.default_value = new ConfigOptionBool{ app_config->has("remember_output_path") ? app_config->get("remember_output_path")[0] == '1' : true }; // 1;
Option option(def, "remember_output_path");
m_optgroup->append_single_option_line(option);
def.label = L("Auto-center parts");
def.type = coBool;
def.tooltip = L("If this is enabled, Slic3r will auto-center objects "
"around the print bed center.");
def.default_value = new ConfigOptionBool{ app_config->get("autocenter")[0] == '1' }; // 1;
option = Option (def,"autocenter");
m_optgroup->append_single_option_line(option);
def.label = L("Background processing");
def.type = coBool;
def.tooltip = L("If this is enabled, Slic3r will pre-process objects as soon "
"as they\'re loaded in order to save time when exporting G-code.");
def.default_value = new ConfigOptionBool{ app_config->get("background_processing")[0] == '1' }; // 1;
option = Option (def,"background_processing");
m_optgroup->append_single_option_line(option);
// Please keep in sync with ConfigWizard
def.label = L("Check for application updates");
def.type = coBool;
def.tooltip = L("If enabled, Slic3r checks for new versions of Slic3r PE online. When a new version becomes available a notification is displayed at the next application startup (never during program usage). This is only a notification mechanisms, no automatic installation is done.");
def.default_value = new ConfigOptionBool(app_config->get("version_check") == "1");
option = Option (def, "version_check");
m_optgroup->append_single_option_line(option);
// Please keep in sync with ConfigWizard
def.label = L("Update built-in Presets automatically");
def.type = coBool;
def.tooltip = L("If enabled, Slic3r downloads updates of built-in system presets in the background. These updates are downloaded into a separate temporary location. When a new preset version becomes available it is offered at application startup.");
def.default_value = new ConfigOptionBool(app_config->get("preset_update") == "1");
option = Option (def, "preset_update");
m_optgroup->append_single_option_line(option);
def.label = L("Suppress \" - default - \" presets");
def.type = coBool;
def.tooltip = L("Suppress \" - default - \" presets in the Print / Filament / Printer "
"selections once there are any other valid presets available.");
def.default_value = new ConfigOptionBool{ app_config->get("no_defaults")[0] == '1' }; // 1;
option = Option (def,"no_defaults");
m_optgroup->append_single_option_line(option);
def.label = L("Show incompatible print and filament presets");
def.type = coBool;
def.tooltip = L("When checked, the print and filament presets are shown in the preset editor "
"even if they are marked as incompatible with the active printer");
def.default_value = new ConfigOptionBool{ app_config->get("show_incompatible_presets")[0] == '1' }; // 1;
option = Option (def,"show_incompatible_presets");
m_optgroup->append_single_option_line(option);
def.label = L("Use legacy OpenGL 1.1 rendering");
def.type = coBool;
def.tooltip = L("If you have rendering issues caused by a buggy OpenGL 2.0 driver, "
"you may try to check this checkbox. This will disable the layer height "
"editing and anti aliasing, so it is likely better to upgrade your graphics driver.");
def.default_value = new ConfigOptionBool{ app_config->get("use_legacy_opengl")[0] == '1' }; // 1;
option = Option (def,"use_legacy_opengl");
m_optgroup->append_single_option_line(option);
auto sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(m_optgroup->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10);
auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
wxButton* btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); });
sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
SetSizer(sizer);
sizer->SetSizeHints(this);
}
void PreferencesDialog::accept()
{
if (m_values.find("no_defaults") != m_values.end()||
m_values.find("use_legacy_opengl")!= m_values.end()) {
warning_catcher(this, _(L("You need to restart Slic3r to make the changes effective.")));
}
auto app_config = get_app_config();
for (std::map<std::string, std::string>::iterator it = m_values.begin(); it != m_values.end(); ++it) {
app_config->set(it->first, it->second);
}
EndModal(wxID_OK);
Close(); // needed on Linux
// Nothify the UI to update itself from the ini file.
wxGetApp().update_ui_from_settings();
}
} // GUI
} // Slic3r

View file

@ -0,0 +1,30 @@
#ifndef slic3r_Preferences_hpp_
#define slic3r_Preferences_hpp_
#include "GUI.hpp"
#include <wx/dialog.h>
#include <map>
namespace Slic3r {
namespace GUI {
class ConfigOptionsGroup;
class PreferencesDialog : public wxDialog
{
std::map<std::string, std::string> m_values;
std::shared_ptr<ConfigOptionsGroup> m_optgroup;
public:
PreferencesDialog(wxWindow* parent);
~PreferencesDialog(){ }
void build();
void accept();
};
} // GUI
} // Slic3r
#endif /* slic3r_Preferences_hpp_ */

1021
src/slic3r/GUI/Preset.cpp Normal file

File diff suppressed because it is too large Load diff

445
src/slic3r/GUI/Preset.hpp Normal file
View file

@ -0,0 +1,445 @@
#ifndef slic3r_Preset_hpp_
#define slic3r_Preset_hpp_
#include <deque>
#include <boost/filesystem/path.hpp>
#include <boost/property_tree/ptree_fwd.hpp>
#include "../../libslic3r/libslic3r.h"
#include "../../libslic3r/PrintConfig.hpp"
#include "slic3r/Utils/Semver.hpp"
class wxBitmap;
class wxChoice;
class wxBitmapComboBox;
class wxItemContainer;
namespace Slic3r {
class AppConfig;
class PresetBundle;
namespace GUI {
class BitmapCache;
class PresetComboBox;
}
enum ConfigFileType
{
CONFIG_FILE_TYPE_UNKNOWN,
CONFIG_FILE_TYPE_APP_CONFIG,
CONFIG_FILE_TYPE_CONFIG,
CONFIG_FILE_TYPE_CONFIG_BUNDLE,
};
extern ConfigFileType guess_config_file_type(const boost::property_tree::ptree &tree);
class VendorProfile
{
public:
std::string name;
std::string id;
Semver config_version;
std::string config_update_url;
struct PrinterVariant {
PrinterVariant() {}
PrinterVariant(const std::string &name) : name(name) {}
std::string name;
};
struct PrinterModel {
PrinterModel() {}
std::string id;
std::string name;
PrinterTechnology technology;
std::vector<PrinterVariant> variants;
PrinterVariant* variant(const std::string &name) {
for (auto &v : this->variants)
if (v.name == name)
return &v;
return nullptr;
}
const PrinterVariant* variant(const std::string &name) const { return const_cast<PrinterModel*>(this)->variant(name); }
};
std::vector<PrinterModel> models;
VendorProfile() {}
VendorProfile(std::string id) : id(std::move(id)) {}
static VendorProfile from_ini(const boost::filesystem::path &path, bool load_all=true);
static VendorProfile from_ini(const boost::property_tree::ptree &tree, const boost::filesystem::path &path, bool load_all=true);
size_t num_variants() const { size_t n = 0; for (auto &model : models) n += model.variants.size(); return n; }
bool operator< (const VendorProfile &rhs) const { return this->id < rhs.id; }
bool operator==(const VendorProfile &rhs) const { return this->id == rhs.id; }
};
class Preset
{
public:
enum Type
{
TYPE_INVALID,
TYPE_PRINT,
TYPE_FILAMENT,
TYPE_SLA_MATERIAL,
TYPE_PRINTER,
};
Preset(Type type, const std::string &name, bool is_default = false) : type(type), is_default(is_default), name(name) {}
Type type = TYPE_INVALID;
// The preset represents a "default" set of properties,
// pulled from the default values of the PrintConfig (see PrintConfigDef for their definitions).
bool is_default;
// External preset points to a configuration, which has been loaded but not imported
// into the Slic3r default configuration location.
bool is_external = false;
// System preset is read-only.
bool is_system = false;
// Preset is visible, if it is associated with a printer model / variant that is enabled in the AppConfig
// or if it has no printer model / variant association.
// Also the "default" preset is only visible, if it is the only preset in the list.
bool is_visible = true;
// Has this preset been modified?
bool is_dirty = false;
// Is this preset compatible with the currently active printer?
bool is_compatible = true;
// Name of the preset, usually derived form the file name.
std::string name;
// File name of the preset. This could be a Print / Filament / Printer preset,
// or a Configuration file bundling the Print + Filament + Printer presets (in that case is_external and possibly is_system will be true),
// or it could be a G-code (again, is_external will be true).
std::string file;
// If this is a system profile, then there should be a vendor data available to display at the UI.
const VendorProfile *vendor = nullptr;
// Has this profile been loaded?
bool loaded = false;
// Configuration data, loaded from a file, or set from the defaults.
DynamicPrintConfig config;
// Load this profile for the following keys only.
DynamicPrintConfig& load(const std::vector<std::string> &keys, const StaticPrintConfig &defaults);
void save();
// Return a label of this preset, consisting of a name and a "(modified)" suffix, if this preset is dirty.
std::string label() const;
// Set the is_dirty flag if the provided config is different from the active one.
void set_dirty(const DynamicPrintConfig &config) { this->is_dirty = ! this->config.diff(config).empty(); }
void set_dirty(bool dirty = true) { this->is_dirty = dirty; }
void reset_dirty() { this->is_dirty = false; }
bool is_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config) const;
bool is_compatible_with_printer(const Preset &active_printer) const;
// Returns the name of the preset, from which this preset inherits.
static std::string& inherits(DynamicPrintConfig &cfg) { return cfg.option<ConfigOptionString>("inherits", true)->value; }
std::string& inherits() { return Preset::inherits(this->config); }
const std::string& inherits() const { return Preset::inherits(const_cast<Preset*>(this)->config); }
// Returns the "compatible_printers_condition".
static std::string& compatible_printers_condition(DynamicPrintConfig &cfg) { return cfg.option<ConfigOptionString>("compatible_printers_condition", true)->value; }
std::string& compatible_printers_condition() { return Preset::compatible_printers_condition(this->config); }
const std::string& compatible_printers_condition() const { return Preset::compatible_printers_condition(const_cast<Preset*>(this)->config); }
static PrinterTechnology& printer_technology(DynamicPrintConfig &cfg) { return cfg.option<ConfigOptionEnum<PrinterTechnology>>("printer_technology", true)->value; }
PrinterTechnology& printer_technology() { return Preset::printer_technology(this->config); }
const PrinterTechnology& printer_technology() const { return Preset::printer_technology(const_cast<Preset*>(this)->config); }
// Mark this preset as compatible if it is compatible with active_printer.
bool update_compatible_with_printer(const Preset &active_printer, const DynamicPrintConfig *extra_config);
// Set is_visible according to application config
void set_visible_from_appconfig(const AppConfig &app_config);
// Resize the extruder specific fields, initialize them with the content of the 1st extruder.
void set_num_extruders(unsigned int n) { set_num_extruders(this->config, n); }
// Sort lexicographically by a preset name. The preset name shall be unique across a single PresetCollection.
bool operator<(const Preset &other) const { return this->name < other.name; }
static const std::vector<std::string>& print_options();
static const std::vector<std::string>& filament_options();
// Printer options contain the nozzle options.
static const std::vector<std::string>& printer_options();
// Nozzle options of the printer options.
static const std::vector<std::string>& nozzle_options();
static const std::vector<std::string>& sla_printer_options();
static const std::vector<std::string>& sla_material_options();
static void update_suffix_modified();
static void normalize(DynamicPrintConfig &config);
protected:
friend class PresetCollection;
friend class PresetBundle;
// Resize the extruder specific vectors ()
static void set_num_extruders(DynamicPrintConfig &config, unsigned int n);
static const std::string& suffix_modified();
static std::string remove_suffix_modified(const std::string &name);
};
// Collections of presets of the same type (one of the Print, Filament or Printer type).
class PresetCollection
{
public:
// Initialize the PresetCollection with the "- default -" preset.
PresetCollection(Preset::Type type, const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name = "- default -");
~PresetCollection();
typedef std::deque<Preset>::iterator Iterator;
typedef std::deque<Preset>::const_iterator ConstIterator;
Iterator begin() { return m_presets.begin() + m_num_default_presets; }
ConstIterator begin() const { return m_presets.begin() + m_num_default_presets; }
Iterator end() { return m_presets.end(); }
ConstIterator end() const { return m_presets.end(); }
void reset(bool delete_files);
Preset::Type type() const { return m_type; }
std::string name() const;
const std::deque<Preset>& operator()() const { return m_presets; }
// Add default preset at the start of the collection, increment the m_default_preset counter.
void add_default_preset(const std::vector<std::string> &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &preset_name);
// Load ini files of the particular type from the provided directory path.
void load_presets(const std::string &dir_path, const std::string &subdir);
// Load a preset from an already parsed config file, insert it into the sorted sequence of presets
// and select it, losing previous modifications.
Preset& load_preset(const std::string &path, const std::string &name, const DynamicPrintConfig &config, bool select = true);
Preset& load_preset(const std::string &path, const std::string &name, DynamicPrintConfig &&config, bool select = true);
Preset& load_external_preset(
// Path to the profile source file (a G-code, an AMF or 3MF file, a config file)
const std::string &path,
// Name of the profile, derived from the source file name.
const std::string &name,
// Original name of the profile, extracted from the loaded config. Empty, if the name has not been stored.
const std::string &original_name,
// Config to initialize the preset from.
const DynamicPrintConfig &config,
// Select the preset after loading?
bool select = true);
// Save the preset under a new name. If the name is different from the old one,
// a new preset is stored into the list of presets.
// All presets are marked as not modified and the new preset is activated.
void save_current_preset(const std::string &new_name);
// Delete the current preset, activate the first visible preset.
void delete_current_preset();
// Load default bitmap to be placed at the wxBitmapComboBox of a MainFrame.
bool load_bitmap_default(const std::string &file_name);
// Compatible & incompatible marks, to be placed at the wxBitmapComboBox items.
void set_bitmap_compatible (const wxBitmap *bmp) { m_bitmap_compatible = bmp; }
void set_bitmap_incompatible(const wxBitmap *bmp) { m_bitmap_incompatible = bmp; }
void set_bitmap_lock (const wxBitmap *bmp) { m_bitmap_lock = bmp; }
void set_bitmap_lock_open (const wxBitmap *bmp) { m_bitmap_lock_open = bmp; }
// Enable / disable the "- default -" preset.
void set_default_suppressed(bool default_suppressed);
bool is_default_suppressed() const { return m_default_suppressed; }
// Select a preset. If an invalid index is provided, the first visible preset is selected.
Preset& select_preset(size_t idx);
// Return the selected preset, without the user modifications applied.
Preset& get_selected_preset() { return m_presets[m_idx_selected]; }
const Preset& get_selected_preset() const { return m_presets[m_idx_selected]; }
int get_selected_idx() const { return m_idx_selected; }
// Returns the name of the selected preset, or an empty string if no preset is selected.
std::string get_selected_preset_name() const { return (m_idx_selected == -1) ? std::string() : this->get_selected_preset().name; }
// For the current edited preset, return the parent preset if there is one.
// If there is no parent preset, nullptr is returned.
// The parent preset may be a system preset or a user preset, which will be
// reflected by the UI.
const Preset* get_selected_preset_parent() const;
// get parent preset for some child preset
const Preset* get_preset_parent(const Preset& child) const;
// Return the selected preset including the user modifications.
Preset& get_edited_preset() { return m_edited_preset; }
const Preset& get_edited_preset() const { return m_edited_preset; }
// used to update preset_choice from Tab
const std::deque<Preset>& get_presets() { return m_presets; }
int get_idx_selected() { return m_idx_selected; }
static const std::string& get_suffix_modified();
// Return a preset possibly with modifications.
Preset& default_preset() { return m_presets.front(); }
const Preset& default_preset() const { return m_presets.front(); }
// Return a preset by an index. If the preset is active, a temporary copy is returned.
Preset& preset(size_t idx) { return (int(idx) == m_idx_selected) ? m_edited_preset : m_presets[idx]; }
const Preset& preset(size_t idx) const { return const_cast<PresetCollection*>(this)->preset(idx); }
void discard_current_changes() { m_presets[m_idx_selected].reset_dirty(); m_edited_preset = m_presets[m_idx_selected]; }
// Return a preset by its name. If the preset is active, a temporary copy is returned.
// If a preset is not found by its name, null is returned.
Preset* find_preset(const std::string &name, bool first_visible_if_not_found = false);
const Preset* find_preset(const std::string &name, bool first_visible_if_not_found = false) const
{ return const_cast<PresetCollection*>(this)->find_preset(name, first_visible_if_not_found); }
size_t first_visible_idx() const;
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
// If one of the prefered_alternates is compatible, select it.
template<typename PreferedCondition>
size_t first_compatible_idx(PreferedCondition prefered_condition) const
{
size_t i = m_default_suppressed ? m_num_default_presets : 0;
size_t n = this->m_presets.size();
size_t i_compatible = n;
for (; i < n; ++ i)
if (m_presets[i].is_compatible) {
if (prefered_condition(m_presets[i].name))
return i;
if (i_compatible == n)
// Store the first compatible profile into i_compatible.
i_compatible = i;
}
return (i_compatible == n) ? 0 : i_compatible;
}
// Return index of the first compatible preset. Certainly at least the '- default -' preset shall be compatible.
size_t first_compatible_idx() const { return this->first_compatible_idx([](const std::string&){return true;}); }
// Return index of the first visible preset. Certainly at least the '- default -' preset shall be visible.
// Return the first visible preset. Certainly at least the '- default -' preset shall be visible.
Preset& first_visible() { return this->preset(this->first_visible_idx()); }
const Preset& first_visible() const { return this->preset(this->first_visible_idx()); }
Preset& first_compatible() { return this->preset(this->first_compatible_idx()); }
template<typename PreferedCondition>
Preset& first_compatible(PreferedCondition prefered_condition) { return this->preset(this->first_compatible_idx(prefered_condition)); }
const Preset& first_compatible() const { return this->preset(this->first_compatible_idx()); }
// Return number of presets including the "- default -" preset.
size_t size() const { return m_presets.size(); }
bool has_defaults_only() const { return m_presets.size() <= m_num_default_presets; }
// For Print / Filament presets, disable those, which are not compatible with the printer.
template<typename PreferedCondition>
void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible, PreferedCondition prefered_condition)
{
if (this->update_compatible_with_printer_internal(active_printer, select_other_if_incompatible) == (size_t)-1)
// Find some other compatible preset, or the "-- default --" preset.
this->select_preset(this->first_compatible_idx(prefered_condition));
}
void update_compatible_with_printer(const Preset &active_printer, bool select_other_if_incompatible)
{ this->update_compatible_with_printer(active_printer, select_other_if_incompatible, [](const std::string&){return true;}); }
size_t num_visible() const { return std::count_if(m_presets.begin(), m_presets.end(), [](const Preset &preset){return preset.is_visible;}); }
// Compare the content of get_selected_preset() with get_edited_preset() configs, return true if they differ.
bool current_is_dirty() const { return ! this->current_dirty_options().empty(); }
// Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ.
std::vector<std::string> current_dirty_options(const bool deep_compare = false) const
{ return dirty_options(&this->get_edited_preset(), &this->get_selected_preset(), deep_compare); }
// Compare the content of get_selected_preset() with get_edited_preset() configs, return the list of keys where they differ.
std::vector<std::string> current_different_from_parent_options(const bool deep_compare = false) const
{ return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), deep_compare); }
// Update the choice UI from the list of presets.
// If show_incompatible, all presets are shown, otherwise only the compatible presets are shown.
// If an incompatible preset is selected, it is shown as well.
size_t update_tab_ui(wxBitmapComboBox *ui, bool show_incompatible);
// Update the choice UI from the list of presets.
// Only the compatible presets are shown.
// If an incompatible preset is selected, it is shown as well.
void update_platter_ui(GUI::PresetComboBox *ui);
// Update a dirty floag of the current preset, update the labels of the UI component accordingly.
// Return true if the dirty flag changed.
bool update_dirty_ui(wxBitmapComboBox *ui);
// Select a profile by its name. Return true if the selection changed.
// Without force, the selection is only updated if the index changes.
// With force, the changes are reverted if the new index is the same as the old index.
bool select_preset_by_name(const std::string &name, bool force);
// Generate a file path from a profile name. Add the ".ini" suffix if it is missing.
std::string path_from_name(const std::string &new_name) const;
protected:
// Select a preset, if it exists. If it does not exist, select an invalid (-1) index.
// This is a temporary state, which shall be fixed immediately by the following step.
bool select_preset_by_name_strict(const std::string &name);
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> merge_presets(PresetCollection &&other, const std::set<VendorProfile> &new_vendors);
private:
PresetCollection();
PresetCollection(const PresetCollection &other);
PresetCollection& operator=(const PresetCollection &other);
// Find a preset position in the sorted list of presets.
// The "-- default -- " preset is always the first, so it needs
// to be handled differently.
// If a preset does not exist, an iterator is returned indicating where to insert a preset with the same name.
std::deque<Preset>::iterator find_preset_internal(const std::string &name)
{
Preset key(m_type, name);
auto it = std::lower_bound(m_presets.begin() + m_num_default_presets, m_presets.end(), key);
if (it == m_presets.end() || it->name != name) {
// Preset has not been not found in the sorted list of non-default presets. Try the defaults.
for (size_t i = 0; i < m_num_default_presets; ++ i)
if (m_presets[i].name == name) {
it = m_presets.begin() + i;
break;
}
}
return it;
}
std::deque<Preset>::const_iterator find_preset_internal(const std::string &name) const
{ return const_cast<PresetCollection*>(this)->find_preset_internal(name); }
size_t update_compatible_with_printer_internal(const Preset &active_printer, bool unselect_if_incompatible);
static std::vector<std::string> dirty_options(const Preset *edited, const Preset *reference, const bool is_printer_type = false);
// Type of this PresetCollection: TYPE_PRINT, TYPE_FILAMENT or TYPE_PRINTER.
Preset::Type m_type;
// List of presets, starting with the "- default -" preset.
// Use deque to force the container to allocate an object per each entry,
// so that the addresses of the presets don't change during resizing of the container.
std::deque<Preset> m_presets;
// Initially this preset contains a copy of the selected preset. Later on, this copy may be modified by the user.
Preset m_edited_preset;
// Selected preset.
int m_idx_selected;
// Is the "- default -" preset suppressed?
bool m_default_suppressed = true;
size_t m_num_default_presets = 0;
// Compatible & incompatible marks, to be placed at the wxBitmapComboBox items of a Platter.
// These bitmaps are not owned by PresetCollection, but by a PresetBundle.
const wxBitmap *m_bitmap_compatible = nullptr;
const wxBitmap *m_bitmap_incompatible = nullptr;
const wxBitmap *m_bitmap_lock = nullptr;
const wxBitmap *m_bitmap_lock_open = nullptr;
// Marks placed at the wxBitmapComboBox of a MainFrame.
// These bitmaps are owned by PresetCollection.
wxBitmap *m_bitmap_main_frame;
// Path to the directory to store the config files into.
std::string m_dir_path;
// Caching color bitmaps for the filament combo box.
GUI::BitmapCache *m_bitmap_cache = nullptr;
// to access select_preset_by_name_strict()
friend class PresetBundle;
};
} // namespace Slic3r
#endif /* slic3r_Preset_hpp_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,173 @@
#ifndef slic3r_PresetBundle_hpp_
#define slic3r_PresetBundle_hpp_
#include "AppConfig.hpp"
#include "Preset.hpp"
#include <set>
#include <boost/filesystem/path.hpp>
namespace Slic3r {
namespace GUI {
class BitmapCache;
};
class PlaceholderParser;
// Bundle of Print + Filament + Printer presets.
class PresetBundle
{
public:
PresetBundle();
~PresetBundle();
// Remove all the presets but the "-- default --".
// Optionally remove all the files referenced by the presets from the user profile directory.
void reset(bool delete_files);
void setup_directories();
// Load ini files of all types (print, filament, printer) from Slic3r::data_dir() / presets.
// Load selections (current print, current filaments, current printer) from config.ini
// This is done just once on application start up.
void load_presets(const AppConfig &config);
// Export selections (current print, current filaments, current printer) into config.ini
void export_selections(AppConfig &config);
// Export selections (current print, current filaments, current printer) into a placeholder parser.
void export_selections(PlaceholderParser &pp);
PresetCollection prints;
PresetCollection filaments;
PresetCollection sla_materials;
PresetCollection printers;
// Filament preset names for a multi-extruder or multi-material print.
// extruders.size() should be the same as printers.get_edited_preset().config.nozzle_diameter.size()
std::vector<std::string> filament_presets;
// The project configuration values are kept separated from the print/filament/printer preset,
// they are being serialized / deserialized from / to the .amf, .3mf, .config, .gcode,
// and they are being used by slicing core.
DynamicPrintConfig project_config;
// There will be an entry for each system profile loaded,
// and the system profiles will point to the VendorProfile instances owned by PresetBundle::vendors.
std::set<VendorProfile> vendors;
struct ObsoletePresets {
std::vector<std::string> prints;
std::vector<std::string> filaments;
std::vector<std::string> sla_materials;
std::vector<std::string> printers;
};
ObsoletePresets obsolete_presets;
bool has_defauls_only() const
{ return prints.has_defaults_only() && filaments.has_defaults_only() && printers.has_defaults_only(); }
DynamicPrintConfig full_config() const;
// Load user configuration and store it into the user profiles.
// This method is called by the configuration wizard.
void load_config(const std::string &name, DynamicPrintConfig config)
{ this->load_config_file_config(name, false, std::move(config)); }
// Load configuration that comes from a model file containing configuration, such as 3MF et al.
// This method is called by the Plater.
void load_config_model(const std::string &name, DynamicPrintConfig config)
{ this->load_config_file_config(name, true, std::move(config)); }
// Load an external config file containing the print, filament and printer presets.
// Instead of a config file, a G-code may be loaded containing the full set of parameters.
// In the future the configuration will likely be read from an AMF file as well.
// If the file is loaded successfully, its print / filament / printer profiles will be activated.
void load_config_file(const std::string &path);
// Load an external config source containing the print, filament and printer presets.
// The given string must contain the full set of parameters (same as those exported to gcode).
// If the string is parsed successfully, its print / filament / printer profiles will be activated.
void load_config_string(const char* str, const char* source_filename = nullptr);
// Load a config bundle file, into presets and store the loaded presets into separate files
// of the local configuration directory.
// Load settings into the provided settings instance.
// Activate the presets stored in the config bundle.
// Returns the number of presets loaded successfully.
enum {
// Save the profiles, which have been loaded.
LOAD_CFGBNDLE_SAVE = 1,
// Delete all old config profiles before loading.
LOAD_CFGBNDLE_RESET_USER_PROFILE = 2,
// Load a system config bundle.
LOAD_CFGBNDLE_SYSTEM = 4,
LOAD_CFGBUNDLE_VENDOR_ONLY = 8,
};
// Load the config bundle, store it to the user profile directory by default.
size_t load_configbundle(const std::string &path, unsigned int flags = LOAD_CFGBNDLE_SAVE);
// Export a config bundle file containing all the presets and the names of the active presets.
void export_configbundle(const std::string &path); // , const DynamicPrintConfig &settings);
// Update a filament selection combo box on the platter for an idx_extruder.
void update_platter_filament_ui(unsigned int idx_extruder, GUI::PresetComboBox *ui);
// Enable / disable the "- default -" preset.
void set_default_suppressed(bool default_suppressed);
// Set the filament preset name. As the name could come from the UI selection box,
// an optional "(modified)" suffix will be removed from the filament name.
void set_filament_preset(size_t idx, const std::string &name);
// Read out the number of extruders from an active printer preset,
// update size and content of filament_presets.
void update_multi_material_filament_presets();
// Update the is_compatible flag of all print and filament presets depending on whether they are marked
// as compatible with the currently selected printer.
// Also updates the is_visible flag of each preset.
// If select_other_if_incompatible is true, then the print or filament preset is switched to some compatible
// preset if the current print or filament preset is not compatible.
void update_compatible_with_printer(bool select_other_if_incompatible);
static bool parse_color(const std::string &scolor, unsigned char *rgb_out);
private:
std::string load_system_presets();
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> merge_presets(PresetBundle &&other);
// Set the "enabled" flag for printer vendors, printer models and printer variants
// based on the user configuration.
// If the "vendor" section is missing, enable all models and variants of the particular vendor.
void load_installed_printers(const AppConfig &config);
// Load selections (current print, current filaments, current printer) from config.ini
// This is done just once on application start up.
void load_selections(const AppConfig &config);
// Load print, filament & printer presets from a config. If it is an external config, then the name is extracted from the external path.
// and the external config is just referenced, not stored into user profile directory.
// If it is not an external config, then the config will be stored into the user profile directory.
void load_config_file_config(const std::string &name_or_path, bool is_external, DynamicPrintConfig &&config);
void load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
bool load_compatible_bitmaps();
DynamicPrintConfig full_fff_config() const;
DynamicPrintConfig full_sla_config() const;
// Indicator, that the preset is compatible with the selected printer.
wxBitmap *m_bitmapCompatible;
// Indicator, that the preset is NOT compatible with the selected printer.
wxBitmap *m_bitmapIncompatible;
// Indicator, that the preset is system and not modified.
wxBitmap *m_bitmapLock;
// Indicator, that the preset is system and user modified.
wxBitmap *m_bitmapLockOpen;
// Caching color bitmaps for the filament combo box.
GUI::BitmapCache *m_bitmapCache;
};
} // namespace Slic3r
#endif /* slic3r_PresetBundle_hpp_ */

View file

@ -0,0 +1,278 @@
//#undef NDEBUG
#include <cassert>
#include "PresetBundle.hpp"
#include "PresetHints.hpp"
#include "Flow.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include <wx/intl.h>
#include "../../libslic3r/libslic3r.h"
#include "GUI.hpp"
namespace Slic3r {
#define MIN_BUF_LENGTH 4096
std::string PresetHints::cooling_description(const Preset &preset)
{
std::string out;
char buf[MIN_BUF_LENGTH/*4096*/];
if (preset.config.opt_bool("cooling", 0)) {
int slowdown_below_layer_time = preset.config.opt_int("slowdown_below_layer_time", 0);
int min_fan_speed = preset.config.opt_int("min_fan_speed", 0);
int max_fan_speed = preset.config.opt_int("max_fan_speed", 0);
int min_print_speed = int(preset.config.opt_float("min_print_speed", 0) + 0.5);
int fan_below_layer_time = preset.config.opt_int("fan_below_layer_time", 0);
sprintf(buf, _CHB(L("If estimated layer time is below ~%ds, fan will run at %d%% and print speed will be reduced so that no less than %ds are spent on that layer (however, speed will never be reduced below %dmm/s).")),
slowdown_below_layer_time, max_fan_speed, slowdown_below_layer_time, min_print_speed);
out += buf;
if (fan_below_layer_time > slowdown_below_layer_time) {
sprintf(buf, _CHB(L("\nIf estimated layer time is greater, but still below ~%ds, fan will run at a proportionally decreasing speed between %d%% and %d%%.")),
fan_below_layer_time, max_fan_speed, min_fan_speed);
out += buf;
}
out += _CHB(L("\nDuring the other layers, fan "));
} else {
out = _CHB(L("Fan "));
}
if (preset.config.opt_bool("fan_always_on", 0)) {
int disable_fan_first_layers = preset.config.opt_int("disable_fan_first_layers", 0);
int min_fan_speed = preset.config.opt_int("min_fan_speed", 0);
sprintf(buf, _CHB(L("will always run at %d%% ")), min_fan_speed);
out += buf;
if (disable_fan_first_layers > 1) {
sprintf(buf, _CHB(L("except for the first %d layers")), disable_fan_first_layers);
out += buf;
}
else if (disable_fan_first_layers == 1)
out += _CHB(L("except for the first layer"));
} else
out += _CHB(L("will be turned off."));
return out;
}
static const ConfigOptionFloatOrPercent& first_positive(const ConfigOptionFloatOrPercent *v1, const ConfigOptionFloatOrPercent &v2, const ConfigOptionFloatOrPercent &v3)
{
return (v1 != nullptr && v1->value > 0) ? *v1 : ((v2.value > 0) ? v2 : v3);
}
std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle &preset_bundle)
{
// Find out, to which nozzle index is the current filament profile assigned.
int idx_extruder = 0;
int num_extruders = (int)preset_bundle.filament_presets.size();
for (; idx_extruder < num_extruders; ++ idx_extruder)
if (preset_bundle.filament_presets[idx_extruder] == preset_bundle.filaments.get_selected_preset().name)
break;
if (idx_extruder == num_extruders)
// The current filament preset is not active for any extruder.
idx_extruder = -1;
const DynamicPrintConfig &print_config = preset_bundle.prints .get_edited_preset().config;
const DynamicPrintConfig &filament_config = preset_bundle.filaments.get_edited_preset().config;
const DynamicPrintConfig &printer_config = preset_bundle.printers .get_edited_preset().config;
// Current printer values.
float nozzle_diameter = (float)printer_config.opt_float("nozzle_diameter", idx_extruder);
// Print config values
double layer_height = print_config.opt_float("layer_height");
double first_layer_height = print_config.get_abs_value("first_layer_height", layer_height);
double support_material_speed = print_config.opt_float("support_material_speed");
double support_material_interface_speed = print_config.get_abs_value("support_material_interface_speed", support_material_speed);
double bridge_speed = print_config.opt_float("bridge_speed");
double bridge_flow_ratio = print_config.opt_float("bridge_flow_ratio");
double perimeter_speed = print_config.opt_float("perimeter_speed");
double external_perimeter_speed = print_config.get_abs_value("external_perimeter_speed", perimeter_speed);
double gap_fill_speed = print_config.opt_float("gap_fill_speed");
double infill_speed = print_config.opt_float("infill_speed");
double small_perimeter_speed = print_config.get_abs_value("small_perimeter_speed", perimeter_speed);
double solid_infill_speed = print_config.get_abs_value("solid_infill_speed", infill_speed);
double top_solid_infill_speed = print_config.get_abs_value("top_solid_infill_speed", solid_infill_speed);
// Maximum print speed when auto-speed is enabled by setting any of the above speed values to zero.
double max_print_speed = print_config.opt_float("max_print_speed");
// Maximum volumetric speed allowed for the print profile.
double max_volumetric_speed = print_config.opt_float("max_volumetric_speed");
const auto &extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("extrusion_width");
const auto &external_perimeter_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("external_perimeter_extrusion_width");
const auto &first_layer_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("first_layer_extrusion_width");
const auto &infill_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("infill_extrusion_width");
const auto &perimeter_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("perimeter_extrusion_width");
const auto &solid_infill_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("solid_infill_extrusion_width");
const auto &support_material_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("support_material_extrusion_width");
const auto &top_infill_extrusion_width = *print_config.option<ConfigOptionFloatOrPercent>("top_infill_extrusion_width");
const auto &first_layer_speed = *print_config.option<ConfigOptionFloatOrPercent>("first_layer_speed");
// Index of an extruder assigned to a feature. If set to 0, an active extruder will be used for a multi-material print.
// If different from idx_extruder, it will not be taken into account for this hint.
auto feature_extruder_active = [idx_extruder, num_extruders](int i) {
return i <= 0 || i > num_extruders || idx_extruder == -1 || idx_extruder == i - 1;
};
bool perimeter_extruder_active = feature_extruder_active(print_config.opt_int("perimeter_extruder"));
bool infill_extruder_active = feature_extruder_active(print_config.opt_int("infill_extruder"));
bool solid_infill_extruder_active = feature_extruder_active(print_config.opt_int("solid_infill_extruder"));
bool support_material_extruder_active = feature_extruder_active(print_config.opt_int("support_material_extruder"));
bool support_material_interface_extruder_active = feature_extruder_active(print_config.opt_int("support_material_interface_extruder"));
// Current filament values
double filament_diameter = filament_config.opt_float("filament_diameter", 0);
double filament_crossection = M_PI * 0.25 * filament_diameter * filament_diameter;
double extrusion_multiplier = filament_config.opt_float("extrusion_multiplier", 0);
// The following value will be annotated by this hint, so it does not take part in the calculation.
// double filament_max_volumetric_speed = filament_config.opt_float("filament_max_volumetric_speed", 0);
std::string out;
for (size_t idx_type = (first_layer_extrusion_width.value == 0) ? 1 : 0; idx_type < 3; ++ idx_type) {
// First test the maximum volumetric extrusion speed for non-bridging extrusions.
bool first_layer = idx_type == 0;
bool bridging = idx_type == 2;
const ConfigOptionFloatOrPercent *first_layer_extrusion_width_ptr = (first_layer && first_layer_extrusion_width.value > 0) ?
&first_layer_extrusion_width : nullptr;
const float lh = float(first_layer ? first_layer_height : layer_height);
const float bfr = bridging ? bridge_flow_ratio : 0.f;
double max_flow = 0.;
std::string max_flow_extrusion_type;
auto limit_by_first_layer_speed = [&first_layer_speed, first_layer](double speed_normal, double speed_max) {
if (first_layer && first_layer_speed.value > 0)
// Apply the first layer limit.
speed_normal = first_layer_speed.get_abs_value(speed_normal);
return (speed_normal > 0.) ? speed_normal : speed_max;
};
if (perimeter_extruder_active) {
double external_perimeter_rate = Flow::new_from_config_width(frExternalPerimeter,
first_positive(first_layer_extrusion_width_ptr, external_perimeter_extrusion_width, extrusion_width),
nozzle_diameter, lh, bfr).mm3_per_mm() *
(bridging ? bridge_speed :
limit_by_first_layer_speed(std::max(external_perimeter_speed, small_perimeter_speed), max_print_speed));
if (max_flow < external_perimeter_rate) {
max_flow = external_perimeter_rate;
max_flow_extrusion_type = _CHB(L("external perimeters"));
}
double perimeter_rate = Flow::new_from_config_width(frPerimeter,
first_positive(first_layer_extrusion_width_ptr, perimeter_extrusion_width, extrusion_width),
nozzle_diameter, lh, bfr).mm3_per_mm() *
(bridging ? bridge_speed :
limit_by_first_layer_speed(std::max(perimeter_speed, small_perimeter_speed), max_print_speed));
if (max_flow < perimeter_rate) {
max_flow = perimeter_rate;
max_flow_extrusion_type = _CHB(L("perimeters"));
}
}
if (! bridging && infill_extruder_active) {
double infill_rate = Flow::new_from_config_width(frInfill,
first_positive(first_layer_extrusion_width_ptr, infill_extrusion_width, extrusion_width),
nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(infill_speed, max_print_speed);
if (max_flow < infill_rate) {
max_flow = infill_rate;
max_flow_extrusion_type = _CHB(L("infill"));
}
}
if (solid_infill_extruder_active) {
double solid_infill_rate = Flow::new_from_config_width(frInfill,
first_positive(first_layer_extrusion_width_ptr, solid_infill_extrusion_width, extrusion_width),
nozzle_diameter, lh, 0).mm3_per_mm() *
(bridging ? bridge_speed : limit_by_first_layer_speed(solid_infill_speed, max_print_speed));
if (max_flow < solid_infill_rate) {
max_flow = solid_infill_rate;
max_flow_extrusion_type = _CHB(L("solid infill"));
}
if (! bridging) {
double top_solid_infill_rate = Flow::new_from_config_width(frInfill,
first_positive(first_layer_extrusion_width_ptr, top_infill_extrusion_width, extrusion_width),
nozzle_diameter, lh, bfr).mm3_per_mm() * limit_by_first_layer_speed(top_solid_infill_speed, max_print_speed);
if (max_flow < top_solid_infill_rate) {
max_flow = top_solid_infill_rate;
max_flow_extrusion_type = _CHB(L("top solid infill"));
}
}
}
if (support_material_extruder_active) {
double support_material_rate = Flow::new_from_config_width(frSupportMaterial,
first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width),
nozzle_diameter, lh, bfr).mm3_per_mm() *
(bridging ? bridge_speed : limit_by_first_layer_speed(support_material_speed, max_print_speed));
if (max_flow < support_material_rate) {
max_flow = support_material_rate;
max_flow_extrusion_type = _CHB(L("support"));
}
}
if (support_material_interface_extruder_active) {
double support_material_interface_rate = Flow::new_from_config_width(frSupportMaterialInterface,
first_positive(first_layer_extrusion_width_ptr, support_material_extrusion_width, extrusion_width),
nozzle_diameter, lh, bfr).mm3_per_mm() *
(bridging ? bridge_speed : limit_by_first_layer_speed(support_material_interface_speed, max_print_speed));
if (max_flow < support_material_interface_rate) {
max_flow = support_material_interface_rate;
max_flow_extrusion_type = _CHB(L("support interface"));
}
}
//FIXME handle gap_fill_speed
if (! out.empty())
out += "\n";
out += (first_layer ? _CHB(L("First layer volumetric")) : (bridging ? _CHB(L("Bridging volumetric")) : _CHB(L("Volumetric"))));
out += _CHB(L(" flow rate is maximized "));
bool limited_by_max_volumetric_speed = max_volumetric_speed > 0 && max_volumetric_speed < max_flow;
out += (limited_by_max_volumetric_speed ?
_CHB(L("by the print profile maximum")) :
(_CHB(L("when printing ")) + max_flow_extrusion_type))
+ _CHB(L(" with a volumetric rate "));
if (limited_by_max_volumetric_speed)
max_flow = max_volumetric_speed;
char buf[MIN_BUF_LENGTH/*2048*/];
sprintf(buf, _CHB(L("%3.2f mm³/s")), max_flow);
out += buf;
sprintf(buf, _CHB(L(" at filament speed %3.2f mm/s.")), max_flow / filament_crossection);
out += buf;
}
return out;
}
std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &preset_bundle)
{
const DynamicPrintConfig &print_config = preset_bundle.prints .get_edited_preset().config;
const DynamicPrintConfig &printer_config = preset_bundle.printers .get_edited_preset().config;
float layer_height = float(print_config.opt_float("layer_height"));
int num_perimeters = print_config.opt_int("perimeters");
bool thin_walls = print_config.opt_bool("thin_walls");
float nozzle_diameter = float(printer_config.opt_float("nozzle_diameter", 0));
std::string out;
if (layer_height <= 0.f){
out += _CHB(L("Recommended object thin wall thickness: Not available due to invalid layer height."));
return out;
}
Flow external_perimeter_flow = Flow::new_from_config_width(
frExternalPerimeter,
*print_config.opt<ConfigOptionFloatOrPercent>("external_perimeter_extrusion_width"),
nozzle_diameter, layer_height, false);
Flow perimeter_flow = Flow::new_from_config_width(
frPerimeter,
*print_config.opt<ConfigOptionFloatOrPercent>("perimeter_extrusion_width"),
nozzle_diameter, layer_height, false);
if (num_perimeters > 0) {
int num_lines = std::min(num_perimeters * 2, 10);
char buf[MIN_BUF_LENGTH/*256*/];
sprintf(buf, _CHB(L("Recommended object thin wall thickness for layer height %.2f and ")), layer_height);
out += buf;
// Start with the width of two closely spaced
double width = external_perimeter_flow.width + external_perimeter_flow.spacing();
for (int i = 2; i <= num_lines; thin_walls ? ++ i : i += 2) {
if (i > 2)
out += ", ";
sprintf(buf, _CHB(L("%d lines: %.2lf mm")), i, width);
out += buf;
width += perimeter_flow.spacing() * (thin_walls ? 1.f : 2.f);
}
}
return out;
}
}; // namespace Slic3r

View file

@ -0,0 +1,30 @@
#ifndef slic3r_PresetHints_hpp_
#define slic3r_PresetHints_hpp_
#include <string>
#include "PresetBundle.hpp"
namespace Slic3r {
// GUI utility functions to produce hint messages from the current profile.
class PresetHints
{
public:
// Produce a textual description of the cooling logic of a currently active filament.
static std::string cooling_description(const Preset &preset);
// Produce a textual description of the maximum flow achived for the current configuration
// (the current printer, filament and print settigns).
// This description will be useful for getting a gut feeling for the maximum volumetric
// print speed achievable with the extruder.
static std::string maximum_volumetric_flow_description(const PresetBundle &preset_bundle);
// Produce a textual description of a recommended thin wall thickness
// from the provided number of perimeters and the external / internal perimeter width.
static std::string recommended_thin_wall_thickness(const PresetBundle &preset_bundle);
};
} // namespace Slic3r
#endif /* slic3r_PresetHints_hpp_ */

View file

@ -0,0 +1,70 @@
#ifndef IPROGRESSINDICATOR_HPP
#define IPROGRESSINDICATOR_HPP
#include <string>
#include <functional>
namespace Slic3r {
/**
* @brief Generic progress indication interface.
*/
class ProgressIndicator {
public:
using CancelFn = std::function<void(void)>; // Cancel function signature.
private:
float m_state = .0f, m_max = 1.f, m_step;
CancelFn m_cancelfunc = [](){};
public:
inline virtual ~ProgressIndicator() {}
/// Get the maximum of the progress range.
float max() const { return m_max; }
/// Get the current progress state
float state() const { return m_state; }
/// Set the maximum of the progress range
virtual void max(float maxval) { m_max = maxval; }
/// Set the current state of the progress.
virtual void state(float val) { m_state = val; }
/**
* @brief Number of states int the progress. Can be used instead of giving a
* maximum value.
*/
virtual void states(unsigned statenum) {
m_step = m_max / statenum;
}
/// Message shown on the next status update.
virtual void message(const std::string&) = 0;
/// Title of the operation.
virtual void title(const std::string&) = 0;
/// Formatted message for the next status update. Works just like sprintf.
virtual void message_fmt(const std::string& fmt, ...);
/// Set up a cancel callback for the operation if feasible.
virtual void on_cancel(CancelFn func = CancelFn()) { m_cancelfunc = func; }
/**
* Explicitly shut down the progress indicator and call the associated
* callback.
*/
virtual void cancel() { m_cancelfunc(); }
/// Convenience function to call message and status update in one function.
void update(float st, const std::string& msg) {
message(msg); state(st);
}
};
}
#endif // IPROGRESSINDICATOR_HPP

View file

@ -0,0 +1,162 @@
#include "ProgressStatusBar.hpp"
#include <wx/timer.h>
#include <wx/gauge.h>
#include <wx/button.h>
#include <wx/statusbr.h>
#include <wx/frame.h>
#include "GUI_App.hpp"
#include <iostream>
namespace Slic3r {
ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id):
self(new wxStatusBar(parent ? parent : GUI::wxGetApp().mainframe,
id == -1? wxID_ANY : id)),
m_timer(new wxTimer(self)),
m_prog (new wxGauge(self,
wxGA_HORIZONTAL,
100,
wxDefaultPosition,
wxDefaultSize)),
m_cancelbutton(new wxButton(self,
-1,
"Cancel",
wxDefaultPosition,
wxDefaultSize))
{
m_prog->Hide();
m_cancelbutton->Hide();
self->SetFieldsCount(3);
int w[] = {-1, 150, 155};
self->SetStatusWidths(3, w);
self->Bind(wxEVT_TIMER, [this](const wxTimerEvent&) {
if (m_prog->IsShown()) m_timer->Stop();
if(is_busy()) m_prog->Pulse();
});
self->Bind(wxEVT_SIZE, [this](wxSizeEvent& event){
wxRect rect;
self->GetFieldRect(1, rect);
auto offset = 0;
m_cancelbutton->Move(rect.GetX() + offset, rect.GetY() + offset);
m_cancelbutton->SetSize(rect.GetWidth() - offset, rect.GetHeight());
self->GetFieldRect(2, rect);
m_prog->Move(rect.GetX() + offset, rect.GetY() + offset);
m_prog->SetSize(rect.GetWidth() - offset, rect.GetHeight());
event.Skip();
});
m_cancelbutton->Bind(wxEVT_BUTTON, [this](const wxCommandEvent&) {
if (m_cancel_cb)
m_cancel_cb();
m_cancelbutton->Hide();
});
}
ProgressStatusBar::~ProgressStatusBar() {
if(m_timer->IsRunning()) m_timer->Stop();
}
int ProgressStatusBar::get_progress() const
{
return m_prog->GetValue();
}
void ProgressStatusBar::set_progress(int val)
{
if(!m_prog->IsShown()) show_progress(true);
if(val == m_prog->GetRange()) {
m_prog->SetValue(0);
show_progress(false);
} else {
m_prog->SetValue(val);
}
}
int ProgressStatusBar::get_range() const
{
return m_prog->GetRange();
}
void ProgressStatusBar::set_range(int val)
{
if(val != m_prog->GetRange()) {
m_prog->SetRange(val);
}
}
void ProgressStatusBar::show_progress(bool show)
{
m_prog->Show(show);
m_prog->Pulse();
}
void ProgressStatusBar::start_busy(int rate)
{
m_busy = true;
show_progress(true);
if (!m_timer->IsRunning()) {
m_timer->Start(rate);
}
}
void ProgressStatusBar::stop_busy()
{
m_timer->Stop();
show_progress(false);
m_prog->SetValue(0);
m_busy = false;
}
void ProgressStatusBar::set_cancel_callback(ProgressStatusBar::CancelFn ccb) {
m_cancel_cb = ccb;
if(ccb) m_cancelbutton->Show();
else m_cancelbutton->Hide();
}
void ProgressStatusBar::run(int rate)
{
if(!m_timer->IsRunning()) {
m_timer->Start(rate);
}
}
void ProgressStatusBar::embed(wxFrame *frame)
{
wxFrame* mf = frame ? frame : GUI::wxGetApp().mainframe;
mf->SetStatusBar(self);
}
void ProgressStatusBar::set_status_text(const wxString& txt)
{
self->SetStatusText(txt);
}
void ProgressStatusBar::set_status_text(const std::string& txt)
{
this->set_status_text(txt.c_str());
}
void ProgressStatusBar::set_status_text(const char *txt)
{
this->set_status_text(wxString::FromUTF8(txt));
}
void ProgressStatusBar::show_cancel_button()
{
m_cancelbutton->Show();
}
void ProgressStatusBar::hide_cancel_button()
{
m_cancelbutton->Hide();
}
}

View file

@ -0,0 +1,68 @@
#ifndef PROGRESSSTATUSBAR_HPP
#define PROGRESSSTATUSBAR_HPP
#include <memory>
#include <functional>
class wxTimer;
class wxGauge;
class wxButton;
class wxTimerEvent;
class wxStatusBar;
class wxWindow;
class wxFrame;
class wxString;
namespace Slic3r {
/**
* @brief The ProgressStatusBar class is the widgets occupying the lower area
* of the Slicer main window. It consists of a message area to the left and a
* progress indication area to the right with an optional cancel button.
*/
class ProgressStatusBar
{
wxStatusBar *self; // we cheat! It should be the base class but: perl!
wxTimer *m_timer;
wxGauge *m_prog;
wxButton *m_cancelbutton;
public:
/// Cancel callback function type
using CancelFn = std::function<void()>;
ProgressStatusBar(wxWindow *parent = nullptr, int id = -1);
~ProgressStatusBar();
int get_progress() const;
void set_progress(int);
int get_range() const;
void set_range(int = 100);
void show_progress(bool);
void start_busy(int = 100);
void stop_busy();
inline bool is_busy() const { return m_busy; }
void set_cancel_callback(CancelFn = CancelFn());
inline void reset_cancel_callback() { set_cancel_callback(); }
void run(int rate);
void embed(wxFrame *frame = nullptr);
void set_status_text(const wxString& txt);
void set_status_text(const std::string& txt);
void set_status_text(const char *txt);
// Temporary methods to satisfy Perl side
void show_cancel_button();
void hide_cancel_button();
private:
bool m_busy = false;
CancelFn m_cancel_cb;
};
namespace GUI {
using Slic3r::ProgressStatusBar;
}
}
#endif // PROGRESSSTATUSBAR_HPP

View file

@ -0,0 +1,279 @@
#include <algorithm>
#include <wx/dcbuffer.h>
#include "RammingChart.hpp"
#include "GUI.hpp"
wxDEFINE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent);
void Chart::draw() {
wxAutoBufferedPaintDC dc(this); // unbuffered DC caused flickering on win
dc.SetBrush(GetBackgroundColour());
dc.SetPen(GetBackgroundColour());
dc.DrawRectangle(GetClientRect()); // otherwise the background would end up black on windows
dc.SetPen(*wxBLACK_PEN);
dc.SetBrush(*wxWHITE_BRUSH);
dc.DrawRectangle(m_rect);
if (visible_area.m_width < 0.499) {
dc.DrawText(_(L("NO RAMMING AT ALL")),wxPoint(m_rect.GetLeft()+m_rect.GetWidth()/2-50,m_rect.GetBottom()-m_rect.GetHeight()/2));
return;
}
if (!m_line_to_draw.empty()) {
for (unsigned int i=0;i<m_line_to_draw.size()-2;++i) {
int color = 510*((m_rect.GetBottom()-(m_line_to_draw)[i])/double(m_rect.GetHeight()));
dc.SetPen( wxPen( wxColor(std::min(255,color),255-std::max(color-255,0),0), 1 ) );
dc.DrawLine(m_rect.GetLeft()+1+i,(m_line_to_draw)[i],m_rect.GetLeft()+1+i,m_rect.GetBottom());
}
dc.SetPen( wxPen( wxColor(0,0,0), 1 ) );
for (unsigned int i=0;i<m_line_to_draw.size()-2;++i) {
if (splines)
dc.DrawLine(m_rect.GetLeft()+i,(m_line_to_draw)[i],m_rect.GetLeft()+i+1,(m_line_to_draw)[i+1]);
else {
dc.DrawLine(m_rect.GetLeft()+i,(m_line_to_draw)[i],m_rect.GetLeft()+i+1,(m_line_to_draw)[i]);
dc.DrawLine(m_rect.GetLeft()+i+1,(m_line_to_draw)[i],m_rect.GetLeft()+i+1,(m_line_to_draw)[i+1]);
}
}
}
// draw draggable buttons
dc.SetBrush(*wxBLUE_BRUSH);
dc.SetPen( wxPen( wxColor(0,0,0), 1 ) );
for (auto& button : m_buttons)
//dc.DrawRectangle(math_to_screen(button.get_pos())-wxPoint(side/2.,side/2.), wxSize(side,side));
dc.DrawCircle(math_to_screen(button.get_pos()),side/2.);
//dc.DrawRectangle(math_to_screen(button.get_pos()-wxPoint2DDouble(0.125,0))-wxPoint(0,5),wxSize(50,10));
// draw x-axis:
float last_mark = -10000;
for (float math_x=int(visible_area.m_x*10)/10 ; math_x < (visible_area.m_x+visible_area.m_width) ; math_x+=0.1) {
int x = math_to_screen(wxPoint2DDouble(math_x,visible_area.m_y)).x;
int y = m_rect.GetBottom();
if (x-last_mark < 50) continue;
dc.DrawLine(x,y+3,x,y-3);
dc.DrawText(wxString().Format(wxT("%.1f"), math_x),wxPoint(x-10,y+7));
last_mark = x;
}
// draw y-axis:
last_mark=10000;
for (int math_y=visible_area.m_y ; math_y < (visible_area.m_y+visible_area.m_height) ; math_y+=1) {
int y = math_to_screen(wxPoint2DDouble(visible_area.m_x,math_y)).y;
int x = m_rect.GetLeft();
if (last_mark-y < 50) continue;
dc.DrawLine(x-3,y,x+3,y);
dc.DrawText(wxString()<<math_y,wxPoint(x-25,y-2/*7*/));
last_mark = y;
}
// axis labels:
wxString label = _(L("Time")) + " ("+_(L("s"))+")";
int text_width = 0;
int text_height = 0;
dc.GetTextExtent(label,&text_width,&text_height);
dc.DrawText(label,wxPoint(0.5*(m_rect.GetRight()+m_rect.GetLeft())-text_width/2.f, m_rect.GetBottom()+25));
label = _(L("Volumetric speed")) + " (" + _(L("mm")) + wxString("³/", wxConvUTF8) + _(L("s")) + ")";
dc.GetTextExtent(label,&text_width,&text_height);
dc.DrawRotatedText(label,wxPoint(0,0.5*(m_rect.GetBottom()+m_rect.GetTop())+text_width/2.f),90);
}
void Chart::mouse_right_button_clicked(wxMouseEvent& event) {
if (!manual_points_manipulation)
return;
wxPoint point = event.GetPosition();
int button_index = which_button_is_clicked(point);
if (button_index != -1 && m_buttons.size()>2) {
m_buttons.erase(m_buttons.begin()+button_index);
recalculate_line();
}
}
void Chart::mouse_clicked(wxMouseEvent& event) {
wxPoint point = event.GetPosition();
int button_index = which_button_is_clicked(point);
if ( button_index != -1) {
m_dragged = &m_buttons[button_index];
m_previous_mouse = point;
}
}
void Chart::mouse_moved(wxMouseEvent& event) {
if (!event.Dragging() || !m_dragged) return;
wxPoint pos = event.GetPosition();
wxRect rect = m_rect;
rect.Deflate(side/2.);
if (!(rect.Contains(pos))) { // the mouse left chart area
mouse_left_window(event);
return;
}
int delta_x = pos.x - m_previous_mouse.x;
int delta_y = pos.y - m_previous_mouse.y;
m_dragged->move(fixed_x?0:double(delta_x)/m_rect.GetWidth() * visible_area.m_width,-double(delta_y)/m_rect.GetHeight() * visible_area.m_height);
m_previous_mouse = pos;
recalculate_line();
}
void Chart::mouse_double_clicked(wxMouseEvent& event) {
if (!manual_points_manipulation)
return;
wxPoint point = event.GetPosition();
if (!m_rect.Contains(point)) // the click is outside the chart
return;
m_buttons.push_back(screen_to_math(point));
std::sort(m_buttons.begin(),m_buttons.end());
recalculate_line();
return;
}
void Chart::recalculate_line() {
std::vector<wxPoint> points;
for (auto& but : m_buttons) {
points.push_back(wxPoint(math_to_screen(but.get_pos())));
if (points.size()>1 && points.back().x==points[points.size()-2].x) points.pop_back();
if (points.size()>1 && points.back().x > m_rect.GetRight()) {
points.pop_back();
break;
}
}
std::sort(points.begin(),points.end(),[](wxPoint& a,wxPoint& b) { return a.x < b.x; });
m_line_to_draw.clear();
m_total_volume = 0.f;
// Cubic spline interpolation: see https://en.wikiversity.org/wiki/Cubic_Spline_Interpolation#Methods
const bool boundary_first_derivative = true; // true - first derivative is 0 at the leftmost and rightmost point
// false - second ---- || -------
const int N = points.size()-1; // last point can be accessed as N, we have N+1 total points
std::vector<float> diag(N+1);
std::vector<float> mu(N+1);
std::vector<float> lambda(N+1);
std::vector<float> h(N+1);
std::vector<float> rhs(N+1);
// let's fill in inner equations
for (int i=1;i<=N;++i) h[i] = points[i].x-points[i-1].x;
std::fill(diag.begin(),diag.end(),2.f);
for (int i=1;i<=N-1;++i) {
mu[i] = h[i]/(h[i]+h[i+1]);
lambda[i] = 1.f - mu[i];
rhs[i] = 6 * ( float(points[i+1].y-points[i].y )/(h[i+1]*(points[i+1].x-points[i-1].x)) -
float(points[i].y -points[i-1].y)/(h[i] *(points[i+1].x-points[i-1].x)) );
}
// now fill in the first and last equations, according to boundary conditions:
if (boundary_first_derivative) {
const float endpoints_derivative = 0;
lambda[0] = 1;
mu[N] = 1;
rhs[0] = (6.f/h[1]) * (float(points[0].y-points[1].y)/(points[0].x-points[1].x) - endpoints_derivative);
rhs[N] = (6.f/h[N]) * (endpoints_derivative - float(points[N-1].y-points[N].y)/(points[N-1].x-points[N].x));
}
else {
lambda[0] = 0;
mu[N] = 0;
rhs[0] = 0;
rhs[N] = 0;
}
// the trilinear system is ready to be solved:
for (int i=1;i<=N;++i) {
float multiple = mu[i]/diag[i-1]; // let's subtract proper multiple of above equation
diag[i]-= multiple * lambda[i-1];
rhs[i] -= multiple * rhs[i-1];
}
// now the back substitution (vector mu contains invalid values from now on):
rhs[N] = rhs[N]/diag[N];
for (int i=N-1;i>=0;--i)
rhs[i] = (rhs[i]-lambda[i]*rhs[i+1])/diag[i];
unsigned int i=1;
float y=0.f;
for (int x=m_rect.GetLeft(); x<=m_rect.GetRight() ; ++x) {
if (splines) {
if (i<points.size()-1 && points[i].x < x ) {
++i;
}
if (points[0].x > x)
y = points[0].y;
else
if (points[N].x < x)
y = points[N].y;
else
y = (rhs[i-1]*pow(points[i].x-x,3)+rhs[i]*pow(x-points[i-1].x,3)) / (6*h[i]) +
(points[i-1].y-rhs[i-1]*h[i]*h[i]/6.f) * (points[i].x-x)/h[i] +
(points[i].y -rhs[i] *h[i]*h[i]/6.f) * (x-points[i-1].x)/h[i];
m_line_to_draw.push_back(y);
}
else {
float x_math = screen_to_math(wxPoint(x,0)).m_x;
if (i+2<=points.size() && m_buttons[i+1].get_pos().m_x-0.125 < x_math)
++i;
m_line_to_draw.push_back(math_to_screen(wxPoint2DDouble(x_math,m_buttons[i].get_pos().m_y)).y);
}
m_line_to_draw.back() = std::max(m_line_to_draw.back(), m_rect.GetTop()-1);
m_line_to_draw.back() = std::min(m_line_to_draw.back(), m_rect.GetBottom()-1);
m_total_volume += (m_rect.GetBottom() - m_line_to_draw.back()) * (visible_area.m_width / m_rect.GetWidth()) * (visible_area.m_height / m_rect.GetHeight());
}
wxPostEvent(this->GetParent(), wxCommandEvent(EVT_WIPE_TOWER_CHART_CHANGED));
Refresh();
}
std::vector<float> Chart::get_ramming_speed(float sampling) const {
std::vector<float> speeds_out;
const int number_of_samples = std::round( visible_area.m_width / sampling);
if (number_of_samples>0) {
const int dx = (m_line_to_draw.size()-1) / number_of_samples;
for (int j=0;j<number_of_samples;++j) {
float left = screen_to_math(wxPoint(0,m_line_to_draw[j*dx])).m_y;
float right = screen_to_math(wxPoint(0,m_line_to_draw[(j+1)*dx])).m_y;
speeds_out.push_back((left+right)/2.f);
}
}
return speeds_out;
}
std::vector<std::pair<float,float>> Chart::get_buttons() const {
std::vector<std::pair<float, float>> buttons_out;
for (const auto& button : m_buttons)
buttons_out.push_back(std::make_pair(float(button.get_pos().m_x),float(button.get_pos().m_y)));
return buttons_out;
}
BEGIN_EVENT_TABLE(Chart, wxWindow)
EVT_MOTION(Chart::mouse_moved)
EVT_LEFT_DOWN(Chart::mouse_clicked)
EVT_LEFT_UP(Chart::mouse_released)
EVT_LEFT_DCLICK(Chart::mouse_double_clicked)
EVT_RIGHT_DOWN(Chart::mouse_right_button_clicked)
EVT_LEAVE_WINDOW(Chart::mouse_left_window)
EVT_PAINT(Chart::paint_event)
END_EVENT_TABLE()

View file

@ -0,0 +1,115 @@
#ifndef RAMMING_CHART_H_
#define RAMMING_CHART_H_
#include <vector>
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif
wxDECLARE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent);
class Chart : public wxWindow {
public:
Chart(wxWindow* parent, wxRect rect,const std::vector<std::pair<float,float>>& initial_buttons,int ramming_speed_size, float sampling) :
wxWindow(parent,wxID_ANY,rect.GetTopLeft(),rect.GetSize())
{
SetBackgroundStyle(wxBG_STYLE_PAINT);
m_rect = wxRect(wxPoint(50,0),rect.GetSize()-wxSize(50,50));
visible_area = wxRect2DDouble(0.0, 0.0, sampling*ramming_speed_size, 20.);
m_buttons.clear();
if (initial_buttons.size()>0)
for (const auto& pair : initial_buttons)
m_buttons.push_back(wxPoint2DDouble(pair.first,pair.second));
recalculate_line();
}
void set_xy_range(float x,float y) {
x = int(x/0.5) * 0.5;
if (x>=0) visible_area.SetRight(x);
if (y>=0) visible_area.SetBottom(y);
recalculate_line();
}
float get_volume() const { return m_total_volume; }
float get_time() const { return visible_area.m_width; }
std::vector<float> get_ramming_speed(float sampling) const; //returns sampled ramming speed
std::vector<std::pair<float,float>> get_buttons() const; // returns buttons position
void draw();
void mouse_clicked(wxMouseEvent& event);
void mouse_right_button_clicked(wxMouseEvent& event);
void mouse_moved(wxMouseEvent& event);
void mouse_double_clicked(wxMouseEvent& event);
void mouse_left_window(wxMouseEvent&) { m_dragged = nullptr; }
void mouse_released(wxMouseEvent&) { m_dragged = nullptr; }
void paint_event(wxPaintEvent&) { draw(); }
DECLARE_EVENT_TABLE()
private:
static const bool fixed_x = true;
static const bool splines = true;
static const bool manual_points_manipulation = false;
static const int side = 10; // side of draggable button
class ButtonToDrag {
public:
bool operator<(const ButtonToDrag& a) const { return m_pos.m_x < a.m_pos.m_x; }
ButtonToDrag(wxPoint2DDouble pos) : m_pos{pos} {};
wxPoint2DDouble get_pos() const { return m_pos; }
void move(double x,double y) { m_pos.m_x+=x; m_pos.m_y+=y; }
private:
wxPoint2DDouble m_pos; // position in math coordinates
};
wxPoint math_to_screen(const wxPoint2DDouble& math) const {
wxPoint screen;
screen.x = (math.m_x-visible_area.m_x) * (m_rect.GetWidth() / visible_area.m_width );
screen.y = (math.m_y-visible_area.m_y) * (m_rect.GetHeight() / visible_area.m_height );
screen.y *= -1;
screen += m_rect.GetLeftBottom();
return screen;
}
wxPoint2DDouble screen_to_math(const wxPoint& screen) const {
wxPoint2DDouble math = screen;
math -= m_rect.GetLeftBottom();
math.m_y *= -1;
math.m_x *= visible_area.m_width / m_rect.GetWidth(); // scales to [0;1]x[0,1]
math.m_y *= visible_area.m_height / m_rect.GetHeight();
return (math+visible_area.GetLeftTop());
}
int which_button_is_clicked(const wxPoint& point) const {
if (!m_rect.Contains(point))
return -1;
for (unsigned int i=0;i<m_buttons.size();++i) {
wxRect rect(math_to_screen(m_buttons[i].get_pos())-wxPoint(side/2.,side/2.),wxSize(side,side)); // bounding rectangle of this button
if ( rect.Contains(point) )
return i;
}
return (-1);
}
void recalculate_line();
void recalculate_volume();
wxRect m_rect; // rectangle on screen the chart is mapped into (screen coordinates)
wxPoint m_previous_mouse;
std::vector<ButtonToDrag> m_buttons;
std::vector<int> m_line_to_draw;
wxRect2DDouble visible_area;
ButtonToDrag* m_dragged = nullptr;
float m_total_volume = 0.f;
};
#endif // RAMMING_CHART_H_

View file

@ -0,0 +1,148 @@
#include "SysInfoDialog.hpp"
// #include "AboutDialog.hpp"
#include <wx/clipbrd.h>
#include <wx/platinfo.h>
// #include "../../libslic3r/Utils.hpp"
#include "3DScene.hpp"
#include "GUI.hpp"
namespace Slic3r {
namespace GUI {
std::string get_main_info(bool format_as_html)
{
std::stringstream out;
std::string b_start = format_as_html ? "<b>" : "";
std::string b_end = format_as_html ? "</b>" : "";
std::string line_end = format_as_html ? "<br>" : "\n";
if (!format_as_html)
out << b_start << SLIC3R_FORK_NAME << b_end << line_end;
out << b_start << "Version: " << b_end << SLIC3R_VERSION << line_end;
out << b_start << "Build: " << b_end << SLIC3R_BUILD << line_end;
out << line_end;
out << b_start << "Operating System: " << b_end << wxPlatformInfo::Get().GetOperatingSystemFamilyName() << line_end;
out << b_start << "System Architecture: " << b_end << wxPlatformInfo::Get().GetArchName() << line_end;
out << b_start <<
#if defined _WIN32
"Windows Version: "
#else
// Hopefully some kind of unix / linux.
"System Version: "
#endif
<< b_end << wxPlatformInfo::Get().GetOperatingSystemDescription() << line_end;
return out.str();
}
SysInfoDialog::SysInfoDialog()
: wxDialog(NULL, wxID_ANY, _(L("Slic3r Prusa Edition - System Information")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
{
wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
SetBackgroundColour(bgr_clr);
wxBoxSizer* hsizer = new wxBoxSizer(wxHORIZONTAL);
hsizer->SetMinSize(wxSize(600, -1));
auto main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(hsizer, 0, wxEXPAND | wxALL, 10);
// logo
wxBitmap logo_bmp = wxBitmap(from_u8(Slic3r::var("Slic3r_128px.png")), wxBITMAP_TYPE_PNG);
auto *logo = new wxStaticBitmap(this, wxID_ANY, std::move(logo_bmp));
hsizer->Add(logo, 0, wxEXPAND | wxTOP | wxBOTTOM, 15);
wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL);
hsizer->Add(vsizer, 1, wxEXPAND|wxLEFT, 20);
// title
{
wxStaticText* title = new wxStaticText(this, wxID_ANY, SLIC3R_FORK_NAME, wxDefaultPosition, wxDefaultSize);
wxFont title_font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
title_font.SetWeight(wxFONTWEIGHT_BOLD);
title_font.SetFamily(wxFONTFAMILY_ROMAN);
title_font.SetPointSize(14);
title->SetFont(title_font);
vsizer->Add(title, 0, wxALIGN_LEFT | wxTOP, 10);
}
// main_info_text
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
const auto text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
const int fs = font.GetPointSize() - 1;
int size[] = { static_cast<int>(fs*1.5), static_cast<int>(fs*1.4), static_cast<int>(fs*1.3), fs, fs, fs, fs };
wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_NEVER);
{
html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
html->SetBorders(2);
const auto text = wxString::Format(
"<html>"
"<body bgcolor= %s link= %s>"
"<font color=%s>"
"%s"
"</font>"
"</body>"
"</html>", bgr_clr_str, text_clr_str, text_clr_str,
get_main_info(true));
html->SetPage(text);
vsizer->Add(html, 1, wxEXPAND);
}
// opengl_info
wxHtmlWindow* opengl_info_html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO);
{
opengl_info_html->SetMinSize(wxSize(-1, 200));
opengl_info_html->SetFonts(font.GetFaceName(), font.GetFaceName(), size);
opengl_info_html->SetBorders(10);
const auto text = wxString::Format(
"<html>"
"<body bgcolor= %s link= %s>"
"<font color=%s>"
"%s"
"</font>"
"</body>"
"</html>", bgr_clr_str, text_clr_str, text_clr_str,
_3DScene::get_gl_info(true, true));
opengl_info_html->SetPage(text);
main_sizer->Add(opengl_info_html, 1, wxEXPAND | wxBOTTOM, 15);
}
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK);
auto btn_copy_to_clipboard = new wxButton(this, wxID_ANY, "Copy to Clipboard", wxDefaultPosition, wxDefaultSize);
buttons->Insert(0, btn_copy_to_clipboard, 0, wxLEFT, 5);
btn_copy_to_clipboard->Bind(wxEVT_BUTTON, &SysInfoDialog::onCopyToClipboard, this);
this->SetEscapeId(wxID_CLOSE);
this->Bind(wxEVT_BUTTON, &SysInfoDialog::onCloseDialog, this, wxID_OK);
main_sizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
this->Bind(wxEVT_LEFT_DOWN, &SysInfoDialog::onCloseDialog, this);
logo->Bind(wxEVT_LEFT_DOWN, &SysInfoDialog::onCloseDialog, this);
SetSizer(main_sizer);
main_sizer->SetSizeHints(this);
}
void SysInfoDialog::onCopyToClipboard(wxEvent &)
{
wxTheClipboard->Open();
const auto text = get_main_info(false)+"\n"+_3DScene::get_gl_info(false, true);
wxTheClipboard->SetData(new wxTextDataObject(text));
wxTheClipboard->Close();
}
void SysInfoDialog::onCloseDialog(wxEvent &)
{
this->EndModal(wxID_CLOSE);
this->Close();
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,24 @@
#ifndef slic3r_GUI_SysInfoDialog_hpp_
#define slic3r_GUI_SysInfoDialog_hpp_
#include <wx/wx.h>
#include <wx/html/htmlwin.h>
namespace Slic3r {
namespace GUI {
class SysInfoDialog : public wxDialog
{
wxString text_info {wxEmptyString};
public:
SysInfoDialog();
private:
void onCopyToClipboard(wxEvent &);
void onCloseDialog(wxEvent &);
};
} // namespace GUI
} // namespace Slic3r
#endif

2943
src/slic3r/GUI/Tab.cpp Normal file

File diff suppressed because it is too large Load diff

384
src/slic3r/GUI/Tab.hpp Normal file
View file

@ -0,0 +1,384 @@
#ifndef slic3r_Tab_hpp_
#define slic3r_Tab_hpp_
// The "Expert" tab at the right of the main tabbed window.
//
// This file implements following packages:
// Slic3r::GUI::Tab;
// Slic3r::GUI::Tab::Print;
// Slic3r::GUI::Tab::Filament;
// Slic3r::GUI::Tab::Printer;
// Slic3r::GUI::Tab::Page
// - Option page: For example, the Slic3r::GUI::Tab::Print has option pages "Layers and perimeters", "Infill", "Skirt and brim" ...
// Slic3r::GUI::SavePresetWindow
// - Dialog to select a new preset name to store the configuration.
// Slic3r::GUI::Tab::Preset;
// - Single preset item: name, file is default or external.
#include <wx/panel.h>
#include <wx/notebook.h>
#include <wx/scrolwin.h>
#include <wx/sizer.h>
#include <wx/bmpcbox.h>
#include <wx/bmpbuttn.h>
#include <wx/treectrl.h>
#include <wx/imaglist.h>
#include <map>
#include <vector>
#include <memory>
#include "BedShapeDialog.hpp"
#include "Event.hpp"
namespace Slic3r {
namespace GUI {
typedef std::pair<wxBitmap*, std::string> t_icon_description;
typedef std::vector<std::pair<wxBitmap*, std::string>> t_icon_descriptions;
// Single Tab page containing a{ vsizer } of{ optgroups }
// package Slic3r::GUI::Tab::Page;
using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>;
class Page : public wxScrolledWindow
{
wxWindow* m_parent;
wxString m_title;
size_t m_iconID;
wxBoxSizer* m_vsizer;
bool m_show = true;
public:
Page(wxWindow* parent, const wxString title, const int iconID) :
m_parent(parent),
m_title(title),
m_iconID(iconID)
{
Create(m_parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
m_vsizer = new wxBoxSizer(wxVERTICAL);
m_item_color = &wxGetApp().get_label_clr_default();
SetSizer(m_vsizer);
}
~Page(){}
bool m_is_modified_values{ false };
bool m_is_nonsys_values{ true };
public:
std::vector <ConfigOptionsGroupShp> m_optgroups;
DynamicPrintConfig* m_config;
wxBoxSizer* vsizer() const { return m_vsizer; }
wxWindow* parent() const { return m_parent; }
wxString title() const { return m_title; }
size_t iconID() const { return m_iconID; }
void set_config(DynamicPrintConfig* config_in) { m_config = config_in; }
void reload_config();
void update_visibility(ConfigOptionMode mode);
Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const;
bool set_value(const t_config_option_key& opt_key, const boost::any& value);
ConfigOptionsGroupShp new_optgroup(const wxString& title, int noncommon_label_width = -1);
bool set_item_colour(const wxColour *clr) {
if (m_item_color != clr) {
m_item_color = clr;
return true;
}
return false;
}
const wxColour get_item_colour() {
return *m_item_color;
}
bool get_show() const { return m_show; }
protected:
// Color of TreeCtrlItem. The wxColour will be updated only if the new wxColour pointer differs from the currently rendered one.
const wxColour* m_item_color;
};
wxDECLARE_EVENT(EVT_TAB_VALUE_CHANGED, wxCommandEvent);
wxDECLARE_EVENT(EVT_TAB_PRESETS_CHANGED, SimpleEvent);
using PageShp = std::shared_ptr<Page>;
class Tab: public wxPanel
{
wxNotebook* m_parent;
#ifdef __WXOSX__
wxPanel* m_tmp_panel;
int m_size_move = -1;
#endif // __WXOSX__
protected:
Preset::Type m_type;
std::string m_name;
const wxString m_title;
wxBitmapComboBox* m_presets_choice;
wxBitmapButton* m_btn_save_preset;
wxBitmapButton* m_btn_delete_preset;
wxBitmapButton* m_btn_hide_incompatible_presets;
wxBoxSizer* m_hsizer;
wxBoxSizer* m_left_sizer;
wxTreeCtrl* m_treectrl;
wxImageList* m_icons;
wxCheckBox* m_compatible_printers_checkbox;
wxButton* m_compatible_printers_btn;
wxButton* m_undo_btn;
wxButton* m_undo_to_sys_btn;
wxButton* m_question_btn;
wxImageList* m_preset_icons;
// Cached bitmaps.
// A "flag" icon to be displayned next to the preset name in the Tab's combo box.
wxBitmap m_bmp_show_incompatible_presets;
wxBitmap m_bmp_hide_incompatible_presets;
// Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field.
wxBitmap m_bmp_value_lock;
wxBitmap m_bmp_value_unlock;
wxBitmap m_bmp_white_bullet;
// The following bitmap points to either m_bmp_value_unlock or m_bmp_white_bullet, depending on whether the current preset has a parent preset.
wxBitmap *m_bmp_non_system;
// Bitmaps to be shown on the "Undo user changes" button next to each input field.
wxBitmap m_bmp_value_revert;
// wxBitmap m_bmp_value_unmodified;
wxBitmap m_bmp_question;
// Colors for ui "decoration"
wxColour m_sys_label_clr;
wxColour m_modified_label_clr;
wxColour m_default_text_clr;
// Tooltip text for reset buttons (for whole options group)
wxString m_ttg_value_lock;
wxString m_ttg_value_unlock;
wxString m_ttg_white_bullet_ns;
// The following text points to either m_ttg_value_unlock or m_ttg_white_bullet_ns, depending on whether the current preset has a parent preset.
wxString *m_ttg_non_system;
// Tooltip text to be shown on the "Undo user changes" button next to each input field.
wxString m_ttg_white_bullet;
wxString m_ttg_value_revert;
// Tooltip text for reset buttons (for each option in group)
wxString m_tt_value_lock;
wxString m_tt_value_unlock;
// The following text points to either m_tt_value_unlock or m_ttg_white_bullet_ns, depending on whether the current preset has a parent preset.
wxString *m_tt_non_system;
// Tooltip text to be shown on the "Undo user changes" button next to each input field.
wxString m_tt_white_bullet;
wxString m_tt_value_revert;
int m_icon_count;
std::map<std::string, size_t> m_icon_index; // Map from an icon file name to its index
std::vector<PageShp> m_pages;
bool m_disable_tree_sel_changed_event;
bool m_show_incompatible_presets;
std::vector<std::string> m_reload_dependent_tabs = {};
enum OptStatus { osSystemValue = 1, osInitValue = 2 };
std::map<std::string, int> m_options_list;
int m_opt_status_value = 0;
t_icon_descriptions m_icon_descriptions = {};
bool m_is_modified_values{ false };
bool m_is_nonsys_values{ true };
bool m_postpone_update_ui {false};
size_t m_selected_preset_item{ 0 };
void set_type();
public:
PresetBundle* m_preset_bundle;
bool m_show_btn_incompatible_presets = false;
PresetCollection* m_presets;
DynamicPrintConfig* m_config;
ogStaticText* m_parent_preset_description_line;
wxStaticText* m_colored_Label = nullptr;
public:
Tab() {}
Tab(wxNotebook* parent, const wxString& title, const char* name) :
m_parent(parent), m_title(title), m_name(name) {
Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL, name);
set_type();
wxGetApp().tabs_list.push_back(this);
}
~Tab(){
wxGetApp().delete_tab_from_list(this);
}
wxWindow* parent() const { return m_parent; }
wxString title() const { return m_title; }
std::string name() const { return m_name; }
Preset::Type type() const { return m_type; }
void create_preset_tab();
void load_current_preset();
void rebuild_page_tree(bool tree_sel_change_event = false);
void update_page_tree_visibility();
void select_preset(std::string preset_name = "");
bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = "");
wxSizer* compatible_printers_widget(wxWindow* parent, wxCheckBox** checkbox, wxButton** btn);
void load_key_value(const std::string& opt_key, const boost::any& value, bool saved_value = false);
void reload_compatible_printers_widget();
void OnTreeSelChange(wxTreeEvent& event);
void OnKeyDown(wxKeyEvent& event);
void save_preset(std::string name = "");
void delete_preset();
void toggle_show_hide_incompatible();
void update_show_hide_incompatible_button();
void update_ui_from_settings();
void update_labels_colour();
void update_changed_ui();
void get_sys_and_mod_flags(const std::string& opt_key, bool& sys_page, bool& modified_page);
void update_changed_tree_ui();
void update_undo_buttons();
void on_roll_back_value(const bool to_sys = false);
PageShp add_options_page(const wxString& title, const std::string& icon, bool is_extruder_pages = false);
virtual void OnActivate();
virtual void on_preset_loaded(){}
virtual void build() = 0;
virtual void update() = 0;
virtual void init_options_list();
void load_initial_data();
void update_dirty();
void update_tab_ui();
void load_config(const DynamicPrintConfig& config);
virtual void reload_config();
void update_visibility(ConfigOptionMode mode);
Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const;
bool set_value(const t_config_option_key& opt_key, const boost::any& value);
wxSizer* description_line_widget(wxWindow* parent, ogStaticText** StaticText);
bool current_preset_is_dirty();
DynamicPrintConfig* get_config() { return m_config; }
PresetCollection* get_presets() { return m_presets; }
std::vector<std::string> get_dependent_tabs() { return m_reload_dependent_tabs; }
size_t get_selected_preset_item() { return m_selected_preset_item; }
void on_value_change(const std::string& opt_key, const boost::any& value);
void update_wiping_button_visibility();
protected:
void on_presets_changed();
void update_preset_description_line();
void update_frequently_changed_parameters();
void fill_icon_descriptions();
void set_tooltips_text();
};
//Slic3r::GUI::Tab::Print;
class TabPrint : public Tab
{
public:
TabPrint() {}
TabPrint(wxNotebook* parent) :
Tab(parent, _(L("Print Settings")), "print") {}
~TabPrint(){}
ogStaticText* m_recommended_thin_wall_thickness_description_line;
bool m_support_material_overhangs_queried = false;
void build() override;
void reload_config() override;
void update() override;
void OnActivate() override;
};
//Slic3r::GUI::Tab::Filament;
class TabFilament : public Tab
{
ogStaticText* m_volumetric_speed_description_line;
ogStaticText* m_cooling_description_line;
public:
TabFilament() {}
TabFilament(wxNotebook* parent) :
Tab(parent, _(L("Filament Settings")), "filament") {}
~TabFilament(){}
void build() override;
void reload_config() override;
void update() override;
void OnActivate() override;
};
//Slic3r::GUI::Tab::Printer;
class TabPrinter : public Tab
{
bool m_has_single_extruder_MM_page = false;
bool m_use_silent_mode = false;
void append_option_line(ConfigOptionsGroupShp optgroup, const std::string opt_key);
bool m_rebuild_kinematics_page = false;
std::vector<PageShp> m_pages_fff;
std::vector<PageShp> m_pages_sla;
public:
wxButton* m_serial_test_btn;
wxButton* m_print_host_test_btn;
wxButton* m_printhost_browse_btn;
size_t m_extruders_count;
size_t m_extruders_count_old = 0;
size_t m_initial_extruders_count;
size_t m_sys_extruders_count;
PrinterTechnology m_printer_technology = ptFFF;
TabPrinter() {}
TabPrinter(wxNotebook* parent) : Tab(parent, _(L("Printer Settings")), "printer") {}
~TabPrinter(){}
void build() override;
void build_fff();
void build_sla();
void update() override;
void update_fff();
void update_sla();
void update_pages(); // update m_pages according to printer technology
void update_serial_ports();
void extruders_count_changed(size_t extruders_count);
PageShp build_kinematics_page();
void build_extruder_pages();
void on_preset_loaded() override;
void init_options_list() override;
};
class TabSLAMaterial : public Tab
{
public:
TabSLAMaterial() {}
TabSLAMaterial(wxNotebook* parent) :
Tab(parent, _(L("SLA Material Settings")), "sla_material") {}
~TabSLAMaterial(){}
void build() override;
void update() override;
void init_options_list() override;
};
class SavePresetWindow :public wxDialog
{
public:
SavePresetWindow(wxWindow* parent) :wxDialog(parent, wxID_ANY, _(L("Save preset"))){}
~SavePresetWindow(){}
std::string m_chosen_name;
wxComboBox* m_combo;
void build(const wxString& title, const std::string& default_name, std::vector<std::string> &values);
void accept();
std::string get_name() { return m_chosen_name; }
};
} // GUI
} // Slic3r
#endif /* slic3r_Tab_hpp_ */

View file

@ -0,0 +1,20 @@
#include "TabIface.hpp"
#include "Tab.hpp"
namespace Slic3r {
void TabIface::load_current_preset() { m_tab->load_current_preset(); }
void TabIface::update_tab_ui() { m_tab->update_tab_ui(); }
void TabIface::update_ui_from_settings() { m_tab->update_ui_from_settings();}
void TabIface::select_preset(char* name) { m_tab->select_preset(name);}
void TabIface::load_config(DynamicPrintConfig* config) { m_tab->load_config(*config);}
void TabIface::load_key_value(char* opt_key, char* value){ m_tab->load_key_value(opt_key, static_cast<std::string>(value)); }
bool TabIface::current_preset_is_dirty() { return m_tab->current_preset_is_dirty();}
void TabIface::OnActivate() { return m_tab->OnActivate();}
size_t TabIface::get_selected_preset_item() { return m_tab->get_selected_preset_item(); }
std::string TabIface::title() { return m_tab->title().ToUTF8().data(); }
DynamicPrintConfig* TabIface::get_config() { return m_tab->get_config(); }
PresetCollection* TabIface::get_presets() { return m_tab!=nullptr ? m_tab->get_presets() : nullptr; }
std::vector<std::string> TabIface::get_dependent_tabs() { return m_tab->get_dependent_tabs(); }
}; // namespace Slic3r

View file

@ -0,0 +1,41 @@
#ifndef slic3r_TabIface_hpp_
#define slic3r_TabIface_hpp_
#include <vector>
#include <string>
namespace Slic3r {
class DynamicPrintConfig;
class PresetCollection;
namespace GUI {
class Tab;
}
class TabIface {
public:
TabIface() : m_tab(nullptr) {}
TabIface(GUI::Tab *tab) : m_tab(tab) {}
// TabIface(const TabIface &rhs) : m_tab(rhs.m_tab) {}
void load_current_preset();
void update_tab_ui();
void update_ui_from_settings();
void select_preset(char* name);
std::string title();
void load_config(DynamicPrintConfig* config);
void load_key_value(char* opt_key, char* value);
bool current_preset_is_dirty();
void OnActivate();
DynamicPrintConfig* get_config();
PresetCollection* get_presets();
std::vector<std::string> get_dependent_tabs();
size_t get_selected_preset_item();
protected:
GUI::Tab *m_tab;
}; // namespace GUI
}; // namespace Slic3r
#endif /* slic3r_TabIface_hpp_ */

View file

@ -0,0 +1,196 @@
#include "UpdateDialogs.hpp"
#include <wx/settings.h>
#include <wx/sizer.h>
#include <wx/event.h>
#include <wx/stattext.h>
#include <wx/button.h>
#include <wx/hyperlink.h>
#include <wx/statbmp.h>
#include <wx/checkbox.h>
#include "libslic3r/libslic3r.h"
#include "libslic3r/Utils.hpp"
#include "GUI.hpp"
#include "ConfigWizard.hpp"
namespace Slic3r {
namespace GUI {
static const std::string CONFIG_UPDATE_WIKI_URL("https://github.com/prusa3d/Slic3r/wiki/Slic3r-PE-1.40-configuration-update");
// MsgUpdateSlic3r
MsgUpdateSlic3r::MsgUpdateSlic3r(const Semver &ver_current, const Semver &ver_online) :
MsgDialog(nullptr, _(L("Update available")), _(L("New version of Slic3r PE is available"))),
ver_current(ver_current),
ver_online(ver_online)
{
const auto url = wxString::Format("https://github.com/prusa3d/Slic3r/releases/tag/version_%s", ver_online.to_string());
auto *link = new wxHyperlinkCtrl(this, wxID_ANY, url, url);
auto *text = new wxStaticText(this, wxID_ANY, _(L("To download, follow the link below.")));
const auto link_width = link->GetSize().GetWidth();
text->Wrap(CONTENT_WIDTH > link_width ? CONTENT_WIDTH : link_width);
content_sizer->Add(text);
content_sizer->AddSpacer(VERT_SPACING);
auto *versions = new wxFlexGridSizer(2, 0, VERT_SPACING);
versions->Add(new wxStaticText(this, wxID_ANY, _(L("Current version:"))));
versions->Add(new wxStaticText(this, wxID_ANY, ver_current.to_string()));
versions->Add(new wxStaticText(this, wxID_ANY, _(L("New version:"))));
versions->Add(new wxStaticText(this, wxID_ANY, ver_online.to_string()));
content_sizer->Add(versions);
content_sizer->AddSpacer(VERT_SPACING);
content_sizer->Add(link);
content_sizer->AddSpacer(2*VERT_SPACING);
cbox = new wxCheckBox(this, wxID_ANY, _(L("Don't notify about new releases any more")));
content_sizer->Add(cbox);
content_sizer->AddSpacer(VERT_SPACING);
Fit();
}
MsgUpdateSlic3r::~MsgUpdateSlic3r() {}
bool MsgUpdateSlic3r::disable_version_check() const
{
return cbox->GetValue();
}
// MsgUpdateConfig
MsgUpdateConfig::MsgUpdateConfig(const std::unordered_map<std::string, std::string> &updates) :
MsgDialog(nullptr, _(L("Configuration update")), _(L("Configuration update is available")), wxID_NONE)
{
auto *text = new wxStaticText(this, wxID_ANY, _(L(
"Would you like to install it?\n\n"
"Note that a full configuration snapshot will be created first. It can then be restored at any time "
"should there be a problem with the new version.\n\n"
"Updated configuration bundles:"
)));
text->Wrap(CONTENT_WIDTH);
content_sizer->Add(text);
content_sizer->AddSpacer(VERT_SPACING);
auto *versions = new wxFlexGridSizer(2, 0, VERT_SPACING);
for (const auto &update : updates) {
auto *text_vendor = new wxStaticText(this, wxID_ANY, update.first);
text_vendor->SetFont(boldfont);
versions->Add(text_vendor);
versions->Add(new wxStaticText(this, wxID_ANY, update.second));
}
content_sizer->Add(versions);
content_sizer->AddSpacer(2*VERT_SPACING);
auto *btn_cancel = new wxButton(this, wxID_CANCEL);
btn_sizer->Add(btn_cancel);
btn_sizer->AddSpacer(HORIZ_SPACING);
auto *btn_ok = new wxButton(this, wxID_OK);
btn_sizer->Add(btn_ok);
btn_ok->SetFocus();
Fit();
}
MsgUpdateConfig::~MsgUpdateConfig() {}
// MsgDataIncompatible
MsgDataIncompatible::MsgDataIncompatible(const std::unordered_map<std::string, wxString> &incompats) :
MsgDialog(nullptr, _(L("Slic3r incompatibility")), _(L("Slic3r configuration is incompatible")), wxBitmap(from_u8(Slic3r::var("Slic3r_192px_grayscale.png")), wxBITMAP_TYPE_PNG), wxID_NONE)
{
auto *text = new wxStaticText(this, wxID_ANY, _(L(
"This version of Slic3r PE is not compatible with currently installed configuration bundles.\n"
"This probably happened as a result of running an older Slic3r PE after using a newer one.\n\n"
"You may either exit Slic3r and try again with a newer version, or you may re-run the initial configuration. "
"Doing so will create a backup snapshot of the existing configuration before installing files compatible with this Slic3r.\n"
)));
text->Wrap(CONTENT_WIDTH);
content_sizer->Add(text);
auto *text2 = new wxStaticText(this, wxID_ANY, wxString::Format(_(L("This Slic3r PE version: %s")), SLIC3R_VERSION));
text2->Wrap(CONTENT_WIDTH);
content_sizer->Add(text2);
content_sizer->AddSpacer(VERT_SPACING);
auto *text3 = new wxStaticText(this, wxID_ANY, _(L("Incompatible bundles:")));
text3->Wrap(CONTENT_WIDTH);
content_sizer->Add(text3);
content_sizer->AddSpacer(VERT_SPACING);
auto *versions = new wxFlexGridSizer(2, 0, VERT_SPACING);
for (const auto &incompat : incompats) {
auto *text_vendor = new wxStaticText(this, wxID_ANY, incompat.first);
text_vendor->SetFont(boldfont);
versions->Add(text_vendor);
versions->Add(new wxStaticText(this, wxID_ANY, incompat.second));
}
content_sizer->Add(versions);
content_sizer->AddSpacer(2*VERT_SPACING);
auto *btn_exit = new wxButton(this, wxID_EXIT, _(L("Exit Slic3r")));
btn_sizer->Add(btn_exit);
btn_sizer->AddSpacer(HORIZ_SPACING);
auto *btn_reconf = new wxButton(this, wxID_REPLACE, _(L("Re-configure")));
btn_sizer->Add(btn_reconf);
btn_exit->SetFocus();
auto exiter = [this](const wxCommandEvent& evt) { this->EndModal(evt.GetId()); };
btn_exit->Bind(wxEVT_BUTTON, exiter);
btn_reconf->Bind(wxEVT_BUTTON, exiter);
Fit();
}
MsgDataIncompatible::~MsgDataIncompatible() {}
// MsgDataLegacy
MsgDataLegacy::MsgDataLegacy() :
MsgDialog(nullptr, _(L("Configuration update")), _(L("Configuration update")))
{
auto *text = new wxStaticText(this, wxID_ANY, wxString::Format(
_(L(
"Slic3r PE now uses an updated configuration structure.\n\n"
"So called 'System presets' have been introduced, which hold the built-in default settings for various "
"printers. These System presets cannot be modified, instead, users now may create their "
"own presets inheriting settings from one of the System presets.\n"
"An inheriting preset may either inherit a particular value from its parent or override it with a customized value.\n\n"
"Please proceed with the %s that follows to set up the new presets "
"and to choose whether to enable automatic preset updates."
)),
ConfigWizard::name()
));
text->Wrap(CONTENT_WIDTH);
content_sizer->Add(text);
content_sizer->AddSpacer(VERT_SPACING);
auto *text2 = new wxStaticText(this, wxID_ANY, _(L("For more information please visit our wiki page:")));
static const wxString url("https://github.com/prusa3d/Slic3r/wiki/Slic3r-PE-1.40-configuration-update");
// The wiki page name is intentionally not localized:
auto *link = new wxHyperlinkCtrl(this, wxID_ANY, "Slic3r PE 1.40 configuration update", CONFIG_UPDATE_WIKI_URL);
content_sizer->Add(text2);
content_sizer->Add(link);
content_sizer->AddSpacer(VERT_SPACING);
Fit();
}
MsgDataLegacy::~MsgDataLegacy() {}
}
}

View file

@ -0,0 +1,81 @@
#ifndef slic3r_UpdateDialogs_hpp_
#define slic3r_UpdateDialogs_hpp_
#include <string>
#include <unordered_map>
#include "slic3r/Utils/Semver.hpp"
#include "MsgDialog.hpp"
class wxBoxSizer;
class wxCheckBox;
namespace Slic3r {
namespace GUI {
// A confirmation dialog listing configuration updates
class MsgUpdateSlic3r : public MsgDialog
{
public:
MsgUpdateSlic3r(const Semver &ver_current, const Semver &ver_online);
MsgUpdateSlic3r(MsgUpdateSlic3r &&) = delete;
MsgUpdateSlic3r(const MsgUpdateSlic3r &) = delete;
MsgUpdateSlic3r &operator=(MsgUpdateSlic3r &&) = delete;
MsgUpdateSlic3r &operator=(const MsgUpdateSlic3r &) = delete;
virtual ~MsgUpdateSlic3r();
// Tells whether the user checked the "don't bother me again" checkbox
bool disable_version_check() const;
private:
const Semver &ver_current;
const Semver &ver_online;
wxCheckBox *cbox;
};
// Confirmation dialog informing about configuration update. Lists updated bundles & their versions.
class MsgUpdateConfig : public MsgDialog
{
public:
// updates is a map of "vendor name" -> "version (comment)"
MsgUpdateConfig(const std::unordered_map<std::string, std::string> &updates);
MsgUpdateConfig(MsgUpdateConfig &&) = delete;
MsgUpdateConfig(const MsgUpdateConfig &) = delete;
MsgUpdateConfig &operator=(MsgUpdateConfig &&) = delete;
MsgUpdateConfig &operator=(const MsgUpdateConfig &) = delete;
~MsgUpdateConfig();
};
// Informs about currently installed bundles not being compatible with the running Slic3r. Asks about action.
class MsgDataIncompatible : public MsgDialog
{
public:
// incompats is a map of "vendor name" -> "version restrictions"
MsgDataIncompatible(const std::unordered_map<std::string, wxString> &incompats);
MsgDataIncompatible(MsgDataIncompatible &&) = delete;
MsgDataIncompatible(const MsgDataIncompatible &) = delete;
MsgDataIncompatible &operator=(MsgDataIncompatible &&) = delete;
MsgDataIncompatible &operator=(const MsgDataIncompatible &) = delete;
~MsgDataIncompatible();
};
// Informs about a legacy data directory - an update from Slic3r PE < 1.40
class MsgDataLegacy : public MsgDialog
{
public:
MsgDataLegacy();
MsgDataLegacy(MsgDataLegacy &&) = delete;
MsgDataLegacy(const MsgDataLegacy &) = delete;
MsgDataLegacy &operator=(MsgDataLegacy &&) = delete;
MsgDataLegacy &operator=(const MsgDataLegacy &) = delete;
~MsgDataLegacy();
};
}
}
#endif

16
src/slic3r/GUI/Widget.hpp Normal file
View file

@ -0,0 +1,16 @@
#ifndef WIDGET_HPP
#define WIDGET_HPP
#include <wx/wxprec.h>
#ifndef WX_PRECOM
#include <wx/wx.h>
#endif
class Widget {
protected:
wxSizer* _sizer;
public:
Widget(): _sizer(nullptr) { }
bool valid() const { return _sizer != nullptr; }
wxSizer* sizer() const { return _sizer; }
};
#endif

View file

@ -0,0 +1,338 @@
#include <algorithm>
#include <sstream>
#include "WipeTowerDialog.hpp"
#include "GUI.hpp"
#include <wx/sizer.h>
RammingDialog::RammingDialog(wxWindow* parent,const std::string& parameters)
: wxDialog(parent, wxID_ANY, _(L("Ramming customization")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/)
{
m_panel_ramming = new RammingPanel(this,parameters);
// Not found another way of getting the background colours of RammingDialog, RammingPanel and Chart correct than setting
// them all explicitely. Reading the parent colour yielded colour that didn't really match it, no wxSYS_COLOUR_... matched
// colour used for the dialog. Same issue (and "solution") here : https://forums.wxwidgets.org/viewtopic.php?f=1&t=39608
// Whoever can fix this, feel free to do so.
this-> SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK));
m_panel_ramming->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK));
m_panel_ramming->Show(true);
this->Show();
auto main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(m_panel_ramming, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5);
main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxTOP | wxBOTTOM, 10);
SetSizer(main_sizer);
main_sizer->SetSizeHints(this);
this->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& e) { EndModal(wxCANCEL); });
this->Bind(wxEVT_BUTTON,[this](wxCommandEvent&) {
m_output_data = m_panel_ramming->get_parameters();
EndModal(wxID_OK);
},wxID_OK);
this->Show();
wxMessageDialog(this,_(L("Ramming denotes the rapid extrusion just before a tool change in a single-extruder MM printer. Its purpose is to "
"properly shape the end of the unloaded filament so it does not prevent insertion of the new filament and can itself "
"be reinserted later. This phase is important and different materials can require different extrusion speeds to get "
"the good shape. For this reason, the extrusion rates during ramming are adjustable.\n\nThis is an expert-level "
"setting, incorrect adjustment will likely lead to jams, extruder wheel grinding into filament etc.")),_(L("Warning")),wxOK|wxICON_EXCLAMATION).ShowModal();
}
RammingPanel::RammingPanel(wxWindow* parent, const std::string& parameters)
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize/*,wxPoint(50,50), wxSize(800,350),wxBORDER_RAISED*/)
{
auto sizer_chart = new wxBoxSizer(wxVERTICAL);
auto sizer_param = new wxBoxSizer(wxVERTICAL);
std::stringstream stream{ parameters };
stream >> m_ramming_line_width_multiplicator >> m_ramming_step_multiplicator;
int ramming_speed_size = 0;
float dummy = 0.f;
while (stream >> dummy)
++ramming_speed_size;
stream.clear();
stream.get();
std::vector<std::pair<float, float>> buttons;
float x = 0.f;
float y = 0.f;
while (stream >> x >> y)
buttons.push_back(std::make_pair(x, y));
m_chart = new Chart(this, wxRect(10, 10, 480, 360), buttons, ramming_speed_size, 0.25f);
m_chart->SetBackgroundColour(parent->GetBackgroundColour()); // see comment in RammingDialog constructor
sizer_chart->Add(m_chart, 0, wxALL, 5);
m_widget_time = new wxSpinCtrlDouble(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,0.,5.0,3.,0.5);
m_widget_volume = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,0,10000,0);
m_widget_ramming_line_width_multiplicator = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,10,200,100);
m_widget_ramming_step_multiplicator = new wxSpinCtrl(this,wxID_ANY,wxEmptyString,wxDefaultPosition,wxSize(75, -1),wxSP_ARROW_KEYS,10,200,100);
auto gsizer_param = new wxFlexGridSizer(2, 5, 15);
gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Total ramming time")) + " (" + _(L("s")) + "):")), 0, wxALIGN_CENTER_VERTICAL);
gsizer_param->Add(m_widget_time);
gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Total rammed volume")) + " (" + _(L("mm")) + wxString("³):", wxConvUTF8))), 0, wxALIGN_CENTER_VERTICAL);
gsizer_param->Add(m_widget_volume);
gsizer_param->AddSpacer(20);
gsizer_param->AddSpacer(20);
gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Ramming line width")) + " (%):")), 0, wxALIGN_CENTER_VERTICAL);
gsizer_param->Add(m_widget_ramming_line_width_multiplicator);
gsizer_param->Add(new wxStaticText(this, wxID_ANY, wxString(_(L("Ramming line spacing")) + " (%):")), 0, wxALIGN_CENTER_VERTICAL);
gsizer_param->Add(m_widget_ramming_step_multiplicator);
sizer_param->Add(gsizer_param, 0, wxTOP, 100);
m_widget_time->SetValue(m_chart->get_time());
m_widget_time->SetDigits(2);
m_widget_volume->SetValue(m_chart->get_volume());
m_widget_volume->Disable();
m_widget_ramming_line_width_multiplicator->SetValue(m_ramming_line_width_multiplicator);
m_widget_ramming_step_multiplicator->SetValue(m_ramming_step_multiplicator);
m_widget_ramming_step_multiplicator->Bind(wxEVT_TEXT,[this](wxCommandEvent&) { line_parameters_changed(); });
m_widget_ramming_line_width_multiplicator->Bind(wxEVT_TEXT,[this](wxCommandEvent&) { line_parameters_changed(); });
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(sizer_chart, 0, wxALL, 5);
sizer->Add(sizer_param, 0, wxALL, 10);
sizer->SetSizeHints(this);
SetSizer(sizer);
m_widget_time->Bind(wxEVT_TEXT,[this](wxCommandEvent&) {m_chart->set_xy_range(m_widget_time->GetValue(),-1);});
m_widget_time->Bind(wxEVT_CHAR,[](wxKeyEvent&){}); // do nothing - prevents the user to change the value
m_widget_volume->Bind(wxEVT_CHAR,[](wxKeyEvent&){}); // do nothing - prevents the user to change the value
Bind(EVT_WIPE_TOWER_CHART_CHANGED,[this](wxCommandEvent&) {m_widget_volume->SetValue(m_chart->get_volume()); m_widget_time->SetValue(m_chart->get_time());} );
Refresh(this);
}
void RammingPanel::line_parameters_changed() {
m_ramming_line_width_multiplicator = m_widget_ramming_line_width_multiplicator->GetValue();
m_ramming_step_multiplicator = m_widget_ramming_step_multiplicator->GetValue();
}
std::string RammingPanel::get_parameters()
{
std::vector<float> speeds = m_chart->get_ramming_speed(0.25f);
std::vector<std::pair<float,float>> buttons = m_chart->get_buttons();
std::stringstream stream;
stream << m_ramming_line_width_multiplicator << " " << m_ramming_step_multiplicator;
for (const float& speed_value : speeds)
stream << " " << speed_value;
stream << "|";
for (const auto& button : buttons)
stream << " " << button.first << " " << button.second;
return stream.str();
}
#define ITEM_WIDTH 60
// Parent dialog for purging volume adjustments - it fathers WipingPanel widget (that contains all controls) and a button to toggle simple/advanced mode:
WipingDialog::WipingDialog(wxWindow* parent,const std::vector<float>& matrix, const std::vector<float>& extruders)
: wxDialog(parent, wxID_ANY, _(L("Wipe tower - Purging volume adjustment")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE/* | wxRESIZE_BORDER*/)
{
auto widget_button = new wxButton(this,wxID_ANY,"-",wxPoint(0,0),wxDefaultSize);
m_panel_wiping = new WipingPanel(this,matrix,extruders, widget_button);
auto main_sizer = new wxBoxSizer(wxVERTICAL);
// set min sizer width according to extruders count
const auto sizer_width = (int)((sqrt(matrix.size()) + 2.8)*ITEM_WIDTH);
main_sizer->SetMinSize(wxSize(sizer_width, -1));
main_sizer->Add(m_panel_wiping, 0, wxEXPAND | wxALL, 5);
main_sizer->Add(widget_button, 0, wxALIGN_CENTER_HORIZONTAL | wxCENTER | wxBOTTOM, 5);
main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
SetSizer(main_sizer);
main_sizer->SetSizeHints(this);
this->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& e) { EndModal(wxCANCEL); });
this->Bind(wxEVT_BUTTON,[this](wxCommandEvent&) { // if OK button is clicked..
m_output_matrix = m_panel_wiping->read_matrix_values(); // ..query wiping panel and save returned values
m_output_extruders = m_panel_wiping->read_extruders_values(); // so they can be recovered later by calling get_...()
EndModal(wxID_OK);
},wxID_OK);
this->Show();
}
// This function allows to "play" with sizers parameters (like align or border)
void WipingPanel::format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& info, const wxString& table_title, int table_lshift/*=0*/)
{
sizer->Add(new wxStaticText(page, wxID_ANY, info,wxDefaultPosition,wxSize(0,50)), 0, wxEXPAND | wxLEFT, 15);
auto table_sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(table_sizer, 0, wxALIGN_CENTER | wxCENTER, table_lshift);
table_sizer->Add(new wxStaticText(page, wxID_ANY, table_title), 0, wxALIGN_CENTER | wxTOP, 50);
table_sizer->Add(grid_sizer, 0, wxALIGN_CENTER | wxTOP, 10);
}
// This panel contains all control widgets for both simple and advanced mode (these reside in separate sizers)
WipingPanel::WipingPanel(wxWindow* parent, const std::vector<float>& matrix, const std::vector<float>& extruders, wxButton* widget_button)
: wxPanel(parent,wxID_ANY, wxDefaultPosition, wxDefaultSize/*,wxBORDER_RAISED*/)
{
m_widget_button = widget_button; // pointer to the button in parent dialog
m_widget_button->Bind(wxEVT_BUTTON,[this](wxCommandEvent&){ toggle_advanced(true); });
m_number_of_extruders = (int)(sqrt(matrix.size())+0.001);
// Create two switched panels with their own sizers
m_sizer_simple = new wxBoxSizer(wxVERTICAL);
m_sizer_advanced = new wxBoxSizer(wxVERTICAL);
m_page_simple = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
m_page_advanced = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL);
m_page_simple->SetSizer(m_sizer_simple);
m_page_advanced->SetSizer(m_sizer_advanced);
auto gridsizer_simple = new wxGridSizer(3, 5, 10);
m_gridsizer_advanced = new wxGridSizer(m_number_of_extruders+1, 5, 1);
// First create controls for advanced mode and assign them to m_page_advanced:
for (unsigned int i = 0; i < m_number_of_extruders; ++i) {
edit_boxes.push_back(std::vector<wxTextCtrl*>(0));
for (unsigned int j = 0; j < m_number_of_extruders; ++j) {
edit_boxes.back().push_back(new wxTextCtrl(m_page_advanced, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH, -1)));
if (i == j)
edit_boxes[i][j]->Disable();
else
edit_boxes[i][j]->SetValue(wxString("") << int(matrix[m_number_of_extruders*j + i]));
}
}
m_gridsizer_advanced->Add(new wxStaticText(m_page_advanced, wxID_ANY, wxString("")));
for (unsigned int i = 0; i < m_number_of_extruders; ++i)
m_gridsizer_advanced->Add(new wxStaticText(m_page_advanced, wxID_ANY, wxString("") << i + 1), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
for (unsigned int i = 0; i < m_number_of_extruders; ++i) {
m_gridsizer_advanced->Add(new wxStaticText(m_page_advanced, wxID_ANY, wxString("") << i + 1), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
for (unsigned int j = 0; j < m_number_of_extruders; ++j)
m_gridsizer_advanced->Add(edit_boxes[j][i], 0);
}
// collect and format sizer
format_sizer(m_sizer_advanced, m_page_advanced, m_gridsizer_advanced,
_(L("Here you can adjust required purging volume (mm³) for any given pair of tools.")),
_(L("Extruder changed to")));
// Hide preview page before new page creating
// It allows to do that from a beginning of the main panel
m_page_advanced->Hide();
// Now the same for simple mode:
gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString("")), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString(_(L("unloaded")))), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
gridsizer_simple->Add(new wxStaticText(m_page_simple,wxID_ANY,wxString(_(L("loaded")))), 0, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
for (unsigned int i=0;i<m_number_of_extruders;++i) {
m_old.push_back(new wxSpinCtrl(m_page_simple,wxID_ANY,wxEmptyString,wxDefaultPosition, wxSize(80, -1),wxSP_ARROW_KEYS|wxALIGN_RIGHT,0,300,extruders[2*i]));
m_new.push_back(new wxSpinCtrl(m_page_simple,wxID_ANY,wxEmptyString,wxDefaultPosition, wxSize(80, -1),wxSP_ARROW_KEYS|wxALIGN_RIGHT,0,300,extruders[2*i+1]));
gridsizer_simple->Add(new wxStaticText(m_page_simple, wxID_ANY, wxString(_(L("Tool #"))) << i + 1 << ": "), 0, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
gridsizer_simple->Add(m_old.back(),0);
gridsizer_simple->Add(m_new.back(),0);
}
// collect and format sizer
format_sizer(m_sizer_simple, m_page_simple, gridsizer_simple,
_(L("Total purging volume is calculated by summing two values below, depending on which tools are loaded/unloaded.")),
_(L("Volume to purge (mm³) when the filament is being")), 50);
m_sizer = new wxBoxSizer(wxVERTICAL);
m_sizer->Add(m_page_simple, 0, wxEXPAND | wxALL, 25);
m_sizer->Add(m_page_advanced, 0, wxEXPAND | wxALL, 25);
m_sizer->SetSizeHints(this);
SetSizer(m_sizer);
toggle_advanced(); // to show/hide what is appropriate
m_page_advanced->Bind(wxEVT_PAINT,[this](wxPaintEvent&) {
wxPaintDC dc(m_page_advanced);
int y_pos = 0.5 * (edit_boxes[0][0]->GetPosition().y + edit_boxes[0][edit_boxes.size()-1]->GetPosition().y + edit_boxes[0][edit_boxes.size()-1]->GetSize().y);
wxString label = _(L("From"));
int text_width = 0;
int text_height = 0;
dc.GetTextExtent(label,&text_width,&text_height);
int xpos = m_gridsizer_advanced->GetPosition().x;
dc.DrawRotatedText(label,xpos-text_height,y_pos + text_width/2.f,90);
});
}
// Reads values from the (advanced) wiping matrix:
std::vector<float> WipingPanel::read_matrix_values() {
if (!m_advanced)
fill_in_matrix();
std::vector<float> output;
for (unsigned int i=0;i<m_number_of_extruders;++i) {
for (unsigned int j=0;j<m_number_of_extruders;++j) {
double val = 0.;
edit_boxes[j][i]->GetValue().ToDouble(&val);
output.push_back((float)val);
}
}
return output;
}
// Reads values from simple mode to save them for next time:
std::vector<float> WipingPanel::read_extruders_values() {
std::vector<float> output;
for (unsigned int i=0;i<m_number_of_extruders;++i) {
output.push_back(m_old[i]->GetValue());
output.push_back(m_new[i]->GetValue());
}
return output;
}
// This updates the "advanced" matrix based on values from "simple" mode
void WipingPanel::fill_in_matrix() {
for (unsigned i=0;i<m_number_of_extruders;++i) {
for (unsigned j=0;j<m_number_of_extruders;++j) {
if (i==j) continue;
edit_boxes[j][i]->SetValue(wxString("")<< (m_old[i]->GetValue() + m_new[j]->GetValue()));
}
}
}
// Function to check if simple and advanced settings are matching
bool WipingPanel::advanced_matches_simple() {
for (unsigned i=0;i<m_number_of_extruders;++i) {
for (unsigned j=0;j<m_number_of_extruders;++j) {
if (i==j) continue;
if (edit_boxes[j][i]->GetValue() != (wxString("")<< (m_old[i]->GetValue() + m_new[j]->GetValue())))
return false;
}
}
return true;
}
// Switches the dialog from simple to advanced mode and vice versa
void WipingPanel::toggle_advanced(bool user_action) {
if (m_advanced && !advanced_matches_simple() && user_action) {
if (wxMessageDialog(this,wxString(_(L("Switching to simple settings will discard changes done in the advanced mode!\n\nDo you want to proceed?"))),
wxString(_(L("Warning"))),wxYES_NO|wxICON_EXCLAMATION).ShowModal() != wxID_YES)
return;
}
if (user_action)
m_advanced = !m_advanced; // user demands a change -> toggle
else
m_advanced = !advanced_matches_simple(); // if called from constructor, show what is appropriate
(m_advanced ? m_page_advanced : m_page_simple)->Show();
(!m_advanced ? m_page_advanced : m_page_simple)->Hide();
m_widget_button->SetLabel(m_advanced ? _(L("Show simplified settings")) : _(L("Show advanced settings")));
if (m_advanced)
if (user_action) fill_in_matrix(); // otherwise keep values loaded from config
m_sizer->Layout();
Refresh();
}

View file

@ -0,0 +1,90 @@
#ifndef _WIPE_TOWER_DIALOG_H_
#define _WIPE_TOWER_DIALOG_H_
#include <wx/spinctrl.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/checkbox.h>
#include <wx/msgdlg.h>
#include "RammingChart.hpp"
class RammingPanel : public wxPanel {
public:
RammingPanel(wxWindow* parent);
RammingPanel(wxWindow* parent,const std::string& data);
std::string get_parameters();
private:
Chart* m_chart = nullptr;
wxSpinCtrl* m_widget_volume = nullptr;
wxSpinCtrl* m_widget_ramming_line_width_multiplicator = nullptr;
wxSpinCtrl* m_widget_ramming_step_multiplicator = nullptr;
wxSpinCtrlDouble* m_widget_time = nullptr;
int m_ramming_step_multiplicator;
int m_ramming_line_width_multiplicator;
void line_parameters_changed();
};
class RammingDialog : public wxDialog {
public:
RammingDialog(wxWindow* parent,const std::string& parameters);
std::string get_parameters() { return m_output_data; }
private:
RammingPanel* m_panel_ramming = nullptr;
std::string m_output_data;
};
class WipingPanel : public wxPanel {
public:
WipingPanel(wxWindow* parent, const std::vector<float>& matrix, const std::vector<float>& extruders, wxButton* widget_button);
std::vector<float> read_matrix_values();
std::vector<float> read_extruders_values();
void toggle_advanced(bool user_action = false);
void format_sizer(wxSizer* sizer, wxPanel* page, wxGridSizer* grid_sizer, const wxString& info, const wxString& table_title, int table_lshift=0);
private:
void fill_in_matrix();
bool advanced_matches_simple();
std::vector<wxSpinCtrl*> m_old;
std::vector<wxSpinCtrl*> m_new;
std::vector<std::vector<wxTextCtrl*>> edit_boxes;
unsigned int m_number_of_extruders = 0;
bool m_advanced = false;
wxPanel* m_page_simple = nullptr;
wxPanel* m_page_advanced = nullptr;
wxBoxSizer* m_sizer = nullptr;
wxBoxSizer* m_sizer_simple = nullptr;
wxBoxSizer* m_sizer_advanced = nullptr;
wxGridSizer* m_gridsizer_advanced = nullptr;
wxButton* m_widget_button = nullptr;
};
class WipingDialog : public wxDialog {
public:
WipingDialog(wxWindow* parent,const std::vector<float>& matrix, const std::vector<float>& extruders);
std::vector<float> get_matrix() const { return m_output_matrix; }
std::vector<float> get_extruders() const { return m_output_extruders; }
private:
WipingPanel* m_panel_wiping = nullptr;
std::vector<float> m_output_matrix;
std::vector<float> m_output_extruders;
};
#endif // _WIPE_TOWER_DIALOG_H_

View file

@ -0,0 +1,32 @@
// I AM A PHONY PLACEHOLDER FOR THE PERL CALLBACK.
// GET RID OF ME!
#ifndef slic3r_GUI_PerlCallback_phony_hpp_
#define slic3r_GUI_PerlCallback_phony_hpp_
#include <vector>
namespace Slic3r {
class PerlCallback {
public:
// PerlCallback(void *) {}
PerlCallback() {}
void register_callback(void *) {}
void deregister_callback() {}
void call() const {}
void call(int) const {}
void call(int, int) const {}
void call(const std::vector<int>&) const {}
void call(double) const {}
void call(double, double) const {}
void call(double, double, double) const {}
void call(double, double, double, double) const {}
void call(double, double, double, double, double) const {}
void call(double, double, double, double, double, double) const {}
void call(bool b) const {}
};
} // namespace Slic3r
#endif /* slic3r_GUI_PerlCallback_phony_hpp_ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,800 @@
#ifndef slic3r_GUI_wxExtensions_hpp_
#define slic3r_GUI_wxExtensions_hpp_
#include <wx/checklst.h>
#include <wx/combo.h>
#include <wx/dataview.h>
#include <wx/dc.h>
#include <wx/collpane.h>
#include <wx/wupdlock.h>
#include <wx/button.h>
#include <wx/slider.h>
#include <vector>
#include <set>
#include <functional>
wxMenuItem* append_menu_item(wxMenu* menu, int id, const wxString& string, const wxString& description,
std::function<void(wxCommandEvent& event)> cb, const std::string& icon = "", wxEvtHandler* event_handler = nullptr);
wxMenuItem* append_submenu(wxMenu* menu, wxMenu* sub_menu, int id, const wxString& string, const wxString& description, const std::string& icon = "");
class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup
{
static const unsigned int DefaultWidth;
static const unsigned int DefaultHeight;
static const unsigned int DefaultItemHeight;
wxString m_text;
// Events sent on mouseclick are quite complex. Function OnListBoxSelection is supposed to pass the event to the checkbox, which works fine on
// Win. On OSX and Linux the events are generated differently - clicking on the checkbox square generates the event twice (and the square
// therefore seems not to respond).
// This enum is meant to save current state of affairs, i.e., if the event forwarding is ok to do or not. It is only used on Linux
// and OSX by some #ifdefs. It also stores information whether OnListBoxSelection is supposed to change the checkbox status,
// or if it changed status on its own already (which happens when the square is clicked). More comments in OnCheckListBox(...)
// There indeed is a better solution, maybe making a custom event used for the event passing to distinguish the original and passed message
// and blocking one of them on OSX and Linux. Feel free to refactor, but carefully test on all platforms.
enum class OnCheckListBoxFunction{
FreeToProceed,
RefuseToProceed,
WasRefusedLastTime
} m_check_box_events_status = OnCheckListBoxFunction::FreeToProceed;
public:
virtual bool Create(wxWindow* parent);
virtual wxWindow* GetControl();
virtual void SetStringValue(const wxString& value);
virtual wxString GetStringValue() const;
virtual wxSize GetAdjustedSize(int minWidth, int prefHeight, int maxHeight);
virtual void OnKeyEvent(wxKeyEvent& evt);
void OnCheckListBox(wxCommandEvent& evt);
void OnListBoxSelection(wxCommandEvent& evt);
};
// *** wxDataViewTreeCtrlComboBox ***
class wxDataViewTreeCtrlComboPopup: public wxDataViewTreeCtrl, public wxComboPopup
{
static const unsigned int DefaultWidth;
static const unsigned int DefaultHeight;
static const unsigned int DefaultItemHeight;
wxString m_text;
int m_cnt_open_items{0};
public:
virtual bool Create(wxWindow* parent);
virtual wxWindow* GetControl() { return this; }
virtual void SetStringValue(const wxString& value) { m_text = value; }
virtual wxString GetStringValue() const { return m_text; }
// virtual wxSize GetAdjustedSize(int minWidth, int prefHeight, int maxHeight);
virtual void OnKeyEvent(wxKeyEvent& evt);
void OnDataViewTreeCtrlSelection(wxCommandEvent& evt);
void SetItemsCnt(int cnt) { m_cnt_open_items = cnt; }
};
// *** PrusaCollapsiblePane ***
// ----------------------------------------------------------------------------
class PrusaCollapsiblePane : public wxCollapsiblePane
{
public:
PrusaCollapsiblePane() {}
PrusaCollapsiblePane(wxWindow *parent,
wxWindowID winid,
const wxString& label,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxCP_DEFAULT_STYLE,
const wxValidator& val = wxDefaultValidator,
const wxString& name = wxCollapsiblePaneNameStr)
{
Create(parent, winid, label, pos, size, style, val, name);
}
~PrusaCollapsiblePane() {}
void OnStateChange(const wxSize& sz); //override/hide of OnStateChange from wxCollapsiblePane
virtual bool Show(bool show = true) override {
wxCollapsiblePane::Show(show);
OnStateChange(GetBestSize());
return true;
}
};
// *** PrusaCollapsiblePaneMSW *** used only #ifdef __WXMSW__
// ----------------------------------------------------------------------------
#ifdef __WXMSW__
class PrusaCollapsiblePaneMSW : public PrusaCollapsiblePane//wxCollapsiblePane
{
wxButton* m_pDisclosureTriangleButton = nullptr;
wxBitmap m_bmp_close;
wxBitmap m_bmp_open;
wxString m_strLabel;
public:
PrusaCollapsiblePaneMSW() {}
PrusaCollapsiblePaneMSW( wxWindow *parent,
wxWindowID winid,
const wxString& label,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize,
long style = wxCP_DEFAULT_STYLE,
const wxValidator& val = wxDefaultValidator,
const wxString& name = wxCollapsiblePaneNameStr)
{
Create(parent, winid, label, pos, size, style, val, name);
}
~PrusaCollapsiblePaneMSW() {}
bool Create(wxWindow *parent,
wxWindowID id,
const wxString& label,
const wxPoint& pos,
const wxSize& size,
long style,
const wxValidator& val,
const wxString& name);
void UpdateBtnBmp();
void SetLabel(const wxString &label) override;
bool Layout() override;
void Collapse(bool collapse) override;
};
#endif //__WXMSW__
// *****************************************************************************
// ----------------------------------------------------------------------------
// PrusaDataViewBitmapText: helper class used by PrusaBitmapTextRenderer
// ----------------------------------------------------------------------------
class PrusaDataViewBitmapText : public wxObject
{
public:
PrusaDataViewBitmapText(const wxString &text = wxEmptyString,
const wxBitmap& bmp = wxNullBitmap) :
m_text(text), m_bmp(bmp)
{ }
PrusaDataViewBitmapText(const PrusaDataViewBitmapText &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 PrusaDataViewBitmapText& other) const {
return m_text == other.m_text && m_bmp.IsSameAs(other.m_bmp);
}
bool operator==(const PrusaDataViewBitmapText& other) const {
return IsSameAs(other);
}
bool operator!=(const PrusaDataViewBitmapText& other) const {
return !IsSameAs(other);
}
private:
wxString m_text;
wxBitmap m_bmp;
wxDECLARE_DYNAMIC_CLASS(PrusaDataViewBitmapText);
};
DECLARE_VARIANT_OBJECT(PrusaDataViewBitmapText)
// ----------------------------------------------------------------------------
// PrusaObjectDataViewModelNode: a node inside PrusaObjectDataViewModel
// ----------------------------------------------------------------------------
enum ItemType{
itUndef = 0,
itObject = 1,
itVolume = 2,
itInstanceRoot = 4,
itInstance = 8,
itSettings = 16
};
class PrusaObjectDataViewModelNode;
WX_DEFINE_ARRAY_PTR(PrusaObjectDataViewModelNode*, MyObjectTreeModelNodePtrArray);
class PrusaObjectDataViewModelNode
{
PrusaObjectDataViewModelNode* m_parent;
MyObjectTreeModelNodePtrArray m_children;
wxBitmap m_empty_bmp;
size_t m_volumes_cnt = 0;
std::vector< std::string > m_opt_categories;
public:
PrusaObjectDataViewModelNode(const wxString &name) {
m_parent = NULL;
m_name = name;
m_type = itObject;
#ifdef __WXGTK__
// it's necessary on GTK because of control have to know if this item will be container
// in another case you couldn't to add subitem for this item
// it will be produce "segmentation fault"
m_container = true;
#endif //__WXGTK__
set_object_action_icon();
}
PrusaObjectDataViewModelNode( PrusaObjectDataViewModelNode* parent,
const wxString& sub_obj_name,
const wxBitmap& bmp,
const wxString& extruder,
const int idx = -1 ) {
m_parent = parent;
m_name = sub_obj_name;
m_bmp = bmp;
m_type = itVolume;
m_idx = idx;
m_extruder = extruder;
#ifdef __WXGTK__
// it's necessary on GTK because of control have to know if this item will be container
// in another case you couldn't to add subitem for this item
// it will be produce "segmentation fault"
m_container = true;
#endif //__WXGTK__
set_part_action_icon();
}
PrusaObjectDataViewModelNode( PrusaObjectDataViewModelNode* parent, const ItemType type) :
m_parent(parent),
m_type(type),
m_extruder(wxEmptyString)
{
if (type == itSettings) {
m_name = "Settings to modified";
}
else if (type == itInstanceRoot) {
m_name = "Instances";
#ifdef __WXGTK__
m_container = true;
#endif //__WXGTK__
}
else if (type == itInstance) {
m_idx = parent->GetChildCount();
m_name = wxString::Format("Instance_%d", m_idx+1);
}
}
~PrusaObjectDataViewModelNode()
{
// free all our children nodes
size_t count = m_children.GetCount();
for (size_t i = 0; i < count; i++)
{
PrusaObjectDataViewModelNode *child = m_children[i];
delete child;
}
}
wxString m_name;
wxBitmap& m_bmp = m_empty_bmp;
ItemType m_type;
int m_idx = -1;
bool m_container = false;
wxString m_extruder = "default";
wxBitmap m_action_icon;
bool IsContainer() const
{
return m_container;
}
PrusaObjectDataViewModelNode* GetParent()
{
return m_parent;
}
MyObjectTreeModelNodePtrArray& GetChildren()
{
return m_children;
}
PrusaObjectDataViewModelNode* GetNthChild(unsigned int n)
{
return m_children.Item(n);
}
void Insert(PrusaObjectDataViewModelNode* child, unsigned int n)
{
if (!m_container)
m_container = true;
m_children.Insert(child, n);
}
void Append(PrusaObjectDataViewModelNode* child)
{
if (!m_container)
m_container = true;
m_children.Add(child);
}
void RemoveAllChildren()
{
if (GetChildCount() == 0)
return;
for (size_t id = GetChildCount() - 1; id >= 0; --id)
{
if (m_children.Item(id)->GetChildCount() > 0)
m_children[id]->RemoveAllChildren();
auto node = m_children[id];
m_children.RemoveAt(id);
delete node;
}
}
size_t GetChildCount() const
{
return m_children.GetCount();
}
bool SetValue(const wxVariant &variant, unsigned int col)
{
switch (col)
{
case 0:{
PrusaDataViewBitmapText data;
data << variant;
m_bmp = data.GetBitmap();
m_name = data.GetText();
return true;}
case 1:
m_extruder = variant.GetString();
return true;
case 2:
m_action_icon << variant;
return true;
default:
printf("MyObjectTreeModel::SetValue: wrong column");
}
return false;
}
void SetBitmap(const wxBitmap &icon)
{
m_bmp = icon;
}
ItemType GetType() const {
return m_type;
}
void SetIdx(const int& idx) {
m_idx = idx;
// update name if this node is instance
if (m_type == itInstance)
m_name = wxString::Format("Instance_%d", m_idx + 1);
}
int GetIdx() const {
return m_idx;
}
// use this function only for childrens
void AssignAllVal(PrusaObjectDataViewModelNode& from_node)
{
// ! Don't overwrite other values because of equality of this values for all children --
m_name = from_node.m_name;
m_bmp = from_node.m_bmp;
m_idx = from_node.m_idx;
m_extruder = from_node.m_extruder;
m_type = from_node.m_type;
}
bool SwapChildrens(int frst_id, int scnd_id) {
if (GetChildCount() < 2 ||
frst_id < 0 || frst_id >= GetChildCount() ||
scnd_id < 0 || scnd_id >= GetChildCount())
return false;
PrusaObjectDataViewModelNode new_scnd = *GetNthChild(frst_id);
PrusaObjectDataViewModelNode new_frst = *GetNthChild(scnd_id);
new_scnd.m_idx = m_children.Item(scnd_id)->m_idx;
new_frst.m_idx = m_children.Item(frst_id)->m_idx;
m_children.Item(frst_id)->AssignAllVal(new_frst);
m_children.Item(scnd_id)->AssignAllVal(new_scnd);
return true;
}
// Set action icons for node
void set_object_action_icon();
void set_part_action_icon();
bool update_settings_digest(const std::vector<std::string>& categories);
private:
friend class PrusaObjectDataViewModel;
};
// ----------------------------------------------------------------------------
// PrusaObjectDataViewModel
// ----------------------------------------------------------------------------
class PrusaObjectDataViewModel :public wxDataViewModel
{
std::vector<PrusaObjectDataViewModelNode*> m_objects;
public:
PrusaObjectDataViewModel();
~PrusaObjectDataViewModel();
wxDataViewItem Add(const wxString &name);
wxDataViewItem AddVolumeChild(const wxDataViewItem &parent_item,
const wxString &name,
const wxBitmap& icon,
const int extruder = 0,
const bool create_frst_child = true);
wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item);
wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num);
wxDataViewItem Delete(const wxDataViewItem &item);
wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num);
void DeleteAll();
void DeleteChildren(wxDataViewItem& parent);
wxDataViewItem GetItemById(int obj_idx);
wxDataViewItem GetItemByVolumeId(int obj_idx, int volume_idx);
int GetIdByItem(const wxDataViewItem& item);
int GetIdByItemAndType(const wxDataViewItem& item, const ItemType type) const;
int GetVolumeIdByItem(const wxDataViewItem& item) const;
int GetInstanceIdByItem(const wxDataViewItem& item) const;
void GetItemInfo(const wxDataViewItem& item, ItemType& type, int& obj_idx, int& idx);
bool IsEmpty() { return m_objects.empty(); }
// helper method for wxLog
wxString GetName(const wxDataViewItem &item) const;
wxBitmap& GetBitmap(const wxDataViewItem &item) const;
// helper methods to change the model
virtual unsigned int GetColumnCount() const override { return 3;}
virtual wxString GetColumnType(unsigned int col) const override{ return wxT("string"); }
virtual void GetValue(wxVariant &variant,
const wxDataViewItem &item, unsigned int col) const override;
virtual bool SetValue(const wxVariant &variant,
const wxDataViewItem &item, unsigned int col) override;
bool SetValue(const wxVariant &variant, const int item_idx, unsigned int col);
// wxDataViewItem MoveChildUp(const wxDataViewItem &item);
// wxDataViewItem MoveChildDown(const wxDataViewItem &item);
// For parent move child from cur_volume_id place to new_volume_id
// Remaining items will moved up/down accordingly
wxDataViewItem ReorganizeChildren(int cur_volume_id,
int new_volume_id,
const wxDataViewItem &parent);
virtual bool IsEnabled(const wxDataViewItem &item, unsigned int col) const override;
virtual wxDataViewItem GetParent(const wxDataViewItem &item) const override;
// get object item
wxDataViewItem GetTopParent(const wxDataViewItem &item) const;
virtual bool IsContainer(const wxDataViewItem &item) const override;
virtual unsigned int GetChildren(const wxDataViewItem &parent,
wxDataViewItemArray &array) const override;
// Is the container just a header or an item with all columns
// In our case it is an item with all columns
virtual bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; }
ItemType GetItemType(const wxDataViewItem &item) const ;
wxDataViewItem GetSettingsItem(const wxDataViewItem &item) const;
bool IsSettingsItem(const wxDataViewItem &item) const;
void UpdateSettingsDigest(const wxDataViewItem &item, const std::vector<std::string>& categories);
};
// ----------------------------------------------------------------------------
// PrusaBitmapTextRenderer
// ----------------------------------------------------------------------------
class PrusaBitmapTextRenderer : public wxDataViewCustomRenderer
{
public:
PrusaBitmapTextRenderer( wxDataViewCellMode mode = wxDATAVIEW_CELL_INERT,
int align = wxDVR_DEFAULT_ALIGNMENT):
wxDataViewCustomRenderer(wxT("PrusaDataViewBitmapText"), mode, align) {}
bool SetValue(const wxVariant &value);
bool GetValue(wxVariant &value) const;
virtual bool Render(wxRect cell, wxDC *dc, int state);
virtual wxSize GetSize() const;
virtual bool HasEditorCtrl() const { return false; }
private:
PrusaDataViewBitmapText m_value;
};
// ----------------------------------------------------------------------------
// MyCustomRenderer
// ----------------------------------------------------------------------------
class MyCustomRenderer : public wxDataViewCustomRenderer
{
public:
// This renderer can be either activatable or editable, for demonstration
// purposes. In real programs, you should select whether the user should be
// able to activate or edit the cell and it doesn't make sense to switch
// between the two -- but this is just an example, so it doesn't stop us.
explicit MyCustomRenderer(wxDataViewCellMode mode)
: wxDataViewCustomRenderer("string", mode, wxALIGN_CENTER)
{ }
virtual bool Render(wxRect rect, wxDC *dc, int state) override/*wxOVERRIDE*/
{
dc->SetBrush(*wxLIGHT_GREY_BRUSH);
dc->SetPen(*wxTRANSPARENT_PEN);
rect.Deflate(2);
dc->DrawRoundedRectangle(rect, 5);
RenderText(m_value,
0, // no offset
wxRect(dc->GetTextExtent(m_value)).CentreIn(rect),
dc,
state);
return true;
}
virtual bool ActivateCell(const wxRect& WXUNUSED(cell),
wxDataViewModel *WXUNUSED(model),
const wxDataViewItem &WXUNUSED(item),
unsigned int WXUNUSED(col),
const wxMouseEvent *mouseEvent) override/*wxOVERRIDE*/
{
wxString position;
if (mouseEvent)
position = wxString::Format("via mouse at %d, %d", mouseEvent->m_x, mouseEvent->m_y);
else
position = "from keyboard";
// wxLogMessage("MyCustomRenderer ActivateCell() %s", position);
return false;
}
virtual wxSize GetSize() const override/*wxOVERRIDE*/
{
return wxSize(60, 20);
}
virtual bool SetValue(const wxVariant &value) override/*wxOVERRIDE*/
{
m_value = value.GetString();
return true;
}
virtual bool GetValue(wxVariant &WXUNUSED(value)) const override/*wxOVERRIDE*/{ return true; }
virtual bool HasEditorCtrl() const override/*wxOVERRIDE*/{ return true; }
virtual wxWindow*
CreateEditorCtrl(wxWindow* parent,
wxRect labelRect,
const wxVariant& value) override/*wxOVERRIDE*/
{
wxTextCtrl* text = new wxTextCtrl(parent, wxID_ANY, value,
labelRect.GetPosition(),
labelRect.GetSize(),
wxTE_PROCESS_ENTER);
text->SetInsertionPointEnd();
return text;
}
virtual bool
GetValueFromEditorCtrl(wxWindow* ctrl, wxVariant& value) override/*wxOVERRIDE*/
{
wxTextCtrl* text = wxDynamicCast(ctrl, wxTextCtrl);
if (!text)
return false;
value = text->GetValue();
return true;
}
private:
wxString m_value;
};
// ----------------------------------------------------------------------------
// PrusaDoubleSlider
// ----------------------------------------------------------------------------
enum SelectedSlider {
ssUndef,
ssLower,
ssHigher
};
enum TicksAction{
taOnIcon,
taAdd,
taDel
};
class PrusaDoubleSlider : public wxControl
{
public:
PrusaDoubleSlider(
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);
~PrusaDoubleSlider(){}
int GetLowerValue() const {
return m_lower_value;
}
int GetHigherValue() const {
return m_higher_value;
}
int GetActiveValue() const;
double GetLowerValueD() const { return get_double_value(ssLower); }
double GetHigherValueD() const { return get_double_value(ssHigher); }
wxSize DoGetBestSize() const override;
void SetLowerValue(const int lower_val);
void SetHigherValue(const int higher_val);
void SetMaxValue(const int max_value);
void SetKoefForLabels(const double koef) {
m_label_koef = koef;
}
void SetSliderValues(const std::vector<std::pair<int, double>>& values) {
m_values = values;
}
void ChangeOneLayerLock();
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 OnWheel(wxMouseEvent& event);
void OnKeyDown(wxKeyEvent &event);
void OnKeyUp(wxKeyEvent &event);
void OnRightDown(wxMouseEvent& event);
void OnRightUp(wxMouseEvent& event);
protected:
void render();
void draw_focus_rect();
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(wxDC& dc);
void draw_one_layer_icon(wxDC& dc);
void draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection);
void draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, SelectedSlider selection);
void draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const;
void update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection);
void detect_selected_slider(const wxPoint& pt, const bool is_mouse_wheel = false);
void correct_lower_value();
void correct_higher_value();
void move_current_thumb(const bool condition);
void action_tick(const TicksAction action);
void enter_window(wxMouseEvent& event, const bool enter);
bool is_point_in_rect(const wxPoint& pt, const wxRect& rect);
bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; }
double get_scroll_step();
wxString get_label(const SelectedSlider& selection) const;
void get_lower_and_higher_position(int& lower_pos, int& higher_pos);
int get_value_from_position(const wxCoord x, const wxCoord y);
wxCoord get_position_from_value(const int value);
wxSize get_size();
void get_size(int *w, int *h);
double get_double_value(const SelectedSlider& selection) const;
private:
int m_min_value;
int m_max_value;
int m_lower_value;
int m_higher_value;
wxBitmap m_bmp_thumb_higher;
wxBitmap m_bmp_thumb_lower;
wxBitmap m_bmp_add_tick_on;
wxBitmap m_bmp_add_tick_off;
wxBitmap m_bmp_del_tick_on;
wxBitmap m_bmp_del_tick_off;
wxBitmap m_bmp_one_layer_lock_on;
wxBitmap m_bmp_one_layer_lock_off;
wxBitmap m_bmp_one_layer_unlock_on;
wxBitmap m_bmp_one_layer_unlock_off;
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_is_action_icon_focesed = false;
bool m_is_one_layer_icon_focesed = false;
wxRect m_rect_lower_thumb;
wxRect m_rect_higher_thumb;
wxRect m_rect_tick_action;
wxRect m_rect_one_layer_icon;
wxSize m_thumb_size;
int m_tick_icon_dim;
int m_lock_icon_dim;
long m_style;
float m_label_koef = 1.0;
// control's view variables
wxCoord SLIDER_MARGIN; // margin around slider
wxPen DARK_ORANGE_PEN;
wxPen ORANGE_PEN;
wxPen LIGHT_ORANGE_PEN;
wxPen DARK_GREY_PEN;
wxPen GREY_PEN;
wxPen LIGHT_GREY_PEN;
std::vector<wxPen*> line_pens;
std::vector<wxPen*> segm_pens;
std::set<int> m_ticks;
std::vector<std::pair<int,double>> m_values;
};
// ----------------------------------------------------------------------------
// PrusaLockButton
// ----------------------------------------------------------------------------
class PrusaLockButton : public wxButton
{
public:
PrusaLockButton(
wxWindow *parent,
wxWindowID id,
const wxPoint& pos = wxDefaultPosition,
const wxSize& size = wxDefaultSize);
~PrusaLockButton(){}
void OnButton(wxCommandEvent& event);
void OnEnterBtn(wxMouseEvent& event){ enter_button(true); event.Skip(); }
void OnLeaveBtn(wxMouseEvent& event){ enter_button(false); event.Skip(); }
bool IsLocked() const { return m_is_pushed; }
protected:
void enter_button(const bool enter);
private:
bool m_is_pushed = false;
wxBitmap m_bmp_lock_on;
wxBitmap m_bmp_lock_off;
wxBitmap m_bmp_unlock_on;
wxBitmap m_bmp_unlock_off;
int m_lock_icon_dim;
};
// ******************************* EXPERIMENTS **********************************************
// ******************************************************************************************
#endif // slic3r_GUI_wxExtensions_hpp_

25
src/slic3r/GUI/wxinit.h Normal file
View file

@ -0,0 +1,25 @@
#ifndef slic3r_wxinit_hpp_
#define slic3r_wxinit_hpp_
#include <wx/wx.h>
#include <wx/intl.h>
#include <wx/html/htmlwin.h>
// Perl redefines a _ macro, so we undef this one
#undef _
// We do want to use translation however, so define it as __ so we can do a find/replace
// later when we no longer need to undef _
#define __(s) wxGetTranslation((s))
// legacy macros
// https://wiki.wxwidgets.org/EventTypes_and_Event-Table_Macros
#ifndef wxEVT_BUTTON
#define wxEVT_BUTTON wxEVT_COMMAND_BUTTON_CLICKED
#endif
#ifndef wxEVT_HTML_LINK_CLICKED
#define wxEVT_HTML_LINK_CLICKED wxEVT_COMMAND_HTML_LINK_CLICKED
#endif
#endif