Merge remote-tracking branch 'origin/master' into ys_unsaved_changes

This commit is contained in:
YuSanka 2020-09-02 09:15:11 +02:00
commit 4d053cc4ee
148 changed files with 13789 additions and 3328 deletions

View file

@ -21,6 +21,8 @@ set(SLIC3R_GUI_SOURCES
GUI/3DScene.cpp
GUI/3DScene.hpp
GUI/format.hpp
GUI/GLShadersManager.hpp
GUI/GLShadersManager.cpp
GUI/GLShader.cpp
GUI/GLShader.hpp
GUI/GLCanvas3D.hpp
@ -51,12 +53,20 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmoCut.hpp
GUI/Gizmos/GLGizmoHollow.cpp
GUI/Gizmos/GLGizmoHollow.hpp
GUI/Gizmos/GLGizmoPainterBase.cpp
GUI/Gizmos/GLGizmoPainterBase.hpp
GUI/Gizmos/GLGizmoSeam.cpp
GUI/Gizmos/GLGizmoSeam.hpp
GUI/GLSelectionRectangle.cpp
GUI/GLSelectionRectangle.hpp
GUI/GLModel.hpp
GUI/GLModel.cpp
GUI/GLTexture.hpp
GUI/GLTexture.cpp
GUI/GLToolbar.hpp
GUI/GLToolbar.cpp
GUI/GCodeViewer.hpp
GUI/GCodeViewer.cpp
GUI/Preferences.cpp
GUI/Preferences.hpp
GUI/PresetHints.cpp
@ -185,13 +195,12 @@ set(SLIC3R_GUI_SOURCES
Utils/Bonjour.hpp
Utils/PresetUpdater.cpp
Utils/PresetUpdater.hpp
Utils/Profile.hpp
Utils/UndoRedo.cpp
Utils/UndoRedo.hpp
Utils/HexFile.cpp
Utils/HexFile.hpp
Utils/Thread.hpp
Utils/SLAImport.hpp
Utils/SLAImport.cpp
)
if (APPLE)

View file

@ -5,15 +5,24 @@
#include "libslic3r/Polygon.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/BoundingBox.hpp"
#if ENABLE_GCODE_VIEWER
#include "libslic3r/Geometry.hpp"
#endif // ENABLE_GCODE_VIEWER
#include "GUI_App.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "GLCanvas3D.hpp"
#if ENABLE_GCODE_VIEWER
#include "3DScene.hpp"
#endif // ENABLE_GCODE_VIEWER
#include <GL/glew.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem/operations.hpp>
#if ENABLE_GCODE_VIEWER
#include <boost/log/trivial.hpp>
#endif // ENABLE_GCODE_VIEWER
static const float GROUND_Z = -0.02f;
@ -36,10 +45,8 @@ bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool
float max_y = min_y;
unsigned int v_count = 0;
for (const Polygon& t : triangles)
{
for (unsigned int i = 0; i < 3; ++i)
{
for (const Polygon& t : triangles) {
for (unsigned int i = 0; i < 3; ++i) {
Vertex& v = m_vertices[v_count];
const Point& p = t.points[i];
@ -50,8 +57,7 @@ bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool
v.position[1] = y;
v.position[2] = z;
if (generate_tex_coords)
{
if (generate_tex_coords) {
v.tex_coords[0] = x;
v.tex_coords[1] = y;
@ -65,17 +71,14 @@ bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool
}
}
if (generate_tex_coords)
{
if (generate_tex_coords) {
float size_x = max_x - min_x;
float size_y = max_y - min_y;
if ((size_x != 0.0f) && (size_y != 0.0f))
{
if ((size_x != 0.0f) && (size_y != 0.0f)) {
float inv_size_x = 1.0f / size_x;
float inv_size_y = -1.0f / size_y;
for (Vertex& v : m_vertices)
{
for (Vertex& v : m_vertices) {
v.tex_coords[0] = (v.tex_coords[0] - min_x) * inv_size_x;
v.tex_coords[1] = (v.tex_coords[1] - min_y) * inv_size_y;
}
@ -96,8 +99,7 @@ bool GeometryBuffer::set_from_lines(const Lines& lines, float z)
m_vertices = std::vector<Vertex>(v_size, Vertex());
unsigned int v_count = 0;
for (const Line& l : lines)
{
for (const Line& l : lines) {
Vertex& v1 = m_vertices[v_count];
v1.position[0] = unscale<float>(l.a(0));
v1.position[1] = unscale<float>(l.a(1));
@ -119,10 +121,24 @@ const float* GeometryBuffer::get_vertices_data() const
return (m_vertices.size() > 0) ? (const float*)m_vertices.data() : nullptr;
}
#if ENABLE_GCODE_VIEWER
const float Bed3D::Axes::DefaultStemRadius = 0.5f;
const float Bed3D::Axes::DefaultStemLength = 25.0f;
const float Bed3D::Axes::DefaultTipRadius = 2.5f * Bed3D::Axes::DefaultStemRadius;
const float Bed3D::Axes::DefaultTipLength = 5.0f;
#else
const double Bed3D::Axes::Radius = 0.5;
const double Bed3D::Axes::ArrowBaseRadius = 2.5 * Bed3D::Axes::Radius;
const double Bed3D::Axes::ArrowLength = 5.0;
#endif // ENABLE_GCODE_VIEWER
#if ENABLE_GCODE_VIEWER
void Bed3D::Axes::set_stem_length(float length)
{
m_stem_length = length;
m_arrow.reset();
}
#else
Bed3D::Axes::Axes()
: origin(Vec3d::Zero())
, length(25.0 * Vec3d::Ones())
@ -137,9 +153,47 @@ Bed3D::Axes::~Axes()
if (m_quadric != nullptr)
::gluDeleteQuadric(m_quadric);
}
#endif // ENABLE_GCODE_VIEWER
void Bed3D::Axes::render() const
{
#if ENABLE_GCODE_VIEWER
auto render_axis = [this](const Transform3f& transform) {
glsafe(::glPushMatrix());
glsafe(::glMultMatrixf(transform.data()));
m_arrow.render();
glsafe(::glPopMatrix());
};
m_arrow.init_from(stilized_arrow(16, DefaultTipRadius, DefaultTipLength, DefaultStemRadius, m_stem_length));
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader == nullptr)
return;
glsafe(::glEnable(GL_DEPTH_TEST));
shader->start_using();
// x axis
std::array<float, 4> color = { 0.75f, 0.0f, 0.0f, 1.0f };
shader->set_uniform("uniform_color", color);
render_axis(Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0f }).cast<float>());
// y axis
color = { 0.0f, 0.75f, 0.0f, 1.0f };
shader->set_uniform("uniform_color", color);
render_axis(Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0f }).cast<float>());
// z axis
color = { 0.0f, 0.0f, 0.75f, 1.0f };
shader->set_uniform("uniform_color", color);
render_axis(Geometry::assemble_transform(m_origin).cast<float>());
shader->stop_using();
glsafe(::glDisable(GL_DEPTH_TEST));
#else
if (m_quadric == nullptr)
return;
@ -171,8 +225,10 @@ void Bed3D::Axes::render() const
glsafe(::glDisable(GL_LIGHTING));
glsafe(::glDisable(GL_DEPTH_TEST));
#endif // !ENABLE_GCODE_VIEWER
}
#if !ENABLE_GCODE_VIEWER
void Bed3D::Axes::render_axis(double length) const
{
::gluQuadricOrientation(m_quadric, GLU_OUTSIDE);
@ -185,6 +241,7 @@ void Bed3D::Axes::render_axis(double length) const
::gluQuadricOrientation(m_quadric, GLU_INSIDE);
::gluDisk(m_quadric, 0.0, ArrowBaseRadius, 32, 1);
}
#endif // !ENABLE_GCODE_VIEWER
Bed3D::Bed3D()
: m_type(Custom)
@ -193,7 +250,7 @@ Bed3D::Bed3D()
{
}
bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model)
bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom)
{
auto check_texture = [](const std::string& texture) {
return !texture.empty() && (boost::algorithm::iends_with(texture, ".png") || boost::algorithm::iends_with(texture, ".svg")) && boost::filesystem::exists(texture);
@ -203,30 +260,39 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c
return !model.empty() && boost::algorithm::iends_with(model, ".stl") && boost::filesystem::exists(model);
};
auto [new_type, system_model, system_texture] = detect_type(shape);
EType type;
std::string model;
std::string texture;
if (force_as_custom)
type = Custom;
else {
auto [new_type, system_model, system_texture] = detect_type(shape);
type = new_type;
model = system_model;
texture = system_texture;
}
std::string texture_filename = custom_texture.empty() ? system_texture : custom_texture;
std::string texture_filename = custom_texture.empty() ? texture : custom_texture;
if (!check_texture(texture_filename))
texture_filename.clear();
std::string model_filename = custom_model.empty() ? system_model : custom_model;
std::string model_filename = custom_model.empty() ? model : custom_model;
if (!check_model(model_filename))
model_filename.clear();
if ((m_shape == shape) && (m_type == new_type) && (m_texture_filename == texture_filename) && (m_model_filename == model_filename))
if (m_shape == shape && m_type == type && m_texture_filename == texture_filename && m_model_filename == model_filename)
// No change, no need to update the UI.
return false;
m_shape = shape;
m_texture_filename = texture_filename;
m_model_filename = model_filename;
m_type = new_type;
m_type = type;
calc_bounding_boxes();
ExPolygon poly;
for (const Vec2d& p : m_shape)
{
for (const Vec2d& p : m_shape) {
poly.contour.append(Point(scale_(p(0)), scale_(p(1))));
}
@ -242,8 +308,13 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c
m_model.reset();
// Set the origin and size for rendering the coordinate system axes.
#if ENABLE_GCODE_VIEWER
m_axes.set_origin({ 0.0, 0.0, static_cast<double>(GROUND_Z) });
m_axes.set_stem_length(0.1f * static_cast<float>(m_bounding_box.max_size()));
#else
m_axes.origin = Vec3d(0.0, 0.0, (double)GROUND_Z);
m_axes.length = 0.1 * m_bounding_box.max_size() * Vec3d::Ones();
#endif // ENABLE_GCODE_VIEWER
// Let the calee to update the UI.
return true;
@ -282,25 +353,35 @@ void Bed3D::render(GLCanvas3D& canvas, bool bottom, float scale_factor,
void Bed3D::calc_bounding_boxes() const
{
m_bounding_box = BoundingBoxf3();
for (const Vec2d& p : m_shape)
{
for (const Vec2d& p : m_shape) {
m_bounding_box.merge(Vec3d(p(0), p(1), 0.0));
}
m_extended_bounding_box = m_bounding_box;
// extend to contain axes
m_extended_bounding_box.merge(m_axes.length + Axes::ArrowLength * Vec3d::Ones());
#if ENABLE_GCODE_VIEWER
m_extended_bounding_box.merge(m_axes.get_origin() + m_axes.get_total_length() * Vec3d::Ones());
m_extended_bounding_box.merge(m_extended_bounding_box.min + Vec3d(-Axes::DefaultTipRadius, -Axes::DefaultTipRadius, m_extended_bounding_box.max(2)));
// extend to contain model, if any
BoundingBoxf3 model_bb = m_model.get_bounding_box();
if (model_bb.defined) {
model_bb.translate(m_model_offset);
m_extended_bounding_box.merge(model_bb);
}
#else
m_extended_bounding_box.merge(m_axes.length + Axes::ArrowLength * Vec3d::Ones());
// extend to contain model, if any
if (!m_model.get_filename().empty())
m_extended_bounding_box.merge(m_model.get_transformed_bounding_box());
#endif // ENABLE_GCODE_VIEWER
}
void Bed3D::calc_triangles(const ExPolygon& poly)
{
Polygons triangles;
poly.triangulate(&triangles);
poly.triangulate_p2t(&triangles);
if (!m_triangles.set_from_triangles(triangles, GROUND_Z, true))
printf("Unable to create bed triangles\n");
@ -309,15 +390,13 @@ void Bed3D::calc_triangles(const ExPolygon& poly)
void Bed3D::calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox)
{
Polylines axes_lines;
for (coord_t x = bed_bbox.min(0); x <= bed_bbox.max(0); x += scale_(10.0))
{
for (coord_t x = bed_bbox.min(0); x <= bed_bbox.max(0); x += scale_(10.0)) {
Polyline line;
line.append(Point(x, bed_bbox.min(1)));
line.append(Point(x, bed_bbox.max(1)));
axes_lines.push_back(line);
}
for (coord_t y = bed_bbox.min(1); y <= bed_bbox.max(1); y += scale_(10.0))
{
for (coord_t y = bed_bbox.min(1); y <= bed_bbox.max(1); y += scale_(10.0)) {
Polyline line;
line.append(Point(bed_bbox.min(0), y));
line.append(Point(bed_bbox.max(0), y));
@ -335,6 +414,7 @@ void Bed3D::calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox)
printf("Unable to create bed grid lines\n");
}
#if !ENABLE_GCODE_VIEWER
static std::string system_print_bed_model(const Preset &preset)
{
std::string out;
@ -352,23 +432,25 @@ static std::string system_print_bed_texture(const Preset &preset)
out = Slic3r::resources_dir() + "/profiles/" + preset.vendor->id + "/" + pm->bed_texture;
return out;
}
#endif // !ENABLE_GCODE_VIEWER
std::tuple<Bed3D::EType, std::string, std::string> Bed3D::detect_type(const Pointfs& shape) const
{
auto bundle = wxGetApp().preset_bundle;
if (bundle != nullptr)
{
if (bundle != nullptr) {
const Preset* curr = &bundle->printers.get_selected_preset();
while (curr != nullptr)
{
if (curr->config.has("bed_shape"))
{
if (shape == dynamic_cast<const ConfigOptionPoints*>(curr->config.option("bed_shape"))->values)
{
while (curr != nullptr) {
if (curr->config.has("bed_shape")) {
if (shape == dynamic_cast<const ConfigOptionPoints*>(curr->config.option("bed_shape"))->values) {
#if ENABLE_GCODE_VIEWER
std::string model_filename = PresetUtils::system_printer_bed_model(*curr);
std::string texture_filename = PresetUtils::system_printer_bed_texture(*curr);
#else
std::string model_filename = system_print_bed_model(*curr);
std::string texture_filename = system_print_bed_texture(*curr);
#endif // ENABLE_GCODE_VIEWER
if (!model_filename.empty() && !texture_filename.empty())
return std::make_tuple(System, model_filename, texture_filename);
return { System, model_filename, texture_filename };
}
}
@ -376,7 +458,7 @@ std::tuple<Bed3D::EType, std::string, std::string> Bed3D::detect_type(const Poin
}
}
return std::make_tuple(Custom, "", "");
return { Custom, "", "" };
}
void Bed3D::render_axes() const
@ -396,26 +478,21 @@ void Bed3D::render_system(GLCanvas3D& canvas, bool bottom, bool show_texture) co
void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
{
if (m_texture_filename.empty())
{
if (m_texture_filename.empty()) {
m_texture.reset();
render_default(bottom);
return;
}
if ((m_texture.get_id() == 0) || (m_texture.get_source() != m_texture_filename))
{
if ((m_texture.get_id() == 0) || (m_texture.get_source() != m_texture_filename)) {
m_texture.reset();
if (boost::algorithm::iends_with(m_texture_filename, ".svg"))
{
if (boost::algorithm::iends_with(m_texture_filename, ".svg")) {
// use higher resolution images if graphic card and opengl version allow
GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size();
if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_texture_filename))
{
if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_texture_filename)) {
// generate a temporary lower resolution texture to show while no main texture levels have been compressed
if (!m_temp_texture.load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8))
{
if (!m_temp_texture.load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8)) {
render_default(bottom);
return;
}
@ -423,19 +500,15 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
}
// starts generating the main texture, compression will run asynchronously
if (!m_texture.load_from_svg_file(m_texture_filename, true, true, true, max_tex_size))
{
if (!m_texture.load_from_svg_file(m_texture_filename, true, true, true, max_tex_size)) {
render_default(bottom);
return;
}
}
else if (boost::algorithm::iends_with(m_texture_filename, ".png"))
{
}
else if (boost::algorithm::iends_with(m_texture_filename, ".png")) {
// generate a temporary lower resolution texture to show while no main texture levels have been compressed
if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_texture_filename))
{
if (!m_temp_texture.load_from_file(m_texture_filename, false, GLTexture::None, false))
{
if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != m_texture_filename)) {
if (!m_temp_texture.load_from_file(m_texture_filename, false, GLTexture::None, false)) {
render_default(bottom);
return;
}
@ -443,20 +516,17 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
}
// starts generating the main texture, compression will run asynchronously
if (!m_texture.load_from_file(m_texture_filename, true, GLTexture::MultiThreaded, true))
{
if (!m_texture.load_from_file(m_texture_filename, true, GLTexture::MultiThreaded, true)) {
render_default(bottom);
return;
}
}
else
{
else {
render_default(bottom);
return;
}
}
else if (m_texture.unsent_compressed_data_available())
{
else if (m_texture.unsent_compressed_data_available()) {
// sends to gpu the already available compressed levels of the main texture
m_texture.send_compressed_data_to_gpu();
@ -468,19 +538,14 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
}
if (m_triangles.get_vertices_count() > 0)
{
if (m_shader.get_shader_program_id() == 0)
m_shader.init("printbed.vs", "printbed.fs");
if (m_triangles.get_vertices_count() > 0) {
GLShaderProgram* shader = wxGetApp().get_shader("printbed");
if (shader != nullptr) {
shader->start_using();
shader->set_uniform("transparent_background", bottom);
shader->set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg"));
if (m_shader.is_initialized())
{
m_shader.start_using();
m_shader.set_uniform("transparent_background", bottom);
m_shader.set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg"));
if (m_vbo_id == 0)
{
if (m_vbo_id == 0) {
glsafe(::glGenBuffers(1, &m_vbo_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id));
glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_triangles.get_vertices_data_size(), (const GLvoid*)m_triangles.get_vertices_data(), GL_STATIC_DRAW));
@ -498,8 +563,8 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
unsigned int stride = m_triangles.get_vertex_data_size();
GLint position_id = m_shader.get_attrib_location("v_position");
GLint tex_coords_id = m_shader.get_attrib_location("v_tex_coords");
GLint position_id = shader->get_attrib_location("v_position");
GLint tex_coords_id = shader->get_attrib_location("v_tex_coords");
// show the temporary texture while no compressed data is available
GLuint tex_id = (GLuint)m_temp_texture.get_id();
@ -509,13 +574,11 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id));
if (position_id != -1)
{
if (position_id != -1) {
glsafe(::glEnableVertexAttribArray(position_id));
glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_position_offset()));
}
if (tex_coords_id != -1)
{
if (tex_coords_id != -1) {
glsafe(::glEnableVertexAttribArray(tex_coords_id));
glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_tex_coords_offset()));
}
@ -537,7 +600,7 @@ void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas) const
glsafe(::glDisable(GL_BLEND));
glsafe(::glDepthMask(GL_TRUE));
m_shader.stop_using();
shader->stop_using();
}
}
}
@ -547,29 +610,41 @@ void Bed3D::render_model() const
if (m_model_filename.empty())
return;
if ((m_model.get_filename() != m_model_filename) && m_model.init_from_file(m_model_filename))
{
if ((m_model.get_filename() != m_model_filename) && m_model.init_from_file(m_model_filename)) {
// move the model so that its origin (0.0, 0.0, 0.0) goes into the bed shape center and a bit down to avoid z-fighting with the texture quad
Vec3d shift = m_bounding_box.center();
shift(2) = -0.03;
#if ENABLE_GCODE_VIEWER
m_model_offset = shift;
#else
m_model.set_offset(shift);
#endif // ENABLE_GCODE_VIEWER
// update extended bounding box
calc_bounding_boxes();
}
if (!m_model.get_filename().empty())
{
glsafe(::glEnable(GL_LIGHTING));
m_model.render();
glsafe(::glDisable(GL_LIGHTING));
if (!m_model.get_filename().empty()) {
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader != nullptr) {
shader->start_using();
#if ENABLE_GCODE_VIEWER
shader->set_uniform("uniform_color", m_model_color);
::glPushMatrix();
::glTranslated(m_model_offset(0), m_model_offset(1), m_model_offset(2));
#endif // ENABLE_GCODE_VIEWER
m_model.render();
#if ENABLE_GCODE_VIEWER
::glPopMatrix();
#endif // ENABLE_GCODE_VIEWER
shader->stop_using();
}
}
}
void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom, bool show_texture) const
{
if (m_texture_filename.empty() && m_model_filename.empty())
{
if (m_texture_filename.empty() && m_model_filename.empty()) {
render_default(bottom);
return;
}
@ -586,8 +661,7 @@ void Bed3D::render_default(bool bottom) const
m_texture.reset();
unsigned int triangles_vcount = m_triangles.get_vertices_count();
if (triangles_vcount > 0)
{
if (triangles_vcount > 0) {
bool has_model = !m_model.get_filename().empty();
glsafe(::glEnable(GL_DEPTH_TEST));
@ -596,11 +670,14 @@ void Bed3D::render_default(bool bottom) const
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
if (!has_model && !bottom)
{
if (!has_model && !bottom) {
// draw background
glsafe(::glDepthMask(GL_FALSE));
#if ENABLE_GCODE_VIEWER
glsafe(::glColor4fv(m_model_color.data()));
#else
glsafe(::glColor4f(0.35f, 0.35f, 0.35f, 0.4f));
#endif // ENABLE_GCODE_VIEWER
glsafe(::glNormal3d(0.0f, 0.0f, 1.0f));
glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_triangles.get_vertices_data()));
glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount));
@ -608,11 +685,11 @@ void Bed3D::render_default(bool bottom) const
}
// draw grid
glsafe(::glLineWidth(3.0f * m_scale_factor));
glsafe(::glLineWidth(1.5f * m_scale_factor));
if (has_model && !bottom)
glsafe(::glColor4f(0.75f, 0.75f, 0.75f, 1.0f));
glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 1.0f));
else
glsafe(::glColor4f(0.2f, 0.2f, 0.2f, 0.4f));
glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 0.6f));
glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_gridlines.get_vertices_data()));
glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_gridlines.get_vertices_count()));
@ -624,8 +701,7 @@ void Bed3D::render_default(bool bottom) const
void Bed3D::reset()
{
if (m_vbo_id > 0)
{
if (m_vbo_id > 0) {
glsafe(::glDeleteBuffers(1, &m_vbo_id));
m_vbo_id = 0;
}

View file

@ -3,12 +3,19 @@
#include "GLTexture.hpp"
#include "3DScene.hpp"
#include "GLShader.hpp"
#if ENABLE_GCODE_VIEWER
#include "GLModel.hpp"
#endif // ENABLE_GCODE_VIEWER
#include <tuple>
#if ENABLE_GCODE_VIEWER
#include <array>
#endif // ENABLE_GCODE_VIEWER
#if !ENABLE_GCODE_VIEWER
class GLUquadric;
typedef class GLUquadric GLUquadricObj;
#endif // !ENABLE_GCODE_VIEWER
namespace Slic3r {
namespace GUI {
@ -45,22 +52,53 @@ public:
class Bed3D
{
#if ENABLE_GCODE_VIEWER
class Axes
{
public:
static const float DefaultStemRadius;
static const float DefaultStemLength;
static const float DefaultTipRadius;
static const float DefaultTipLength;
private:
#else
struct Axes
{
static const double Radius;
static const double ArrowBaseRadius;
static const double ArrowLength;
#endif // ENABLE_GCODE_VIEWER
#if ENABLE_GCODE_VIEWER
Vec3d m_origin{ Vec3d::Zero() };
float m_stem_length{ DefaultStemLength };
mutable GLModel m_arrow;
public:
#else
Vec3d origin;
Vec3d length;
GLUquadricObj* m_quadric;
#endif // ENABLE_GCODE_VIEWER
#if !ENABLE_GCODE_VIEWER
Axes();
~Axes();
#endif // !ENABLE_GCODE_VIEWER
#if ENABLE_GCODE_VIEWER
const Vec3d& get_origin() const { return m_origin; }
void set_origin(const Vec3d& origin) { m_origin = origin; }
void set_stem_length(float length);
float get_total_length() const { return m_stem_length + DefaultTipLength; }
#endif // ENABLE_GCODE_VIEWER
void render() const;
#if !ENABLE_GCODE_VIEWER
private:
void render_axis(double length) const;
#endif // !ENABLE_GCODE_VIEWER
};
public:
@ -82,10 +120,15 @@ private:
GeometryBuffer m_triangles;
GeometryBuffer m_gridlines;
mutable GLTexture m_texture;
#if ENABLE_GCODE_VIEWER
mutable GLModel m_model;
mutable Vec3d m_model_offset{ Vec3d::Zero() };
std::array<float, 4> m_model_color{ 0.235f, 0.235f, 0.235f, 1.0f };
#else
mutable GLBed m_model;
#endif // ENABLE_GCODE_VIEWER
// temporary texture shown until the main texture has still no levels compressed
mutable GLTexture m_temp_texture;
mutable Shader m_shader;
mutable unsigned int m_vbo_id;
Axes m_axes;
@ -101,7 +144,7 @@ public:
const Pointfs& get_shape() const { return m_shape; }
// Return true if the bed shape changed, so the calee will update the UI.
bool set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model);
bool set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false);
const BoundingBoxf3& get_bounding_box(bool extended) const {
return extended ? m_extended_bounding_box : m_bounding_box;

View file

@ -7,19 +7,24 @@
#endif // ENABLE_SMOOTH_NORMALS
#include "3DScene.hpp"
#if ENABLE_ENVIRONMENT_MAP
#include "GLShader.hpp"
#include "GUI_App.hpp"
#if ENABLE_ENVIRONMENT_MAP
#include "Plater.hpp"
#endif // ENABLE_ENVIRONMENT_MAP
#include "libslic3r/ExtrusionEntity.hpp"
#include "libslic3r/ExtrusionEntityCollection.hpp"
#include "libslic3r/Geometry.hpp"
#if !ENABLE_GCODE_VIEWER
#include "libslic3r/GCode/PreviewData.hpp"
#endif // !ENABLE_GCODE_VIEWER
#include "libslic3r/Print.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/Slicing.hpp"
#if !ENABLE_GCODE_VIEWER
#include "libslic3r/GCode/Analyzer.hpp"
#endif // !ENABLE_GCODE_VIEWER
#include "slic3r/GUI/BitmapCache.hpp"
#include "libslic3r/Format/STL.hpp"
#include "libslic3r/Utils.hpp"
@ -442,7 +447,6 @@ BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d &
bounding_box().transformed(trafo);
}
void GLVolume::set_range(double min_z, double max_z)
{
this->qverts_range.first = 0;
@ -747,6 +751,10 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo
void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disable_cullface, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func) const
{
GLShaderProgram* shader = GUI::wxGetApp().get_current_shader();
if (shader == nullptr)
return;
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
@ -757,80 +765,32 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
GLint current_program_id;
glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, &current_program_id));
GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1;
GLint z_range_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "z_range") : -1;
GLint clipping_plane_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "clipping_plane") : -1;
GLint print_box_min_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.min") : -1;
GLint print_box_max_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.max") : -1;
GLint print_box_active_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.actived") : -1;
GLint print_box_worldmatrix_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.volume_world_matrix") : -1;
shader->set_uniform("print_box.min", m_print_box_min, 3);
shader->set_uniform("print_box.max", m_print_box_max, 3);
shader->set_uniform("z_range", m_z_range, 2);
shader->set_uniform("clipping_plane", m_clipping_plane, 4);
#if ENABLE_SLOPE_RENDERING
GLint slope_active_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "slope.actived") : -1;
GLint slope_normal_matrix_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "slope.volume_world_normal_matrix") : -1;
GLint slope_z_range_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "slope.z_range") : -1;
#endif // ENABLE_SLOPE_RENDERING
#if ENABLE_ENVIRONMENT_MAP
GLint use_environment_tex_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "use_environment_tex") : -1;
#endif // ENABLE_ENVIRONMENT_MAP
glcheck();
if (print_box_min_id != -1)
glsafe(::glUniform3fv(print_box_min_id, 1, (const GLfloat*)m_print_box_min));
if (print_box_max_id != -1)
glsafe(::glUniform3fv(print_box_max_id, 1, (const GLfloat*)m_print_box_max));
if (z_range_id != -1)
glsafe(::glUniform2fv(z_range_id, 1, (const GLfloat*)m_z_range));
if (clipping_plane_id != -1)
glsafe(::glUniform4fv(clipping_plane_id, 1, (const GLfloat*)m_clipping_plane));
#if ENABLE_SLOPE_RENDERING
if (slope_z_range_id != -1)
glsafe(::glUniform2fv(slope_z_range_id, 1, (const GLfloat*)m_slope.z_range.data()));
shader->set_uniform("slope.z_range", m_slope.z_range);
#endif // ENABLE_SLOPE_RENDERING
#if ENABLE_ENVIRONMENT_MAP
unsigned int environment_texture_id = GUI::wxGetApp().plater()->get_environment_texture_id();
bool use_environment_texture = current_program_id > 0 && environment_texture_id > 0 && GUI::wxGetApp().app_config->get("use_environment_map") == "1";
if (use_environment_tex_id != -1)
{
glsafe(::glUniform1i(use_environment_tex_id, use_environment_texture ? 1 : 0));
if (use_environment_texture)
glsafe(::glBindTexture(GL_TEXTURE_2D, environment_texture_id));
}
bool use_environment_texture = environment_texture_id > 0 && GUI::wxGetApp().app_config->get("use_environment_map") == "1";
shader->set_uniform("use_environment_tex", use_environment_texture);
if (use_environment_texture)
glsafe(::glBindTexture(GL_TEXTURE_2D, environment_texture_id));
#endif // ENABLE_ENVIRONMENT_MAP
glcheck();
GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func);
for (GLVolumeWithIdAndZ& volume : to_render) {
volume.first->set_render_color();
#if ENABLE_SLOPE_RENDERING
if (color_id >= 0)
glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)volume.first->render_color));
else
glsafe(::glColor4fv(volume.first->render_color));
if (print_box_active_id != -1)
glsafe(::glUniform1i(print_box_active_id, volume.first->shader_outside_printer_detection_enabled ? 1 : 0));
if (print_box_worldmatrix_id != -1)
glsafe(::glUniformMatrix4fv(print_box_worldmatrix_id, 1, GL_FALSE, (const GLfloat*)volume.first->world_matrix().cast<float>().data()));
if (slope_active_id != -1)
glsafe(::glUniform1i(slope_active_id, m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower ? 1 : 0));
if (slope_normal_matrix_id != -1)
{
Matrix3f normal_matrix = volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>();
glsafe(::glUniformMatrix3fv(slope_normal_matrix_id, 1, GL_FALSE, (const GLfloat*)normal_matrix.data()));
}
shader->set_uniform("uniform_color", volume.first->render_color, 4);
shader->set_uniform("print_box.actived", volume.first->shader_outside_printer_detection_enabled);
shader->set_uniform("print_box.volume_world_matrix", volume.first->world_matrix());
shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower);
shader->set_uniform("slope.volume_world_normal_matrix", static_cast<Matrix3f>(volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>()));
volume.first->render();
#else
@ -839,7 +799,7 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
}
#if ENABLE_ENVIRONMENT_MAP
if (use_environment_tex_id != -1 && use_environment_texture)
if (use_environment_texture)
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
#endif // ENABLE_ENVIRONMENT_MAP
@ -1057,6 +1017,7 @@ bool GLVolumeCollection::has_toolpaths_to_export() const
return false;
}
#if !ENABLE_GCODE_VIEWER
void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const
{
if (filename == nullptr)
@ -1338,6 +1299,7 @@ void GLVolumeCollection::export_toolpaths_to_obj(const char* filename) const
fclose(fp);
}
#endif // !ENABLE_GCODE_VIEWER
// caller is responsible for supplying NO lines with zero length
static void thick_lines_to_indexed_vertex_array(
@ -1985,6 +1947,7 @@ void _3DScene::point3_to_verts(const Vec3crd& point, double width, double height
thick_point_to_verts(point, width, height, volume);
}
#if !ENABLE_GCODE_VIEWER
GLModel::GLModel()
: m_filename("")
{
@ -2040,6 +2003,10 @@ void GLModel::reset()
void GLModel::render() const
{
GLShaderProgram* shader = GUI::wxGetApp().get_current_shader();
if (shader == nullptr)
return;
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
@ -2047,17 +2014,8 @@ void GLModel::render() const
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
GLint current_program_id;
glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, &current_program_id));
GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1;
glcheck();
#if ENABLE_SLOPE_RENDERING
if (color_id >= 0)
glsafe(::glUniform4fv(color_id, 1, (const GLfloat*)m_volume.render_color));
else
glsafe(::glColor4fv(m_volume.render_color));
shader->set_uniform("uniform_color", m_volume.render_color, 4);
m_volume.render();
#else
m_volume.render(color_id, -1, -1);
@ -2274,5 +2232,6 @@ bool GLBed::on_init_from_file(const std::string& filename)
return true;
}
#endif // !ENABLE_GCODE_VIEWER
} // namespace Slic3r

View file

@ -26,12 +26,6 @@
#endif // HAS_GLSAFE
namespace Slic3r {
namespace GUI {
class Bed3D;
struct Camera;
class GLToolbar;
} // namespace GUI
class SLAPrintObject;
enum SLAPrintObjectStep : unsigned int;
class DynamicPrintConfig;
@ -603,8 +597,10 @@ public:
std::string log_memory_info() const;
bool has_toolpaths_to_export() const;
#if !ENABLE_GCODE_VIEWER
// Export the geometry of the GLVolumes toolpaths of this collection into the file with the given path, in obj format
void export_toolpaths_to_obj(const char* filename) const;
#endif // !ENABLE_GCODE_VIEWER
private:
GLVolumeCollection(const GLVolumeCollection &other);
@ -613,6 +609,7 @@ private:
GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func = nullptr);
#if !ENABLE_GCODE_VIEWER
class GLModel
{
protected:
@ -672,6 +669,7 @@ class GLBed : public GLModel
protected:
bool on_init_from_file(const std::string& filename) override;
};
#endif // !ENABLE_GCODE_VIEWER
struct _3DScene
{

View file

@ -19,7 +19,9 @@
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/GCode/PostProcessor.hpp"
#if !ENABLE_GCODE_VIEWER
#include "libslic3r/GCode/PreviewData.hpp"
#endif // !ENABLE_GCODE_VIEWER
#include "libslic3r/Format/SL1.hpp"
#include "libslic3r/libslic3r.h"
@ -92,7 +94,11 @@ void BackgroundSlicingProcess::process_fff()
wxCommandEvent evt(m_event_slicing_completed_id);
evt.SetInt((int)(m_fff_print->step_state_with_timestamp(PrintStep::psBrim).timestamp));
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, evt.Clone());
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb);
#if ENABLE_GCODE_VIEWER
m_fff_print->export_gcode(m_temp_output_path, m_gcode_result, m_thumbnail_cb);
#else
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb);
#endif // ENABLE_GCODE_VIEWER
if (this->set_step_started(bspsGCodeFinalize)) {
if (! m_export_path.empty()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
@ -382,6 +388,17 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const Dyn
assert(m_print != nullptr);
assert(config.opt_enum<PrinterTechnology>("printer_technology") == m_print->technology());
Print::ApplyStatus invalidated = m_print->apply(model, config);
#if ENABLE_GCODE_VIEWER
if ((invalidated & PrintBase::APPLY_STATUS_INVALIDATED) != 0 && m_print->technology() == ptFFF &&
!this->m_fff_print->is_step_done(psGCodeExport))
{
// Some FFF status was invalidated, and the G-code was not exported yet.
// Let the G-code preview UI know that the final G-code preview is not valid.
// In addition, this early memory deallocation reduces memory footprint.
if (m_gcode_result != nullptr)
m_gcode_result->reset();
}
#else
if ((invalidated & PrintBase::APPLY_STATUS_INVALIDATED) != 0 && m_print->technology() == ptFFF &&
m_gcode_preview_data != nullptr && ! this->m_fff_print->is_step_done(psGCodeExport)) {
// Some FFF status was invalidated, and the G-code was not exported yet.
@ -389,6 +406,7 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const Dyn
// In addition, this early memory deallocation reduces memory footprint.
m_gcode_preview_data->reset();
}
#endif // ENABLE_GCODE_VIEWER
return invalidated;
}

View file

@ -11,6 +11,9 @@
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "libslic3r/Format/SL1.hpp"
#include "slic3r/Utils/PrintHost.hpp"
#if ENABLE_GCODE_VIEWER
#include "libslic3r/GCode/GCodeProcessor.hpp"
#endif // ENABLE_GCODE_VIEWER
namespace boost { namespace filesystem { class path; } }
@ -18,7 +21,9 @@ namespace boost { namespace filesystem { class path; } }
namespace Slic3r {
class DynamicPrintConfig;
#if !ENABLE_GCODE_VIEWER
class GCodePreviewData;
#endif // !ENABLE_GCODE_VIEWER
class Model;
class SLAPrint;
@ -50,8 +55,12 @@ public:
void set_fff_print(Print *print) { m_fff_print = print; }
void set_sla_print(SLAPrint *print) { m_sla_print = print; m_sla_print->set_printer(&m_sla_archive); }
void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; }
void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; }
void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; }
#if ENABLE_GCODE_VIEWER
void set_gcode_result(GCodeProcessor::Result* result) { m_gcode_result = result; }
#else
void set_gcode_preview_data(GCodePreviewData* gpd) { m_gcode_preview_data = gpd; }
#endif // ENABLE_GCODE_VIEWER
// The following wxCommandEvent will be sent to the UI thread / Plater window, when the slicing is finished
// and the background processing will transition into G-code export.
@ -157,12 +166,17 @@ private:
// Non-owned pointers to Print instances.
Print *m_fff_print = nullptr;
SLAPrint *m_sla_print = nullptr;
#if ENABLE_GCODE_VIEWER
// Data structure, to which the G-code export writes its annotations.
GCodeProcessor::Result *m_gcode_result = nullptr;
#else
// Data structure, to which the G-code export writes its annotations.
GCodePreviewData *m_gcode_preview_data = nullptr;
// Callback function, used to write thumbnails into gcode.
ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
SL1Archive m_sla_archive;
// Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID.
#endif // ENABLE_GCODE_VIEWER
// Callback function, used to write thumbnails into gcode.
ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
SL1Archive m_sla_archive;
// 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.

View file

@ -3,6 +3,9 @@
#include "libslic3r/Utils.hpp"
#include "../Utils/MacDarkMode.hpp"
#include "GUI.hpp"
#if ENABLE_GCODE_VIEWER
#include "GUI_Utils.hpp"
#endif // ENABLE_GCODE_VIEWER
#include <boost/filesystem.hpp>
@ -355,6 +358,7 @@ wxBitmap BitmapCache::mksolid(size_t width, size_t height, unsigned char r, unsi
}
#if !ENABLE_GCODE_VIEWER
static inline int hex_digit_to_int(const char c)
{
return
@ -362,6 +366,7 @@ static inline int hex_digit_to_int(const char c)
(c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
(c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
}
#endif // !ENABLE_GCODE_VIEWER
bool BitmapCache::parse_color(const std::string& scolor, unsigned char* rgb_out)
{

View file

@ -5,6 +5,7 @@
#include "GUI_App.hpp"
#if ENABLE_CAMERA_STATISTICS
#include "Mouse3DController.hpp"
#include "Plater.hpp"
#endif // ENABLE_CAMERA_STATISTICS
#include <GL/glew.h>

View file

@ -84,6 +84,7 @@ public:
double get_near_z() const { return m_frustrum_zs.first; }
double get_far_z() const { return m_frustrum_zs.second; }
const std::pair<double, double>& get_z_range() const { return m_frustrum_zs; }
double get_fov() const;

View file

@ -10,6 +10,7 @@
#include <boost/format.hpp>
#include <boost/log/trivial.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/nowide/convert.hpp>
#include <wx/settings.h>
#include <wx/stattext.h>
@ -561,30 +562,37 @@ const std::string PageMaterials::EMPTY;
PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name)
: ConfigWizardPage(parent, std::move(title), std::move(shortname))
, materials(materials)
, list_l1(new StringList(this))
, list_l2(new StringList(this))
, list_l3(new PresetList(this))
, list_printer(new StringList(this, wxLB_MULTIPLE))
, list_type(new StringList(this))
, list_vendor(new StringList(this))
, list_profile(new PresetList(this))
, compatible_printers(new wxStaticText(this, wxID_ANY, _(L(""))))
{
append_spacer(VERTICAL_SPACING);
const int em = parent->em_unit();
const int list_h = 30*em;
list_l1->SetMinSize(wxSize(8*em, list_h));
list_l2->SetMinSize(wxSize(13*em, list_h));
list_l3->SetMinSize(wxSize(25*em, list_h));
list_printer->SetWindowStyle(wxLB_EXTENDED);
auto *grid = new wxFlexGridSizer(3, em/2, em);
grid->AddGrowableCol(2, 1);
list_printer->SetMinSize(wxSize(23*em, list_h));
list_type->SetMinSize(wxSize(8*em, list_h));
list_vendor->SetMinSize(wxSize(13*em, list_h));
list_profile->SetMinSize(wxSize(23*em, list_h));
grid = new wxFlexGridSizer(4, em/2, em);
grid->AddGrowableCol(3, 1);
grid->AddGrowableRow(1, 1);
grid->Add(new wxStaticText(this, wxID_ANY, _(L("Printer:"))));
grid->Add(new wxStaticText(this, wxID_ANY, list1name));
grid->Add(new wxStaticText(this, wxID_ANY, _(L("Vendor:"))));
grid->Add(new wxStaticText(this, wxID_ANY, _(L("Profile:"))));
grid->Add(list_l1, 0, wxEXPAND);
grid->Add(list_l2, 0, wxEXPAND);
grid->Add(list_l3, 1, wxEXPAND);
grid->Add(list_printer, 0, wxEXPAND);
grid->Add(list_type, 0, wxEXPAND);
grid->Add(list_vendor, 0, wxEXPAND);
grid->Add(list_profile, 1, wxEXPAND);
auto *btn_sizer = new wxBoxSizer(wxHORIZONTAL);
auto *sel_all = new wxButton(this, wxID_ANY, _(L("All")));
@ -592,121 +600,342 @@ PageMaterials::PageMaterials(ConfigWizard *parent, Materials *materials, wxStrin
btn_sizer->Add(sel_all, 0, wxRIGHT, em / 2);
btn_sizer->Add(sel_none);
grid->Add(new wxBoxSizer(wxHORIZONTAL));
grid->Add(new wxBoxSizer(wxHORIZONTAL));
grid->Add(btn_sizer, 0, wxALIGN_RIGHT);
auto* notes_sizer = new wxBoxSizer(wxHORIZONTAL);
notes_sizer->Add(compatible_printers);
grid->Add(notes_sizer);
append(grid, 1, wxEXPAND);
list_l1->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) {
update_lists(list_l1->GetSelection(), list_l2->GetSelection());
list_printer->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) {
update_lists(evt.GetInt(), list_type->GetSelection(), list_vendor->GetSelection());
});
list_type->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) {
update_lists(list_printer->GetSelection(), list_type->GetSelection(), list_vendor->GetSelection());
});
list_l2->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) {
update_lists(list_l1->GetSelection(), list_l2->GetSelection());
list_vendor->Bind(wxEVT_LISTBOX, [this](wxCommandEvent &) {
update_lists(list_printer->GetSelection(), list_type->GetSelection(), list_vendor->GetSelection());
});
list_l3->Bind(wxEVT_CHECKLISTBOX, [this](wxCommandEvent &evt) { select_material(evt.GetInt()); });
list_profile->Bind(wxEVT_CHECKLISTBOX, [this](wxCommandEvent &evt) { select_material(evt.GetInt()); });
list_profile->Bind(wxEVT_LISTBOX, [this](wxCommandEvent& evt) { on_material_highlighted(evt.GetInt()); });
sel_all->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(true); });
sel_none->Bind(wxEVT_BUTTON, [this](wxCommandEvent &) { select_all(false); });
Bind(wxEVT_PAINT, [this](wxPaintEvent& evt) {on_paint();});
list_profile->Bind(wxEVT_MOTION, [this](wxMouseEvent& evt) { on_mouse_move_on_profiles(evt); });
list_profile->Bind(wxEVT_ENTER_WINDOW, [this](wxMouseEvent& evt) { on_mouse_enter_profiles(evt); });
list_profile->Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& evt) { on_mouse_leave_profiles(evt); });
reload_presets();
}
void PageMaterials::on_paint()
{
if (first_paint) {
first_paint = false;
prepare_compatible_printers_label();
}
}
void PageMaterials::on_mouse_move_on_profiles(wxMouseEvent& evt)
{
const wxClientDC dc(list_profile);
const wxPoint pos = evt.GetLogicalPosition(dc);
int item = list_profile->HitTest(pos);
BOOST_LOG_TRIVIAL(error) << "hit test: " << item;
on_material_hovered(item);
}
void PageMaterials::on_mouse_enter_profiles(wxMouseEvent& evt)
{}
void PageMaterials::on_mouse_leave_profiles(wxMouseEvent& evt)
{
on_material_hovered(-1);
}
void PageMaterials::reload_presets()
{
clear();
list_l1->append(_(L("(All)")), &EMPTY);
list_printer->append(_(L("(All)")), &EMPTY);
list_printer->SetLabelMarkup("<b>bald</b>");
for (const Preset* printer : materials->printers) {
list_printer->append(printer->name, &printer->name);
}
for (const std::string &type : materials->types) {
list_l1->append(type, &type);
}
if (list_l1->GetCount() > 0) {
list_l1->SetSelection(0);
sel1_prev = wxNOT_FOUND;
sel2_prev = wxNOT_FOUND;
update_lists(0, 0);
if (list_printer->GetCount() > 0) {
list_printer->SetSelection(0);
sel_printer_prev = wxNOT_FOUND;
sel_type_prev = wxNOT_FOUND;
sel_vendor_prev = wxNOT_FOUND;
update_lists(0, 0, 0);
}
presets_loaded = true;
}
void PageMaterials::update_lists(int sel1, int sel2)
void PageMaterials::prepare_compatible_printers_label()
{
wxWindowUpdateLocker freeze_guard(this);
(void)freeze_guard;
if (sel1 != sel1_prev) {
// Refresh the second list
// XXX: The vendor list is created with quadratic complexity here,
// but the number of vendors is going to be very small this shouldn't be a problem.
list_l2->Clear();
list_l2->append(_(L("(All)")), &EMPTY);
if (sel1 != wxNOT_FOUND) {
const std::string &type = list_l1->get_data(sel1);
materials->filter_presets(type, EMPTY, [this](const Preset *p) {
const std::string &vendor = this->materials->get_vendor(p);
if (list_l2->find(vendor) == wxNOT_FOUND) {
list_l2->append(vendor, &vendor);
}
});
}
sel1_prev = sel1;
sel2 = 0;
sel2_prev = wxNOT_FOUND;
list_l2->SetSelection(sel2);
list_l3->Clear();
assert(grid->GetColWidths().size() == 4);
compatible_printers_width = grid->GetColWidths()[3];
empty_printers_label = "Compatible printers:";
for (const Preset* printer : materials->printers) {
empty_printers_label += "\n";
}
clear_compatible_printers_label();
}
if (sel2 != sel2_prev) {
// Refresh the third list
void PageMaterials::clear_compatible_printers_label()
{
compatible_printers->SetLabel(boost::nowide::widen(empty_printers_label));
compatible_printers->Wrap(compatible_printers_width);
Layout();
}
list_l3->Clear();
if (sel1 != wxNOT_FOUND && sel2 != wxNOT_FOUND) {
const std::string &type = list_l1->get_data(sel1);
const std::string &vendor = list_l2->get_data(sel2);
materials->filter_presets(type, vendor, [this](const Preset *p) {
bool was_checked = false;
int cur_i = list_l3->find(p->alias);
if (cur_i == wxNOT_FOUND)
cur_i = list_l3->append(p->alias, &p->alias);
void PageMaterials::on_material_hovered(int sel_material)
{
if ( sel_material == last_hovered_item)
return;
if (sel_material == -1) {
clear_compatible_printers_label();
return;
}
last_hovered_item = sel_material;
std::string compatible_printers_label = "compatible printers:\n";
//selected material string
std::string material_name = list_profile->get_data(sel_material);
// get material preset
const std::vector<const Preset*> matching_materials = materials->get_presets_by_alias(material_name);
if (matching_materials.empty())
{
clear_compatible_printers_label();
return;
}
//find matching printers
bool first = true;
for (const Preset* printer : materials->printers) {
bool compatible = false;
for (const Preset* material : matching_materials) {
if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) {
if (first)
first = false;
else
was_checked = list_l3->IsChecked(cur_i);
const std::string& section = materials->appconfig_section();
const bool checked = wizard_p()->appconfig_new.has(section, p->name);
list_l3->Check(cur_i, checked | was_checked);
/* Update preset selection in config.
* If one preset from aliases bundle is selected,
* than mark all presets with this aliases as selected
* */
if (checked && !was_checked)
wizard_p()->update_presets_in_config(section, p->alias, true);
else if (!checked && was_checked)
wizard_p()->appconfig_new.set(section, p->name, "1");
} );
compatible_printers_label += "\n";//", ";
compatible_printers_label += printer->name;
compatible = true;
break;
}
}
sel2_prev = sel2;
}
this->compatible_printers->SetLabel(boost::nowide::widen(compatible_printers_label));
this->compatible_printers->Wrap(compatible_printers_width);
}
void PageMaterials::on_material_highlighted(int sel_material)
{
wxWindowUpdateLocker freeze_guard(this);
(void)freeze_guard;
//std::string compatible_printers_label = "compatible printers:\n";
//std::string empty_suplement = std::string();
//unselect all printers
list_printer->SetSelection(wxNOT_FOUND);
//selected material string
std::string material_name = list_profile->get_data(sel_material);
// get material preset
const std::vector<const Preset*> matching_materials = materials->get_presets_by_alias(material_name);
if (matching_materials.empty())
return;
//find matching printers
//bool first = true;
for (const Preset* printer : materials->printers) {
bool compatible = false;
for (const Preset* material : matching_materials) {
if (is_compatible_with_printer(PresetWithVendorProfile(*material, material->vendor), PresetWithVendorProfile(*printer, printer->vendor))) {
//select printer
int index = list_printer->find(printer->name);
list_printer->SetSelection(index);
/*if (first)
first = false;
else
compatible_printers_label += "\n";//", ";
compatible_printers_label += printer->name;
compatible = true;
break;*/
}
}
//if(!compatible)
// empty_suplement += std::string(printer->name.length() + 2, ' ');
}
// fill rest of label with blanks so it maintains legth
//compatible_printers_label += empty_suplement;
update_lists(0,0,0);
list_profile->SetSelection(list_profile->find(material_name));
//this->compatible_printers->SetLabel(boost::nowide::widen(compatible_printers_label));
//this->compatible_printers->Wrap(compatible_printers_width);
//Refresh();
}
void PageMaterials::update_lists(int sel_printer, int sel_type, int sel_vendor)
{
wxWindowUpdateLocker freeze_guard(this);
(void)freeze_guard;
wxArrayInt sel_printers;
int sel_printers_count = list_printer->GetSelections(sel_printers);
if (sel_printers_count != sel_printer_prev) {
// Refresh type list
list_type->Clear();
list_type->append(_(L("(All)")), &EMPTY);
if (sel_printers_count > 0) {
// If all is selected with other printers
// unselect "all" or all printers depending on last value
if (sel_printers[0] == 0 && sel_printers_count > 1) {
if (sel_printer == 0) {
list_printer->SetSelection(wxNOT_FOUND);
list_printer->SetSelection(0);
} else {
list_printer->SetSelection(0, false);
sel_printers_count = list_printer->GetSelections(sel_printers);
}
}
if (sel_printers[0] != 0) {
for (size_t i = 0; i < sel_printers_count; i++) {
const std::string& printer_name = list_printer->get_data(sel_printers[i]);
const Preset* printer = nullptr;
for (const Preset* it : materials->printers) {
if (it->name == printer_name) {
printer = it;
break;
}
}
materials->filter_presets(printer, EMPTY, EMPTY, [this](const Preset* p) {
const std::string& type = this->materials->get_type(p);
if (list_type->find(type) == wxNOT_FOUND) {
list_type->append(type, &type);
}
});
}
} else {
//clear selection except "ALL"
list_printer->SetSelection(wxNOT_FOUND);
list_printer->SetSelection(0);
materials->filter_presets(nullptr, EMPTY, EMPTY, [this](const Preset* p) {
const std::string& type = this->materials->get_type(p);
if (list_type->find(type) == wxNOT_FOUND) {
list_type->append(type, &type);
}
});
}
}
sel_printer_prev = sel_printers_count;
sel_type = 0;
sel_type_prev = wxNOT_FOUND;
list_type->SetSelection(sel_type);
list_profile->Clear();
}
if (sel_type != sel_type_prev) {
// Refresh vendor list
// XXX: The vendor list is created with quadratic complexity here,
// but the number of vendors is going to be very small this shouldn't be a problem.
list_vendor->Clear();
list_vendor->append(_(L("(All)")), &EMPTY);
if (sel_printers_count != 0 && sel_type != wxNOT_FOUND) {
const std::string& type = list_type->get_data(sel_type);
// find printer preset
for (size_t i = 0; i < sel_printers_count; i++) {
const std::string& printer_name = list_printer->get_data(sel_printers[i]);
const Preset* printer = nullptr;
for (const Preset* it : materials->printers) {
if (it->name == printer_name) {
printer = it;
break;
}
}
materials->filter_presets(printer, type, EMPTY, [this](const Preset* p) {
const std::string& vendor = this->materials->get_vendor(p);
if (list_vendor->find(vendor) == wxNOT_FOUND) {
list_vendor->append(vendor, &vendor);
}
});
}
}
sel_type_prev = sel_type;
sel_vendor = 0;
sel_vendor_prev = wxNOT_FOUND;
list_vendor->SetSelection(sel_vendor);
list_profile->Clear();
}
if (sel_vendor != sel_vendor_prev) {
// Refresh material list
list_profile->Clear();
clear_compatible_printers_label();
if (sel_printers_count != 0 && sel_type != wxNOT_FOUND && sel_vendor != wxNOT_FOUND) {
const std::string& type = list_type->get_data(sel_type);
const std::string& vendor = list_vendor->get_data(sel_vendor);
// finst printer preset
for (size_t i = 0; i < sel_printers_count; i++) {
const std::string& printer_name = list_printer->get_data(sel_printers[i]);
const Preset* printer = nullptr;
for (const Preset* it : materials->printers) {
if (it->name == printer_name) {
printer = it;
break;
}
}
materials->filter_presets(printer, type, vendor, [this](const Preset* p) {
bool was_checked = false;
//size_t printer_counter = materials->get_printer_counter(p);
int cur_i = list_profile->find(p->alias);
if (cur_i == wxNOT_FOUND)
//cur_i = list_profile->append(p->alias + " " + std::to_string(printer_counter)/*+ (omnipresent ? "" : " ONLY SOME PRINTERS")*/, &p->alias);
cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias);
else
was_checked = list_profile->IsChecked(cur_i);
const std::string& section = materials->appconfig_section();
const bool checked = wizard_p()->appconfig_new.has(section, p->name);
list_profile->Check(cur_i, checked | was_checked);
/* Update preset selection in config.
* If one preset from aliases bundle is selected,
* than mark all presets with this aliases as selected
* */
if (checked && !was_checked)
wizard_p()->update_presets_in_config(section, p->alias, true);
else if (!checked && was_checked)
wizard_p()->appconfig_new.set(section, p->name, "1");
});
}
}
sel_vendor_prev = sel_vendor;
}
}
void PageMaterials::select_material(int i)
{
const bool checked = list_l3->IsChecked(i);
const bool checked = list_profile->IsChecked(i);
const std::string& alias_key = list_l3->get_data(i);
const std::string& alias_key = list_profile->get_data(i);
wizard_p()->update_presets_in_config(materials->appconfig_section(), alias_key, checked);
}
@ -715,10 +944,10 @@ void PageMaterials::select_all(bool select)
wxWindowUpdateLocker freeze_guard(this);
(void)freeze_guard;
for (unsigned i = 0; i < list_l3->GetCount(); i++) {
const bool current = list_l3->IsChecked(i);
for (unsigned i = 0; i < list_profile->GetCount(); i++) {
const bool current = list_profile->IsChecked(i);
if (current != select) {
list_l3->Check(i, select);
list_profile->Check(i, select);
select_material(i);
}
}
@ -726,11 +955,13 @@ void PageMaterials::select_all(bool select)
void PageMaterials::clear()
{
list_l1->Clear();
list_l2->Clear();
list_l3->Clear();
sel1_prev = wxNOT_FOUND;
sel2_prev = wxNOT_FOUND;
list_printer->Clear();
list_type->Clear();
list_vendor->Clear();
list_profile->Clear();
sel_printer_prev = wxNOT_FOUND;
sel_type_prev = wxNOT_FOUND;
sel_vendor_prev = wxNOT_FOUND;
presets_loaded = false;
}
@ -740,6 +971,7 @@ void PageMaterials::on_activate()
wizard_p()->update_materials(materials->technology);
reload_presets();
}
first_paint = true;
}
@ -1314,16 +1546,22 @@ const std::string Materials::UNKNOWN = "(Unknown)";
void Materials::push(const Preset *preset)
{
presets.push_back(preset);
presets.emplace_back(preset, 0);
types.insert(technology & T_FFF
? Materials::get_filament_type(preset)
: Materials::get_material_type(preset));
}
void Materials::add_printer(const Preset* preset)
{
printers.insert(preset);
}
void Materials::clear()
{
presets.clear();
types.clear();
printers.clear();
}
const std::string& Materials::appconfig_section() const
@ -1373,7 +1611,6 @@ const std::string& Materials::get_material_vendor(const Preset *preset)
return opt != nullptr ? opt->value : UNKNOWN;
}
// priv
static const std::unordered_map<std::string, std::pair<std::string, std::string>> legacy_preset_map {{
@ -1601,26 +1838,28 @@ void ConfigWizard::priv::update_materials(Technology technology)
if (any_fff_selected && (technology & T_FFF)) {
filaments.clear();
aliases_fff.clear();
// Iterate filaments in all bundles
for (const auto &pair : bundles) {
for (const auto &filament : pair.second.preset_bundle->filaments) {
// Check if filament is already added
if (filaments.containts(&filament))
continue;
if (filaments.containts(&filament))
continue;
// Iterate printers in all bundles
// For now, we only allow the profiles to be compatible with another profiles inside the same bundle.
// for (const auto &pair : bundles)
for (const auto &printer : pair.second.preset_bundle->printers)
// Filter out inapplicable printers
if (printer.is_visible && printer.printer_technology() == ptFFF &&
is_compatible_with_printer(PresetWithVendorProfile(filament, nullptr), PresetWithVendorProfile(printer, nullptr)) &&
// Check if filament is already added
! filaments.containts(&filament)) {
filaments.push(&filament);
if (!filament.alias.empty())
aliases_fff[filament.alias].insert(filament.name);
}
for (const auto &printer : pair.second.preset_bundle->printers) {
if (!printer.is_visible || printer.printer_technology() != ptFFF)
continue;
// Filter out inapplicable printers
if (is_compatible_with_printer(PresetWithVendorProfile(filament, filament.vendor), PresetWithVendorProfile(printer, printer.vendor))) {
if (!filaments.containts(&filament)) {
filaments.push(&filament);
if (!filament.alias.empty())
aliases_fff[filament.alias].insert(filament.name);
}
filaments.add_printer_counter(&filament);
filaments.add_printer(&printer);
}
}
}
}
}
@ -1637,17 +1876,21 @@ void ConfigWizard::priv::update_materials(Technology technology)
continue;
// Iterate printers in all bundles
// For now, we only allow the profiles to be compatible with another profiles inside the same bundle.
// for (const auto &pair : bundles)
for (const auto &printer : pair.second.preset_bundle->printers)
// Filter out inapplicable printers
if (printer.is_visible && printer.printer_technology() == ptSLA &&
is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr)) &&
// Check if material is already added
! sla_materials.containts(&material)) {
for (const auto& printer : pair.second.preset_bundle->printers) {
if(!printer.is_visible || printer.printer_technology() != ptSLA)
continue;
// Filter out inapplicable printers
if (is_compatible_with_printer(PresetWithVendorProfile(material, nullptr), PresetWithVendorProfile(printer, nullptr))) {
// Check if material is already added
if(!sla_materials.containts(&material)) {
sla_materials.push(&material);
if (!material.alias.empty())
aliases_sla[material.alias].insert(material.name);
}
sla_materials.add_printer_counter(&material);
sla_materials.add_printer(&printer);
}
}
}
}
}

View file

@ -8,6 +8,7 @@
#include <unordered_map>
#include <functional>
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#include <wx/sizer.h>
#include <wx/panel.h>
@ -57,32 +58,98 @@ enum Technology {
T_ANY = ~0,
};
struct Bundle
{
std::unique_ptr<PresetBundle> preset_bundle;
VendorProfile* vendor_profile{ nullptr };
bool is_in_resources{ false };
bool is_prusa_bundle{ false };
Bundle() = default;
Bundle(Bundle&& other);
// Returns false if not loaded. Reason for that is logged as boost::log error.
bool load(fs::path source_path, bool is_in_resources, bool is_prusa_bundle = false);
const std::string& vendor_id() const { return vendor_profile->id; }
};
struct BundleMap : std::unordered_map<std::string /* = vendor ID */, Bundle>
{
static BundleMap load();
Bundle& prusa_bundle();
const Bundle& prusa_bundle() const;
};
struct Materials
{
Technology technology;
// use vector for the presets to purpose of save of presets sorting in the bundle
std::vector<const Preset*> presets;
// bool is true if material is present in all printers (omnipresent)
// size_t is counter of printers compatible with material
std::vector<std::pair<const Preset*, size_t>> presets;
std::set<std::string> types;
std::set<const Preset*> printers;
Materials(Technology technology) : technology(technology) {}
void push(const Preset *preset);
void add_printer(const Preset* preset);
void clear();
bool containts(const Preset *preset) const {
return std::find(presets.begin(), presets.end(), preset) != presets.end();
//return std::find(presets.begin(), presets.end(), preset) != presets.end();
return std::find_if(presets.begin(), presets.end(),
[preset](const std::pair<const Preset*, bool>& element) { return element.first == preset; }) != presets.end();
}
bool get_omnipresent(const Preset* preset) {
return get_printer_counter(preset) == printers.size();
}
const std::vector<const Preset*> get_presets_by_alias(const std::string name) {
std::vector<const Preset*> ret_vec;
for (auto it = presets.begin(); it != presets.end(); ++it) {
if ((*it).first->alias == name)
ret_vec.push_back((*it).first);
}
return ret_vec;
}
void add_printer_counter(const Preset* preset) {
for (auto it = presets.begin(); it != presets.end(); ++it) {
if ((*it).first->alias == preset->alias)
(*it).second += 1;
}
}
size_t get_printer_counter(const Preset* preset) {
size_t highest = 0;
for (auto it : presets) {
if (it.first->alias == preset->alias && it.second > highest)
highest = it.second;
}
return highest;
}
const std::string& appconfig_section() const;
const std::string& get_type(const Preset *preset) const;
const std::string& get_vendor(const Preset *preset) const;
template<class F> void filter_presets(const std::string &type, const std::string &vendor, F cb) {
for (const Preset *preset : presets) {
if ((type.empty() || get_type(preset) == type) && (vendor.empty() || get_vendor(preset) == vendor)) {
cb(preset);
}
}
}
template<class F> void filter_presets(const Preset* printer, const std::string& type, const std::string& vendor, F cb) {
for (auto preset : presets) {
const Preset& prst = *(preset.first);
const Preset& prntr = *printer;
if ((printer == nullptr || is_compatible_with_printer(PresetWithVendorProfile(prst, prst.vendor), PresetWithVendorProfile(prntr, prntr.vendor))) &&
(type.empty() || get_type(preset.first) == type) &&
(vendor.empty() || get_vendor(preset.first) == vendor)) {
cb(preset.first);
}
}
}
static const std::string UNKNOWN;
static const std::string& get_filament_type(const Preset *preset);
@ -91,33 +158,9 @@ struct Materials
static const std::string& get_material_vendor(const Preset *preset);
};
struct Bundle
{
std::unique_ptr<PresetBundle> preset_bundle;
VendorProfile *vendor_profile { nullptr };
bool is_in_resources { false };
bool is_prusa_bundle { false };
Bundle() = default;
Bundle(Bundle &&other);
// Returns false if not loaded. Reason for that is logged as boost::log error.
bool load(fs::path source_path, bool is_in_resources, bool is_prusa_bundle = false);
const std::string& vendor_id() const { return vendor_profile->id; }
};
struct BundleMap: std::unordered_map<std::string /* = vendor ID */, Bundle>
{
static BundleMap load();
Bundle& prusa_bundle();
const Bundle& prusa_bundle() const;
};
struct PrinterPickerEvent;
// GUI elements
typedef std::function<bool(const VendorProfile::PrinterModel&)> ModelFilter;
@ -225,6 +268,7 @@ struct PagePrinters: ConfigWizardPage
template<class T, class D> struct DataList : public T
{
DataList(wxWindow *parent) : T(parent, wxID_ANY) {}
DataList(wxWindow* parent, int style) : T(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, style) {}
// Note: We're _not_ using wxLB_SORT here because it doesn't do the right thing,
// eg. "ABS" is sorted before "(All)"
@ -252,6 +296,25 @@ template<class T, class D> struct DataList : public T
}
int size() { return this->GetCount(); }
void on_mouse_move(const wxPoint& position) {
int item = T::HitTest(position);
if(item == wxHitTest::wxHT_WINDOW_INSIDE)
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_INSIDE";
else if (item == wxHitTest::wxHT_WINDOW_OUTSIDE)
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_OUTSIDE";
else if(item == wxHitTest::wxHT_WINDOW_CORNER)
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_CORNER";
else if (item == wxHitTest::wxHT_WINDOW_VERT_SCROLLBAR)
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_WINDOW_VERT_SCROLLBAR";
else if (item == wxHitTest::wxHT_NOWHERE)
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_NOWHERE";
else if (item == wxHitTest::wxHT_MAX)
BOOST_LOG_TRIVIAL(error) << "hit test wxHT_MAX";
else
BOOST_LOG_TRIVIAL(error) << "hit test: " << item;
}
};
typedef DataList<wxListBox, std::string> StringList;
@ -260,21 +323,35 @@ typedef DataList<wxCheckListBox, std::string> PresetList;
struct PageMaterials: ConfigWizardPage
{
Materials *materials;
StringList *list_l1, *list_l2;
PresetList *list_l3;
int sel1_prev, sel2_prev;
StringList *list_printer, *list_type, *list_vendor;
PresetList *list_profile;
int sel_printer_prev, sel_type_prev, sel_vendor_prev;
bool presets_loaded;
wxFlexGridSizer *grid;
wxStaticText *compatible_printers;
int compatible_printers_width = { 100 };
std::string empty_printers_label;
bool first_paint = { false };
static const std::string EMPTY;
int last_hovered_item = { -1 } ;
PageMaterials(ConfigWizard *parent, Materials *materials, wxString title, wxString shortname, wxString list1name);
void reload_presets();
void update_lists(int sel1, int sel2);
void update_lists(int sel1, int sel2, int sel3);
void on_material_highlighted(int sel_material);
void on_material_hovered(int sel_material);
void select_material(int i);
void select_all(bool select);
void clear();
void prepare_compatible_printers_label();
void clear_compatible_printers_label();
void on_paint();
void on_mouse_move_on_profiles(wxMouseEvent& evt);
void on_mouse_enter_profiles(wxMouseEvent& evt);
void on_mouse_leave_profiles(wxMouseEvent& evt);
virtual void on_activate() override;
};

View file

@ -1,5 +1,11 @@
#include "libslic3r/libslic3r.h"
#if ENABLE_GCODE_VIEWER
#include "DoubleSlider.hpp"
#include "libslic3r/GCode.hpp"
#else
#include "wxExtensions.hpp"
#include "libslic3r/GCode/PreviewData.hpp"
#endif // ENABLE_GCODE_VIEWER
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "Plater.hpp"
@ -15,7 +21,9 @@
#include <wx/bmpcbox.h>
#include <wx/statline.h>
#include <wx/dcclient.h>
#if !ENABLE_GCODE_VIEWER
#include <wx/numformatter.h>
#endif // !ENABLE_GCODE_VIEWER
#include <wx/colordlg.h>
#include <cmath>
@ -72,8 +80,13 @@ Control::Control( wxWindow *parent,
if (!is_osx)
SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
#if ENABLE_GCODE_VIEWER
m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "thumb_right") : ScalableBitmap(this, "thumb_up"));
m_bmp_thumb_lower = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "thumb_left") : ScalableBitmap(this, "thumb_down"));
#else
m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "right_half_circle.png") : ScalableBitmap(this, "thumb_up"));
m_bmp_thumb_lower = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "left_half_circle.png" ) : ScalableBitmap(this, "thumb_down"));
#endif // ENABLE_GCODE_VIEWER
m_thumb_size = m_bmp_thumb_lower.GetBmpSize();
m_bmp_add_tick_on = ScalableBitmap(this, "colorchange_add");
@ -275,14 +288,14 @@ wxCoord Control::get_position_from_value(const int value)
return wxCoord(SLIDER_MARGIN + int(val*step + 0.5));
}
wxSize Control::get_size()
wxSize Control::get_size() const
{
int w, h;
get_size(&w, &h);
return wxSize(w, h);
}
void Control::get_size(int *w, int *h)
void Control::get_size(int* w, int* h) const
{
GetSize(w, h);
is_horizontal() ? *w -= m_lock_icon_dim : *h -= m_lock_icon_dim;
@ -302,14 +315,22 @@ double Control::get_double_value(const SelectedSlider& selection)
Info Control::GetTicksValues() const
{
Info custom_gcode_per_print_z;
#if ENABLE_GCODE_VIEWER
std::vector<CustomGCode::Item>& values = custom_gcode_per_print_z.gcodes;
#else
std::vector<Item>& values = custom_gcode_per_print_z.gcodes;
#endif // ENABLE_GCODE_VIEWER
const int val_size = m_values.size();
if (!m_values.empty())
for (const TickCode& tick : m_ticks.ticks) {
if (tick.tick > val_size)
break;
#if ENABLE_GCODE_VIEWER
values.emplace_back(CustomGCode::Item{ m_values[tick.tick], tick.type, tick.extruder, tick.color, tick.extra });
#else
values.emplace_back(Item{m_values[tick.tick], tick.type, tick.extruder, tick.color, tick.extra});
#endif // ENABLE_GCODE_VIEWER
}
if (m_force_mode_apply)
@ -329,7 +350,11 @@ void Control::SetTicksValues(const Info& custom_gcode_per_print_z)
const bool was_empty = m_ticks.empty();
m_ticks.ticks.clear();
#if ENABLE_GCODE_VIEWER
const std::vector<CustomGCode::Item>& heights = custom_gcode_per_print_z.gcodes;
#else
const std::vector<Item>& heights = custom_gcode_per_print_z.gcodes;
#endif // ENABLE_GCODE_VIEWER
for (auto h : heights) {
auto it = std::lower_bound(m_values.begin(), m_values.end(), h.print_z - epsilon());
@ -401,7 +426,15 @@ void Control::draw_focus_rect()
void Control::render()
{
#if ENABLE_GCODE_VIEWER
#ifdef _WIN32
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
#else
SetBackgroundColour(GetParent()->GetBackgroundColour());
#endif // _WIN32
#else
SetBackgroundColour(GetParent()->GetBackgroundColour());
#endif // ENABLE_GCODE_VIEWER
draw_focus_rect();
wxPaintDC dc(this);
@ -417,22 +450,22 @@ void Control::render()
// draw line
draw_scroll_line(dc, lower_pos, higher_pos);
//draw color print ticks
// draw color print ticks
draw_ticks(dc);
// draw both sliders
draw_thumbs(dc, lower_pos, higher_pos);
//draw lock/unlock
// draw lock/unlock
draw_one_layer_icon(dc);
//draw revert bitmap (if it's shown)
// draw revert bitmap (if it's shown)
draw_revert_icon(dc);
//draw cog bitmap (if it's shown)
// draw cog bitmap (if it's shown)
draw_cog_icon(dc);
//draw mouse position
// draw mouse position
draw_tick_on_mouse_position(dc);
}
@ -544,10 +577,21 @@ wxString Control::get_label(int tick) const
if (value >= m_values.size())
return "ErrVal";
const wxString str = m_values.empty() ?
wxNumberFormatter::ToString(m_label_koef*value, 2, wxNumberFormatter::Style_None) :
wxNumberFormatter::ToString(m_values[value], 2, wxNumberFormatter::Style_None);
return format_wxstr("%1%\n(%2%)", str, m_values.empty() ? value : value+1);
#if ENABLE_GCODE_VIEWER
if (m_draw_mode == dmSequentialGCodeView)
return wxString::Format("%d", static_cast<unsigned int>(m_values[value]));
else {
const wxString str = m_values.empty() ?
wxString::Format("%.*f", 2, m_label_koef * value) :
wxString::Format("%.*f", 2, m_values[value]);
return format_wxstr("%1%\n(%2%)", str, m_values.empty() ? value : value + 1);
}
#else
const wxString str = m_values.empty() ?
wxNumberFormatter::ToString(m_label_koef * value, 2, wxNumberFormatter::Style_None) :
wxNumberFormatter::ToString(m_values[value], 2, wxNumberFormatter::Style_None);
return format_wxstr("%1%\n(%2%)", str, m_values.empty() ? value : value + 1);
#endif // ENABLE_GCODE_VIEWER
}
void Control::draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, bool right_side/*=true*/) const
@ -556,13 +600,36 @@ void Control::draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, bool right_
const wxString label = get_label(tick);
dc.GetMultiLineTextExtent(label, &text_width, &text_height);
wxPoint text_pos;
if (right_side)
text_pos = is_horizontal() ? wxPoint(pos.x + 1, pos.y + m_thumb_size.x) :
wxPoint(pos.x + m_thumb_size.x+1, pos.y - 0.5*text_height - 1);
else
text_pos = is_horizontal() ? wxPoint(pos.x - text_width - 1, pos.y - m_thumb_size.x - text_height) :
wxPoint(pos.x - text_width - 1 - m_thumb_size.x, pos.y - 0.5*text_height + 1);
dc.DrawText(label, text_pos);
if (right_side) {
if (is_horizontal()) {
int width;
int height;
get_size(&width, &height);
int x_right = pos.x + 1 + text_width;
int xx = (x_right < width) ? pos.x + 1 : pos.x - text_width - 1;
text_pos = wxPoint(xx, pos.y + m_thumb_size.x / 2 + 1);
}
else
text_pos = wxPoint(pos.x + m_thumb_size.x + 1, pos.y - 0.5 * text_height - 1);
// update text rectangle
m_rect_lower_thumb_text = wxRect(text_pos, wxSize(text_width, text_height));
}
else {
if (is_horizontal()) {
int x = pos.x - text_width - 1;
int xx = (x > 0) ? x : pos.x + 1;
text_pos = wxPoint(xx, pos.y - m_thumb_size.x / 2 - text_height - 1);
}
else
text_pos = wxPoint(pos.x - text_width - 1 - m_thumb_size.x, pos.y - 0.5 * text_height + 1);
// update text rectangle
m_rect_higher_thumb_text = wxRect(text_pos, wxSize(text_width, text_height));
}
dc.DrawText(label, text_pos);
}
void Control::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const
@ -572,6 +639,10 @@ void Control::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider
void Control::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection)
{
#if ENABLE_GCODE_VIEWER
wxCoord x_draw = pos.x - int(0.5 * m_thumb_size.x);
wxCoord y_draw = pos.y - int(0.5 * m_thumb_size.y);
#else
wxCoord x_draw, y_draw;
if (selection == ssLower) {
if (is_horizontal()) {
@ -583,7 +654,7 @@ void Control::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider
y_draw = pos.y - int(0.5*m_thumb_size.y);
}
}
else{
else {
if (is_horizontal()) {
x_draw = pos.x;
y_draw = pos.y - int(0.5*m_thumb_size.y);
@ -593,6 +664,7 @@ void Control::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider
y_draw = pos.y - int(0.5*m_thumb_size.y);
}
}
#endif // ENABLE_GCODE_VIEWER
dc.DrawBitmap(selection == ssLower ? m_bmp_thumb_lower.bmp() : m_bmp_thumb_higher.bmp(), x_draw, y_draw);
// Update thumb rect
@ -756,7 +828,15 @@ void Control::draw_colored_band(wxDC& dc)
// don't color a band for MultiExtruder mode
if (m_ticks.empty() || m_mode == MultiExtruder)
{
#if ENABLE_GCODE_VIEWER
#ifdef _WIN32
draw_band(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW), main_band);
#else
draw_band(dc, GetParent()->GetBackgroundColour(), main_band);
#endif // _WIN32
#else
draw_band(dc, GetParent()->GetBackgroundColour(), main_band);
#endif // ENABLE_GCODE_VIEWER
return;
}
@ -788,6 +868,11 @@ void Control::draw_colored_band(wxDC& dc)
void Control::draw_one_layer_icon(wxDC& dc)
{
#if ENABLE_GCODE_VIEWER
if (m_draw_mode == dmSequentialGCodeView)
return;
#endif // ENABLE_GCODE_VIEWER
const wxBitmap& icon = m_is_one_layer ?
m_focus == fiOneLayerIcon ? m_bmp_one_layer_lock_off.bmp() : m_bmp_one_layer_lock_on.bmp() :
m_focus == fiOneLayerIcon ? m_bmp_one_layer_unlock_off.bmp() : m_bmp_one_layer_unlock_on.bmp();
@ -829,8 +914,20 @@ void Control::draw_cog_icon(wxDC& dc)
get_size(&width, &height);
wxCoord x_draw, y_draw;
is_horizontal() ? x_draw = width-2 : x_draw = width - m_cog_icon_dim - 2;
is_horizontal() ? y_draw = height - m_cog_icon_dim - 2 : y_draw = height-2;
#if ENABLE_GCODE_VIEWER
if (m_draw_mode == dmSequentialGCodeView)
{
is_horizontal() ? x_draw = width - 2 : x_draw = 0.5 * width - 0.5 * m_cog_icon_dim;
is_horizontal() ? y_draw = 0.5 * height - 0.5 * m_cog_icon_dim : y_draw = height - 2;
}
else
{
#endif // ENABLE_GCODE_VIEWER
is_horizontal() ? x_draw = width - 2 : x_draw = width - m_cog_icon_dim - 2;
is_horizontal() ? y_draw = height - m_cog_icon_dim - 2 : y_draw = height - 2;
#if ENABLE_GCODE_VIEWER
}
#endif // ENABLE_GCODE_VIEWER
dc.DrawBitmap(m_bmp_cog.bmp(), x_draw, y_draw);
@ -838,9 +935,12 @@ void Control::draw_cog_icon(wxDC& dc)
m_rect_cog_icon = wxRect(x_draw, y_draw, m_cog_icon_dim, m_cog_icon_dim);
}
void Control::update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection)
void Control::update_thumb_rect(const wxCoord begin_x, const wxCoord begin_y, const SelectedSlider& selection)
{
const wxRect& rect = wxRect(begin_x, begin_y + (selection == ssLower ? int(m_thumb_size.y * 0.5) : 0), m_thumb_size.x, int(m_thumb_size.y*0.5));
const wxRect rect = is_horizontal() ?
wxRect(begin_x + (selection == ssHigher ? m_thumb_size.x / 2 : 0), begin_y, m_thumb_size.x / 2, m_thumb_size.y) :
wxRect(begin_x, begin_y + (selection == ssLower ? m_thumb_size.y / 2 : 0), m_thumb_size.x, m_thumb_size.y / 2);
if (selection == ssLower)
m_rect_lower_thumb = rect;
else
@ -968,10 +1068,19 @@ wxString Control::get_tooltip(int tick/*=-1*/)
if (m_focus == fiRevertIcon)
return _L("Discard all custom changes");
if (m_focus == fiCogIcon)
return m_mode == MultiAsSingle ?
#if ENABLE_GCODE_VIEWER
{
if (m_draw_mode == dmSequentialGCodeView)
return _L("Jump to move") + " (Shift + G)";
else
#endif // ENABLE_GCODE_VIEWER
return m_mode == MultiAsSingle ?
GUI::from_u8((boost::format(_u8L("Jump to height %s or "
"Set extruder sequence for the entire print")) % " (Shift + G)\n").str()) :
_L("Jump to height") + " (Shift + G)";
"Set extruder sequence for the entire print")) % " (Shift + G)\n").str()) :
_L("Jump to height") + " (Shift + G)";
#if ENABLE_GCODE_VIEWER
}
#endif // ENABLE_GCODE_VIEWER
if (m_focus == fiColorBand)
return m_mode != SingleExtruder ? "" :
_L("Edit current color - Right click the colored slider segment");
@ -1099,6 +1208,14 @@ void Control::OnMotion(wxMouseEvent& event)
else if (m_mode == SingleExtruder && is_point_in_rect(pos, get_colored_band_rect()) &&
get_edited_tick_for_position(pos) >= 0 )
m_focus = fiColorBand;
else if (is_point_in_rect(pos, m_rect_lower_thumb))
m_focus = fiLowerThumb;
else if (is_point_in_rect(pos, m_rect_higher_thumb))
m_focus = fiHigherThumb;
else if (is_point_in_rect(pos, m_rect_lower_thumb_text))
m_focus = fiLowerThumbText;
else if (is_point_in_rect(pos, m_rect_higher_thumb_text))
m_focus = fiHigherThumbText;
else {
m_focus = fiTick;
tick = get_tick_near_point(pos);
@ -1223,7 +1340,11 @@ void Control::OnLeftUp(wxMouseEvent& event)
if (m_mode == MultiAsSingle && m_draw_mode == dmRegular)
show_cog_icon_context_menu();
else
#if ENABLE_GCODE_VIEWER
jump_to_value();
#else
jump_to_print_z();
#endif // ENABLE_GCODE_VIEWER
break;
case maOneLayerIconClick:
switch_one_layer_mode();
@ -1262,6 +1383,15 @@ void Control::move_current_thumb(const bool condition)
if (is_horizontal())
delta *= -1;
// accelerators
int accelerator = 0;
if (wxGetKeyState(WXK_SHIFT))
accelerator += 5;
if (wxGetKeyState(WXK_CONTROL))
accelerator += 5;
if (accelerator > 0)
delta *= accelerator;
if (m_selection == ssLower) {
m_lower_value -= delta;
correct_lower_value();
@ -1295,12 +1425,32 @@ void Control::OnWheel(wxMouseEvent& event)
ssLower : ssHigher;
}
#if ENABLE_GCODE_VIEWER
move_current_thumb((m_draw_mode == dmSequentialGCodeView) ? event.GetWheelRotation() < 0 : event.GetWheelRotation() > 0);
#else
move_current_thumb(event.GetWheelRotation() > 0);
#endif // ENABLE_GCODE_VIEWER
}
void Control::OnKeyDown(wxKeyEvent &event)
{
const int key = event.GetKeyCode();
#if ENABLE_GCODE_VIEWER
if (m_draw_mode != dmSequentialGCodeView && key == WXK_NUMPAD_ADD) {
// OnChar() is called immediately after OnKeyDown(), which can cause call of add_tick() twice.
// To avoid this case we should suppress second add_tick() call.
m_ticks.suppress_plus(true);
add_current_tick(true);
}
else if (m_draw_mode != dmSequentialGCodeView && (key == WXK_NUMPAD_SUBTRACT || key == WXK_DELETE || key == WXK_BACK)) {
// OnChar() is called immediately after OnKeyDown(), which can cause call of delete_tick() twice.
// To avoid this case we should suppress second delete_tick() call.
m_ticks.suppress_minus(true);
delete_current_tick();
}
else if (m_draw_mode != dmSequentialGCodeView && event.GetKeyCode() == WXK_SHIFT)
UseDefaultColors(false);
#else
if (key == WXK_NUMPAD_ADD) {
// OnChar() is called immediately after OnKeyDown(), which can cause call of add_tick() twice.
// To avoid this case we should suppress second add_tick() call.
@ -1315,22 +1465,37 @@ void Control::OnKeyDown(wxKeyEvent &event)
}
else if (event.GetKeyCode() == WXK_SHIFT)
UseDefaultColors(false);
#endif // ENABLE_GCODE_VIEWER
else if (is_horizontal())
{
if (key == WXK_LEFT || key == WXK_RIGHT)
move_current_thumb(key == WXK_LEFT);
else if (key == WXK_UP || key == WXK_DOWN) {
m_selection = key == WXK_UP ? ssHigher : ssLower;
Refresh();
#if ENABLE_GCODE_VIEWER
if (m_is_focused)
{
#endif // ENABLE_GCODE_VIEWER
if (key == WXK_LEFT || key == WXK_RIGHT)
move_current_thumb(key == WXK_LEFT);
else if (key == WXK_UP || key == WXK_DOWN) {
m_selection = key == WXK_UP ? ssHigher : ssLower;
Refresh();
}
#if ENABLE_GCODE_VIEWER
}
#endif // ENABLE_GCODE_VIEWER
}
}
else {
if (key == WXK_LEFT || key == WXK_RIGHT) {
m_selection = key == WXK_LEFT ? ssHigher : ssLower;
Refresh();
#if ENABLE_GCODE_VIEWER
if (m_is_focused)
{
#endif // ENABLE_GCODE_VIEWER
if (key == WXK_LEFT || key == WXK_RIGHT) {
m_selection = key == WXK_LEFT ? ssHigher : ssLower;
Refresh();
}
else if (key == WXK_UP || key == WXK_DOWN)
move_current_thumb(key == WXK_UP);
#if ENABLE_GCODE_VIEWER
}
else if (key == WXK_UP || key == WXK_DOWN)
move_current_thumb(key == WXK_UP);
#endif // ENABLE_GCODE_VIEWER
}
event.Skip(); // !Needed to have EVT_CHAR generated as well
@ -1351,16 +1516,27 @@ void Control::OnKeyUp(wxKeyEvent &event)
void Control::OnChar(wxKeyEvent& event)
{
const int key = event.GetKeyCode();
if (key == '+' && !m_ticks.suppressed_plus()) {
add_current_tick(true);
m_ticks.suppress_plus(false);
}
else if (key == '-' && !m_ticks.suppressed_minus()) {
delete_current_tick();
m_ticks.suppress_minus(false);
#if ENABLE_GCODE_VIEWER
if (m_draw_mode != dmSequentialGCodeView)
{
#endif // ENABLE_GCODE_VIEWER
if (key == '+' && !m_ticks.suppressed_plus()) {
add_current_tick(true);
m_ticks.suppress_plus(false);
}
else if (key == '-' && !m_ticks.suppressed_minus()) {
delete_current_tick();
m_ticks.suppress_minus(false);
}
#if ENABLE_GCODE_VIEWER
}
#endif // ENABLE_GCODE_VIEWER
if (key == 'G')
#if ENABLE_GCODE_VIEWER
jump_to_value();
#else
jump_to_print_z();
#endif // ENABLE_GCODE_VIEWER
}
void Control::OnRightDown(wxMouseEvent& event)
@ -1550,7 +1726,11 @@ void Control::show_cog_icon_context_menu()
wxMenu menu;
append_menu_item(&menu, wxID_ANY, _(L("Jump to height")) + " (Shift+G)", "",
[this](wxCommandEvent&) { jump_to_print_z(); }, "", &menu);
#if ENABLE_GCODE_VIEWER
[this](wxCommandEvent&) { jump_to_value(); }, "", & menu);
#else
[this](wxCommandEvent&) { jump_to_print_z(); }, "", &menu);
#endif // ENABLE_GCODE_VIEWER
append_menu_item(&menu, wxID_ANY, _(L("Set extruder sequence for the entire print")), "",
[this](wxCommandEvent&) { edit_extruder_sequence(); }, "", &menu);
@ -1670,11 +1850,21 @@ static std::string get_pause_print_msg(const std::string& msg_in, double height)
return into_u8(dlg.GetValue());
}
#if ENABLE_GCODE_VIEWER
static double get_value_to_jump(double active_value, double min_z, double max_z, DrawMode mode)
#else
static double get_print_z_to_jump(double active_print_z, double min_z, double max_z)
#endif // ENABLE_GCODE_VIEWER
{
#if ENABLE_GCODE_VIEWER
wxString msg_text = (mode == dmSequentialGCodeView) ? _L("Enter the move you want to jump to") + ":" : _L("Enter the height you want to jump to") + ":";
wxString msg_header = (mode == dmSequentialGCodeView) ? _L("Jump to move") : _L("Jump to height");
wxString msg_in = GUI::double_to_string(active_value);
#else
wxString msg_text = _(L("Enter the height you want to jump to")) + ":";
wxString msg_header = _(L("Jump to height"));
wxString msg_in = GUI::double_to_string(active_print_z);
#endif // ENABLE_GCODE_VIEWER
// get custom gcode
wxTextEntryDialog dlg(nullptr, msg_text, msg_header, msg_in, wxTextEntryDialogStyle);
@ -1883,6 +2073,23 @@ void Control::edit_extruder_sequence()
post_ticks_changed_event(ToolChange);
}
#if ENABLE_GCODE_VIEWER
void Control::jump_to_value()
{
double value = get_value_to_jump(m_values[m_selection == ssLower ? m_lower_value : m_higher_value],
m_values[m_min_value], m_values[m_max_value], m_draw_mode);
if (value < 0.0)
return;
auto it = std::lower_bound(m_values.begin(), m_values.end(), value - epsilon());
int tick_value = it - m_values.begin();
if (m_selection == ssLower)
SetLowerValue(tick_value);
else
SetHigherValue(tick_value);
}
#else
void Control::jump_to_print_z()
{
double print_z = get_print_z_to_jump(m_values[m_selection == ssLower ? m_lower_value : m_higher_value],
@ -1898,6 +2105,7 @@ void Control::jump_to_print_z()
else
SetHigherValue(tick_value);
}
#endif // ENABLE_GCODE_VIEWER
void Control::post_ticks_changed_event(Type type /*= Custom*/)
{
@ -1968,7 +2176,11 @@ std::string TickCodeInfo::get_color_for_tick(TickCode tick, Type type, const int
{
if (mode == SingleExtruder && type == ColorChange && m_use_default_colors)
{
#if ENABLE_GCODE_VIEWER
const std::vector<std::string>& colors = ColorPrintColors::get();
#else
const std::vector<std::string>& colors = GCodePreviewData::ColorPrintColors();
#endif // ENABLE_GCODE_VIEWER
if (ticks.empty())
return colors[0];
m_default_color_idx++;

View file

@ -4,7 +4,9 @@
#include "libslic3r/CustomGCode.hpp"
#include "wxExtensions.hpp"
#if !ENABLE_GCODE_VIEWER
#include <wx/wx.h>
#endif // !ENABLE_GCODE_VIEWER
#include <wx/window.h>
#include <wx/control.h>
#include <wx/dc.h>
@ -42,6 +44,10 @@ enum FocusedItem {
fiCogIcon,
fiColorBand,
fiActionIcon,
fiLowerThumb,
fiHigherThumb,
fiLowerThumbText,
fiHigherThumbText,
fiTick
};
@ -73,6 +79,9 @@ enum DrawMode
dmRegular,
dmSlaPrint,
dmSequentialFffPrint,
#if ENABLE_GCODE_VIEWER
dmSequentialGCodeView,
#endif // ENABLE_GCODE_VIEWER
};
struct TickCode
@ -210,6 +219,9 @@ public:
void SetTicksValues(const Info &custom_gcode_per_print_z);
void SetDrawMode(bool is_sla_print, bool is_sequential_print);
#if ENABLE_GCODE_VIEWER
void SetDrawMode(DrawMode mode) { m_draw_mode = mode; }
#endif // ENABLE_GCODE_VIEWER
void SetManipulationMode(Mode mode) { m_mode = mode; }
Mode GetManipulationMode() const { return m_mode; }
@ -222,7 +234,7 @@ public:
bool is_higher_at_max() const { return m_higher_value == m_max_value; }
bool is_full_span() const { return this->is_lower_at_min() && this->is_higher_at_max(); }
void OnPaint(wxPaintEvent& ) { render();}
void OnPaint(wxPaintEvent& ) { render(); }
void OnLeftDown(wxMouseEvent& event);
void OnMotion(wxMouseEvent& event);
void OnLeftUp(wxMouseEvent& event);
@ -246,7 +258,11 @@ public:
void discard_all_thicks();
void move_current_thumb_to_pos(wxPoint pos);
void edit_extruder_sequence();
#if ENABLE_GCODE_VIEWER
void jump_to_value();
#else
void jump_to_print_z();
#endif // ENABLE_GCODE_VIEWER
void show_add_context_menu();
void show_edit_context_menu();
void show_cog_icon_context_menu();
@ -272,7 +288,7 @@ protected:
void draw_tick_text(wxDC& dc, const wxPoint& pos, int tick, bool right_side = true) const;
void draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection) const;
void update_thumb_rect(const wxCoord& begin_x, const wxCoord& begin_y, const SelectedSlider& selection);
void update_thumb_rect(const wxCoord begin_x, const wxCoord begin_y, const SelectedSlider& selection);
bool detect_selected_slider(const wxPoint& pt);
void correct_lower_value();
void correct_higher_value();
@ -290,8 +306,8 @@ private:
int get_value_from_position(const wxCoord x, const wxCoord y);
int get_value_from_position(const wxPoint pos) { return get_value_from_position(pos.x, pos.y); }
wxCoord get_position_from_value(const int value);
wxSize get_size();
void get_size(int *w, int *h);
wxSize get_size() const;
void get_size(int* w, int* h) const;
double get_double_value(const SelectedSlider& selection);
wxString get_tooltip(int tick = -1);
int get_edited_tick_for_position(wxPoint pos, Type type = ColorChange);
@ -348,6 +364,8 @@ private:
wxRect m_rect_lower_thumb;
wxRect m_rect_higher_thumb;
mutable wxRect m_rect_lower_thumb_text;
mutable wxRect m_rect_higher_thumb_text;
wxRect m_rect_tick_action;
wxRect m_rect_one_layer_icon;
wxRect m_rect_revert_icon;

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,477 @@
#ifndef slic3r_GCodeViewer_hpp_
#define slic3r_GCodeViewer_hpp_
#if ENABLE_GCODE_VIEWER
#include "3DScene.hpp"
#include "libslic3r/GCode/GCodeProcessor.hpp"
#include "GLModel.hpp"
#include <float.h>
namespace Slic3r {
class Print;
class TriangleMesh;
namespace GUI {
class GCodeViewer
{
using Color = std::array<float, 3>;
static const std::vector<Color> Extrusion_Role_Colors;
static const std::vector<Color> Options_Colors;
static const std::vector<Color> Travel_Colors;
static const std::vector<Color> Range_Colors;
enum class EOptionsColors : unsigned char
{
Retractions,
Unretractions,
ToolChanges,
ColorChanges,
PausePrints,
CustomGCodes
};
// vbo buffer containing vertices data used to rendder a specific toolpath type
struct VBuffer
{
enum class EFormat : unsigned char
{
// vertex format: 3 floats -> position.x|position.y|position.z
Position,
// vertex format: 4 floats -> position.x|position.y|position.z|normal.x
PositionNormal1,
// vertex format: 6 floats -> position.x|position.y|position.z|normal.x|normal.y|normal.z
PositionNormal3
};
EFormat format{ EFormat::Position };
// vbo id
unsigned int id{ 0 };
// count of vertices, updated after data are sent to gpu
size_t count{ 0 };
size_t data_size_bytes() const { return count * vertex_size_bytes(); }
size_t vertex_size_floats() const { return position_size_floats() + normal_size_floats(); }
size_t vertex_size_bytes() const { return vertex_size_floats() * sizeof(float); }
size_t position_offset_floats() const { return 0; }
size_t position_offset_size() const { return position_offset_floats() * sizeof(float); }
size_t position_size_floats() const
{
switch (format)
{
case EFormat::Position:
case EFormat::PositionNormal3: { return 3; }
case EFormat::PositionNormal1: { return 4; }
default: { return 0; }
}
}
size_t position_size_bytes() const { return position_size_floats() * sizeof(float); }
size_t normal_offset_floats() const
{
switch (format)
{
case EFormat::Position:
case EFormat::PositionNormal1: { return 0; }
case EFormat::PositionNormal3: { return 3; }
default: { return 0; }
}
}
size_t normal_offset_size() const { return normal_offset_floats() * sizeof(float); }
size_t normal_size_floats() const {
switch (format)
{
default:
case EFormat::Position:
case EFormat::PositionNormal1: { return 0; }
case EFormat::PositionNormal3: { return 3; }
}
}
size_t normal_size_bytes() const { return normal_size_floats() * sizeof(float); }
void reset();
};
// ibo buffer containing indices data (lines/triangles) used to render a specific toolpath type
struct IBuffer
{
// ibo id
unsigned int id{ 0 };
// count of indices, updated after data are sent to gpu
size_t count{ 0 };
void reset();
};
// Used to identify different toolpath sub-types inside a IBuffer
struct Path
{
struct Endpoint
{
// index into the indices buffer
unsigned int i_id{ 0u };
// sequential id
unsigned int s_id{ 0u };
Vec3f position{ Vec3f::Zero() };
};
EMoveType type{ EMoveType::Noop };
ExtrusionRole role{ erNone };
Endpoint first;
Endpoint last;
float delta_extruder{ 0.0f };
float height{ 0.0f };
float width{ 0.0f };
float feedrate{ 0.0f };
float fan_speed{ 0.0f };
float volumetric_rate{ 0.0f };
unsigned char extruder_id{ 0 };
unsigned char cp_color_id{ 0 };
bool matches(const GCodeProcessor::MoveVertex& move) const;
size_t vertices_count() const { return last.s_id - first.s_id + 1; }
bool contains(unsigned int id) const { return first.s_id <= id && id <= last.s_id; }
};
// Used to batch the indices needed to render paths
struct RenderPath
{
Color color;
size_t path_id;
std::vector<unsigned int> sizes;
std::vector<size_t> offsets; // use size_t because we need the pointer's size (used in the call glMultiDrawElements())
};
// buffer containing data for rendering a specific toolpath type
struct TBuffer
{
enum class ERenderPrimitiveType : unsigned char
{
Point,
Line,
Triangle
};
ERenderPrimitiveType render_primitive_type;
VBuffer vertices;
IBuffer indices;
std::string shader;
std::vector<Path> paths;
std::vector<RenderPath> render_paths;
bool visible{ false };
void reset();
void add_path(const GCodeProcessor::MoveVertex& move, unsigned int i_id, unsigned int s_id);
unsigned int indices_per_segment() const {
switch (render_primitive_type)
{
case ERenderPrimitiveType::Point: { return 1; }
case ERenderPrimitiveType::Line: { return 2; }
case ERenderPrimitiveType::Triangle: { return 42; } // 3 indices x 14 triangles
default: { return 0; }
}
}
unsigned int start_segment_vertex_offset() const {
switch (render_primitive_type)
{
case ERenderPrimitiveType::Point:
case ERenderPrimitiveType::Line:
case ERenderPrimitiveType::Triangle:
default: { return 0; }
}
}
unsigned int end_segment_vertex_offset() const {
switch (render_primitive_type)
{
case ERenderPrimitiveType::Point: { return 0; }
case ERenderPrimitiveType::Line: { return 1; }
case ERenderPrimitiveType::Triangle: { return 36; } // 1 vertex of 13th triangle
default: { return 0; }
}
}
};
// helper to render shells
struct Shells
{
GLVolumeCollection volumes;
bool visible{ false };
};
// helper to render extrusion paths
struct Extrusions
{
struct Range
{
float min;
float max;
unsigned int count;
Range() { reset(); }
void update_from(const float value) {
if (value != max && value != min)
++count;
min = std::min(min, value);
max = std::max(max, value);
}
void reset() { min = FLT_MAX; max = -FLT_MAX; count = 0; }
float step_size() const { return (max - min) / (static_cast<float>(Range_Colors.size()) - 1.0f); }
Color get_color_at(float value) const;
};
struct Ranges
{
// Color mapping by layer height.
Range height;
// Color mapping by extrusion width.
Range width;
// Color mapping by feedrate.
Range feedrate;
// Color mapping by fan speed.
Range fan_speed;
// Color mapping by volumetric extrusion rate.
Range volumetric_rate;
void reset() {
height.reset();
width.reset();
feedrate.reset();
fan_speed.reset();
volumetric_rate.reset();
}
};
unsigned int role_visibility_flags{ 0 };
Ranges ranges;
void reset_role_visibility_flags() {
role_visibility_flags = 0;
for (unsigned int i = 0; i < erCount; ++i) {
role_visibility_flags |= 1 << i;
}
}
void reset_ranges() { ranges.reset(); }
};
#if ENABLE_GCODE_VIEWER_STATISTICS
struct Statistics
{
// times
long long results_time{ 0 };
long long load_time{ 0 };
long long refresh_time{ 0 };
long long refresh_paths_time{ 0 };
// opengl calls
long long gl_multi_points_calls_count{ 0 };
long long gl_multi_lines_calls_count{ 0 };
long long gl_multi_triangles_calls_count{ 0 };
// memory
long long results_size{ 0 };
long long vertices_gpu_size{ 0 };
long long indices_gpu_size{ 0 };
long long paths_size{ 0 };
long long render_paths_size{ 0 };
// others
long long travel_segments_count{ 0 };
long long extrude_segments_count{ 0 };
void reset_all() {
reset_times();
reset_opengl();
reset_sizes();
reset_counters();
}
void reset_times() {
results_time = 0;
load_time = 0;
refresh_time = 0;
refresh_paths_time = 0;
}
void reset_opengl() {
gl_multi_points_calls_count = 0;
gl_multi_lines_calls_count = 0;
gl_multi_triangles_calls_count = 0;
}
void reset_sizes() {
results_size = 0;
vertices_gpu_size = 0;
indices_gpu_size = 0;
paths_size = 0;
render_paths_size = 0;
}
void reset_counters() {
travel_segments_count = 0;
extrude_segments_count = 0;
}
};
#endif // ENABLE_GCODE_VIEWER_STATISTICS
public:
struct SequentialView
{
class Marker
{
GLModel m_model;
Vec3f m_world_position;
Transform3f m_world_transform;
float m_z_offset{ 0.5f };
std::array<float, 4> m_color{ 1.0f, 1.0f, 1.0f, 1.0f };
bool m_visible{ false };
public:
void init();
const BoundingBoxf3& get_bounding_box() const { return m_model.get_bounding_box(); }
void set_world_position(const Vec3f& position);
void set_color(const std::array<float, 4>& color) { m_color = color; }
bool is_visible() const { return m_visible; }
void set_visible(bool visible) { m_visible = visible; }
void render() const;
};
struct Endpoints
{
unsigned int first{ 0 };
unsigned int last{ 0 };
};
Endpoints endpoints;
Endpoints current;
Vec3f current_position{ Vec3f::Zero() };
Marker marker;
};
enum class EViewType : unsigned char
{
FeatureType,
Height,
Width,
Feedrate,
FanSpeed,
VolumetricRate,
Tool,
ColorPrint,
Count
};
private:
unsigned int m_last_result_id{ 0 };
size_t m_vertices_count{ 0 };
mutable std::vector<TBuffer> m_buffers{ static_cast<size_t>(EMoveType::Extrude) };
// bounding box of toolpaths
BoundingBoxf3 m_paths_bounding_box;
// bounding box of toolpaths + marker tools
BoundingBoxf3 m_max_bounding_box;
std::vector<Color> m_tool_colors;
std::vector<double> m_layers_zs;
std::array<double, 2> m_layers_z_range;
std::vector<ExtrusionRole> m_roles;
std::vector<unsigned char> m_extruder_ids;
mutable Extrusions m_extrusions;
mutable SequentialView m_sequential_view;
Shells m_shells;
EViewType m_view_type{ EViewType::FeatureType };
bool m_legend_enabled{ true };
PrintEstimatedTimeStatistics m_time_statistics;
mutable PrintEstimatedTimeStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedTimeStatistics::ETimeMode::Normal };
#if ENABLE_GCODE_VIEWER_STATISTICS
mutable Statistics m_statistics;
#endif // ENABLE_GCODE_VIEWER_STATISTICS
std::array<float, 2> m_detected_point_sizes = { 0.0f, 0.0f };
public:
GCodeViewer() = default;
~GCodeViewer() { reset(); }
bool init();
// extract rendering data from the given parameters
void load(const GCodeProcessor::Result& gcode_result, const Print& print, bool initialized);
// recalculate ranges in dependence of what is visible and sets tool/print colors
void refresh(const GCodeProcessor::Result& gcode_result, const std::vector<std::string>& str_tool_colors);
void reset();
void render() const;
bool has_data() const { return !m_roles.empty(); }
const BoundingBoxf3& get_paths_bounding_box() const { return m_paths_bounding_box; }
const BoundingBoxf3& get_max_bounding_box() const { return m_max_bounding_box; }
const std::vector<double>& get_layers_zs() const { return m_layers_zs; };
const SequentialView& get_sequential_view() const { return m_sequential_view; }
void update_sequential_view_current(unsigned int first, unsigned int last)
{
m_sequential_view.current.first = first;
m_sequential_view.current.last = last;
refresh_render_paths(true, true);
}
EViewType get_view_type() const { return m_view_type; }
void set_view_type(EViewType type) {
if (type == EViewType::Count)
type = EViewType::FeatureType;
m_view_type = type;
}
bool is_toolpath_move_type_visible(EMoveType type) const;
void set_toolpath_move_type_visible(EMoveType type, bool visible);
unsigned int get_toolpath_role_visibility_flags() const { return m_extrusions.role_visibility_flags; }
void set_toolpath_role_visibility_flags(unsigned int flags) { m_extrusions.role_visibility_flags = flags; }
unsigned int get_options_visibility_flags() const;
void set_options_visibility_from_flags(unsigned int flags);
void set_layers_z_range(const std::array<double, 2>& layers_z_range);
bool is_legend_enabled() const { return m_legend_enabled; }
void enable_legend(bool enable) { m_legend_enabled = enable; }
void export_toolpaths_to_obj(const char* filename) const;
private:
void init_shaders();
void load_toolpaths(const GCodeProcessor::Result& gcode_result);
void load_shells(const Print& print, bool initialized);
void refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const;
void render_toolpaths() const;
void render_shells() const;
void render_legend() const;
#if ENABLE_GCODE_VIEWER_STATISTICS
void render_statistics() const;
#endif // ENABLE_GCODE_VIEWER_STATISTICS
bool is_visible(ExtrusionRole role) const {
return role < erCount && (m_extrusions.role_visibility_flags & (1 << role)) != 0;
}
bool is_visible(const Path& path) const { return is_visible(path.role); }
bool is_in_z_range(const Path& path) const {
auto in_z_range = [this](double z) {
return z > m_layers_z_range[0] - EPSILON && z < m_layers_z_range[1] + EPSILON;
};
return in_z_range(path.first.position[2]) || in_z_range(path.last.position[2]);
}
bool is_travel_in_z_range(size_t id) const;
};
} // namespace GUI
} // namespace Slic3r
#endif // ENABLE_GCODE_VIEWER
#endif // slic3r_GCodeViewer_hpp_

File diff suppressed because it is too large Load diff

View file

@ -6,13 +6,16 @@
#include <chrono>
#include "GLToolbar.hpp"
#include "GLShader.hpp"
#include "Event.hpp"
#include "Selection.hpp"
#include "Gizmos/GLGizmosManager.hpp"
#include "GUI_ObjectLayers.hpp"
#include "GLSelectionRectangle.hpp"
#include "MeshUtils.hpp"
#if ENABLE_GCODE_VIEWER
#include "libslic3r/GCode/GCodeProcessor.hpp"
#include "GCodeViewer.hpp"
#endif // ENABLE_GCODE_VIEWER
#include "libslic3r/Slicing.hpp"
@ -36,7 +39,9 @@ namespace Slic3r {
struct Camera;
class BackgroundSlicingProcess;
#if !ENABLE_GCODE_VIEWER
class GCodePreviewData;
#endif // !ENABLE_GCODE_VIEWER
struct ThumbnailData;
class ModelObject;
class ModelInstance;
@ -103,7 +108,11 @@ wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
#if ENABLE_GCODE_VIEWER
wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_LAYERS_SLIDER, wxKeyEvent);
#else
wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent);
#endif // ENABLE_GCODE_VIEWER
wxDECLARE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
@ -118,6 +127,7 @@ class GLCanvas3D
static const double DefaultCameraZoomToBoxMarginFactor;
public:
#if !ENABLE_GCODE_VIEWER
struct GCodePreviewVolumeIndex
{
enum EType
@ -144,6 +154,7 @@ public:
void reset() { first_volumes.clear(); }
};
#endif // !ENABLE_GCODE_VIEWER
private:
class LayersEditing
@ -161,7 +172,6 @@ private:
private:
bool m_enabled;
Shader m_shader;
unsigned int m_z_texture_id;
// Not owned by LayersEditing.
const DynamicPrintConfig *m_config;
@ -208,8 +218,9 @@ private:
LayersEditing();
~LayersEditing();
bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename);
void set_config(const DynamicPrintConfig* config);
void init();
void set_config(const DynamicPrintConfig* config);
void select_object(const Model &model, int object_id);
bool is_allowed() const;
@ -339,6 +350,7 @@ private:
bool generate(const std::string& msg, const GLCanvas3D& canvas, bool compress, bool red_colored = false);
};
#if !ENABLE_GCODE_VIEWER
class LegendTexture : public GUI::GLTexture
{
static const int Px_Title_Offset = 5;
@ -365,6 +377,7 @@ private:
void render(const GLCanvas3D& canvas) const;
};
#endif // !ENABLE_GCODE_VIEWER
#if ENABLE_RENDER_STATISTICS
struct RenderStats
@ -443,11 +456,12 @@ private:
std::unique_ptr<RetinaHelper> m_retina_helper;
#endif
bool m_in_render;
#if !ENABLE_GCODE_VIEWER
LegendTexture m_legend_texture;
#endif // !ENABLE_GCODE_VIEWER
WarningTexture m_warning_texture;
wxTimer m_timer;
LayersEditing m_layers_editing;
Shader m_shader;
Mouse m_mouse;
mutable GLGizmosManager m_gizmos;
mutable GLToolbar m_main_toolbar;
@ -462,6 +476,10 @@ private:
bool m_extra_frame_requested;
mutable GLVolumeCollection m_volumes;
#if ENABLE_GCODE_VIEWER
GCodeViewer m_gcode_viewer;
#endif // ENABLE_GCODE_VIEWER
Selection m_selection;
const DynamicPrintConfig* m_config;
Model* m_model;
@ -472,7 +490,9 @@ private:
bool m_initialized;
bool m_apply_zoom_to_volumes_filter;
mutable std::vector<int> m_hover_volume_idxs;
#if !ENABLE_GCODE_VIEWER
bool m_legend_texture_enabled;
#endif // !ENABLE_GCODE_VIEWER
bool m_picking_enabled;
bool m_moving_enabled;
bool m_dynamic_background_enabled;
@ -490,7 +510,9 @@ private:
bool m_reload_delayed;
#if !ENABLE_GCODE_VIEWER
GCodePreviewVolumeIndex m_gcode_preview_volume_index;
#endif // !ENABLE_GCODE_VIEWER
#if ENABLE_RENDER_PICKING_PASS
bool m_show_picking_texture;
@ -532,6 +554,12 @@ public:
void reset_volumes();
int check_volumes_outside_state() const;
#if ENABLE_GCODE_VIEWER
void reset_gcode_toolpaths() { m_gcode_viewer.reset(); }
const GCodeViewer::SequentialView& get_gcode_sequential_view() const { return m_gcode_viewer.get_sequential_view(); }
void update_gcode_sequential_view_current(unsigned int first, unsigned int last) { m_gcode_viewer.update_sequential_view_current(first, last); }
#endif // ENABLE_GCODE_VIEWER
void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1);
void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1);
void update_instance_printable_state_for_object(size_t obj_idx);
@ -564,7 +592,6 @@ public:
void set_color_by(const std::string& value);
void refresh_camera_scene_box();
const Shader& get_shader() const { return m_shader; }
BoundingBoxf3 volumes_bounding_box() const;
BoundingBoxf3 scene_bounding_box() const;
@ -597,6 +624,9 @@ public:
void zoom_to_bed();
void zoom_to_volumes();
void zoom_to_selection();
#if ENABLE_GCODE_VIEWER
void zoom_to_gcode();
#endif // ENABLE_GCODE_VIEWER
void select_view(const std::string& direction);
void update_volumes_colors_by_extruder();
@ -613,7 +643,20 @@ public:
void delete_selected();
void ensure_on_bed(unsigned int object_idx);
#if ENABLE_GCODE_VIEWER
bool is_gcode_legend_enabled() const { return m_gcode_viewer.is_legend_enabled(); }
GCodeViewer::EViewType get_gcode_view_type() const { return m_gcode_viewer.get_view_type(); }
const std::vector<double>& get_gcode_layers_zs() const;
std::vector<double> get_volumes_print_zs(bool active_only) const;
unsigned int get_gcode_options_visibility_flags() const { return m_gcode_viewer.get_options_visibility_flags(); }
void set_gcode_options_visibility_from_flags(unsigned int flags);
unsigned int get_toolpath_role_visibility_flags() const { return m_gcode_viewer.get_toolpath_role_visibility_flags(); }
void set_toolpath_role_visibility_flags(unsigned int flags);
void set_toolpath_view_type(GCodeViewer::EViewType type);
void set_toolpaths_z_range(const std::array<double, 2>& range);
#else
std::vector<double> get_current_print_zs(bool active_only) const;
#endif // ENABLE_GCODE_VIEWER
void set_toolpaths_range(double low, double high);
std::vector<int> load_object(const ModelObject& model_object, int obj_idx, std::vector<int> instance_idxs);
@ -623,7 +666,14 @@ public:
void reload_scene(bool refresh_immediately, bool force_full_scene_refresh = false);
#if ENABLE_GCODE_VIEWER
void load_gcode_preview(const GCodeProcessor::Result& gcode_result);
void refresh_gcode_preview(const GCodeProcessor::Result& gcode_result, const std::vector<std::string>& str_tool_colors);
void set_gcode_view_preview_type(GCodeViewer::EViewType type) { return m_gcode_viewer.set_view_type(type); }
GCodeViewer::EViewType get_gcode_view_preview_type() const { return m_gcode_viewer.get_view_type(); }
#else
void load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors);
#endif // ENABLE_GCODE_VIEWER
void load_sla_preview();
void load_preview(const std::vector<std::string>& str_tool_colors, const std::vector<CustomGCode::Item>& color_print_values);
void bind_event_handlers();
@ -642,7 +692,9 @@ public:
Size get_canvas_size() const;
Vec2d get_local_mouse_position() const;
#if !ENABLE_GCODE_VIEWER
void reset_legend_texture();
#endif // !ENABLE_GCODE_VIEWER
void set_tooltip(const std::string& tooltip) const;
@ -744,6 +796,9 @@ private:
void _render_background() const;
void _render_bed(bool bottom, bool show_axes) const;
void _render_objects() const;
#if ENABLE_GCODE_VIEWER
void _render_gcode() const;
#endif // ENABLE_GCODE_VIEWER
void _render_selection() const;
#if ENABLE_RENDER_SELECTION_CENTER
void _render_selection_center() const;
@ -751,7 +806,9 @@ private:
void _check_and_update_toolbar_icon_scale() const;
void _render_overlays() const;
void _render_warning_texture() const;
#if !ENABLE_GCODE_VIEWER
void _render_legend_texture() const;
#endif // !ENABLE_GCODE_VIEWER
void _render_volumes_for_picking() const;
void _render_current_gizmo() const;
void _render_gizmos_overlay() const;
@ -799,22 +856,28 @@ private:
// Create 3D thick extrusion lines for wipe tower extrusions
void _load_wipe_tower_toolpaths(const std::vector<std::string>& str_tool_colors);
#if !ENABLE_GCODE_VIEWER
// generates gcode extrusion paths geometry
void _load_gcode_extrusion_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
// generates gcode travel paths geometry
void _load_gcode_travel_paths(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
// generates objects and wipe tower geometry
void _load_fff_shells();
#endif // !ENABLE_GCODE_VIEWER
// Load SLA objects and support structures for objects, for which the slaposSliceSupports step has been finished.
void _load_sla_shells();
#if !ENABLE_GCODE_VIEWER
// sets gcode geometry visibility according to user selection
void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data);
#endif // !ENABLE_GCODE_VIEWER
void _update_toolpath_volumes_outside_state();
void _update_sla_shells_outside_state();
void _show_warning_texture_if_needed(WarningTexture::Warning warning);
#if !ENABLE_GCODE_VIEWER
// generates the legend texture in dependence of the current shown view type
void _generate_legend_texture(const GCodePreviewData& preview_data, const std::vector<float>& tool_colors);
#endif // !ENABLE_GCODE_VIEWER
// generates a warning texture containing the given message
void _set_warning_texture(WarningTexture::Warning warning, bool state);

531
src/slic3r/GUI/GLModel.cpp Normal file
View file

@ -0,0 +1,531 @@
#include "libslic3r/libslic3r.h"
#include "GLModel.hpp"
#include "3DScene.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/Model.hpp"
#include <boost/filesystem/operations.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <GL/glew.h>
namespace Slic3r {
namespace GUI {
void GLModel::init_from(const GLModelInitializationData& data)
{
assert(!data.positions.empty() && !data.triangles.empty());
assert(data.positions.size() == data.normals.size());
if (m_vbo_id > 0) // call reset() if you want to reuse this model
return;
// vertices/normals data
std::vector<float> vertices(6 * data.positions.size());
for (size_t i = 0; i < data.positions.size(); ++i) {
size_t offset = i * 6;
::memcpy(static_cast<void*>(&vertices[offset]), static_cast<const void*>(data.positions[i].data()), 3 * sizeof(float));
::memcpy(static_cast<void*>(&vertices[3 + offset]), static_cast<const void*>(data.normals[i].data()), 3 * sizeof(float));
}
// indices data
std::vector<unsigned int> indices(3 * data.triangles.size());
for (size_t i = 0; i < data.triangles.size(); ++i) {
for (size_t j = 0; j < 3; ++j) {
indices[i * 3 + j] = static_cast<unsigned int>(data.triangles[i][j]);
}
}
m_indices_count = static_cast<unsigned int>(indices.size());
m_bounding_box = BoundingBoxf3();
for (size_t i = 0; i < data.positions.size(); ++i) {
m_bounding_box.merge(data.positions[i].cast<double>());
}
send_to_gpu(vertices, indices);
}
void GLModel::init_from(const TriangleMesh& mesh)
{
if (m_vbo_id > 0) // call reset() if you want to reuse this model
return;
std::vector<float> vertices = std::vector<float>(18 * mesh.stl.stats.number_of_facets);
std::vector<unsigned int> indices = std::vector<unsigned int>(3 * mesh.stl.stats.number_of_facets);
unsigned int vertices_count = 0;
for (uint32_t i = 0; i < mesh.stl.stats.number_of_facets; ++i) {
const stl_facet& facet = mesh.stl.facet_start[i];
for (uint32_t j = 0; j < 3; ++j) {
uint32_t offset = i * 18 + j * 6;
::memcpy(static_cast<void*>(&vertices[offset]), static_cast<const void*>(facet.vertex[j].data()), 3 * sizeof(float));
::memcpy(static_cast<void*>(&vertices[3 + offset]), static_cast<const void*>(facet.normal.data()), 3 * sizeof(float));
}
for (uint32_t j = 0; j < 3; ++j) {
indices[i * 3 + j] = vertices_count + j;
}
vertices_count += 3;
}
m_indices_count = static_cast<unsigned int>(indices.size());
m_bounding_box = mesh.bounding_box();
send_to_gpu(vertices, indices);
}
bool GLModel::init_from_file(const std::string& filename)
{
if (!boost::filesystem::exists(filename))
return false;
if (!boost::algorithm::iends_with(filename, ".stl"))
return false;
Model model;
try
{
model = Model::read_from_file(filename);
}
catch (std::exception&)
{
return false;
}
init_from(model.mesh());
m_filename = filename;
return true;
}
void GLModel::reset()
{
// release gpu memory
if (m_ibo_id > 0) {
glsafe(::glDeleteBuffers(1, &m_ibo_id));
m_ibo_id = 0;
}
if (m_vbo_id > 0) {
glsafe(::glDeleteBuffers(1, &m_vbo_id));
m_vbo_id = 0;
}
m_indices_count = 0;
m_bounding_box = BoundingBoxf3();
m_filename = std::string();
}
void GLModel::render() const
{
if (m_vbo_id == 0 || m_ibo_id == 0)
return;
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id));
glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float), (const void*)0));
glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), (const void*)(3 * sizeof(float))));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo_id));
glsafe(::glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(m_indices_count), GL_UNSIGNED_INT, (const void*)0));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
void GLModel::send_to_gpu(const std::vector<float>& vertices, const std::vector<unsigned int>& indices)
{
// vertex data -> send to gpu
glsafe(::glGenBuffers(1, &m_vbo_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id));
glsafe(::glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
// indices data -> send to gpu
glsafe(::glGenBuffers(1, &m_ibo_id));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_ibo_id));
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
}
GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height)
{
auto append_vertex = [](GLModelInitializationData& data, const Vec3f& position, const Vec3f& normal) {
data.positions.emplace_back(position);
data.normals.emplace_back(normal);
};
resolution = std::max(4, resolution);
GLModelInitializationData data;
const float angle_step = 2.0f * M_PI / static_cast<float>(resolution);
std::vector<float> cosines(resolution);
std::vector<float> sines(resolution);
for (int i = 0; i < resolution; ++i)
{
float angle = angle_step * static_cast<float>(i);
cosines[i] = ::cos(angle);
sines[i] = -::sin(angle);
}
const float total_height = tip_height + stem_height;
// tip vertices/normals
append_vertex(data, { 0.0f, 0.0f, total_height }, Vec3f::UnitZ());
for (int i = 0; i < resolution; ++i)
{
append_vertex(data, { tip_radius * sines[i], tip_radius * cosines[i], stem_height }, { sines[i], cosines[i], 0.0f });
}
// tip triangles
for (int i = 0; i < resolution; ++i)
{
int v3 = (i < resolution - 1) ? i + 2 : 1;
data.triangles.emplace_back(0, i + 1, v3);
}
// tip cap outer perimeter vertices
for (int i = 0; i < resolution; ++i)
{
append_vertex(data, { tip_radius * sines[i], tip_radius * cosines[i], stem_height }, -Vec3f::UnitZ());
}
// tip cap inner perimeter vertices
for (int i = 0; i < resolution; ++i)
{
append_vertex(data, { stem_radius * sines[i], stem_radius * cosines[i], stem_height }, -Vec3f::UnitZ());
}
// tip cap triangles
for (int i = 0; i < resolution; ++i)
{
int v2 = (i < resolution - 1) ? i + resolution + 2 : resolution + 1;
int v3 = (i < resolution - 1) ? i + 2 * resolution + 2 : 2 * resolution + 1;
data.triangles.emplace_back(i + resolution + 1, v3, v2);
data.triangles.emplace_back(i + resolution + 1, i + 2 * resolution + 1, v3);
}
// stem bottom vertices
for (int i = 0; i < resolution; ++i)
{
append_vertex(data, { stem_radius * sines[i], stem_radius * cosines[i], stem_height }, { sines[i], cosines[i], 0.0f });
}
// stem top vertices
for (int i = 0; i < resolution; ++i)
{
append_vertex(data, { stem_radius * sines[i], stem_radius * cosines[i], 0.0f }, { sines[i], cosines[i], 0.0f });
}
// stem triangles
for (int i = 0; i < resolution; ++i)
{
int v2 = (i < resolution - 1) ? i + 3 * resolution + 2 : 3 * resolution + 1;
int v3 = (i < resolution - 1) ? i + 4 * resolution + 2 : 4 * resolution + 1;
data.triangles.emplace_back(i + 3 * resolution + 1, v3, v2);
data.triangles.emplace_back(i + 3 * resolution + 1, i + 4 * resolution + 1, v3);
}
// stem cap vertices
append_vertex(data, Vec3f::Zero(), -Vec3f::UnitZ());
for (int i = 0; i < resolution; ++i)
{
append_vertex(data, { stem_radius * sines[i], stem_radius * cosines[i], 0.0f }, -Vec3f::UnitZ());
}
// stem cap triangles
for (int i = 0; i < resolution; ++i)
{
int v3 = (i < resolution - 1) ? i + 5 * resolution + 3 : 5 * resolution + 2;
data.triangles.emplace_back(5 * resolution + 1, v3, i + 5 * resolution + 2);
}
return data;
}
GLModelInitializationData circular_arrow(int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness)
{
auto append_vertex = [](GLModelInitializationData& data, const Vec3f& position, const Vec3f& normal) {
data.positions.emplace_back(position);
data.normals.emplace_back(normal);
};
resolution = std::max(2, resolution);
GLModelInitializationData data;
const float half_thickness = 0.5f * thickness;
const float half_stem_width = 0.5f * stem_width;
const float half_tip_width = 0.5f * tip_width;
const float outer_radius = radius + half_stem_width;
const float inner_radius = radius - half_stem_width;
const float step_angle = 0.5f * PI / static_cast<float>(resolution);
// tip
// top face vertices
append_vertex(data, { 0.0f, outer_radius, half_thickness }, Vec3f::UnitZ());
append_vertex(data, { 0.0f, radius + half_tip_width, half_thickness }, Vec3f::UnitZ());
append_vertex(data, { -tip_height, radius, half_thickness }, Vec3f::UnitZ());
append_vertex(data, { 0.0f, radius - half_tip_width, half_thickness }, Vec3f::UnitZ());
append_vertex(data, { 0.0f, inner_radius, half_thickness }, Vec3f::UnitZ());
// top face triangles
data.triangles.emplace_back(0, 1, 2);
data.triangles.emplace_back(0, 2, 4);
data.triangles.emplace_back(4, 2, 3);
// bottom face vertices
append_vertex(data, { 0.0f, outer_radius, -half_thickness }, -Vec3f::UnitZ());
append_vertex(data, { 0.0f, radius + half_tip_width, -half_thickness }, -Vec3f::UnitZ());
append_vertex(data, { -tip_height, radius, -half_thickness }, -Vec3f::UnitZ());
append_vertex(data, { 0.0f, radius - half_tip_width, -half_thickness }, -Vec3f::UnitZ());
append_vertex(data, { 0.0f, inner_radius, -half_thickness }, -Vec3f::UnitZ());
// bottom face triangles
data.triangles.emplace_back(5, 7, 6);
data.triangles.emplace_back(5, 9, 7);
data.triangles.emplace_back(9, 8, 7);
// side faces vertices
append_vertex(data, { 0.0f, outer_radius, -half_thickness }, Vec3f::UnitX());
append_vertex(data, { 0.0f, radius + half_tip_width, -half_thickness }, Vec3f::UnitX());
append_vertex(data, { 0.0f, outer_radius, half_thickness }, Vec3f::UnitX());
append_vertex(data, { 0.0f, radius + half_tip_width, half_thickness }, Vec3f::UnitX());
Vec3f normal(-half_tip_width, tip_height, 0.0f);
normal.normalize();
append_vertex(data, { 0.0f, radius + half_tip_width, -half_thickness }, normal);
append_vertex(data, { -tip_height, radius, -half_thickness }, normal);
append_vertex(data, { 0.0f, radius + half_tip_width, half_thickness }, normal);
append_vertex(data, { -tip_height, radius, half_thickness }, normal);
normal = Vec3f(-half_tip_width, -tip_height, 0.0f);
normal.normalize();
append_vertex(data, { -tip_height, radius, -half_thickness }, normal);
append_vertex(data, { 0.0f, radius - half_tip_width, -half_thickness }, normal);
append_vertex(data, { -tip_height, radius, half_thickness }, normal);
append_vertex(data, { 0.0f, radius - half_tip_width, half_thickness }, normal);
append_vertex(data, { 0.0f, radius - half_tip_width, -half_thickness }, Vec3f::UnitX());
append_vertex(data, { 0.0f, inner_radius, -half_thickness }, Vec3f::UnitX());
append_vertex(data, { 0.0f, radius - half_tip_width, half_thickness }, Vec3f::UnitX());
append_vertex(data, { 0.0f, inner_radius, half_thickness }, Vec3f::UnitX());
// side face triangles
for (int i = 0; i < 4; ++i)
{
int ii = i * 4;
data.triangles.emplace_back(10 + ii, 11 + ii, 13 + ii);
data.triangles.emplace_back(10 + ii, 13 + ii, 12 + ii);
}
// stem
// top face vertices
for (int i = 0; i <= resolution; ++i)
{
float angle = static_cast<float>(i) * step_angle;
append_vertex(data, { inner_radius * ::sin(angle), inner_radius * ::cos(angle), half_thickness }, Vec3f::UnitZ());
}
for (int i = 0; i <= resolution; ++i)
{
float angle = static_cast<float>(i) * step_angle;
append_vertex(data, { outer_radius * ::sin(angle), outer_radius * ::cos(angle), half_thickness }, Vec3f::UnitZ());
}
// top face triangles
for (int i = 0; i < resolution; ++i)
{
data.triangles.emplace_back(26 + i, 27 + i, 27 + resolution + i);
data.triangles.emplace_back(27 + i, 28 + resolution + i, 27 + resolution + i);
}
// bottom face vertices
for (int i = 0; i <= resolution; ++i)
{
float angle = static_cast<float>(i) * step_angle;
append_vertex(data, { inner_radius * ::sin(angle), inner_radius * ::cos(angle), -half_thickness }, -Vec3f::UnitZ());
}
for (int i = 0; i <= resolution; ++i)
{
float angle = static_cast<float>(i) * step_angle;
append_vertex(data, { outer_radius * ::sin(angle), outer_radius * ::cos(angle), -half_thickness }, -Vec3f::UnitZ());
}
// bottom face triangles
for (int i = 0; i < resolution; ++i)
{
data.triangles.emplace_back(28 + 2 * resolution + i, 29 + 3 * resolution + i, 29 + 2 * resolution + i);
data.triangles.emplace_back(29 + 2 * resolution + i, 29 + 3 * resolution + i, 30 + 3 * resolution + i);
}
// side faces vertices and triangles
for (int i = 0; i <= resolution; ++i)
{
float angle = static_cast<float>(i) * step_angle;
float c = ::cos(angle);
float s = ::sin(angle);
append_vertex(data, { inner_radius * s, inner_radius * c, -half_thickness }, { -s, -c, 0.0f });
}
for (int i = 0; i <= resolution; ++i)
{
float angle = static_cast<float>(i) * step_angle;
float c = ::cos(angle);
float s = ::sin(angle);
append_vertex(data, { inner_radius * s, inner_radius * c, half_thickness }, { -s, -c, 0.0f });
}
int first_id = 26 + 4 * (resolution + 1);
for (int i = 0; i < resolution; ++i)
{
int ii = first_id + i;
data.triangles.emplace_back(ii, ii + 1, ii + resolution + 2);
data.triangles.emplace_back(ii, ii + resolution + 2, ii + resolution + 1);
}
append_vertex(data, { inner_radius, 0.0f, -half_thickness }, -Vec3f::UnitY());
append_vertex(data, { outer_radius, 0.0f, -half_thickness }, -Vec3f::UnitY());
append_vertex(data, { inner_radius, 0.0f, half_thickness }, -Vec3f::UnitY());
append_vertex(data, { outer_radius, 0.0f, half_thickness }, -Vec3f::UnitY());
first_id = 26 + 6 * (resolution + 1);
data.triangles.emplace_back(first_id, first_id + 1, first_id + 3);
data.triangles.emplace_back(first_id, first_id + 3, first_id + 2);
for (int i = resolution; i >= 0; --i)
{
float angle = static_cast<float>(i) * step_angle;
float c = ::cos(angle);
float s = ::sin(angle);
append_vertex(data, { outer_radius * s, outer_radius * c, -half_thickness }, { s, c, 0.0f });
}
for (int i = resolution; i >= 0; --i)
{
float angle = static_cast<float>(i) * step_angle;
float c = ::cos(angle);
float s = ::sin(angle);
append_vertex(data, { outer_radius * s, outer_radius * c, +half_thickness }, { s, c, 0.0f });
}
first_id = 30 + 6 * (resolution + 1);
for (int i = 0; i < resolution; ++i)
{
int ii = first_id + i;
data.triangles.emplace_back(ii, ii + 1, ii + resolution + 2);
data.triangles.emplace_back(ii, ii + resolution + 2, ii + resolution + 1);
}
return data;
}
GLModelInitializationData straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness)
{
auto append_vertex = [](GLModelInitializationData& data, const Vec3f& position, const Vec3f& normal) {
data.positions.emplace_back(position);
data.normals.emplace_back(normal);
};
GLModelInitializationData data;
const float half_thickness = 0.5f * thickness;
const float half_stem_width = 0.5f * stem_width;
const float half_tip_width = 0.5f * tip_width;
const float total_height = tip_height + stem_height;
// top face vertices
append_vertex(data, { half_stem_width, 0.0, half_thickness }, Vec3f::UnitZ());
append_vertex(data, { half_stem_width, stem_height, half_thickness }, Vec3f::UnitZ());
append_vertex(data, { half_tip_width, stem_height, half_thickness }, Vec3f::UnitZ());
append_vertex(data, { 0.0, total_height, half_thickness }, Vec3f::UnitZ());
append_vertex(data, { -half_tip_width, stem_height, half_thickness }, Vec3f::UnitZ());
append_vertex(data, { -half_stem_width, stem_height, half_thickness }, Vec3f::UnitZ());
append_vertex(data, { -half_stem_width, 0.0, half_thickness }, Vec3f::UnitZ());
// top face triangles
data.triangles.emplace_back(0, 1, 6);
data.triangles.emplace_back(6, 1, 5);
data.triangles.emplace_back(4, 5, 3);
data.triangles.emplace_back(5, 1, 3);
data.triangles.emplace_back(1, 2, 3);
// bottom face vertices
append_vertex(data, { half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitZ());
append_vertex(data, { half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitZ());
append_vertex(data, { half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitZ());
append_vertex(data, { 0.0, total_height, -half_thickness }, -Vec3f::UnitZ());
append_vertex(data, { -half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitZ());
append_vertex(data, { -half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitZ());
append_vertex(data, { -half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitZ());
// bottom face triangles
data.triangles.emplace_back(7, 13, 8);
data.triangles.emplace_back(13, 12, 8);
data.triangles.emplace_back(12, 11, 10);
data.triangles.emplace_back(8, 12, 10);
data.triangles.emplace_back(9, 8, 10);
// side faces vertices
append_vertex(data, { half_stem_width, 0.0, -half_thickness }, Vec3f::UnitX());
append_vertex(data, { half_stem_width, stem_height, -half_thickness }, Vec3f::UnitX());
append_vertex(data, { half_stem_width, 0.0, half_thickness }, Vec3f::UnitX());
append_vertex(data, { half_stem_width, stem_height, half_thickness }, Vec3f::UnitX());
append_vertex(data, { half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitY());
append_vertex(data, { half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitY());
append_vertex(data, { half_stem_width, stem_height, half_thickness }, -Vec3f::UnitY());
append_vertex(data, { half_tip_width, stem_height, half_thickness }, -Vec3f::UnitY());
Vec3f normal(tip_height, half_tip_width, 0.0f);
normal.normalize();
append_vertex(data, { half_tip_width, stem_height, -half_thickness }, normal);
append_vertex(data, { 0.0, total_height, -half_thickness }, normal);
append_vertex(data, { half_tip_width, stem_height, half_thickness }, normal);
append_vertex(data, { 0.0, total_height, half_thickness }, normal);
normal = Vec3f(-tip_height, half_tip_width, 0.0f);
normal.normalize();
append_vertex(data, { 0.0, total_height, -half_thickness }, normal);
append_vertex(data, { -half_tip_width, stem_height, -half_thickness }, normal);
append_vertex(data, { 0.0, total_height, half_thickness }, normal);
append_vertex(data, { -half_tip_width, stem_height, half_thickness }, normal);
append_vertex(data, { -half_tip_width, stem_height, -half_thickness }, -Vec3f::UnitY());
append_vertex(data, { -half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitY());
append_vertex(data, { -half_tip_width, stem_height, half_thickness }, -Vec3f::UnitY());
append_vertex(data, { -half_stem_width, stem_height, half_thickness }, -Vec3f::UnitY());
append_vertex(data, { -half_stem_width, stem_height, -half_thickness }, -Vec3f::UnitX());
append_vertex(data, { -half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitX());
append_vertex(data, { -half_stem_width, stem_height, half_thickness }, -Vec3f::UnitX());
append_vertex(data, { -half_stem_width, 0.0, half_thickness }, -Vec3f::UnitX());
append_vertex(data, { -half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitY());
append_vertex(data, { half_stem_width, 0.0, -half_thickness }, -Vec3f::UnitY());
append_vertex(data, { -half_stem_width, 0.0, half_thickness }, -Vec3f::UnitY());
append_vertex(data, { half_stem_width, 0.0, half_thickness }, -Vec3f::UnitY());
// side face triangles
for (int i = 0; i < 7; ++i)
{
int ii = i * 4;
data.triangles.emplace_back(14 + ii, 15 + ii, 17 + ii);
data.triangles.emplace_back(14 + ii, 17 + ii, 16 + ii);
}
return data;
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,68 @@
#ifndef slic3r_GLModel_hpp_
#define slic3r_GLModel_hpp_
#include "libslic3r/Point.hpp"
#include "libslic3r/BoundingBox.hpp"
#include <vector>
#include <string>
namespace Slic3r {
class TriangleMesh;
namespace GUI {
struct GLModelInitializationData
{
std::vector<Vec3f> positions;
std::vector<Vec3f> normals;
std::vector<Vec3i> triangles;
};
class GLModel
{
unsigned int m_vbo_id{ 0 };
unsigned int m_ibo_id{ 0 };
size_t m_indices_count{ 0 };
BoundingBoxf3 m_bounding_box;
std::string m_filename;
public:
virtual ~GLModel() { reset(); }
void init_from(const GLModelInitializationData& data);
void init_from(const TriangleMesh& mesh);
bool init_from_file(const std::string& filename);
void reset();
void render() const;
const BoundingBoxf3& get_bounding_box() const { return m_bounding_box; }
const std::string& get_filename() const { return m_filename; }
private:
void send_to_gpu(const std::vector<float>& vertices, const std::vector<unsigned int>& indices);
};
// create an arrow with cylindrical stem and conical tip, with the given dimensions and resolution
// the origin of the arrow is in the center of the stem cap
// the arrow has its axis of symmetry along the Z axis and is pointing upward
GLModelInitializationData stilized_arrow(int resolution, float tip_radius, float tip_height, float stem_radius, float stem_height);
// create an arrow whose stem is a quarter of circle, with the given dimensions and resolution
// the origin of the arrow is in the center of the circle
// the arrow is contained in the 1st quadrant of the XY plane and is pointing counterclockwise
GLModelInitializationData circular_arrow(int resolution, float radius, float tip_height, float tip_width, float stem_width, float thickness);
// create an arrow with the given dimensions
// the origin of the arrow is in the center of the stem cap
// the arrow is contained in XY plane and has its main axis along the Y axis
GLModelInitializationData straight_arrow(float tip_width, float tip_height, float stem_width, float stem_height, float thickness);
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLModel_hpp_

View file

@ -1,366 +1,348 @@
#include <GL/glew.h>
#include "libslic3r/libslic3r.h"
#include "GLShader.hpp"
#include "libslic3r/Utils.hpp"
#include "3DScene.hpp"
#include <boost/nowide/fstream.hpp>
#include "libslic3r/Utils.hpp"
#include <string>
#include <utility>
#include <assert.h>
#include <boost/nowide/fstream.hpp>
#include <GL/glew.h>
#include <cassert>
#include <boost/log/trivial.hpp>
namespace Slic3r {
GLShader::~GLShader()
GLShaderProgram::~GLShaderProgram()
{
assert(fragment_program_id == 0);
assert(vertex_program_id == 0);
assert(shader_program_id == 0);
if (m_id > 0)
glsafe(::glDeleteProgram(m_id));
}
// 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)
bool GLShaderProgram::init_from_files(const std::string& name, const ShaderFilenames& filenames)
{
const char *value = (const char*)glGetString(param);
return std::string(value ? value : "N/A");
auto load_from_file = [](const std::string& filename) {
std::string path = resources_dir() + "/shaders/" + filename;
boost::nowide::ifstream s(path, boost::nowide::ifstream::binary);
if (!s.good()) {
BOOST_LOG_TRIVIAL(error) << "Couldn't open file: '" << path << "'";
return std::string();
}
s.seekg(0, s.end);
int file_length = static_cast<int>(s.tellg());
s.seekg(0, s.beg);
std::string source(file_length, '\0');
s.read(source.data(), file_length);
if (!s.good()) {
BOOST_LOG_TRIVIAL(error) << "Error while loading file: '" << path << "'";
return std::string();
}
s.close();
return source;
};
ShaderSources sources = {};
for (size_t i = 0; i < static_cast<size_t>(EShaderType::Count); ++i) {
sources[i] = filenames[i].empty() ? std::string() : load_from_file(filenames[i]);
}
bool valid = !sources[static_cast<size_t>(EShaderType::Vertex)].empty() && !sources[static_cast<size_t>(EShaderType::Fragment)].empty() && sources[static_cast<size_t>(EShaderType::Compute)].empty();
valid |= !sources[static_cast<size_t>(EShaderType::Compute)].empty() && sources[static_cast<size_t>(EShaderType::Vertex)].empty() && sources[static_cast<size_t>(EShaderType::Fragment)].empty() &&
sources[static_cast<size_t>(EShaderType::Geometry)].empty() && sources[static_cast<size_t>(EShaderType::TessEvaluation)].empty() && sources[static_cast<size_t>(EShaderType::TessControl)].empty();
return valid ? init_from_texts(name, sources) : false;
}
bool GLShader::load_from_text(const char *fragment_shader, const char *vertex_shader)
bool GLShaderProgram::init_from_texts(const std::string& name, const ShaderSources& sources)
{
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";
auto shader_type_as_string = [](EShaderType type) {
switch (type)
{
case EShaderType::Vertex: { return "vertex"; }
case EShaderType::Fragment: { return "fragment"; }
case EShaderType::Geometry: { return "geometry"; }
case EShaderType::TessEvaluation: { return "tesselation evaluation"; }
case EShaderType::TessControl: { return "tesselation control"; }
case EShaderType::Compute: { return "compute"; }
default: { return "unknown"; }
}
#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);
glcheck();
if (this->fragment_program_id == 0) {
last_error = "glCreateShader(GL_FRAGMENT_SHADER) failed.";
return false;
auto create_shader = [](EShaderType type) {
GLuint id = 0;
switch (type)
{
case EShaderType::Vertex: { id = ::glCreateShader(GL_VERTEX_SHADER); glcheck(); break; }
case EShaderType::Fragment: { id = ::glCreateShader(GL_FRAGMENT_SHADER); glcheck(); break; }
case EShaderType::Geometry: { id = ::glCreateShader(GL_GEOMETRY_SHADER); glcheck(); break; }
case EShaderType::TessEvaluation: { id = ::glCreateShader(GL_TESS_EVALUATION_SHADER); glcheck(); break; }
case EShaderType::TessControl: { id = ::glCreateShader(GL_TESS_CONTROL_SHADER); glcheck(); break; }
case EShaderType::Compute: { id = ::glCreateShader(GL_COMPUTE_SHADER); glcheck(); break; }
default: { break; }
}
GLint len = (GLint)strlen(fragment_shader);
glsafe(::glShaderSource(this->fragment_program_id, 1, &fragment_shader, &len));
glsafe(::glCompileShader(this->fragment_program_id));
GLint params;
glsafe(::glGetShaderiv(this->fragment_program_id, GL_COMPILE_STATUS, &params));
if (params == GL_FALSE) {
// Compilation failed. Get the log.
glsafe(::glGetShaderiv(this->fragment_program_id, GL_INFO_LOG_LENGTH, &params));
std::vector<char> msg(params);
glsafe(::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;
return (id == 0) ? std::make_pair(false, GLuint(0)) : std::make_pair(true, id);
};
auto release_shaders = [](const std::array<GLuint, static_cast<size_t>(EShaderType::Count)>& shader_ids) {
for (size_t i = 0; i < static_cast<size_t>(EShaderType::Count); ++i) {
if (shader_ids[i] > 0)
glsafe(::glDeleteShader(shader_ids[i]));
}
};
assert(m_id == 0);
m_name = name;
std::array<GLuint, static_cast<size_t>(EShaderType::Count)> shader_ids = { 0 };
for (size_t i = 0; i < static_cast<size_t>(EShaderType::Count); ++i) {
const std::string& source = sources[i];
if (!source.empty())
{
EShaderType type = static_cast<EShaderType>(i);
auto [result, id] = create_shader(type);
if (result)
shader_ids[i] = id;
else {
BOOST_LOG_TRIVIAL(error) << "glCreateShader() failed for " << shader_type_as_string(type) << " shader of shader program '" << name << "'";
// release shaders
release_shaders(shader_ids);
return false;
}
const char* source_ptr = source.c_str();
glsafe(::glShaderSource(id, 1, &source_ptr, nullptr));
glsafe(::glCompileShader(id));
GLint params;
glsafe(::glGetShaderiv(id, GL_COMPILE_STATUS, &params));
if (params == GL_FALSE) {
// Compilation failed.
glsafe(::glGetShaderiv(id, GL_INFO_LOG_LENGTH, &params));
std::vector<char> msg(params);
glsafe(::glGetShaderInfoLog(id, params, &params, msg.data()));
BOOST_LOG_TRIVIAL(error) << "Unable to compile " << shader_type_as_string(type) << " shader of shader program '" << name << "':\n" << msg.data();
// release shaders
release_shaders(shader_ids);
return false;
}
}
}
if (vertex_shader != nullptr) {
this->vertex_program_id = ::glCreateShader(GL_VERTEX_SHADER);
glcheck();
if (this->vertex_program_id == 0) {
last_error = "glCreateShader(GL_VERTEX_SHADER) failed.";
this->release();
return false;
}
GLint len = (GLint)strlen(vertex_shader);
glsafe(::glShaderSource(this->vertex_program_id, 1, &vertex_shader, &len));
glsafe(::glCompileShader(this->vertex_program_id));
GLint params;
glsafe(::glGetShaderiv(this->vertex_program_id, GL_COMPILE_STATUS, &params));
if (params == GL_FALSE) {
// Compilation failed. Get the log.
glsafe(::glGetShaderiv(this->vertex_program_id, GL_INFO_LOG_LENGTH, &params));
std::vector<char> msg(params);
glsafe(::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();
m_id = ::glCreateProgram();
glcheck();
if (this->shader_program_id == 0) {
last_error = "glCreateProgram() failed.";
this->release();
if (m_id == 0) {
BOOST_LOG_TRIVIAL(error) << "glCreateProgram() failed for shader program '" << name << "'";
// release shaders
release_shaders(shader_ids);
return false;
}
if (this->fragment_program_id)
glsafe(::glAttachShader(this->shader_program_id, this->fragment_program_id));
if (this->vertex_program_id)
glsafe(::glAttachShader(this->shader_program_id, this->vertex_program_id));
glsafe(::glLinkProgram(this->shader_program_id));
for (size_t i = 0; i < static_cast<size_t>(EShaderType::Count); ++i) {
if (shader_ids[i] > 0)
glsafe(::glAttachShader(m_id, shader_ids[i]));
}
glsafe(::glLinkProgram(m_id));
GLint params;
glsafe(::glGetProgramiv(this->shader_program_id, GL_LINK_STATUS, &params));
glsafe(::glGetProgramiv(m_id, GL_LINK_STATUS, &params));
if (params == GL_FALSE) {
// Linking failed. Get the log.
glsafe(::glGetProgramiv(this->shader_program_id, GL_INFO_LOG_LENGTH, &params));
// Linking failed.
glsafe(::glGetProgramiv(m_id, GL_INFO_LOG_LENGTH, &params));
std::vector<char> msg(params);
glsafe(::glGetProgramInfoLog(this->shader_program_id, params, &params, msg.data()));
this->last_error = std::string("Shader linking failed:\n") + msg.data();
this->release();
glsafe(::glGetProgramInfoLog(m_id, params, &params, msg.data()));
BOOST_LOG_TRIVIAL(error) << "Unable to link shader program '" << name << "':\n" << msg.data();
// release shaders
release_shaders(shader_ids);
// release shader program
glsafe(::glDeleteProgram(m_id));
m_id = 0;
return false;
}
last_error.clear();
// release shaders, they are no more needed
release_shaders(shader_ids);
return true;
}
bool GLShader::load_from_file(const char* fragment_shader_filename, const char* vertex_shader_filename)
void GLShaderProgram::start_using() const
{
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(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(fragment_shader.data(), file_length);
if (!fs.good())
return false;
fs.close();
return load_from_text(fragment_shader.c_str(), vertex_shader.c_str());
assert(m_id > 0);
glsafe(::glUseProgram(m_id));
}
void GLShader::release()
{
if (this->shader_program_id) {
if (this->vertex_program_id)
glsafe(::glDetachShader(this->shader_program_id, this->vertex_program_id));
if (this->fragment_program_id)
glsafe(::glDetachShader(this->shader_program_id, this->fragment_program_id));
glsafe(::glDeleteProgram(this->shader_program_id));
this->shader_program_id = 0;
}
if (this->vertex_program_id) {
glsafe(::glDeleteShader(this->vertex_program_id));
this->vertex_program_id = 0;
}
if (this->fragment_program_id) {
glsafe(::glDeleteShader(this->fragment_program_id));
this->fragment_program_id = 0;
}
}
void GLShader::enable() const
{
glsafe(::glUseProgram(this->shader_program_id));
}
void GLShader::disable() const
void GLShaderProgram::stop_using() const
{
glsafe(::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) {
glsafe(::glUniform1fARB(id, value));
return true;
}
return false;
}
bool GLShader::set_uniform(const char* name, const float* matrix) const
bool GLShaderProgram::set_uniform(const char* name, int value) const
{
int id = get_uniform_location(name);
if (id >= 0)
{
glsafe(::glUniformMatrix4fv(id, 1, GL_FALSE, (const GLfloat*)matrix));
if (id >= 0) {
glsafe(::glUniform1i(id, static_cast<GLint>(value)));
return true;
}
return false;
}
bool GLShader::set_uniform(const char* name, int value) const
bool GLShaderProgram::set_uniform(const char* name, bool value) const
{
return set_uniform(name, value ? 1 : 0);
}
bool GLShaderProgram::set_uniform(const char* name, float value) const
{
int id = get_uniform_location(name);
if (id >= 0)
{
glsafe(::glUniform1i(id, value));
if (id >= 0) {
glsafe(::glUniform1f(id, static_cast<GLfloat>(value)));
return true;
}
return false;
}
/*
# Set shader vector
sub SetVector
bool GLShaderProgram::set_uniform(const char* name, double value) const
{
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 '';
return set_uniform(name, static_cast<float>(value));
}
# Set shader 4x4 matrix
sub SetMatrix
bool GLShaderProgram::set_uniform(const char* name, const std::array<int, 2>& value) const
{
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 '';
}
*/
Shader::Shader()
: m_shader(nullptr)
{
}
Shader::~Shader()
{
reset();
}
bool Shader::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename)
{
if (is_initialized())
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniform2iv(id, 1, static_cast<const GLint*>(value.data())));
return true;
}
return false;
}
m_shader = new GLShader();
if (m_shader != nullptr)
{
if (!m_shader->load_from_file(fragment_shader_filename.c_str(), vertex_shader_filename.c_str()))
{
std::cout << "Compilaton of shader failed:" << std::endl;
std::cout << m_shader->last_error << std::endl;
reset();
return false;
bool GLShaderProgram::set_uniform(const char* name, const std::array<int, 3>& value) const
{
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniform3iv(id, 1, static_cast<const GLint*>(value.data())));
return true;
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, const std::array<int, 4>& value) const
{
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniform4iv(id, 1, static_cast<const GLint*>(value.data())));
return true;
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, const std::array<float, 2>& value) const
{
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniform2fv(id, 1, static_cast<const GLfloat*>(value.data())));
return true;
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, const std::array<float, 3>& value) const
{
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniform3fv(id, 1, static_cast<const GLfloat*>(value.data())));
return true;
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, const std::array<float, 4>& value) const
{
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniform4fv(id, 1, static_cast<const GLfloat*>(value.data())));
return true;
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, const float* value, size_t size) const
{
if (size == 1)
return set_uniform(name, value[0]);
else if (size < 5) {
int id = get_uniform_location(name);
if (id >= 0) {
if (size == 2)
glsafe(::glUniform2fv(id, 1, static_cast<const GLfloat*>(value)));
else if (size == 3)
glsafe(::glUniform3fv(id, 1, static_cast<const GLfloat*>(value)));
else
glsafe(::glUniform4fv(id, 1, static_cast<const GLfloat*>(value)));
return true;
}
}
return true;
return false;
}
bool Shader::is_initialized() const
bool GLShaderProgram::set_uniform(const char* name, const Transform3f& value) const
{
return (m_shader != nullptr);
}
bool Shader::start_using() const
{
if (is_initialized())
{
m_shader->enable();
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniformMatrix4fv(id, 1, GL_FALSE, static_cast<const GLfloat*>(value.matrix().data())));
return true;
}
else
return false;
return false;
}
void Shader::stop_using() const
bool GLShaderProgram::set_uniform(const char* name, const Transform3d& value) const
{
if (m_shader != nullptr)
m_shader->disable();
return set_uniform(name, value.cast<float>());
}
int Shader::get_attrib_location(const std::string& name) const
bool GLShaderProgram::set_uniform(const char* name, const Matrix3f& value) const
{
return (m_shader != nullptr) ? m_shader->get_attrib_location(name.c_str()) : -1;
}
int Shader::get_uniform_location(const std::string& name) const
{
return (m_shader != nullptr) ? m_shader->get_uniform_location(name.c_str()) : -1;
}
void Shader::set_uniform(const std::string& name, float value) const
{
if (m_shader != nullptr)
m_shader->set_uniform(name.c_str(), value);
}
void Shader::set_uniform(const std::string& name, const float* matrix) const
{
if (m_shader != nullptr)
m_shader->set_uniform(name.c_str(), matrix);
}
void Shader::set_uniform(const std::string& name, bool value) const
{
if (m_shader != nullptr)
m_shader->set_uniform(name.c_str(), value ? 1 : 0);
}
unsigned int Shader::get_shader_program_id() const
{
return (m_shader != nullptr) ? m_shader->shader_program_id : 0;
}
void Shader::reset()
{
if (m_shader != nullptr)
{
m_shader->release();
delete m_shader;
m_shader = nullptr;
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniformMatrix3fv(id, 1, GL_FALSE, static_cast<const GLfloat*>(value.data())));
return true;
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, const Vec3f& value) const
{
int id = get_uniform_location(name);
if (id >= 0) {
glsafe(::glUniform3fv(id, 1, static_cast<const GLfloat*>(value.data())));
return true;
}
return false;
}
bool GLShaderProgram::set_uniform(const char* name, const Vec3d& value) const
{
return set_uniform(name, static_cast<Vec3f>(value.cast<float>()));
}
int GLShaderProgram::get_attrib_location(const char* name) const
{
return (m_id > 0) ? ::glGetAttribLocation(m_id, name) : -1;
}
int GLShaderProgram::get_uniform_location(const char* name) const
{
return (m_id > 0) ? ::glGetUniformLocation(m_id, name) : -1;
}
} // namespace Slic3r

View file

@ -1,71 +1,69 @@
#ifndef slic3r_GLShader_hpp_
#define slic3r_GLShader_hpp_
#include "libslic3r/libslic3r.h"
#include <array>
#include <string>
#include "libslic3r/Point.hpp"
namespace Slic3r {
class GLShader
class GLShaderProgram
{
public:
GLShader() :
fragment_program_id(0),
vertex_program_id(0),
shader_program_id(0)
{}
~GLShader();
enum class EShaderType
{
Vertex,
Fragment,
Geometry,
TessEvaluation,
TessControl,
Compute,
Count
};
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;
bool set_uniform(const char* name, int value) 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;
};
class Shader
{
GLShader* m_shader;
public:
Shader();
~Shader();
bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename);
bool is_initialized() const;
bool start_using() const;
void stop_using() const;
int get_attrib_location(const std::string& name) const;
int get_uniform_location(const std::string& name) const;
void set_uniform(const std::string& name, float value) const;
void set_uniform(const std::string& name, const float* matrix) const;
void set_uniform(const std::string& name, bool value) const;
const GLShader* get_shader() const { return m_shader; }
unsigned int get_shader_program_id() const;
typedef std::array<std::string, static_cast<size_t>(EShaderType::Count)> ShaderFilenames;
typedef std::array<std::string, static_cast<size_t>(EShaderType::Count)> ShaderSources;
private:
void reset();
std::string m_name;
unsigned int m_id{ 0 };
public:
~GLShaderProgram();
bool init_from_files(const std::string& name, const ShaderFilenames& filenames);
bool init_from_texts(const std::string& name, const ShaderSources& sources);
const std::string& get_name() const { return m_name; }
unsigned int get_id() const { return m_id; }
void start_using() const;
void stop_using() const;
bool set_uniform(const char* name, int value) const;
bool set_uniform(const char* name, bool value) const;
bool set_uniform(const char* name, float value) const;
bool set_uniform(const char* name, double value) const;
bool set_uniform(const char* name, const std::array<int, 2>& value) const;
bool set_uniform(const char* name, const std::array<int, 3>& value) const;
bool set_uniform(const char* name, const std::array<int, 4>& value) const;
bool set_uniform(const char* name, const std::array<float, 2>& value) const;
bool set_uniform(const char* name, const std::array<float, 3>& value) const;
bool set_uniform(const char* name, const std::array<float, 4>& value) const;
bool set_uniform(const char* name, const float* value, size_t size) const;
bool set_uniform(const char* name, const Transform3f& value) const;
bool set_uniform(const char* name, const Transform3d& value) const;
bool set_uniform(const char* name, const Matrix3f& value) const;
bool set_uniform(const char* name, const Vec3f& value) const;
bool set_uniform(const char* name, const Vec3d& value) const;
// returns -1 if not found
int get_attrib_location(const char* name) const;
// returns -1 if not found
int get_uniform_location(const char* name) const;
};
}
} // namespace Slic3r
#endif /* slic3r_GLShader_hpp_ */

View file

@ -0,0 +1,75 @@
#include "libslic3r/libslic3r.h"
#include "GLShadersManager.hpp"
#include "3DScene.hpp"
#include "GUI_App.hpp"
#include <cassert>
#include <algorithm>
#include <GL/glew.h>
namespace Slic3r {
std::pair<bool, std::string> GLShadersManager::init()
{
std::string error;
auto append_shader = [this, &error](const std::string& name, const GLShaderProgram::ShaderFilenames& filenames) {
m_shaders.push_back(std::make_unique<GLShaderProgram>());
if (!m_shaders.back()->init_from_files(name, filenames)) {
error += name + "\n";
// if any error happens while initializating the shader, we remove it from the list
m_shaders.pop_back();
return false;
}
return true;
};
assert(m_shaders.empty());
bool valid = true;
// used to render bed axes and model, selection hints, gcode sequential view marker model, preview shells
valid &= append_shader("gouraud_light", { "gouraud_light.vs", "gouraud_light.fs" });
// used to render printbed
valid &= append_shader("printbed", { "printbed.vs", "printbed.fs" });
// used to render options in gcode preview
valid &= append_shader("options_110", { "options_110.vs", "options_110.fs" });
if (GUI::wxGetApp().is_glsl_version_greater_or_equal_to(1, 20))
valid &= append_shader("options_120", { "options_120.vs", "options_120.fs" });
// used to render extrusion and travel paths as lines in gcode preview
valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" });
// used to render objects in 3d editor
valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" });
// used to render variable layers heights in 3d editor
valid &= append_shader("variable_layer_height", { "variable_layer_height.vs", "variable_layer_height.fs" });
return { valid, error };
}
void GLShadersManager::shutdown()
{
for (std::unique_ptr<GLShaderProgram>& shader : m_shaders) {
shader.reset();
}
}
GLShaderProgram* GLShadersManager::get_shader(const std::string& shader_name)
{
auto it = std::find_if(m_shaders.begin(), m_shaders.end(), [shader_name](std::unique_ptr<GLShaderProgram>& p) { return p->get_name() == shader_name; });
return (it != m_shaders.end()) ? it->get() : nullptr;
}
GLShaderProgram* GLShadersManager::get_current_shader()
{
GLint id = 0;
glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, &id));
if (id == 0)
return nullptr;
auto it = std::find_if(m_shaders.begin(), m_shaders.end(), [id](std::unique_ptr<GLShaderProgram>& p) { return static_cast<GLint>(p->get_id()) == id; });
return (it != m_shaders.end()) ? it->get() : nullptr;
}
} // namespace Slic3r

View file

@ -0,0 +1,30 @@
#ifndef slic3r_GLShadersManager_hpp_
#define slic3r_GLShadersManager_hpp_
#include "GLShader.hpp"
#include <vector>
#include <string>
#include <memory>
namespace Slic3r {
class GLShadersManager
{
std::vector<std::unique_ptr<GLShaderProgram>> m_shaders;
public:
std::pair<bool, std::string> init();
// call this method before to release the OpenGL context
void shutdown();
// returns nullptr if not found
GLShaderProgram* get_shader(const std::string& shader_name);
// returns currently active shader, nullptr if none
GLShaderProgram* get_current_shader();
};
} // namespace Slic3r
#endif // slic3r_GLShadersManager_hpp_

View file

@ -230,24 +230,13 @@ void GLToolbar::set_icons_size(float size)
void GLToolbar::set_scale(float scale)
{
if (m_layout.scale != scale)
{
if (m_layout.scale != scale) {
m_layout.scale = scale;
m_layout.dirty = true;
m_icons_texture_dirty = true;
}
}
bool GLToolbar::is_enabled() const
{
return m_enabled;
}
void GLToolbar::set_enabled(bool enable)
{
m_enabled = enable;//true; etFIXME
}
bool GLToolbar::add_item(const GLToolbarItem::Data& data)
{
GLToolbarItem* item = new GLToolbarItem(GLToolbarItem::Action, data);

View file

@ -276,8 +276,8 @@ public:
void set_icons_size(float size);
void set_scale(float scale);
bool is_enabled() const;
void set_enabled(bool enable);
bool is_enabled() const { return m_enabled; }
void set_enabled(bool enable) { m_enabled = enable; }
bool add_item(const GLToolbarItem::Data& data);
bool add_separator();

View file

@ -255,7 +255,7 @@ void warning_catcher(wxWindow* parent, const wxString& message)
msg.ShowModal();
}
void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string items, bool initial_value)
void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items)
{
if (comboCtrl == nullptr)
return;
@ -266,41 +266,59 @@ void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string
// On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10.
comboCtrl->UseAltPopupWindow();
int max_width = 0;
// the following line messes up the popup size the first time it is shown on wxWidgets 3.1.3
// comboCtrl->EnablePopupAnimation(false);
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); });
wxString title = from_u8(text);
max_width = std::max(max_width, 60 + comboCtrl->GetTextExtent(title).x);
popup->SetStringValue(title);
popup->Bind(wxEVT_CHECKLISTBOX, [popup](wxCommandEvent& evt) { popup->OnCheckListBox(evt); });
popup->Bind(wxEVT_LISTBOX, [popup](wxCommandEvent& evt) { popup->OnListBoxSelection(evt); });
popup->Bind(wxEVT_KEY_DOWN, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); });
popup->Bind(wxEVT_KEY_UP, [popup](wxKeyEvent& evt) { popup->OnKeyEvent(evt); });
std::vector<std::string> items_str;
boost::split(items_str, items, boost::is_any_of("|"), boost::token_compress_off);
for (const std::string& item : items_str) {
popup->Append(from_u8(item));
}
// each item must be composed by 2 parts
assert(items_str.size() %2 == 0);
for (unsigned int i = 0; i < popup->GetCount(); ++i) {
popup->Check(i, initial_value);
}
}
for (size_t i = 0; i < items_str.size(); i += 2) {
wxString label = from_u8(items_str[i]);
max_width = std::max(max_width, 60 + popup->GetTextExtent(label).x);
popup->Append(label);
popup->Check(i / 2, items_str[i + 1] == "1");
}
comboCtrl->SetMinClientSize(wxSize(max_width, -1));
}
}
int combochecklist_get_flags(wxComboCtrl* comboCtrl)
unsigned int combochecklist_get_flags(wxComboCtrl* comboCtrl)
{
int flags = 0;
unsigned int flags = 0;
wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup);
if (popup != nullptr) {
for (unsigned int i = 0; i < popup->GetCount(); ++i) {
if (popup->IsChecked(i))
flags |= 1 << i;
}
}
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;
return flags;
}
void combochecklist_set_flags(wxComboCtrl* comboCtrl, unsigned int flags)
{
wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup);
if (popup != nullptr) {
for (unsigned int i = 0; i < popup->GetCount(); ++i) {
popup->Check(i, (flags & (1 << i)) != 0);
}
}
}
AppConfig* get_app_config()

View file

@ -49,13 +49,17 @@ inline void show_info(wxWindow* parent, const std::string& message,const std::st
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);
// Items data must be separated by '|', and contain the item name to be shown followed by its initial value (0 for false, 1 for true).
// For example "Item1|0|Item2|1|Item3|0", and so on.
void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items);
// Returns the current state of the items listed in the wxCheckListBoxComboPopup contained in the given wxComboCtrl,
// encoded inside an int.
int combochecklist_get_flags(wxComboCtrl* comboCtrl);
// encoded inside an unsigned int.
unsigned int combochecklist_get_flags(wxComboCtrl* comboCtrl);
// Sets the current state of the items listed in the wxCheckListBoxComboPopup contained in the given wxComboCtrl,
// with the flags encoded in the given unsigned int.
void combochecklist_set_flags(wxComboCtrl* comboCtrl, unsigned int flags);
// wxString conversions:

View file

@ -41,6 +41,7 @@
#include "3DScene.hpp"
#include "MainFrame.hpp"
#include "Plater.hpp"
#include "GLCanvas3D.hpp"
#include "../Utils/PresetUpdater.hpp"
#include "../Utils/PrintHost.hpp"
@ -791,6 +792,20 @@ void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const
dialog.GetPaths(input_files);
}
#if ENABLE_GCODE_VIEWER
void GUI_App::load_gcode(wxWindow* parent, wxString& input_file) const
{
input_file.Clear();
wxFileDialog dialog(parent ? parent : GetTopWindow(),
_(L("Choose one file (GCODE/.GCO/.G/.ngc/NGC):")),
app_config->get_last_dir(), "",
file_wildcards(FT_GCODE), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() == wxID_OK)
input_file = dialog.GetPath();
}
#endif // ENABLE_GCODE_VIEWER
bool GUI_App::switch_language()
{
if (select_language()) {
@ -1014,6 +1029,7 @@ void GUI_App::update_mode()
tab->update_mode();
plater()->update_object_menu();
plater()->canvas3D()->update_gizmos_on_off_state();
}
void GUI_App::add_config_menu(wxMenuBar *menu)

View file

@ -163,6 +163,10 @@ public:
void keyboard_shortcuts();
void load_project(wxWindow *parent, wxString& input_file) const;
void import_model(wxWindow *parent, wxArrayString& input_files) const;
#if ENABLE_GCODE_VIEWER
void load_gcode(wxWindow* parent, wxString& input_file) const;
#endif // ENABLE_GCODE_VIEWER
static bool catch_error(std::function<void()> cb, const std::string& err);
void persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false);
@ -237,6 +241,12 @@ public:
void gcode_thumbnails_debug();
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
GLShaderProgram* get_shader(const std::string& shader_name) { return m_opengl_mgr.get_shader(shader_name); }
GLShaderProgram* get_current_shader() { return m_opengl_mgr.get_current_shader(); }
bool is_gl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { return m_opengl_mgr.get_gl_info().is_version_greater_or_equal_to(major, minor); }
bool is_glsl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const { return m_opengl_mgr.get_gl_info().is_glsl_version_greater_or_equal_to(major, minor); }
private:
bool on_init_inner();
void init_app_config();
@ -255,6 +265,6 @@ private:
DECLARE_APP(GUI_App)
} // GUI
} //Slic3r
} // Slic3r
#endif // slic3r_GUI_App_hpp_

View file

@ -2217,7 +2217,7 @@ void ObjectList::load_shape_object(const std::string& type_name)
load_mesh_object(mesh, _(L("Shape")) + "-" + _(type_name));
}
void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name)
void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center)
{
// Add mesh to model as a new object
Model& model = wxGetApp().plater()->model();
@ -2227,6 +2227,7 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name
#endif /* _DEBUG */
std::vector<size_t> object_idxs;
auto bb = mesh.bounding_box();
ModelObject* new_object = model.add_object();
new_object->name = into_u8(name);
new_object->add_instance(); // each object should have at list one instance
@ -2236,13 +2237,17 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name
// set a default extruder value, since user can't add it manually
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
new_object->invalidate_bounding_box();
new_object->center_around_origin();
new_object->translate(-bb.center());
if (center) {
const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb();
new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -new_object->origin_translation(2)));
} else {
new_object->instances[0]->set_offset(bb.center());
}
new_object->ensure_on_bed();
const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb();
new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -new_object->origin_translation(2)));
object_idxs.push_back(model.objects.size() - 1);
#ifdef _DEBUG
check_model_ids_validity(model);

View file

@ -294,7 +294,7 @@ public:
void load_part(ModelObject* model_object, std::vector<std::pair<wxString, bool>> &volumes_info, ModelVolumeType type);
void load_generic_subobject(const std::string& type_name, const ModelVolumeType type);
void load_shape_object(const std::string &type_name);
void load_mesh_object(const TriangleMesh &mesh, const wxString &name);
void load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center = true);
void del_object(const int obj_idx);
void del_subobject_item(wxDataViewItem& item);
void del_settings_from_config(const wxDataViewItem& parent_item);

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,9 @@
#include "libslic3r/CustomGCode.hpp"
#include <string>
#if ENABLE_GCODE_VIEWER
#include "libslic3r/GCode/GCodeProcessor.hpp"
#endif // ENABLE_GCODE_VIEWER
class wxNotebook;
class wxGLCanvas;
@ -23,7 +25,9 @@ namespace Slic3r {
class DynamicPrintConfig;
class Print;
class BackgroundSlicingProcess;
#if !ENABLE_GCODE_VIEWER
class GCodePreviewData;
#endif // !ENABLE_GCODE_VIEWER
class Model;
namespace DoubleSlider {
@ -79,20 +83,35 @@ class Preview : public wxPanel
{
wxGLCanvas* m_canvas_widget;
GLCanvas3D* m_canvas;
#if ENABLE_GCODE_VIEWER
wxBoxSizer* m_left_sizer;
wxBoxSizer* m_layers_slider_sizer;
wxPanel* m_bottom_toolbar_panel;
#else
wxBoxSizer* m_double_slider_sizer;
#endif // ENABLE_GCODE_VIEWER
wxStaticText* m_label_view_type;
wxChoice* m_choice_view_type;
wxStaticText* m_label_show_features;
wxStaticText* m_label_show;
wxComboCtrl* m_combochecklist_features;
#if ENABLE_GCODE_VIEWER
size_t m_combochecklist_features_pos;
wxComboCtrl* m_combochecklist_options;
#else
wxCheckBox* m_checkbox_travel;
wxCheckBox* m_checkbox_retractions;
wxCheckBox* m_checkbox_unretractions;
wxCheckBox* m_checkbox_shells;
wxCheckBox* m_checkbox_legend;
#endif // ENABLE_GCODE_VIEWER
DynamicPrintConfig* m_config;
BackgroundSlicingProcess* m_process;
#if ENABLE_GCODE_VIEWER
GCodeProcessor::Result* m_gcode_result;
#else
GCodePreviewData* m_gcode_preview_data;
#endif // ENABLE_GCODE_VIEWER
#ifdef __linux__
// We are getting mysterious crashes on Linux in gtk due to OpenGL context activation GH #1874 #1955.
@ -107,13 +126,39 @@ class Preview : public wxPanel
std::string m_preferred_color_mode;
bool m_loaded;
#if !ENABLE_GCODE_VIEWER
bool m_enabled;
#endif // !ENABLE_GCODE_VIEWER
#if ENABLE_GCODE_VIEWER
DoubleSlider::Control* m_layers_slider{ nullptr };
DoubleSlider::Control* m_moves_slider{ nullptr };
#else
DoubleSlider::Control* m_slider {nullptr};
#endif // ENABLE_GCODE_VIEWER
public:
Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config,
#if ENABLE_GCODE_VIEWER
enum class OptionType : unsigned int
{
Travel,
Retractions,
Unretractions,
ToolChanges,
ColorChanges,
PausePrints,
CustomGCodes,
Shells,
ToolMarker,
Legend
};
Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config, BackgroundSlicingProcess* process,
GCodeProcessor::Result* gcode_result, std::function<void()> schedule_background_process = []() {});
#else
Preview(wxWindow* parent, Model* model, DynamicPrintConfig* config,
BackgroundSlicingProcess* process, GCodePreviewData* gcode_preview_data, std::function<void()> schedule_background_process = []() {});
#endif // ENABLE_GCODE_VIEWER
virtual ~Preview();
wxGLCanvas* get_wxglcanvas() { return m_canvas_widget; }
@ -122,7 +167,9 @@ public:
void set_as_dirty();
void set_number_extruders(unsigned int number_extruders);
#if !ENABLE_GCODE_VIEWER
void set_enabled(bool enabled);
#endif // !ENABLE_GCODE_VIEWER
void bed_shape_changed();
void select_view(const std::string& direction);
void set_drop_target(wxDropTarget* target);
@ -132,47 +179,83 @@ public:
void refresh_print();
void msw_rescale();
#if ENABLE_GCODE_VIEWER
void move_layers_slider(wxKeyEvent& evt);
void edit_layers_slider(wxKeyEvent& evt);
#else
void move_double_slider(wxKeyEvent& evt);
void edit_double_slider(wxKeyEvent& evt);
#endif // ENABLE_GCODE_VIEWER
void update_view_type(bool slice_completed);
void update_view_type(bool keep_volumes);
bool is_loaded() const { return m_loaded; }
#if ENABLE_GCODE_VIEWER
void update_bottom_toolbar();
void update_moves_slider();
#endif // ENABLE_GCODE_VIEWER
private:
bool init(wxWindow* parent, Model* model);
void bind_event_handlers();
void unbind_event_handlers();
#if ENABLE_GCODE_VIEWER
void hide_layers_slider();
#else
void show_hide_ui_elements(const std::string& what);
void reset_sliders(bool reset_all);
void update_sliders(const std::vector<double>& layers_z, bool keep_z_range = false);
#endif // ENABLE_GCODE_VIEWER
void on_size(wxSizeEvent& evt);
void on_choice_view_type(wxCommandEvent& evt);
void on_combochecklist_features(wxCommandEvent& evt);
#if ENABLE_GCODE_VIEWER
void on_combochecklist_options(wxCommandEvent& evt);
#else
void on_checkbox_travel(wxCommandEvent& evt);
void on_checkbox_retractions(wxCommandEvent& evt);
void on_checkbox_unretractions(wxCommandEvent& evt);
void on_checkbox_shells(wxCommandEvent& evt);
void on_checkbox_legend(wxCommandEvent& evt);
#endif // ENABLE_GCODE_VIEWER
#if ENABLE_GCODE_VIEWER
// Create/Update/Reset double slider on 3dPreview
wxBoxSizer* create_layers_slider_sizer();
void check_layers_slider_values(std::vector<CustomGCode::Item>& ticks_from_model,
const std::vector<double>& layers_z);
void reset_layers_slider();
void update_layers_slider(const std::vector<double>& layers_z, bool keep_z_range = false);
void update_layers_slider_mode();
// update vertical DoubleSlider after keyDown in canvas
void update_layers_slider_from_canvas(wxKeyEvent& event);
#else
// Create/Update/Reset double slider on 3dPreview
void create_double_slider();
void check_slider_values(std::vector<CustomGCode::Item> &ticks_from_model,
const std::vector<double> &layers_z);
void check_slider_values(std::vector<CustomGCode::Item>& ticks_from_model,
const std::vector<double>& layers_z);
void reset_double_slider();
void update_double_slider(const std::vector<double>& layers_z, bool keep_z_range = false);
void update_double_slider_mode();
// update DoubleSlider after keyDown in canvas
void update_double_slider_from_canvas(wxKeyEvent& event);
#endif // ENABLE_GCODE_VIEWER
void load_print_as_fff(bool keep_z_range = false);
void load_print_as_sla();
#if ENABLE_GCODE_VIEWER
void on_layers_slider_scroll_changed(wxCommandEvent& event);
void on_moves_slider_scroll_changed(wxCommandEvent& event);
wxString get_option_type_string(OptionType type) const;
#else
void on_sliders_scroll_changed(wxCommandEvent& event);
#endif // ENABLE_GCODE_VIEWER
};
} // namespace GUI

View file

@ -387,6 +387,15 @@ public:
std::ostream& operator<<(std::ostream &os, const WindowMetrics& metrics);
#if ENABLE_GCODE_VIEWER
inline int hex_digit_to_int(const char c)
{
return
(c >= '0' && c <= '9') ? int(c - '0') :
(c >= 'A' && c <= 'F') ? int(c - 'A') + 10 :
(c >= 'a' && c <= 'f') ? int(c - 'a') + 10 : -1;
}
#endif // ENABLE_GCODE_VIEWER
}}

View file

@ -124,7 +124,6 @@ public:
void set_state(EState state) { m_state = state; on_set_state(); }
int get_shortcut_key() const { return m_shortcut_key; }
void set_shortcut_key(int key) { m_shortcut_key = key; }
const std::string& get_icon_filename() const { return m_icon_filename; }

View file

@ -1,40 +1,40 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoFdmSupports.hpp"
#include "libslic3r/Model.hpp"
//#include "slic3r/GUI/3DScene.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/Plater.hpp"
#include <GL/glew.h>
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/Model.hpp"
namespace Slic3r {
namespace GUI {
GLGizmoFdmSupports::GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
, m_quadric(nullptr)
void GLGizmoFdmSupports::on_shutdown()
{
m_clipping_plane.reset(new ClippingPlane());
m_quadric = ::gluNewQuadric();
if (m_quadric != nullptr)
// using GLU_FILL does not work when the instance's transformation
// contains mirroring (normals are reverted)
::gluQuadricDrawStyle(m_quadric, GLU_FILL);
if (m_setting_angle) {
m_setting_angle = false;
m_parent.use_slope(false);
}
}
GLGizmoFdmSupports::~GLGizmoFdmSupports()
std::string GLGizmoFdmSupports::on_get_name() const
{
if (m_quadric != nullptr)
::gluDeleteQuadric(m_quadric);
return (_(L("FDM Support Editing")) + " [L]").ToUTF8().data();
}
bool GLGizmoFdmSupports::on_init()
{
m_shortcut_key = WXK_CONTROL_L;
@ -54,38 +54,6 @@ bool GLGizmoFdmSupports::on_init()
}
void GLGizmoFdmSupports::activate_internal_undo_redo_stack(bool activate)
{
if (activate && ! m_internal_stack_active) {
Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned on"));
wxGetApp().plater()->enter_gizmos_stack();
m_internal_stack_active = true;
}
if (! activate && m_internal_stack_active) {
wxGetApp().plater()->leave_gizmos_stack();
Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned off"));
m_internal_stack_active = false;
}
}
void GLGizmoFdmSupports::set_fdm_support_data(ModelObject* model_object, const Selection& selection)
{
if (m_state != On)
return;
const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr;
if (mo && selection.is_from_single_instance()
&& (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size))
{
update_from_model_object();
m_old_mo_id = mo->id();
m_old_volumes_size = mo->volumes.size();
m_schedule_update = false;
}
}
void GLGizmoFdmSupports::on_render() const
{
@ -94,7 +62,8 @@ void GLGizmoFdmSupports::on_render() const
glsafe(::glEnable(GL_BLEND));
glsafe(::glEnable(GL_DEPTH_TEST));
render_triangles(selection);
if (! m_setting_angle)
render_triangles(selection);
m_c->object_clipper()->render_cut();
render_cursor_circle();
@ -102,382 +71,6 @@ void GLGizmoFdmSupports::on_render() const
glsafe(::glDisable(GL_BLEND));
}
void GLGizmoFdmSupports::render_triangles(const Selection& selection) const
{
if (m_setting_angle)
return;
const ModelObject* mo = m_c->selection_info()->model_object();
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
ScopeGuard offset_fill_guard([]() { glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); } );
glsafe(::glPolygonOffset(-1.0, 1.0));
// Take care of the clipping plane. The normal of the clipping plane is
// saved with opposite sign than we need to pass to OpenGL (FIXME)
bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.;
if (clipping_plane_active) {
const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane();
double clp_data[4];
memcpy(clp_data, clp->get_data(), 4 * sizeof(double));
for (int i=0; i<3; ++i)
clp_data[i] = -1. * clp_data[i];
glsafe(::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)clp_data));
glsafe(::glEnable(GL_CLIP_PLANE0));
}
int mesh_id = -1;
for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++mesh_id;
const Transform3d trafo_matrix =
mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() *
mv->get_matrix();
bool is_left_handed = trafo_matrix.matrix().determinant() < 0.;
if (is_left_handed)
glsafe(::glFrontFace(GL_CW));
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(trafo_matrix.data()));
if (! m_setting_angle)
m_triangle_selectors[mesh_id]->render(m_imgui);
glsafe(::glPopMatrix());
if (is_left_handed)
glsafe(::glFrontFace(GL_CCW));
}
if (clipping_plane_active)
glsafe(::glDisable(GL_CLIP_PLANE0));
}
void GLGizmoFdmSupports::render_cursor_circle() const
{
const Camera& camera = wxGetApp().plater()->get_camera();
float zoom = (float)camera.get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
Size cnv_size = m_parent.get_canvas_size();
float cnv_half_width = 0.5f * (float)cnv_size.get_width();
float cnv_half_height = 0.5f * (float)cnv_size.get_height();
if ((cnv_half_width == 0.0f) || (cnv_half_height == 0.0f))
return;
Vec2d mouse_pos(m_parent.get_local_mouse_position()(0), m_parent.get_local_mouse_position()(1));
Vec2d center(mouse_pos(0) - cnv_half_width, cnv_half_height - mouse_pos(1));
center = center * inv_zoom;
glsafe(::glLineWidth(1.5f));
float color[3];
color[0] = 0.f;
color[1] = 1.f;
color[2] = 0.3f;
glsafe(::glColor3fv(color));
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glPushMatrix());
glsafe(::glLoadIdentity());
// ensure that the circle is renderered inside the frustrum
glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5)));
// ensure that the overlay fits the frustrum near z plane
double gui_scale = camera.get_gui_scale();
glsafe(::glScaled(gui_scale, gui_scale, 1.0));
glsafe(::glPushAttrib(GL_ENABLE_BIT));
glsafe(::glLineStipple(4, 0xAAAA));
glsafe(::glEnable(GL_LINE_STIPPLE));
::glBegin(GL_LINE_LOOP);
for (double angle=0; angle<2*M_PI; angle+=M_PI/20.)
::glVertex2f(GLfloat(center.x()+m_cursor_radius*cos(angle)), GLfloat(center.y()+m_cursor_radius*sin(angle)));
glsafe(::glEnd());
glsafe(::glPopAttrib());
glsafe(::glPopMatrix());
}
void GLGizmoFdmSupports::update_model_object() const
{
bool updated = false;
ModelObject* mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++idx;
updated |= mv->m_supported_facets.set(*m_triangle_selectors[idx].get());
}
if (updated)
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
void GLGizmoFdmSupports::update_from_model_object()
{
wxBusyCursor wait;
const ModelObject* mo = m_c->selection_info()->model_object();
m_triangle_selectors.clear();
int volume_id = -1;
for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++volume_id;
// This mesh does not account for the possible Z up SLA offset.
const TriangleMesh* mesh = &mv->mesh();
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh));
m_triangle_selectors.back()->deserialize(mv->m_supported_facets.get_data());
}
}
bool GLGizmoFdmSupports::is_mesh_point_clipped(const Vec3d& point) const
{
if (m_c->object_clipper()->get_position() == 0.)
return false;
auto sel_info = m_c->selection_info();
int active_inst = m_c->selection_info()->get_active_instance();
const ModelInstance* mi = sel_info->model_object()->instances[active_inst];
const Transform3d& trafo = mi->get_transformation().get_matrix();
Vec3d transformed_point = trafo * point;
transformed_point(2) += sel_info->get_sla_shift();
return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point);
}
// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is
// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo
// concludes that the event was not intended for it, it should return false.
bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down)
{
if (action == SLAGizmoEventType::MouseWheelUp
|| action == SLAGizmoEventType::MouseWheelDown) {
if (control_down) {
double pos = m_c->object_clipper()->get_position();
pos = action == SLAGizmoEventType::MouseWheelDown
? std::max(0., pos - 0.01)
: std::min(1., pos + 0.01);
m_c->object_clipper()->set_position(pos, true);
return true;
}
else if (alt_down) {
m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown
? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin)
: std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax);
m_parent.set_as_dirty();
return true;
}
}
if (action == SLAGizmoEventType::ResetClippingPlane) {
m_c->object_clipper()->set_position(-1., false);
return true;
}
if (action == SLAGizmoEventType::LeftDown
|| action == SLAGizmoEventType::RightDown
|| (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) {
if (m_triangle_selectors.empty())
return false;
FacetSupportType new_state = FacetSupportType::NONE;
if (! shift_down) {
if (action == SLAGizmoEventType::Dragging)
new_state = m_button_down == Button::Left
? FacetSupportType::ENFORCER
: FacetSupportType::BLOCKER;
else
new_state = action == SLAGizmoEventType::LeftDown
? FacetSupportType::ENFORCER
: FacetSupportType::BLOCKER;
}
const Camera& camera = wxGetApp().plater()->get_camera();
const Selection& selection = m_parent.get_selection();
const ModelObject* mo = m_c->selection_info()->model_object();
const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
const Transform3d& instance_trafo = mi->get_transformation().get_matrix();
std::vector<std::vector<std::pair<Vec3f, size_t>>> hit_positions_and_facet_ids;
bool clipped_mesh_was_hit = false;
Vec3f normal = Vec3f::Zero();
Vec3f hit = Vec3f::Zero();
size_t facet = 0;
Vec3f closest_hit = Vec3f::Zero();
double closest_hit_squared_distance = std::numeric_limits<double>::max();
size_t closest_facet = 0;
int closest_hit_mesh_id = -1;
// Transformations of individual meshes
std::vector<Transform3d> trafo_matrices;
int mesh_id = -1;
// Cast a ray on all meshes, pick the closest hit and save it for the respective mesh
for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++mesh_id;
trafo_matrices.push_back(instance_trafo * mv->get_matrix());
hit_positions_and_facet_ids.push_back(std::vector<std::pair<Vec3f, size_t>>());
if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(
mouse_position,
trafo_matrices[mesh_id],
camera,
hit,
normal,
m_clipping_plane.get(),
&facet))
{
// In case this hit is clipped, skip it.
if (is_mesh_point_clipped(hit.cast<double>())) {
clipped_mesh_was_hit = true;
continue;
}
// Is this hit the closest to the camera so far?
double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast<double>()).squaredNorm();
if (hit_squared_distance < closest_hit_squared_distance) {
closest_hit_squared_distance = hit_squared_distance;
closest_facet = facet;
closest_hit_mesh_id = mesh_id;
closest_hit = hit;
}
}
}
bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None);
// The mouse button click detection is enabled when there is a valid hit
// or when the user clicks the clipping plane. Missing the object entirely
// shall not capture the mouse.
if (closest_hit_mesh_id != -1 || clipped_mesh_was_hit) {
if (m_button_down == Button::None)
m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right);
}
if (closest_hit_mesh_id == -1) {
// In case we have no valid hit, we can return. The event will
// be stopped in following two cases:
// 1. clicking the clipping plane
// 2. dragging while painting (to prevent scene rotations and moving the object)
return clipped_mesh_was_hit
|| dragging_while_painting;
}
// Find respective mesh id.
// FIXME We need a separate TriangleSelector for each volume mesh.
mesh_id = -1;
//const TriangleMesh* mesh = nullptr;
for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++mesh_id;
if (mesh_id == closest_hit_mesh_id) {
//mesh = &mv->mesh();
break;
}
}
const Transform3d& trafo_matrix = trafo_matrices[mesh_id];
// Calculate how far can a point be from the line (in mesh coords).
// FIXME: The scaling of the mesh can be non-uniform.
const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor();
const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.;
const float limit = m_cursor_radius/avg_scaling;
// Calculate direction from camera to the hit (in mesh coords):
Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
Vec3f dir = (closest_hit - camera_pos).normalized();
assert(mesh_id < int(m_triangle_selectors.size()));
m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos,
dir, limit, new_state);
return true;
}
if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp)
&& m_button_down != Button::None) {
// Take snapshot and update ModelVolume data.
wxString action_name = shift_down
? _L("Remove selection")
: (m_button_down == Button::Left
? _L("Add supports")
: _L("Block supports"));
activate_internal_undo_redo_stack(true);
Plater::TakeSnapshot(wxGetApp().plater(), action_name);
update_model_object();
m_button_down = Button::None;
return true;
}
return false;
}
void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block)
{
float threshold = (M_PI/180.)*threshold_deg;
const Selection& selection = m_parent.get_selection();
const ModelObject* mo = m_c->selection_info()->model_object();
const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
int mesh_id = -1;
for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++mesh_id;
const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true);
Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast<float>().normalized();
Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast<float>().normalized();
float dot_limit = limit.dot(down);
// Now calculate dot product of vert_direction and facets' normals.
int idx = -1;
for (const stl_facet& facet : mv->mesh().stl.facet_start) {
++idx;
if (facet.normal.dot(down) > dot_limit)
m_triangle_selectors[mesh_id]->set_facet(idx,
block
? FacetSupportType::BLOCKER
: FacetSupportType::ENFORCER);
}
}
activate_internal_undo_redo_stack(true);
Plater::TakeSnapshot(wxGetApp().plater(), block ? _L("Block supports by angle")
: _L("Add supports by angle"));
update_model_object();
m_parent.set_as_dirty();
m_setting_angle = false;
}
void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_limit)
@ -512,8 +105,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
window_width = std::max(window_width, button_width);
auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f);
m_imgui->text_colored(ORANGE, caption);
m_imgui->text_colored(ImGuiWrapper::COL_ORANGE_LIGHT, caption);
ImGui::SameLine(caption_max);
m_imgui->text(text);
};
@ -539,6 +131,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
m_triangle_selectors[idx]->reset();
}
}
update_model_object();
m_parent.set_as_dirty();
}
@ -582,12 +175,6 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
}
m_imgui->end();
if (m_setting_angle) {
m_parent.show_slope(false);
m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg});
m_parent.use_slope(true);
m_parent.set_as_dirty();
}
}
else {
std::string name = "Autoset custom supports";
@ -612,248 +199,91 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
}
}
bool GLGizmoFdmSupports::on_is_activable() const
void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block)
{
float threshold = (M_PI/180.)*threshold_deg;
const Selection& selection = m_parent.get_selection();
const ModelObject* mo = m_c->selection_info()->model_object();
const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF
|| !selection.is_single_full_instance())
return false;
// Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside.
const Selection::IndicesList& list = selection.get_volume_idxs();
for (const auto& idx : list)
if (selection.get_volume(idx)->is_outside)
return false;
return true;
}
bool GLGizmoFdmSupports::on_is_selectable() const
{
return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF );
}
std::string GLGizmoFdmSupports::on_get_name() const
{
return (_(L("FDM Support Editing")) + " [L]").ToUTF8().data();
}
CommonGizmosDataID GLGizmoFdmSupports::on_get_requirements() const
{
return CommonGizmosDataID(
int(CommonGizmosDataID::SelectionInfo)
| int(CommonGizmosDataID::InstancesHider)
| int(CommonGizmosDataID::Raycaster)
| int(CommonGizmosDataID::ObjectClipper));
}
void GLGizmoFdmSupports::on_set_state()
{
if (m_state == m_old_state)
return;
if (m_state == On && m_old_state != On) { // the gizmo was just turned on
if (! m_parent.get_gizmos_manager().is_serializing()) {
wxGetApp().CallAfter([this]() {
activate_internal_undo_redo_stack(true);
});
}
}
if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
// we are actually shutting down
if (m_setting_angle) {
m_setting_angle = false;
m_parent.use_slope(false);
}
activate_internal_undo_redo_stack(false);
m_old_mo_id = -1;
//m_iva.release_geometry();
m_triangle_selectors.clear();
}
m_old_state = m_state;
}
void GLGizmoFdmSupports::on_start_dragging()
{
}
void GLGizmoFdmSupports::on_stop_dragging()
{
}
void GLGizmoFdmSupports::on_load(cereal::BinaryInputArchive&)
{
// We should update the gizmo from current ModelObject, but it is not
// possible at this point. That would require having updated selection and
// common gizmos data, which is not done at this point. Instead, save
// a flag to do the update in set_fdm_support_data, which will be called
// soon after.
m_schedule_update = true;
}
void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const
{
}
void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
{
int enf_cnt = 0;
int blc_cnt = 0;
m_iva_enforcers.release_geometry();
m_iva_blockers.release_geometry();
for (const Triangle& tr : m_triangles) {
if (! tr.valid || tr.is_split() || tr.get_state() == FacetSupportType::NONE)
int mesh_id = -1;
for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
GLIndexedVertexArray& va = tr.get_state() == FacetSupportType::ENFORCER
? m_iva_enforcers
: m_iva_blockers;
int& cnt = tr.get_state() == FacetSupportType::ENFORCER
? enf_cnt
: blc_cnt;
++mesh_id;
for (int i=0; i<3; ++i)
va.push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]),
double(m_vertices[tr.verts_idxs[i]].v[1]),
double(m_vertices[tr.verts_idxs[i]].v[2]),
0., 0., 1.);
va.push_triangle(cnt,
cnt+1,
cnt+2);
cnt += 3;
const Transform3d trafo_matrix = mi->get_matrix(true) * mv->get_matrix(true);
Vec3f down = (trafo_matrix.inverse() * (-Vec3d::UnitZ())).cast<float>().normalized();
Vec3f limit = (trafo_matrix.inverse() * Vec3d(std::sin(threshold), 0, -std::cos(threshold))).cast<float>().normalized();
float dot_limit = limit.dot(down);
// Now calculate dot product of vert_direction and facets' normals.
int idx = -1;
for (const stl_facet& facet : mv->mesh().stl.facet_start) {
++idx;
if (facet.normal.dot(down) > dot_limit)
m_triangle_selectors[mesh_id]->set_facet(idx,
block
? EnforcerBlockerType::BLOCKER
: EnforcerBlockerType::ENFORCER);
}
}
m_iva_enforcers.finalize_geometry(true);
m_iva_blockers.finalize_geometry(true);
activate_internal_undo_redo_stack(true);
if (m_iva_enforcers.has_VBOs()) {
::glColor4f(0.f, 0.f, 1.f, 0.2f);
m_iva_enforcers.render();
}
if (m_iva_blockers.has_VBOs()) {
::glColor4f(1.f, 0.f, 0.f, 0.2f);
m_iva_blockers.render();
}
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
if (imgui)
render_debug(imgui);
else
assert(false); // If you want debug output, pass ptr to ImGuiWrapper.
#endif
Plater::TakeSnapshot(wxGetApp().plater(), block ? _L("Block supports by angle")
: _L("Add supports by angle"));
update_model_object();
m_parent.set_as_dirty();
m_setting_angle = false;
}
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui)
void GLGizmoFdmSupports::update_model_object() const
{
imgui->begin(std::string("TriangleSelector dialog (DEV ONLY)"),
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
static float edge_limit = 1.f;
imgui->text("Edge limit (mm): ");
imgui->slider_float("", &edge_limit, 0.1f, 8.f);
set_edge_limit(edge_limit);
imgui->checkbox("Show split triangles: ", m_show_triangles);
imgui->checkbox("Show invalid triangles: ", m_show_invalid);
int valid_triangles = m_triangles.size() - m_invalid_triangles;
imgui->text("Valid triangles: " + std::to_string(valid_triangles) +
"/" + std::to_string(m_triangles.size()));
imgui->text("Vertices: " + std::to_string(m_vertices.size()));
if (imgui->button("Force garbage collection"))
garbage_collect();
if (imgui->button("Serialize - deserialize")) {
auto map = serialize();
deserialize(map);
bool updated = false;
ModelObject* mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++idx;
updated |= mv->m_supported_facets.set(*m_triangle_selectors[idx].get());
}
imgui->end();
if (! m_show_triangles)
return;
enum vtype {
ORIGINAL = 0,
SPLIT,
INVALID
};
for (auto& va : m_varrays)
va.release_geometry();
std::array<int, 3> cnts;
::glScalef(1.01f, 1.01f, 1.01f);
for (int tr_id=0; tr_id<int(m_triangles.size()); ++tr_id) {
const Triangle& tr = m_triangles[tr_id];
GLIndexedVertexArray* va = nullptr;
int* cnt = nullptr;
if (tr_id < m_orig_size_indices) {
va = &m_varrays[ORIGINAL];
cnt = &cnts[ORIGINAL];
}
else if (tr.valid) {
va = &m_varrays[SPLIT];
cnt = &cnts[SPLIT];
}
else {
if (! m_show_invalid)
continue;
va = &m_varrays[INVALID];
cnt = &cnts[INVALID];
}
for (int i=0; i<3; ++i)
va->push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]),
double(m_vertices[tr.verts_idxs[i]].v[1]),
double(m_vertices[tr.verts_idxs[i]].v[2]),
0., 0., 1.);
va->push_triangle(*cnt,
*cnt+1,
*cnt+2);
*cnt += 3;
}
::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
for (vtype i : {ORIGINAL, SPLIT, INVALID}) {
GLIndexedVertexArray& va = m_varrays[i];
va.finalize_geometry(true);
if (va.has_VBOs()) {
switch (i) {
case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break;
case SPLIT : ::glColor3f(1.f, 0.f, 0.f); break;
case INVALID : ::glColor3f(1.f, 1.f, 0.f); break;
}
va.render();
}
}
::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
if (updated)
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
#endif
void GLGizmoFdmSupports::update_from_model_object()
{
wxBusyCursor wait;
const ModelObject* mo = m_c->selection_info()->model_object();
m_triangle_selectors.clear();
int volume_id = -1;
for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++volume_id;
// This mesh does not account for the possible Z up SLA offset.
const TriangleMesh* mesh = &mv->mesh();
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh));
m_triangle_selectors.back()->deserialize(mv->m_supported_facets.get_data());
}
}
} // namespace GUI
} // namespace Slic3r

View file

@ -1,126 +1,46 @@
#ifndef slic3r_GLGizmoFdmSupports_hpp_
#define slic3r_GLGizmoFdmSupports_hpp_
#include "GLGizmoBase.hpp"
#include "slic3r/GUI/3DScene.hpp"
#include "libslic3r/ObjectID.hpp"
#include "libslic3r/TriangleSelector.hpp"
#include <cereal/types/vector.hpp>
#include "GLGizmoPainterBase.hpp"
namespace Slic3r {
enum class FacetSupportType : int8_t;
namespace GUI {
enum class SLAGizmoEventType : unsigned char;
class ClippingPlane;
class TriangleSelectorGUI : public TriangleSelector {
public:
explicit TriangleSelectorGUI(const TriangleMesh& mesh)
: TriangleSelector(mesh) {}
// Render current selection. Transformation matrices are supposed
// to be already set.
void render(ImGuiWrapper* imgui = nullptr);
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
void render_debug(ImGuiWrapper* imgui);
bool m_show_triangles{false};
bool m_show_invalid{false};
#endif
private:
GLIndexedVertexArray m_iva_enforcers;
GLIndexedVertexArray m_iva_blockers;
std::array<GLIndexedVertexArray, 3> m_varrays;
};
class GLGizmoFdmSupports : public GLGizmoBase
class GLGizmoFdmSupports : public GLGizmoPainterBase
{
private:
ObjectID m_old_mo_id;
size_t m_old_volumes_size = 0;
GLUquadricObj* m_quadric;
float m_cursor_radius = 2.f;
static constexpr float CursorRadiusMin = 0.4f; // cannot be zero
static constexpr float CursorRadiusMax = 8.f;
static constexpr float CursorRadiusStep = 0.2f;
// For each model-part volume, store status and division of the triangles.
std::vector<std::unique_ptr<TriangleSelectorGUI>> m_triangle_selectors;
public:
GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
~GLGizmoFdmSupports() override;
void set_fdm_support_data(ModelObject* model_object, const Selection& selection);
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoPainterBase(parent, icon_filename, sprite_id) {}
protected:
void on_render_input_window(float x, float y, float bottom_limit) override;
std::string on_get_name() const override;
private:
bool on_init() override;
void on_render() const override;
void on_render_for_picking() const override {}
void render_triangles(const Selection& selection) const;
void render_cursor_circle() const;
void update_model_object() const override;
void update_from_model_object() override;
void update_model_object() const;
void update_from_model_object();
void activate_internal_undo_redo_stack(bool activate);
void on_opening() override {}
void on_shutdown() override;
void select_facets_by_angle(float threshold, bool block);
float m_angle_threshold_deg = 45.f;
bool is_mesh_point_clipped(const Vec3d& point) const;
float m_clipping_plane_distance = 0.f;
std::unique_ptr<ClippingPlane> m_clipping_plane;
bool m_setting_angle = false;
bool m_internal_stack_active = false;
bool m_schedule_update = false;
// This map holds all translated description texts, so they can be easily referenced during layout calculations
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
std::map<std::string, wxString> m_desc;
enum class Button {
None,
Left,
Right
};
Button m_button_down = Button::None;
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
protected:
void on_set_state() override;
void on_start_dragging() override;
void on_stop_dragging() override;
void on_render_input_window(float x, float y, float bottom_limit) override;
std::string on_get_name() const override;
bool on_is_activable() const override;
bool on_is_selectable() const override;
void on_load(cereal::BinaryInputArchive& ar) override;
void on_save(cereal::BinaryOutputArchive& ar) const override;
CommonGizmosDataID on_get_requirements() const override;
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoFdmSupports_hpp_

View file

@ -130,7 +130,7 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons
const sla::DrainHole& drain_hole = drain_holes[i];
const bool& point_selected = m_selected[i];
if (is_mesh_point_clipped((drain_hole.pos+HoleStickOutLength*drain_hole.normal).cast<double>()))
if (is_mesh_point_clipped(drain_hole.pos.cast<double>()))
continue;
// First decide about the color of the point.
@ -174,10 +174,10 @@ void GLGizmoHollow::render_points(const Selection& selection, bool picking) cons
glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2)));
glsafe(::glPushMatrix());
glsafe(::glTranslated(0., 0., -drain_hole.height));
::gluCylinder(m_quadric, drain_hole.radius, drain_hole.radius, drain_hole.height, 24, 1);
glsafe(::glTranslated(0., 0., drain_hole.height));
::gluCylinder(m_quadric, drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength, 24, 1);
glsafe(::glTranslated(0., 0., drain_hole.height + sla::HoleStickOutLength));
::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1);
glsafe(::glTranslated(0., 0., -drain_hole.height));
glsafe(::glTranslated(0., 0., -drain_hole.height - sla::HoleStickOutLength));
glsafe(::glRotatef(180.f, 1.f, 0.f, 0.f));
::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1);
glsafe(::glPopMatrix());
@ -307,13 +307,8 @@ bool GLGizmoHollow::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_pos
if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add drainage hole")));
Vec3d scaling = mo->instances[active_inst]->get_scaling_factor();
Vec3f normal_transformed(pos_and_normal.second(0)/scaling(0),
pos_and_normal.second(1)/scaling(1),
pos_and_normal.second(2)/scaling(2));
mo->sla_drain_holes.emplace_back(pos_and_normal.first + HoleStickOutLength * pos_and_normal.second/* normal_transformed.normalized()*/,
-pos_and_normal.second, m_new_hole_radius, m_new_hole_height);
mo->sla_drain_holes.emplace_back(pos_and_normal.first,
-pos_and_normal.second, m_new_hole_radius, m_new_hole_height);
m_selected.push_back(false);
assert(m_selected.size() == mo->sla_drain_holes.size());
m_parent.set_as_dirty();
@ -447,7 +442,7 @@ void GLGizmoHollow::on_update(const UpdateData& data)
std::pair<Vec3f, Vec3f> pos_and_normal;
if (! unproject_on_mesh(data.mouse_pos.cast<double>(), pos_and_normal))
return;
drain_holes[m_hover_id].pos = pos_and_normal.first + HoleStickOutLength * pos_and_normal.second;
drain_holes[m_hover_id].pos = pos_and_normal.first;
drain_holes[m_hover_id].normal = -pos_and_normal.second;
}
}
@ -661,9 +656,7 @@ RENDER_AGAIN:
m_imgui->text(m_desc["hole_depth"]);
ImGui::SameLine(diameter_slider_left);
m_new_hole_height -= HoleStickOutLength;
ImGui::SliderFloat(" ", &m_new_hole_height, 0.f, 10.f, "%.1f mm");
m_new_hole_height += HoleStickOutLength;
clicked |= ImGui::IsItemClicked();
edited |= ImGui::IsItemEdited();

View file

@ -0,0 +1,597 @@
// Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro.
#include "GLGizmoPainterBase.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp"
#include <GL/glew.h>
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "libslic3r/Model.hpp"
namespace Slic3r {
namespace GUI {
GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
{
m_clipping_plane.reset(new ClippingPlane());
}
void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate)
{
if (activate && ! m_internal_stack_active) {
Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned on"));
wxGetApp().plater()->enter_gizmos_stack();
m_internal_stack_active = true;
}
if (! activate && m_internal_stack_active) {
wxGetApp().plater()->leave_gizmos_stack();
Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned off"));
m_internal_stack_active = false;
}
}
void GLGizmoPainterBase::set_painter_gizmo_data(const Selection& selection)
{
if (m_state != On)
return;
const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr;
if (mo && selection.is_from_single_instance()
&& (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size))
{
update_from_model_object();
m_old_mo_id = mo->id();
m_old_volumes_size = mo->volumes.size();
m_schedule_update = false;
}
}
void GLGizmoPainterBase::render_triangles(const Selection& selection) const
{
const ModelObject* mo = m_c->selection_info()->model_object();
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
ScopeGuard offset_fill_guard([]() { glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); } );
glsafe(::glPolygonOffset(-1.0, 1.0));
// Take care of the clipping plane. The normal of the clipping plane is
// saved with opposite sign than we need to pass to OpenGL (FIXME)
bool clipping_plane_active = m_c->object_clipper()->get_position() != 0.;
if (clipping_plane_active) {
const ClippingPlane* clp = m_c->object_clipper()->get_clipping_plane();
double clp_data[4];
memcpy(clp_data, clp->get_data(), 4 * sizeof(double));
for (int i=0; i<3; ++i)
clp_data[i] = -1. * clp_data[i];
glsafe(::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)clp_data));
glsafe(::glEnable(GL_CLIP_PLANE0));
}
int mesh_id = -1;
for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++mesh_id;
const Transform3d trafo_matrix =
mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() *
mv->get_matrix();
bool is_left_handed = trafo_matrix.matrix().determinant() < 0.;
if (is_left_handed)
glsafe(::glFrontFace(GL_CW));
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(trafo_matrix.data()));
m_triangle_selectors[mesh_id]->render(m_imgui);
glsafe(::glPopMatrix());
if (is_left_handed)
glsafe(::glFrontFace(GL_CCW));
}
if (clipping_plane_active)
glsafe(::glDisable(GL_CLIP_PLANE0));
}
void GLGizmoPainterBase::render_cursor_circle() const
{
const Camera& camera = wxGetApp().plater()->get_camera();
float zoom = (float)camera.get_zoom();
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
Size cnv_size = m_parent.get_canvas_size();
float cnv_half_width = 0.5f * (float)cnv_size.get_width();
float cnv_half_height = 0.5f * (float)cnv_size.get_height();
if ((cnv_half_width == 0.0f) || (cnv_half_height == 0.0f))
return;
Vec2d mouse_pos(m_parent.get_local_mouse_position()(0), m_parent.get_local_mouse_position()(1));
Vec2d center(mouse_pos(0) - cnv_half_width, cnv_half_height - mouse_pos(1));
center = center * inv_zoom;
glsafe(::glLineWidth(1.5f));
float color[3];
color[0] = 0.f;
color[1] = 1.f;
color[2] = 0.3f;
glsafe(::glColor3fv(color));
glsafe(::glDisable(GL_DEPTH_TEST));
glsafe(::glPushMatrix());
glsafe(::glLoadIdentity());
// ensure that the circle is renderered inside the frustrum
glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.5)));
// ensure that the overlay fits the frustrum near z plane
double gui_scale = camera.get_gui_scale();
glsafe(::glScaled(gui_scale, gui_scale, 1.0));
glsafe(::glPushAttrib(GL_ENABLE_BIT));
glsafe(::glLineStipple(4, 0xAAAA));
glsafe(::glEnable(GL_LINE_STIPPLE));
::glBegin(GL_LINE_LOOP);
for (double angle=0; angle<2*M_PI; angle+=M_PI/20.)
::glVertex2f(GLfloat(center.x()+m_cursor_radius*cos(angle)), GLfloat(center.y()+m_cursor_radius*sin(angle)));
glsafe(::glEnd());
glsafe(::glPopAttrib());
glsafe(::glPopMatrix());
}
bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point) const
{
if (m_c->object_clipper()->get_position() == 0.)
return false;
auto sel_info = m_c->selection_info();
int active_inst = m_c->selection_info()->get_active_instance();
const ModelInstance* mi = sel_info->model_object()->instances[active_inst];
const Transform3d& trafo = mi->get_transformation().get_matrix();
Vec3d transformed_point = trafo * point;
transformed_point(2) += sel_info->get_sla_shift();
return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point);
}
// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is
// aware that the event was reacted to and stops trying to make different sense of it. If the gizmo
// concludes that the event was not intended for it, it should return false.
bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down)
{
if (action == SLAGizmoEventType::MouseWheelUp
|| action == SLAGizmoEventType::MouseWheelDown) {
if (control_down) {
double pos = m_c->object_clipper()->get_position();
pos = action == SLAGizmoEventType::MouseWheelDown
? std::max(0., pos - 0.01)
: std::min(1., pos + 0.01);
m_c->object_clipper()->set_position(pos, true);
return true;
}
else if (alt_down) {
m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown
? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin)
: std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax);
m_parent.set_as_dirty();
return true;
}
}
if (action == SLAGizmoEventType::ResetClippingPlane) {
m_c->object_clipper()->set_position(-1., false);
return true;
}
if (action == SLAGizmoEventType::LeftDown
|| action == SLAGizmoEventType::RightDown
|| (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) {
if (m_triangle_selectors.empty())
return false;
EnforcerBlockerType new_state = EnforcerBlockerType::NONE;
if (! shift_down) {
if (action == SLAGizmoEventType::Dragging)
new_state = m_button_down == Button::Left
? EnforcerBlockerType::ENFORCER
: EnforcerBlockerType::BLOCKER;
else
new_state = action == SLAGizmoEventType::LeftDown
? EnforcerBlockerType::ENFORCER
: EnforcerBlockerType::BLOCKER;
}
const Camera& camera = wxGetApp().plater()->get_camera();
const Selection& selection = m_parent.get_selection();
const ModelObject* mo = m_c->selection_info()->model_object();
const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
const Transform3d& instance_trafo = mi->get_transformation().get_matrix();
// List of mouse positions that will be used as seeds for painting.
std::vector<Vec2d> mouse_positions{mouse_position};
// In case current mouse position is far from the last one,
// add several positions from between into the list, so there
// are no gaps in the painted region.
{
if (m_last_mouse_position == Vec2d::Zero())
m_last_mouse_position = mouse_position;
// resolution describes minimal distance limit using circle radius
// as a unit (e.g., 2 would mean the patches will be touching).
double resolution = 0.7;
double diameter_px = resolution * m_cursor_radius * camera.get_zoom();
int patches_in_between = int(((mouse_position - m_last_mouse_position).norm() - diameter_px) / diameter_px);
if (patches_in_between > 0) {
Vec2d diff = (mouse_position - m_last_mouse_position)/(patches_in_between+1);
for (int i=1; i<=patches_in_between; ++i)
mouse_positions.emplace_back(m_last_mouse_position + i*diff);
}
}
m_last_mouse_position = Vec2d::Zero(); // only actual hits should be saved
// Now "click" into all the prepared points and spill paint around them.
for (const Vec2d& mp : mouse_positions) {
std::vector<std::vector<std::pair<Vec3f, size_t>>> hit_positions_and_facet_ids;
bool clipped_mesh_was_hit = false;
Vec3f normal = Vec3f::Zero();
Vec3f hit = Vec3f::Zero();
size_t facet = 0;
Vec3f closest_hit = Vec3f::Zero();
double closest_hit_squared_distance = std::numeric_limits<double>::max();
size_t closest_facet = 0;
int closest_hit_mesh_id = -1;
// Transformations of individual meshes
std::vector<Transform3d> trafo_matrices;
int mesh_id = -1;
// Cast a ray on all meshes, pick the closest hit and save it for the respective mesh
for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++mesh_id;
trafo_matrices.push_back(instance_trafo * mv->get_matrix());
hit_positions_and_facet_ids.push_back(std::vector<std::pair<Vec3f, size_t>>());
if (m_c->raycaster()->raycasters()[mesh_id]->unproject_on_mesh(
mp,
trafo_matrices[mesh_id],
camera,
hit,
normal,
m_clipping_plane.get(),
&facet))
{
// In case this hit is clipped, skip it.
if (is_mesh_point_clipped(hit.cast<double>())) {
clipped_mesh_was_hit = true;
continue;
}
// Is this hit the closest to the camera so far?
double hit_squared_distance = (camera.get_position()-trafo_matrices[mesh_id]*hit.cast<double>()).squaredNorm();
if (hit_squared_distance < closest_hit_squared_distance) {
closest_hit_squared_distance = hit_squared_distance;
closest_facet = facet;
closest_hit_mesh_id = mesh_id;
closest_hit = hit;
}
}
}
bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None);
// The mouse button click detection is enabled when there is a valid hit
// or when the user clicks the clipping plane. Missing the object entirely
// shall not capture the mouse.
if (closest_hit_mesh_id != -1 || clipped_mesh_was_hit) {
if (m_button_down == Button::None)
m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right);
}
if (closest_hit_mesh_id == -1) {
// In case we have no valid hit, we can return. The event will
// be stopped in following two cases:
// 1. clicking the clipping plane
// 2. dragging while painting (to prevent scene rotations and moving the object)
return clipped_mesh_was_hit
|| dragging_while_painting;
}
// Find respective mesh id.
mesh_id = -1;
for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++mesh_id;
if (mesh_id == closest_hit_mesh_id)
break;
}
const Transform3d& trafo_matrix = trafo_matrices[mesh_id];
// Calculate how far can a point be from the line (in mesh coords).
// FIXME: The scaling of the mesh can be non-uniform.
const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor();
const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.;
const float limit = m_cursor_radius/avg_scaling;
// Calculate direction from camera to the hit (in mesh coords):
Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
Vec3f dir = (closest_hit - camera_pos).normalized();
assert(mesh_id < int(m_triangle_selectors.size()));
m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos,
dir, limit, new_state);
m_last_mouse_position = mouse_position;
}
return true;
}
if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp)
&& m_button_down != Button::None) {
// Take snapshot and update ModelVolume data.
wxString action_name = shift_down
? _L("Remove selection")
: (m_button_down == Button::Left
? _L("Add supports")
: _L("Block supports"));
activate_internal_undo_redo_stack(true);
Plater::TakeSnapshot(wxGetApp().plater(), action_name);
update_model_object();
m_button_down = Button::None;
m_last_mouse_position = Vec2d::Zero();
return true;
}
return false;
}
bool GLGizmoPainterBase::on_is_activable() const
{
const Selection& selection = m_parent.get_selection();
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptFFF
|| !selection.is_single_full_instance())
return false;
// Check that none of the selected volumes is outside. Only SLA auxiliaries (supports) are allowed outside.
const Selection::IndicesList& list = selection.get_volume_idxs();
for (const auto& idx : list)
if (selection.get_volume(idx)->is_outside)
return false;
return true;
}
bool GLGizmoPainterBase::on_is_selectable() const
{
return (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptFFF
&& wxGetApp().get_mode() != comSimple );
}
CommonGizmosDataID GLGizmoPainterBase::on_get_requirements() const
{
return CommonGizmosDataID(
int(CommonGizmosDataID::SelectionInfo)
| int(CommonGizmosDataID::InstancesHider)
| int(CommonGizmosDataID::Raycaster)
| int(CommonGizmosDataID::ObjectClipper));
}
void GLGizmoPainterBase::on_set_state()
{
if (m_state == m_old_state)
return;
if (m_state == On && m_old_state != On) { // the gizmo was just turned on
on_opening();
if (! m_parent.get_gizmos_manager().is_serializing()) {
wxGetApp().CallAfter([this]() {
activate_internal_undo_redo_stack(true);
});
}
}
if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
// we are actually shutting down
on_shutdown();
activate_internal_undo_redo_stack(false);
m_old_mo_id = -1;
//m_iva.release_geometry();
m_triangle_selectors.clear();
}
m_old_state = m_state;
}
void GLGizmoPainterBase::on_load(cereal::BinaryInputArchive&)
{
// We should update the gizmo from current ModelObject, but it is not
// possible at this point. That would require having updated selection and
// common gizmos data, which is not done at this point. Instead, save
// a flag to do the update in set_painter_gizmo_data, which will be called
// soon after.
m_schedule_update = true;
}
void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
{
int enf_cnt = 0;
int blc_cnt = 0;
m_iva_enforcers.release_geometry();
m_iva_blockers.release_geometry();
for (const Triangle& tr : m_triangles) {
if (! tr.valid || tr.is_split() || tr.get_state() == EnforcerBlockerType::NONE)
continue;
GLIndexedVertexArray& va = tr.get_state() == EnforcerBlockerType::ENFORCER
? m_iva_enforcers
: m_iva_blockers;
int& cnt = tr.get_state() == EnforcerBlockerType::ENFORCER
? enf_cnt
: blc_cnt;
for (int i=0; i<3; ++i)
va.push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]),
double(m_vertices[tr.verts_idxs[i]].v[1]),
double(m_vertices[tr.verts_idxs[i]].v[2]),
0., 0., 1.);
va.push_triangle(cnt,
cnt+1,
cnt+2);
cnt += 3;
}
m_iva_enforcers.finalize_geometry(true);
m_iva_blockers.finalize_geometry(true);
if (m_iva_enforcers.has_VBOs()) {
::glColor4f(0.f, 0.f, 1.f, 0.2f);
m_iva_enforcers.render();
}
if (m_iva_blockers.has_VBOs()) {
::glColor4f(1.f, 0.f, 0.f, 0.2f);
m_iva_blockers.render();
}
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
if (imgui)
render_debug(imgui);
else
assert(false); // If you want debug output, pass ptr to ImGuiWrapper.
#endif
}
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui)
{
imgui->begin(std::string("TriangleSelector dialog (DEV ONLY)"),
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
static float edge_limit = 1.f;
imgui->text("Edge limit (mm): ");
imgui->slider_float("", &edge_limit, 0.1f, 8.f);
set_edge_limit(edge_limit);
imgui->checkbox("Show split triangles: ", m_show_triangles);
imgui->checkbox("Show invalid triangles: ", m_show_invalid);
int valid_triangles = m_triangles.size() - m_invalid_triangles;
imgui->text("Valid triangles: " + std::to_string(valid_triangles) +
"/" + std::to_string(m_triangles.size()));
imgui->text("Vertices: " + std::to_string(m_vertices.size()));
if (imgui->button("Force garbage collection"))
garbage_collect();
if (imgui->button("Serialize - deserialize")) {
auto map = serialize();
deserialize(map);
}
imgui->end();
if (! m_show_triangles)
return;
enum vtype {
ORIGINAL = 0,
SPLIT,
INVALID
};
for (auto& va : m_varrays)
va.release_geometry();
std::array<int, 3> cnts;
::glScalef(1.01f, 1.01f, 1.01f);
for (int tr_id=0; tr_id<int(m_triangles.size()); ++tr_id) {
const Triangle& tr = m_triangles[tr_id];
GLIndexedVertexArray* va = nullptr;
int* cnt = nullptr;
if (tr_id < m_orig_size_indices) {
va = &m_varrays[ORIGINAL];
cnt = &cnts[ORIGINAL];
}
else if (tr.valid) {
va = &m_varrays[SPLIT];
cnt = &cnts[SPLIT];
}
else {
if (! m_show_invalid)
continue;
va = &m_varrays[INVALID];
cnt = &cnts[INVALID];
}
for (int i=0; i<3; ++i)
va->push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]),
double(m_vertices[tr.verts_idxs[i]].v[1]),
double(m_vertices[tr.verts_idxs[i]].v[2]),
0., 0., 1.);
va->push_triangle(*cnt,
*cnt+1,
*cnt+2);
*cnt += 3;
}
::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
for (vtype i : {ORIGINAL, SPLIT, INVALID}) {
GLIndexedVertexArray& va = m_varrays[i];
va.finalize_geometry(true);
if (va.has_VBOs()) {
switch (i) {
case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break;
case SPLIT : ::glColor3f(1.f, 0.f, 0.f); break;
case INVALID : ::glColor3f(1.f, 1.f, 0.f); break;
}
va.render();
}
}
::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
}
#endif
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,118 @@
#ifndef slic3r_GLGizmoPainterBase_hpp_
#define slic3r_GLGizmoPainterBase_hpp_
#include "GLGizmoBase.hpp"
#include "slic3r/GUI/3DScene.hpp"
#include "libslic3r/ObjectID.hpp"
#include "libslic3r/TriangleSelector.hpp"
#include <cereal/types/vector.hpp>
namespace Slic3r {
enum class EnforcerBlockerType : int8_t;
namespace GUI {
enum class SLAGizmoEventType : unsigned char;
class ClippingPlane;
class TriangleSelectorGUI : public TriangleSelector {
public:
explicit TriangleSelectorGUI(const TriangleMesh& mesh)
: TriangleSelector(mesh) {}
// Render current selection. Transformation matrices are supposed
// to be already set.
void render(ImGuiWrapper* imgui = nullptr);
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
void render_debug(ImGuiWrapper* imgui);
bool m_show_triangles{false};
bool m_show_invalid{false};
#endif
private:
GLIndexedVertexArray m_iva_enforcers;
GLIndexedVertexArray m_iva_blockers;
std::array<GLIndexedVertexArray, 3> m_varrays;
};
// Following class is a base class for a gizmo with ability to paint on mesh
// using circular blush (such as FDM supports gizmo and seam painting gizmo).
// The purpose is not to duplicate code related to mesh painting.
class GLGizmoPainterBase : public GLGizmoBase
{
private:
ObjectID m_old_mo_id;
size_t m_old_volumes_size = 0;
public:
GLGizmoPainterBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
~GLGizmoPainterBase() override {}
void set_painter_gizmo_data(const Selection& selection);
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
protected:
void render_triangles(const Selection& selection) const;
void render_cursor_circle() const;
virtual void update_model_object() const = 0;
virtual void update_from_model_object() = 0;
void activate_internal_undo_redo_stack(bool activate);
float m_cursor_radius = 2.f;
static constexpr float CursorRadiusMin = 0.4f; // cannot be zero
static constexpr float CursorRadiusMax = 8.f;
static constexpr float CursorRadiusStep = 0.2f;
// For each model-part volume, store status and division of the triangles.
std::vector<std::unique_ptr<TriangleSelectorGUI>> m_triangle_selectors;
private:
bool is_mesh_point_clipped(const Vec3d& point) const;
float m_clipping_plane_distance = 0.f;
std::unique_ptr<ClippingPlane> m_clipping_plane;
bool m_internal_stack_active = false;
bool m_schedule_update = false;
Vec2d m_last_mouse_position = Vec2d::Zero();
enum class Button {
None,
Left,
Right
};
Button m_button_down = Button::None;
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
protected:
void on_set_state() override;
void on_start_dragging() override {}
void on_stop_dragging() override {}
virtual void on_opening() = 0;
virtual void on_shutdown() = 0;
bool on_is_activable() const override;
bool on_is_selectable() const override;
void on_load(cereal::BinaryInputArchive& ar) override;
void on_save(cereal::BinaryOutputArchive& ar) const override {}
CommonGizmosDataID on_get_requirements() const override;
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoPainterBase_hpp_

View file

@ -0,0 +1,208 @@
#include "GLGizmoSeam.hpp"
#include "libslic3r/Model.hpp"
//#include "slic3r/GUI/3DScene.hpp"
#include "slic3r/GUI/GLCanvas3D.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/Plater.hpp"
#include <GL/glew.h>
namespace Slic3r {
namespace GUI {
bool GLGizmoSeam::on_init()
{
m_shortcut_key = WXK_CONTROL_P;
m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
m_desc["reset_direction"] = _L("Reset direction");
m_desc["cursor_size"] = _L("Cursor size") + ": ";
m_desc["enforce_caption"] = _L("Left mouse button") + ": ";
m_desc["enforce"] = _L("Enforce seam");
m_desc["block_caption"] = _L("Right mouse button") + " ";
m_desc["block"] = _L("Block seam");
m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
m_desc["remove"] = _L("Remove selection");
m_desc["remove_all"] = _L("Remove all selection");
return true;
}
std::string GLGizmoSeam::on_get_name() const
{
return (_(L("Seam Editing")) + " [P]").ToUTF8().data();
}
void GLGizmoSeam::on_render() const
{
const Selection& selection = m_parent.get_selection();
glsafe(::glEnable(GL_BLEND));
glsafe(::glEnable(GL_DEPTH_TEST));
render_triangles(selection);
m_c->object_clipper()->render_cut();
render_cursor_circle();
glsafe(::glDisable(GL_BLEND));
}
void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
{
if (! m_c->selection_info()->model_object())
return;
const float approx_height = m_imgui->scaled(18.0f);
y = std::min(y, bottom_limit - approx_height);
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
// First calculate width of all the texts that are could possibly be shown. We will decide set the dialog width based on that:
const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x, m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
const float minimal_slider_width = m_imgui->scaled(4.f);
float caption_max = 0.f;
float total_text_max = 0.;
for (const std::string& t : {"enforce", "block", "remove"}) {
caption_max = std::max(caption_max, m_imgui->calc_text_size(m_desc.at(t+"_caption")).x);
total_text_max = std::max(total_text_max, caption_max + m_imgui->calc_text_size(m_desc.at(t)).x);
}
caption_max += m_imgui->scaled(1.f);
total_text_max += m_imgui->scaled(1.f);
float window_width = minimal_slider_width + std::max(cursor_slider_left, clipping_slider_left);
window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, button_width);
auto draw_text_with_caption = [this, &caption_max](const wxString& caption, const wxString& text) {
static const ImVec4 ORANGE(1.0f, 0.49f, 0.22f, 1.0f);
m_imgui->text_colored(ORANGE, caption);
ImGui::SameLine(caption_max);
m_imgui->text(text);
};
for (const std::string& t : {"enforce", "block", "remove"})
draw_text_with_caption(m_desc.at(t + "_caption"), m_desc.at(t));
m_imgui->text("");
if (m_imgui->button(m_desc.at("remove_all"))) {
Plater::TakeSnapshot(wxGetApp().plater(), wxString(_L("Reset selection")));
ModelObject* mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume* mv : mo->volumes) {
if (mv->is_model_part()) {
++idx;
m_triangle_selectors[idx]->reset();
}
}
update_model_object();
m_parent.set_as_dirty();
}
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(clipping_slider_left);
ImGui::PushItemWidth(window_width - clipping_slider_left);
ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::Separator();
if (m_c->object_clipper()->get_position() == 0.f)
m_imgui->text(m_desc.at("clipping_of_view"));
else {
if (m_imgui->button(m_desc.at("reset_direction"))) {
wxGetApp().CallAfter([this](){
m_c->object_clipper()->set_position(-1., false);
});
}
}
ImGui::SameLine(clipping_slider_left);
ImGui::PushItemWidth(window_width - clipping_slider_left);
float clp_dist = m_c->object_clipper()->get_position();
if (ImGui::SliderFloat(" ", &clp_dist, 0.f, 1.f, "%.2f"))
m_c->object_clipper()->set_position(clp_dist, true);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Ctrl + Mouse wheel").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
m_imgui->end();
}
void GLGizmoSeam::update_model_object() const
{
bool updated = false;
ModelObject* mo = m_c->selection_info()->model_object();
int idx = -1;
for (ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++idx;
updated |= mv->m_seam_facets.set(*m_triangle_selectors[idx].get());
}
if (updated)
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
}
void GLGizmoSeam::update_from_model_object()
{
wxBusyCursor wait;
const ModelObject* mo = m_c->selection_info()->model_object();
m_triangle_selectors.clear();
int volume_id = -1;
for (const ModelVolume* mv : mo->volumes) {
if (! mv->is_model_part())
continue;
++volume_id;
// This mesh does not account for the possible Z up SLA offset.
const TriangleMesh* mesh = &mv->mesh();
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh));
m_triangle_selectors.back()->deserialize(mv->m_seam_facets.get_data());
}
}
} // namespace GUI
} // namespace Slic3r

View file

@ -0,0 +1,42 @@
#ifndef slic3r_GLGizmoSeam_hpp_
#define slic3r_GLGizmoSeam_hpp_
#include "GLGizmoPainterBase.hpp"
namespace Slic3r {
namespace GUI {
class GLGizmoSeam : public GLGizmoPainterBase
{
public:
GLGizmoSeam(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoPainterBase(parent, icon_filename, sprite_id) {}
protected:
void on_render_input_window(float x, float y, float bottom_limit) override;
std::string on_get_name() const override;
private:
bool on_init() override;
void on_render() const override;
void on_render_for_picking() const override {}
void update_model_object() const override;
void update_from_model_object() override;
void on_opening() override {}
void on_shutdown() override {}
// This map holds all translated description texts, so they can be easily referenced during layout calculations
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
std::map<std::string, wxString> m_desc;
};
} // namespace GUI
} // namespace Slic3r
#endif // slic3r_GLGizmoSeam_hpp_

View file

@ -222,7 +222,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
render_color[3] = 0.7f;
glsafe(::glColor4fv(render_color));
for (const sla::DrainHole& drain_hole : m_c->selection_info()->model_object()->sla_drain_holes) {
if (is_mesh_point_clipped((drain_hole.pos+HoleStickOutLength*drain_hole.normal).cast<double>()))
if (is_mesh_point_clipped(drain_hole.pos.cast<double>()))
continue;
// Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
@ -241,10 +241,10 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2)));
glsafe(::glPushMatrix());
glsafe(::glTranslated(0., 0., -drain_hole.height));
::gluCylinder(m_quadric, drain_hole.radius, drain_hole.radius, drain_hole.height, 24, 1);
glsafe(::glTranslated(0., 0., drain_hole.height));
::gluCylinder(m_quadric, drain_hole.radius, drain_hole.radius, drain_hole.height + sla::HoleStickOutLength, 24, 1);
glsafe(::glTranslated(0., 0., drain_hole.height + sla::HoleStickOutLength));
::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1);
glsafe(::glTranslated(0., 0., -drain_hole.height));
glsafe(::glTranslated(0., 0., -drain_hole.height - sla::HoleStickOutLength));
glsafe(::glRotatef(180.f, 1.f, 0.f, 0.f));
::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1);
glsafe(::glPopMatrix());

View file

@ -15,8 +15,6 @@ namespace GUI {
class GLCanvas3D;
static constexpr float HoleStickOutLength = 1.f;
enum class SLAGizmoEventType : unsigned char {
LeftDown = 1,
LeftUp,

View file

@ -16,6 +16,7 @@
#include "slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoSeam.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp"
@ -104,6 +105,7 @@ bool GLGizmosManager::init()
m_gizmos.emplace_back(new GLGizmoHollow(m_parent, "hollow.svg", 5));
m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 6));
m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "sla_supports.svg", 7));
m_gizmos.emplace_back(new GLGizmoSeam(m_parent, "seam.svg", 8));
m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent));
@ -144,8 +146,11 @@ void GLGizmosManager::refresh_on_off_state()
if (m_serializing || m_current == Undefined || m_gizmos.empty())
return;
if (m_current != Undefined && ! m_gizmos[m_current]->is_activable())
if (m_current != Undefined
&& (! m_gizmos[m_current]->is_activable() || ! m_gizmos[m_current]->is_selectable())) {
activate_gizmo(Undefined);
update_data();
}
}
void GLGizmosManager::reset_all_states()
@ -204,9 +209,10 @@ void GLGizmosManager::update_data()
enable_grabber(Scale, i, enable_scale_xyz);
}
m_common_gizmos_data->update(get_current()
? get_current()->get_requirements()
: CommonGizmosDataID(0));
if (m_common_gizmos_data)
m_common_gizmos_data->update(get_current()
? get_current()->get_requirements()
: CommonGizmosDataID(0));
if (selection.is_single_full_instance())
{
@ -217,7 +223,7 @@ void GLGizmosManager::update_data()
ModelObject* model_object = selection.get_model()->objects[selection.get_object_idx()];
set_flattening_data(model_object);
set_sla_support_data(model_object);
set_fdm_support_data(model_object);
set_painter_gizmo_data();
}
else if (selection.is_single_volume() || selection.is_single_modifier())
{
@ -226,7 +232,7 @@ void GLGizmosManager::update_data()
set_rotation(Vec3d::Zero());
set_flattening_data(nullptr);
set_sla_support_data(nullptr);
set_fdm_support_data(nullptr);
set_painter_gizmo_data();
}
else if (is_wipe_tower)
{
@ -235,7 +241,7 @@ void GLGizmosManager::update_data()
set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast<const ConfigOptionFloat*>(config.option("wipe_tower_rotation_angle"))->value));
set_flattening_data(nullptr);
set_sla_support_data(nullptr);
set_fdm_support_data(nullptr);
set_painter_gizmo_data();
}
else
{
@ -243,7 +249,7 @@ void GLGizmosManager::update_data()
set_rotation(Vec3d::Zero());
set_flattening_data(selection.is_from_single_object() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr);
set_sla_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr);
set_fdm_support_data(selection.is_from_single_instance() ? selection.get_model()->objects[selection.get_object_idx()] : nullptr);
set_painter_gizmo_data();
}
}
@ -378,12 +384,13 @@ void GLGizmosManager::set_sla_support_data(ModelObject* model_object)
gizmo_supports->set_sla_support_data(model_object, m_parent.get_selection());
}
void GLGizmosManager::set_fdm_support_data(ModelObject* model_object)
void GLGizmosManager::set_painter_gizmo_data()
{
if (!m_enabled || m_gizmos.empty())
return;
dynamic_cast<GLGizmoFdmSupports*>(m_gizmos[FdmSupports].get())->set_fdm_support_data(model_object, m_parent.get_selection());
dynamic_cast<GLGizmoFdmSupports*>(m_gizmos[FdmSupports].get())->set_painter_gizmo_data(m_parent.get_selection());
dynamic_cast<GLGizmoSeam*>(m_gizmos[Seam].get())->set_painter_gizmo_data(m_parent.get_selection());
}
// Returns true if the gizmo used the event to do something, false otherwise.
@ -398,6 +405,8 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
return dynamic_cast<GLGizmoHollow*>(m_gizmos[Hollow].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == FdmSupports)
return dynamic_cast<GLGizmoFdmSupports*>(m_gizmos[FdmSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else if (m_current == Seam)
return dynamic_cast<GLGizmoSeam*>(m_gizmos[Seam].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
else
return false;
}
@ -461,7 +470,7 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt)
{
bool processed = false;
if (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports) {
if (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam) {
float rot = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta();
if (gizmo_event((rot > 0.f ? SLAGizmoEventType::MouseWheelUp : SLAGizmoEventType::MouseWheelDown), Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
processed = true;
@ -603,7 +612,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
if (evt.LeftDown())
{
if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports)
if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports ||m_current == Seam)
&& gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
// the gizmo got the event and took some action, there is no need to do anything more
processed = true;
@ -630,23 +639,24 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
// event was taken care of by the SlaSupports gizmo
processed = true;
}
else if (evt.RightDown() && (selected_object_idx != -1) && m_current == FdmSupports
else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == FdmSupports || m_current == Seam)
&& gizmo_event(SLAGizmoEventType::RightDown, mouse_pos))
{
// event was taken care of by the FdmSupports gizmo
// event was taken care of by the FdmSupports / Seam gizmo
processed = true;
}
else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports || m_current == Hollow))
else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1)
&& (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam))
// don't allow dragging objects with the Sla gizmo on
processed = true;
else if (evt.Dragging() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports )
else if (evt.Dragging() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam )
&& gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
{
// the gizmo got the event and took some action, no need to do anything more here
m_parent.set_as_dirty();
processed = true;
}
else if (evt.LeftUp() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports) && !m_parent.is_mouse_dragging())
else if (evt.LeftUp() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam) && !m_parent.is_mouse_dragging())
{
// in case SLA/FDM gizmo is selected, we just pass the LeftUp event and stop processing - neither
// object moving or selecting is suppressed in that case
@ -658,7 +668,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
// to avoid to loose the selection when user clicks an the white faces of a different object while the Flatten gizmo is active
processed = true;
}
else if (evt.RightUp() && m_current == FdmSupports && !m_parent.is_mouse_dragging())
else if (evt.RightUp() && (m_current == FdmSupports || m_current == Seam) && !m_parent.is_mouse_dragging())
{
gizmo_event(SLAGizmoEventType::RightUp, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown());
processed = true;
@ -748,7 +758,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
case 'r' :
case 'R' :
{
if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports) && gizmo_event(SLAGizmoEventType::ResetClippingPlane))
if ((m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports || m_current == Seam) && gizmo_event(SLAGizmoEventType::ResetClippingPlane))
processed = true;
break;

View file

@ -67,6 +67,7 @@ public:
Hollow,
SlaSupports,
FdmSupports,
Seam,
Undefined
};
@ -203,7 +204,7 @@ public:
void set_sla_support_data(ModelObject* model_object);
void set_fdm_support_data(ModelObject* model_object);
void set_painter_gizmo_data();
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false, bool alt_down = false, bool control_down = false);
ClippingPlane get_clipping_plane() const;

View file

@ -37,19 +37,30 @@ namespace GUI {
static const std::map<const char, std::string> font_icons = {
{ImGui::PrintIconMarker , "cog" },
{ImGui::PrinterIconMarker , "printer" },
{ImGui::PrinterSlaIconMarker, "sla_printer" },
{ImGui::FilamentIconMarker , "spool" },
{ImGui::MaterialIconMarker , "resin" },
{ImGui::CloseIconMarker , "cross" },
{ImGui::CloseIconHoverMarker, "cross_focus_large" },
{ImGui::TimerDotMarker , "timer_dot" },
{ImGui::TimerDotEmptyMarker , "timer_dot_empty" },
{ImGui::WarningMarker , "flag_green" },
{ImGui::ErrorMarker , "flag_red" }
{ImGui::PrintIconMarker , "cog" },
{ImGui::PrinterIconMarker , "printer" },
{ImGui::PrinterSlaIconMarker , "sla_printer" },
{ImGui::FilamentIconMarker , "spool" },
{ImGui::MaterialIconMarker , "resin" },
{ImGui::CloseIconMarker , "notification_close" },
{ImGui::CloseIconHoverMarker , "notification_close_hover" },
//{ImGui::TimerDotMarker , "timer_dot" },
//{ImGui::TimerDotEmptyMarker , "timer_dot_empty" },
{ImGui::MinimalizeMarker , "notification_minimalize" },
{ImGui::MinimalizeHoverMarker , "notification_minimalize_hover" },
{ImGui::WarningMarker , "notification_warning" },
{ImGui::ErrorMarker , "notification_error" }
};
const ImVec4 ImGuiWrapper::COL_GREY_DARK = { 0.333f, 0.333f, 0.333f, 1.0f };
const ImVec4 ImGuiWrapper::COL_GREY_LIGHT = { 0.4f, 0.4f, 0.4f, 1.0f };
const ImVec4 ImGuiWrapper::COL_ORANGE_DARK = { 0.757f, 0.404f, 0.216f, 1.0f };
const ImVec4 ImGuiWrapper::COL_ORANGE_LIGHT = { 1.0f, 0.49f, 0.216f, 1.0f };
const ImVec4 ImGuiWrapper::COL_WINDOW_BACKGROUND = { 0.133f, 0.133f, 0.133f, 0.8f };
const ImVec4 ImGuiWrapper::COL_BUTTON_BACKGROUND = { 0.233f, 0.233f, 0.233f, 1.0f };
const ImVec4 ImGuiWrapper::COL_BUTTON_HOVERED = { 0.433f, 0.433f, 0.433f, 1.8f };
const ImVec4 ImGuiWrapper::COL_BUTTON_ACTIVE = ImGuiWrapper::COL_BUTTON_HOVERED;
ImGuiWrapper::ImGuiWrapper()
: m_glyph_ranges(nullptr)
, m_font_cjk(false)
@ -792,6 +803,12 @@ void ImGuiWrapper::search_list(const ImVec2& size_, bool (*items_getter)(int, co
check_box(_L("Search in English"), view_params.english);
}
void ImGuiWrapper::title(const std::string& str)
{
text(str);
ImGui::Separator();
}
void ImGuiWrapper::disabled_begin(bool disabled)
{
wxCHECK_RET(!m_disabled, "ImGUI: Unbalanced disabled_begin() call");
@ -1011,23 +1028,13 @@ void ImGuiWrapper::init_style()
{
ImGuiStyle &style = ImGui::GetStyle();
auto set_color = [&](ImGuiCol_ col, unsigned hex_color) {
style.Colors[col] = ImVec4(
((hex_color >> 24) & 0xff) / 255.0f,
((hex_color >> 16) & 0xff) / 255.0f,
((hex_color >> 8) & 0xff) / 255.0f,
(hex_color & 0xff) / 255.0f);
auto set_color = [&](ImGuiCol_ entity, ImVec4 color) {
style.Colors[entity] = color;
};
static const unsigned COL_WINDOW_BACKGROND = 0x222222cc;
static const unsigned COL_GREY_DARK = 0x555555ff;
static const unsigned COL_GREY_LIGHT = 0x666666ff;
static const unsigned COL_ORANGE_DARK = 0xc16737ff;
static const unsigned COL_ORANGE_LIGHT = 0xff7d38ff;
// Window
style.WindowRounding = 4.0f;
set_color(ImGuiCol_WindowBg, COL_WINDOW_BACKGROND);
set_color(ImGuiCol_WindowBg, COL_WINDOW_BACKGROUND);
set_color(ImGuiCol_TitleBgActive, COL_ORANGE_DARK);
// Generics
@ -1039,9 +1046,9 @@ void ImGuiWrapper::init_style()
set_color(ImGuiCol_TextSelectedBg, COL_ORANGE_DARK);
// Buttons
set_color(ImGuiCol_Button, COL_ORANGE_DARK);
set_color(ImGuiCol_ButtonHovered, COL_ORANGE_LIGHT);
set_color(ImGuiCol_ButtonActive, COL_ORANGE_LIGHT);
set_color(ImGuiCol_Button, COL_BUTTON_BACKGROUND);
set_color(ImGuiCol_ButtonHovered, COL_BUTTON_HOVERED);
set_color(ImGuiCol_ButtonActive, COL_BUTTON_ACTIVE);
// Checkbox
set_color(ImGuiCol_CheckMark, COL_ORANGE_LIGHT);
@ -1057,6 +1064,13 @@ void ImGuiWrapper::init_style()
// Separator
set_color(ImGuiCol_Separator, COL_ORANGE_LIGHT);
// Tabs
set_color(ImGuiCol_Tab, COL_ORANGE_DARK);
set_color(ImGuiCol_TabHovered, COL_ORANGE_LIGHT);
set_color(ImGuiCol_TabActive, COL_ORANGE_LIGHT);
set_color(ImGuiCol_TabUnfocused, COL_GREY_DARK);
set_color(ImGuiCol_TabUnfocusedActive, COL_GREY_LIGHT);
}
void ImGuiWrapper::render_draw_data(ImDrawData *draw_data)

View file

@ -86,6 +86,7 @@ public:
bool undo_redo_list(const ImVec2& size, const bool is_undo, bool (*items_getter)(const bool, int, const char**), int& hovered, int& selected, int& mouse_wheel);
void search_list(const ImVec2& size, bool (*items_getter)(int, const char** label, const char** tooltip), char* search_str,
Search::OptionViewParameters& view_params, int& selected, bool& edited, int& mouse_wheel, bool is_localized);
void title(const std::string& str);
void disabled_begin(bool disabled);
void disabled_end();
@ -95,6 +96,15 @@ public:
bool want_text_input() const;
bool want_any_input() const;
static const ImVec4 COL_GREY_DARK;
static const ImVec4 COL_GREY_LIGHT;
static const ImVec4 COL_ORANGE_DARK;
static const ImVec4 COL_ORANGE_LIGHT;
static const ImVec4 COL_WINDOW_BACKGROUND;
static const ImVec4 COL_BUTTON_BACKGROUND;
static const ImVec4 COL_BUTTON_HOVERED;
static const ImVec4 COL_BUTTON_ACTIVE;
private:
void init_font(bool compress);
void init_input();

View file

@ -1,10 +1,11 @@
#include "SLAImportJob.hpp"
#include "libslic3r/Format/SL1.hpp"
#include "slic3r/GUI/GUI.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/Utils/SLAImport.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/PresetBundle.hpp"
@ -218,8 +219,10 @@ void SLAImportJob::finalize()
wxGetApp().load_current_presets();
}
if (!p->mesh.empty())
p->plater->sidebar().obj_list()->load_mesh_object(p->mesh, name);
if (!p->mesh.empty()) {
bool is_centered = false;
p->plater->sidebar().obj_list()->load_mesh_object(p->mesh, name, is_centered);
}
reset();
}

View file

@ -1,3 +1,4 @@
#include "libslic3r/libslic3r.h"
#include "KBShortcutsDialog.hpp"
#include "I18N.hpp"
#include "libslic3r/Utils.hpp"
@ -6,6 +7,9 @@
#include <wx/display.h>
#include "GUI_App.hpp"
#include "wxExtensions.hpp"
#if ENABLE_GCODE_VIEWER
#include "MainFrame.hpp"
#endif // ENABLE_GCODE_VIEWER
#define NOTEBOOK_TOP 1
#define NOTEBOOK_LEFT 2
@ -29,12 +33,8 @@ namespace Slic3r {
namespace GUI {
KBShortcutsDialog::KBShortcutsDialog()
: DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("Keyboard Shortcuts")),
#if ENABLE_SCROLLABLE
: DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _L("Keyboard Shortcuts"),
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
#else
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
#endif // ENABLE_SCROLLABLE
{
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
@ -65,13 +65,9 @@ main_sizer->Add(book, 1, wxEXPAND | wxALL, 10);
fill_shortcuts();
for (size_t i = 0; i < m_full_shortcuts.size(); ++i)
{
#if ENABLE_SCROLLABLE
wxPanel* page = create_page(book, m_full_shortcuts[i], font, bold_font);
m_pages.push_back(page);
book->AddPage(page, m_full_shortcuts[i].first, i == 0);
#else
book->AddPage(create_page(book, m_full_shortcuts[i], font, bold_font), m_full_shortcuts[i].first, i == 0);
#endif // ENABLE_SCROLLABLE
}
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK);
@ -98,114 +94,122 @@ void KBShortcutsDialog::fill_shortcuts()
const std::string& ctrl = GUI::shortkey_ctrl_prefix();
const std::string& alt = GUI::shortkey_alt_prefix();
Shortcuts commands_shortcuts = {
// File
{ ctrl + "N", L("New project, clear plater") },
{ ctrl + "O", L("Open project STL/OBJ/AMF/3MF with config, clear plater") },
{ ctrl + "S", L("Save project (3mf)") },
{ ctrl + alt + "S", L("Save project as (3mf)") },
{ ctrl + "R", L("(Re)slice") },
// File>Import
{ ctrl + "I", L("Import STL/OBJ/AMF/3MF without config, keep plater") },
{ ctrl + "L", L("Import Config from ini/amf/3mf/gcode") },
{ ctrl + alt + "L", L("Load Config from ini/amf/3mf/gcode and merge") },
// File>Export
{ ctrl + "G", L("Export G-code") },
{ ctrl + "Shift+" + "G", L("Send G-code") },
{ ctrl + "E", L("Export config") },
{ ctrl + "U", L("Export to SD card / Flash drive") },
{ ctrl + "T", L("Eject SD card / Flash drive") },
// Edit
{ ctrl + "A", L("Select all objects") },
{ "Esc", L("Deselect all") },
{ "Del", L("Delete selected") },
{ ctrl + "Del", L("Delete all") },
{ ctrl + "Z", L("Undo") },
{ ctrl + "Y", L("Redo") },
{ ctrl + "C", L("Copy to clipboard") },
{ ctrl + "V", L("Paste from clipboard") },
{ "F5", L("Reload plater from disk") },
{ ctrl + "F", L("Search") },
// Window
{ ctrl + "1", L("Select Plater Tab") },
{ ctrl + "2", L("Select Print Settings Tab") },
{ ctrl + "3", L("Select Filament Settings Tab") },
{ ctrl + "4", L("Select Printer Settings Tab") },
{ ctrl + "5", L("Switch to 3D") },
{ ctrl + "6", L("Switch to Preview") },
{ ctrl + "J", L("Print host upload queue") },
// View
{ "0-6", L("Camera view") },
{ "E", L("Show/Hide object/instance labels") },
#if ENABLE_GCODE_VIEWER
bool is_gcode_viewer = wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer;
if (!is_gcode_viewer) {
#endif // ENABLE_GCODE_VIEWER
Shortcuts commands_shortcuts = {
// File
{ ctrl + "N", L("New project, clear plater") },
{ ctrl + "O", L("Open project STL/OBJ/AMF/3MF with config, clear plater") },
{ ctrl + "S", L("Save project (3mf)") },
{ ctrl + alt + "S", L("Save project as (3mf)") },
{ ctrl + "R", L("(Re)slice") },
// File>Import
{ ctrl + "I", L("Import STL/OBJ/AMF/3MF without config, keep plater") },
{ ctrl + "L", L("Import Config from ini/amf/3mf/gcode") },
{ ctrl + alt + "L", L("Load Config from ini/amf/3mf/gcode and merge") },
// File>Export
{ ctrl + "G", L("Export G-code") },
{ ctrl + "Shift+" + "G", L("Send G-code") },
{ ctrl + "E", L("Export config") },
{ ctrl + "U", L("Export to SD card / Flash drive") },
{ ctrl + "T", L("Eject SD card / Flash drive") },
// Edit
{ ctrl + "A", L("Select all objects") },
{ "Esc", L("Deselect all") },
{ "Del", L("Delete selected") },
{ ctrl + "Del", L("Delete all") },
{ ctrl + "Z", L("Undo") },
{ ctrl + "Y", L("Redo") },
{ ctrl + "C", L("Copy to clipboard") },
{ ctrl + "V", L("Paste from clipboard") },
{ "F5", L("Reload plater from disk") },
{ ctrl + "F", L("Search") },
// Window
{ ctrl + "1", L("Select Plater Tab") },
{ ctrl + "2", L("Select Print Settings Tab") },
{ ctrl + "3", L("Select Filament Settings Tab") },
{ ctrl + "4", L("Select Printer Settings Tab") },
{ ctrl + "5", L("Switch to 3D") },
{ ctrl + "6", L("Switch to Preview") },
{ ctrl + "J", L("Print host upload queue") },
// View
{ "0-6", L("Camera view") },
{ "E", L("Show/Hide object/instance labels") },
#if ENABLE_SLOPE_RENDERING
{ "D", L("Turn On/Off facets' slope rendering") },
{ "D", L("Turn On/Off facets' slope rendering") },
#endif // ENABLE_SLOPE_RENDERING
// Configuration
{ ctrl + "P", L("Preferences") },
// Help
{ "?", L("Show keyboard shortcuts list") }
};
// Configuration
{ ctrl + "P", L("Preferences") },
// Help
{ "?", L("Show keyboard shortcuts list") }
};
m_full_shortcuts.push_back(std::make_pair(_(L("Commands")), commands_shortcuts));
m_full_shortcuts.push_back(std::make_pair(_L("Commands"), commands_shortcuts));
Shortcuts plater_shortcuts = {
{ "A", L("Arrange") },
{ "Shift+A", L("Arrange selection") },
{ "+", L("Add Instance of the selected object") },
{ "-", L("Remove Instance of the selected object") },
{ ctrl, L("Press to select multiple objects\nor move multiple objects with mouse") },
{ "Shift+", L("Press to activate selection rectangle") },
{ alt, L("Press to activate deselection rectangle") },
{ L("Arrow Up"), L("Move selection 10 mm in positive Y direction") },
{ L("Arrow Down"), L("Move selection 10 mm in negative Y direction") },
{ L("Arrow Left"), L("Move selection 10 mm in negative X direction") },
{ L("Arrow Right"), L("Move selection 10 mm in positive X direction") },
{ std::string("Shift+") + L("Any arrow"), L("Movement step set to 1 mm") },
{ ctrl + L("Any arrow"), L("Movement in camera space") },
{ L("Page Up"), L("Rotate selection 45 degrees CCW") },
{ L("Page Down"), L("Rotate selection 45 degrees CW") },
{ "M", L("Gizmo move") },
{ "S", L("Gizmo scale") },
{ "R", L("Gizmo rotate") },
{ "C", L("Gizmo cut") },
{ "F", L("Gizmo Place face on bed") },
{ "H", L("Gizmo SLA hollow") },
{ "L", L("Gizmo SLA support points") },
{ "Esc", L("Unselect gizmo or clear selection") },
{ "K", L("Change camera type (perspective, orthographic)") },
{ "B", L("Zoom to Bed") },
{ "Z", L("Zoom to selected object\nor all objects in scene, if none selected") },
{ "I", L("Zoom in") },
{ "O", L("Zoom out") },
Shortcuts plater_shortcuts = {
{ "A", L("Arrange") },
{ "Shift+A", L("Arrange selection") },
{ "+", L("Add Instance of the selected object") },
{ "-", L("Remove Instance of the selected object") },
{ ctrl, L("Press to select multiple objects\nor move multiple objects with mouse") },
{ "Shift+", L("Press to activate selection rectangle") },
{ alt, L("Press to activate deselection rectangle") },
{ L("Arrow Up"), L("Move selection 10 mm in positive Y direction") },
{ L("Arrow Down"), L("Move selection 10 mm in negative Y direction") },
{ L("Arrow Left"), L("Move selection 10 mm in negative X direction") },
{ L("Arrow Right"), L("Move selection 10 mm in positive X direction") },
{ std::string("Shift+") + L("Any arrow"), L("Movement step set to 1 mm") },
{ ctrl + L("Any arrow"), L("Movement in camera space") },
{ L("Page Up"), L("Rotate selection 45 degrees CCW") },
{ L("Page Down"), L("Rotate selection 45 degrees CW") },
{ "M", L("Gizmo move") },
{ "S", L("Gizmo scale") },
{ "R", L("Gizmo rotate") },
{ "C", L("Gizmo cut") },
{ "F", L("Gizmo Place face on bed") },
{ "H", L("Gizmo SLA hollow") },
{ "L", L("Gizmo SLA support points") },
{ "Esc", L("Unselect gizmo or clear selection") },
{ "K", L("Change camera type (perspective, orthographic)") },
{ "B", L("Zoom to Bed") },
{ "Z", L("Zoom to selected object\nor all objects in scene, if none selected") },
{ "I", L("Zoom in") },
{ "O", L("Zoom out") },
#ifdef __linux__
{ ctrl + "M", L("Show/Hide 3Dconnexion devices settings dialog") },
{ ctrl + "M", L("Show/Hide 3Dconnexion devices settings dialog") },
#endif // __linux__
#if ENABLE_RENDER_PICKING_PASS
// Don't localize debugging texts.
{ "T", "Toggle picking pass texture rendering on/off" },
// Don't localize debugging texts.
{ "P", "Toggle picking pass texture rendering on/off" },
#endif // ENABLE_RENDER_PICKING_PASS
};
};
m_full_shortcuts.push_back(std::make_pair(_(L("Plater")), plater_shortcuts));
m_full_shortcuts.push_back(std::make_pair(_L("Plater"), plater_shortcuts));
Shortcuts gizmos_shortcuts = {
{ "Shift+", L("Press to snap by 5% in Gizmo scale\nor to snap by 1mm in Gizmo move") },
{ "F", L("Scale selection to fit print volume\nin Gizmo scale") },
{ ctrl, L("Press to activate one direction scaling in Gizmo scale") },
{ alt, L("Press to scale (in Gizmo scale) or rotate (in Gizmo rotate)\nselected objects around their own center") },
};
Shortcuts gizmos_shortcuts = {
{ "Shift+", L("Press to snap by 5% in Gizmo scale\nor to snap by 1mm in Gizmo move") },
{ "F", L("Scale selection to fit print volume\nin Gizmo scale") },
{ ctrl, L("Press to activate one direction scaling in Gizmo scale") },
{ alt, L("Press to scale (in Gizmo scale) or rotate (in Gizmo rotate)\nselected objects around their own center") },
};
m_full_shortcuts.push_back(std::make_pair(_(L("Gizmos")), gizmos_shortcuts));
m_full_shortcuts.push_back(std::make_pair(_L("Gizmos"), gizmos_shortcuts));
#if ENABLE_GCODE_VIEWER
}
#endif // ENABLE_GCODE_VIEWER
Shortcuts preview_shortcuts = {
{ L("Arrow Up"), L("Upper Layer") },
{ L("Arrow Down"), L("Lower Layer") },
{ "U", L("Upper Layer") },
{ "D", L("Lower Layer") },
{ "L", L("Show/Hide Legend") }
{ "L", L("Show/Hide Legend/Estimated printing time") },
};
m_full_shortcuts.push_back(std::make_pair(_(L("Preview")), preview_shortcuts));
m_full_shortcuts.push_back(std::make_pair(_L("Preview"), preview_shortcuts));
Shortcuts layers_slider_shortcuts = {
{ L("Arrow Up"), L("Move current slider thumb Up") },
@ -213,10 +217,23 @@ void KBShortcutsDialog::fill_shortcuts()
{ L("Arrow Left"), L("Set upper thumb to current slider thumb") },
{ L("Arrow Right"), L("Set lower thumb to current slider thumb") },
{ "+", L("Add color change marker for current layer") },
{ "-", L("Delete color change marker for current layer") }
{ "-", L("Delete color change marker for current layer") },
{ "Shift+", L("Press to speed up 5 times while moving thumb\nwith arrow keys or mouse wheel") },
{ ctrl, L("Press to speed up 5 times while moving thumb\nwith arrow keys or mouse wheel") },
};
m_full_shortcuts.push_back(std::make_pair(_(L("Layers Slider")), layers_slider_shortcuts));
m_full_shortcuts.push_back(std::make_pair(_L("Layers Slider"), layers_slider_shortcuts));
#if ENABLE_GCODE_VIEWER
Shortcuts sequential_slider_shortcuts = {
{ L("Arrow Left"), L("Move current slider thumb Left") },
{ L("Arrow Right"), L("Move current slider thumb Right") },
{ "Shift+", L("Press to speed up 5 times while moving thumb\nwith arrow keys or mouse wheel") },
{ ctrl, L("Press to speed up 5 times while moving thumb\nwith arrow keys or mouse wheel") },
};
m_full_shortcuts.push_back(std::make_pair(_L("Sequential Slider"), sequential_slider_shortcuts));
#endif // ENABLE_GCODE_VIEWER
}
wxPanel* KBShortcutsDialog::create_header(wxWindow* parent, const wxFont& bold_font)
@ -239,7 +256,7 @@ wxPanel* KBShortcutsDialog::create_header(wxWindow* parent, const wxFont& bold_f
sizer->Add(m_header_bitmap, 0, wxEXPAND | wxLEFT | wxRIGHT, 10);
// text
wxStaticText* text = new wxStaticText(panel, wxID_ANY, _(L("Keyboard shortcuts")));
wxStaticText* text = new wxStaticText(panel, wxID_ANY, _L("Keyboard shortcuts"));
text->SetFont(header_font);
sizer->Add(text, 0, wxALIGN_CENTER_VERTICAL);
@ -254,13 +271,9 @@ wxPanel* KBShortcutsDialog::create_page(wxWindow* parent, const std::pair<wxStri
static const int max_items_per_column = 20;
int columns_count = 1 + (int)shortcuts.second.size() / max_items_per_column;
#if ENABLE_SCROLLABLE
wxScrolledWindow* page = new wxScrolledWindow(parent);
page->SetScrollbars(20, 20, 50, 50);
page->SetInitialSize(wxSize(850, 450));
#else
wxPanel* page = new wxPanel(parent);
#endif // ENABLE_SCROLLABLE
#if (BOOK_TYPE == LISTBOOK_TOP) || (BOOK_TYPE == LISTBOOK_LEFT)
wxStaticBoxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, page, " " + shortcuts.first + " ");

View file

@ -8,8 +8,6 @@
#include "GUI_Utils.hpp"
#include "wxExtensions.hpp"
#define ENABLE_SCROLLABLE 1
namespace Slic3r {
namespace GUI {
@ -22,9 +20,7 @@ class KBShortcutsDialog : public DPIDialog
ShortcutsVec m_full_shortcuts;
ScalableBitmap m_logo_bmp;
wxStaticBitmap* m_header_bitmap;
#if ENABLE_SCROLLABLE
std::vector<wxPanel*> m_pages;
#endif // ENABLE_SCROLLABLE
public:
KBShortcutsDialog();

View file

@ -8,6 +8,8 @@
#include <wx/progdlg.h>
#include <wx/tooltip.h>
//#include <wx/glcanvas.h>
#include <wx/filename.h>
#include <wx/stdpaths.h>
#include <wx/debug.h>
#include <boost/algorithm/string/predicate.hpp>
@ -38,6 +40,12 @@
#include <shlobj.h>
#endif // _WIN32
// For starting another PrusaSlicer instance on OSX.
// Fails to compile on Windows on the build server.
#ifdef __APPLE__
#include <boost/process/spawn.hpp>
#endif
namespace Slic3r {
namespace GUI {
@ -84,9 +92,34 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
#endif
// Font is already set in DPIFrame constructor
*/
#if ENABLE_GCODE_VIEWER_TASKBAR_ICON
if (wxTaskBarIcon::IsAvailable()) {
#if defined(__WXOSX__) && wxOSX_USE_COCOA
m_taskbar_icon = new wxTaskBarIcon(wxTBI_DOCK);
#else
m_taskbar_icon = new wxTaskBarIcon();
#endif
m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer");
m_taskbar_icon->Bind(wxEVT_TASKBAR_CLICK, [this](wxTaskBarIconEvent& evt) {
wxString msg = _L("You pressed the icon in taskbar for ") + "\n";
if (m_mode == EMode::Editor)
msg += wxString(SLIC3R_APP_NAME);
else
msg += wxString(SLIC3R_APP_NAME) + "-GCode viewer";
wxMessageDialog dialog(nullptr, msg, _("Taskbar icon clicked"), wxOK);
dialog.ShowModal();
});
}
#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON
// SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG));
// 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));
@ -105,7 +138,25 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
// initialize tabpanel and menubar
init_tabpanel();
#if ENABLE_GCODE_VIEWER
init_editor_menubar();
init_gcodeviewer_menubar();
#if _WIN32
// This is needed on Windows to fake the CTRL+# of the window menu when using the numpad
wxAcceleratorEntry entries[6];
entries[0].Set(wxACCEL_CTRL, WXK_NUMPAD1, wxID_HIGHEST + 1);
entries[1].Set(wxACCEL_CTRL, WXK_NUMPAD2, wxID_HIGHEST + 2);
entries[2].Set(wxACCEL_CTRL, WXK_NUMPAD3, wxID_HIGHEST + 3);
entries[3].Set(wxACCEL_CTRL, WXK_NUMPAD4, wxID_HIGHEST + 4);
entries[4].Set(wxACCEL_CTRL, WXK_NUMPAD5, wxID_HIGHEST + 5);
entries[5].Set(wxACCEL_CTRL, WXK_NUMPAD6, wxID_HIGHEST + 6);
wxAcceleratorTable accel(6, entries);
SetAcceleratorTable(accel);
#endif // _WIN32
#else
init_menubar();
#endif // ENABLE_GCODE_VIEWER
// set default tooltip timer in msec
// SetAutoPop supposedly accepts long integers but some bug doesn't allow for larger values
@ -218,10 +269,21 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
update_ui_from_settings(); // FIXME (?)
if (m_plater != nullptr)
if (m_plater != nullptr) {
#if ENABLE_GCODE_VIEWER
m_plater->get_collapse_toolbar().set_enabled(wxGetApp().app_config->get("show_collapse_button") == "1");
#endif // ENABLE_GCODE_VIEWER
m_plater->show_action_buttons(true);
}
}
#if ENABLE_GCODE_VIEWER_TASKBAR_ICON
MainFrame::~MainFrame()
{
delete m_taskbar_icon;
}
#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON
void MainFrame::update_layout()
{
auto restore_to_creation = [this]() {
@ -263,9 +325,16 @@ void MainFrame::update_layout()
Layout();
};
#if ENABLE_GCODE_VIEWER
ESettingsLayout layout = (m_mode == EMode::GCodeViewer) ? ESettingsLayout::GCodeViewer :
(wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old :
wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New :
wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? ESettingsLayout::Dlg : ESettingsLayout::Old);
#else
ESettingsLayout layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old :
wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New :
wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? ESettingsLayout::Dlg : ESettingsLayout::Old;
#endif // ENABLE_GCODE_VIEWER
if (m_layout == layout)
return;
@ -286,6 +355,10 @@ void MainFrame::update_layout()
// Set new settings
switch (m_layout)
{
case ESettingsLayout::Unknown:
{
break;
}
case ESettingsLayout::Old:
{
m_plater->Reparent(m_tabpanel);
@ -317,6 +390,14 @@ void MainFrame::update_layout()
m_plater->Show();
break;
}
#if ENABLE_GCODE_VIEWER
case ESettingsLayout::GCodeViewer:
{
m_main_sizer->Add(m_plater, 1, wxEXPAND);
m_plater->Show();
break;
}
#endif // ENABLE_GCODE_VIEWER
}
//#ifdef __APPLE__
@ -351,6 +432,20 @@ void MainFrame::shutdown()
}
#endif // _WIN32
#if ENABLE_GCODE_VIEWER
if (m_plater != nullptr) {
m_plater->stop_jobs();
// Unbinding of wxWidgets event handling in canvases needs to be done here because on MAC,
// when closing the application using Command+Q, a mouse event is triggered after this lambda is completed,
// causing a crash
m_plater->unbind_canvas_event_handlers();
// Cleanup of canvases' volumes needs to be done here or a crash may happen on some Linux Debian flavours
// see: https://github.com/prusa3d/PrusaSlicer/issues/3964
m_plater->reset_canvas_volumes();
}
#else
if (m_plater)
m_plater->stop_jobs();
@ -362,6 +457,7 @@ void MainFrame::shutdown()
// Cleanup of canvases' volumes needs to be done here or a crash may happen on some Linux Debian flavours
// see: https://github.com/prusa3d/PrusaSlicer/issues/3964
if (m_plater) m_plater->reset_canvas_volumes();
#endif // ENABLE_GCODE_VIEWER
// Weird things happen as the Paint messages are floating around the windows being destructed.
// Avoid the Paint messages by hiding the main window.
@ -373,11 +469,22 @@ void MainFrame::shutdown()
// call Close() to trigger call to lambda defined into GUI_App::persist_window_geometry()
m_settings_dialog.Close();
// Stop the background thread (Windows and Linux).
// Disconnect from a 3DConnextion driver (OSX).
m_plater->get_mouse3d_controller().shutdown();
// Store the device parameter database back to appconfig.
m_plater->get_mouse3d_controller().save_config(*wxGetApp().app_config);
if (m_plater != nullptr) {
#if ENABLE_GCODE_VIEWER
// restore sidebar if it was hidden when switching to gcode viewer mode
if (m_restore_from_gcode_viewer.collapsed_sidebar)
m_plater->collapse_sidebar(false);
// restore sla printer if it was deselected when switching to gcode viewer mode
if (m_restore_from_gcode_viewer.sla_technology)
m_plater->set_printer_technology(ptSLA);
#endif // ENABLE_GCODE_VIEWER
// Stop the background thread (Windows and Linux).
// Disconnect from a 3DConnextion driver (OSX).
m_plater->get_mouse3d_controller().shutdown();
// Store the device parameter database back to appconfig.
m_plater->get_mouse3d_controller().save_config(*wxGetApp().app_config);
}
// Stop the background thread of the removable drive manager, so that no new updates will be sent to the Plater.
wxGetApp().removable_drive_manager()->shutdown();
@ -705,7 +812,81 @@ void MainFrame::on_sys_color_changed()
msw_rescale_menu(menu_bar->GetMenu(id));
}
#if ENABLE_GCODE_VIEWER
#ifdef _MSC_VER
// \xA0 is a non-breaking space. It is entered here to spoil the automatic accelerators,
// as the simple numeric accelerators spoil all numeric data entry.
static const wxString sep = "\t\xA0";
static const wxString sep_space = "\xA0";
#else
static const wxString sep = " - ";
static const wxString sep_space = "";
#endif
static wxMenu* generate_help_menu()
{
wxMenu* helpMenu = new wxMenu();
append_menu_item(helpMenu, wxID_ANY, _L("Prusa 3D &Drivers"), _L("Open the Prusa3D drivers download page in your browser"),
[](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/downloads"); });
append_menu_item(helpMenu, wxID_ANY, _(L("Software &Releases")), _(L("Open the software releases page in your browser")),
[](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); });
//# 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, wxString::Format(_L("%s &Website"), SLIC3R_APP_NAME),
wxString::Format(_L("Open the %s website in your browser"), SLIC3R_APP_NAME),
[](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); });
// append_menu_item(helpMenu, wxID_ANY, wxString::Format(_(L("%s &Manual")), SLIC3R_APP_NAME),
// wxString::Format(_(L("Open the %s manual in your browser")), SLIC3R_APP_NAME),
// [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://manual.slic3r.org/"); });
helpMenu->AppendSeparator();
append_menu_item(helpMenu, wxID_ANY, _L("System &Info"), _L("Show system information"),
[](wxCommandEvent&) { wxGetApp().system_info(); });
append_menu_item(helpMenu, wxID_ANY, _L("Show &Configuration Folder"), _L("Show user configuration folder (datadir)"),
[](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); });
append_menu_item(helpMenu, wxID_ANY, _(L"Report an I&ssue"), wxString::Format(_L("Report an issue on %s"), SLIC3R_APP_NAME),
[](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/slic3r/issues/new"); });
append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), SLIC3R_APP_NAME), _L("Show about dialog"),
[](wxCommandEvent&) { Slic3r::GUI::about(); });
helpMenu->AppendSeparator();
append_menu_item(helpMenu, wxID_ANY, _L("Keyboard Shortcuts") + sep + "&?", _L("Show the list of the keyboard shortcuts"),
[](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); });
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
helpMenu->AppendSeparator();
append_menu_item(helpMenu, wxID_ANY, "DEBUG gcode thumbnails", "DEBUG ONLY - read the selected gcode file and generates png for the contained thumbnails",
[](wxCommandEvent&) { wxGetApp().gcode_thumbnails_debug(); });
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
return helpMenu;
}
static void add_common_view_menu_items(wxMenu* view_menu, MainFrame* mainFrame, std::function<bool(void)> can_change_view)
{
// The camera control accelerators are captured by GLCanvas3D::on_char().
append_menu_item(view_menu, wxID_ANY, _L("Iso") + sep + "&0", _L("Iso View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("iso"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
view_menu->AppendSeparator();
//TRN To be shown in the main menu View->Top
append_menu_item(view_menu, wxID_ANY, _L("Top") + sep + "&1", _L("Top View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("top"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
//TRN To be shown in the main menu View->Bottom
append_menu_item(view_menu, wxID_ANY, _L("Bottom") + sep + "&2", _L("Bottom View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("bottom"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
append_menu_item(view_menu, wxID_ANY, _L("Front") + sep + "&3", _L("Front View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("front"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
append_menu_item(view_menu, wxID_ANY, _L("Rear") + sep + "&4", _L("Rear View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("rear"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
append_menu_item(view_menu, wxID_ANY, _L("Left") + sep + "&5", _L("Left View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("left"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
append_menu_item(view_menu, wxID_ANY, _L("Right") + sep + "&6", _L("Right View"), [mainFrame](wxCommandEvent&) { mainFrame->select_view("right"); },
"", nullptr, [can_change_view]() { return can_change_view(); }, mainFrame);
}
void MainFrame::init_editor_menubar()
#else
void MainFrame::init_menubar()
#endif // ENABLE_GCODE_VIEWER
{
#ifdef __APPLE__
wxMenuBar::SetAutoWindowMenu(false);
@ -714,15 +895,15 @@ void MainFrame::init_menubar()
// File menu
wxMenu* fileMenu = new wxMenu;
{
append_menu_item(fileMenu, wxID_ANY, _(L("&New Project")) + "\tCtrl+N", _(L("Start a new project")),
append_menu_item(fileMenu, wxID_ANY, _L("&New Project") + "\tCtrl+N", _L("Start a new project"),
[this](wxCommandEvent&) { if (m_plater) m_plater->new_project(); }, "", nullptr,
[this](){return m_plater != nullptr && can_start_new_project(); }, this);
append_menu_item(fileMenu, wxID_ANY, _(L("&Open Project")) + dots + "\tCtrl+O", _(L("Open a project file")),
append_menu_item(fileMenu, wxID_ANY, _L("&Open Project") + dots + "\tCtrl+O", _L("Open a project file"),
[this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, "open", nullptr,
[this](){return m_plater != nullptr; }, this);
wxMenu* recent_projects_menu = new wxMenu();
wxMenuItem* recent_projects_submenu = append_submenu(fileMenu, recent_projects_menu, wxID_ANY, _(L("Recent projects")), "");
wxMenuItem* recent_projects_submenu = append_submenu(fileMenu, recent_projects_menu, wxID_ANY, _L("Recent projects"), "");
m_recent_projects.UseMenu(recent_projects_menu);
Bind(wxEVT_MENU, [this](wxCommandEvent& evt) {
size_t file_id = evt.GetId() - wxID_FILE1;
@ -731,7 +912,7 @@ void MainFrame::init_menubar()
m_plater->load_project(filename);
else
{
wxMessageDialog msg(this, _(L("The selected project is no longer available.\nDo you want to remove it from the recent projects list?")), _(L("Error")), wxYES_NO | wxYES_DEFAULT);
wxMessageDialog msg(this, _L("The selected project is no longer available.\nDo you want to remove it from the recent projects list?"), _L("Error"), wxYES_NO | wxYES_DEFAULT);
if (msg.ShowModal() == wxID_YES)
{
m_recent_projects.RemoveFileFromHistory(file_id);
@ -756,13 +937,13 @@ void MainFrame::init_menubar()
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_recent_projects.GetCount() > 0); }, recent_projects_submenu->GetId());
append_menu_item(fileMenu, wxID_ANY, _(L("&Save Project")) + "\tCtrl+S", _(L("Save current project file")),
append_menu_item(fileMenu, wxID_ANY, _L("&Save Project") + "\tCtrl+S", _L("Save current project file"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename(".3mf"))); }, "save", nullptr,
[this](){return m_plater != nullptr && can_save(); }, this);
#ifdef __APPLE__
append_menu_item(fileMenu, wxID_ANY, _(L("Save Project &as")) + dots + "\tCtrl+Shift+S", _(L("Save current project file as")),
append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Shift+S", _L("Save current project file as"),
#else
append_menu_item(fileMenu, wxID_ANY, _(L("Save Project &as")) + dots + "\tCtrl+Alt+S", _(L("Save current project file as")),
append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Alt+S", _L("Save current project file as"),
#endif // __APPLE__
[this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(); }, "save", nullptr,
[this](){return m_plater != nullptr && can_save(); }, this);
@ -770,7 +951,7 @@ void MainFrame::init_menubar()
fileMenu->AppendSeparator();
wxMenu* import_menu = new wxMenu();
append_menu_item(import_menu, wxID_ANY, _(L("Import STL/OBJ/AM&F/3MF")) + dots + "\tCtrl+I", _(L("Load a model")),
append_menu_item(import_menu, wxID_ANY, _L("Import STL/OBJ/AM&F/3MF") + dots + "\tCtrl+I", _L("Load a model"),
[this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "import_plater", nullptr,
[this](){return m_plater != nullptr; }, this);
@ -778,59 +959,59 @@ void MainFrame::init_menubar()
[this](wxCommandEvent&) { if (m_plater) m_plater->add_model(true); }, "import_plater", nullptr,
[this](){return m_plater != nullptr; }, this);
append_menu_item(import_menu, wxID_ANY, _(L("Import SL1 archive")) + dots, _(L("Load an SL1 output archive")),
append_menu_item(import_menu, wxID_ANY, _L("Import SL1 archive") + dots, _L("Load an SL1 output archive"),
[this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr,
[this](){return m_plater != nullptr; }, this);
import_menu->AppendSeparator();
append_menu_item(import_menu, wxID_ANY, _(L("Import &Config")) + dots + "\tCtrl+L", _(L("Load exported configuration file")),
append_menu_item(import_menu, wxID_ANY, _L("Import &Config") + dots + "\tCtrl+L", _L("Load exported configuration file"),
[this](wxCommandEvent&) { load_config_file(); }, "import_config", nullptr,
[this]() {return true; }, this);
append_menu_item(import_menu, wxID_ANY, _(L("Import Config from &project")) + dots +"\tCtrl+Alt+L", _(L("Load configuration from project file")),
append_menu_item(import_menu, wxID_ANY, _L("Import Config from &project") + dots +"\tCtrl+Alt+L", _L("Load configuration from project file"),
[this](wxCommandEvent&) { if (m_plater) m_plater->extract_config_from_project(); }, "import_config", nullptr,
[this]() {return true; }, this);
import_menu->AppendSeparator();
append_menu_item(import_menu, wxID_ANY, _(L("Import Config &Bundle")) + dots, _(L("Load presets from a bundle")),
append_menu_item(import_menu, wxID_ANY, _L("Import Config &Bundle") + dots, _L("Load presets from a bundle"),
[this](wxCommandEvent&) { load_configbundle(); }, "import_config_bundle", nullptr,
[this]() {return true; }, this);
append_submenu(fileMenu, import_menu, wxID_ANY, _(L("&Import")), "");
append_submenu(fileMenu, import_menu, wxID_ANY, _L("&Import"), "");
wxMenu* export_menu = new wxMenu();
wxMenuItem* item_export_gcode = append_menu_item(export_menu, wxID_ANY, _(L("Export &G-code")) + dots +"\tCtrl+G", _(L("Export current plate as G-code")),
wxMenuItem* item_export_gcode = append_menu_item(export_menu, wxID_ANY, _L("Export &G-code") + dots +"\tCtrl+G", _L("Export current plate as G-code"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(false); }, "export_gcode", nullptr,
[this](){return can_export_gcode(); }, this);
m_changeable_menu_items.push_back(item_export_gcode);
wxMenuItem* item_send_gcode = append_menu_item(export_menu, wxID_ANY, _(L("S&end G-code")) + dots +"\tCtrl+Shift+G", _(L("Send to print current plate as G-code")),
wxMenuItem* item_send_gcode = append_menu_item(export_menu, wxID_ANY, _L("S&end G-code") + dots +"\tCtrl+Shift+G", _L("Send to print current plate as G-code"),
[this](wxCommandEvent&) { if (m_plater) m_plater->send_gcode(); }, "export_gcode", nullptr,
[this](){return can_send_gcode(); }, this);
m_changeable_menu_items.push_back(item_send_gcode);
append_menu_item(export_menu, wxID_ANY, _(L("Export G-code to SD card / Flash drive")) + dots + "\tCtrl+U", _(L("Export current plate as G-code to SD card / Flash drive")),
append_menu_item(export_menu, wxID_ANY, _L("Export G-code to SD card / Flash drive") + dots + "\tCtrl+U", _L("Export current plate as G-code to SD card / Flash drive"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(true); }, "export_to_sd", nullptr,
[this]() {return can_export_gcode_sd(); }, this);
export_menu->AppendSeparator();
append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &STL")) + dots, _(L("Export current plate as STL")),
append_menu_item(export_menu, wxID_ANY, _L("Export plate as &STL") + dots, _L("Export current plate as STL"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, "export_plater", nullptr,
[this](){return can_export_model(); }, this);
append_menu_item(export_menu, wxID_ANY, _(L("Export plate as STL &including supports")) + dots, _(L("Export current plate as STL including supports")),
append_menu_item(export_menu, wxID_ANY, _L("Export plate as STL &including supports") + dots, _L("Export current plate as STL including supports"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(true); }, "export_plater", nullptr,
[this](){return can_export_supports(); }, this);
append_menu_item(export_menu, wxID_ANY, _(L("Export plate as &AMF")) + dots, _(L("Export current plate as AMF")),
append_menu_item(export_menu, wxID_ANY, _L("Export plate as &AMF") + dots, _L("Export current plate as AMF"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, "export_plater", nullptr,
[this](){return can_export_model(); }, this);
export_menu->AppendSeparator();
append_menu_item(export_menu, wxID_ANY, _(L("Export &toolpaths as OBJ")) + dots, _(L("Export toolpaths as OBJ")),
append_menu_item(export_menu, wxID_ANY, _L("Export &toolpaths as OBJ") + dots, _L("Export toolpaths as OBJ"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_toolpaths_to_obj(); }, "export_plater", nullptr,
[this]() {return can_export_toolpaths(); }, this);
export_menu->AppendSeparator();
append_menu_item(export_menu, wxID_ANY, _(L("Export &Config")) +dots +"\tCtrl+E", _(L("Export current configuration to file")),
append_menu_item(export_menu, wxID_ANY, _L("Export &Config") + dots +"\tCtrl+E", _L("Export current configuration to file"),
[this](wxCommandEvent&) { export_config(); }, "export_config", nullptr,
[this]() {return true; }, this);
append_menu_item(export_menu, wxID_ANY, _(L("Export Config &Bundle")) + dots, _(L("Export all presets to file")),
append_menu_item(export_menu, wxID_ANY, _L("Export Config &Bundle") + dots, _L("Export all presets to file"),
[this](wxCommandEvent&) { export_configbundle(); }, "export_config_bundle", nullptr,
[this]() {return true; }, this);
append_submenu(fileMenu, export_menu, wxID_ANY, _(L("&Export")), "");
append_submenu(fileMenu, export_menu, wxID_ANY, _L("&Export"), "");
append_menu_item(fileMenu, wxID_ANY, _(L("Ejec&t SD card / Flash drive")) + dots + "\tCtrl+T", _(L("Eject SD card / Flash drive after the G-code was exported to it.")),
append_menu_item(fileMenu, wxID_ANY, _L("Ejec&t SD card / Flash drive") + dots + "\tCtrl+T", _L("Eject SD card / Flash drive after the G-code was exported to it."),
[this](wxCommandEvent&) { if (m_plater) m_plater->eject_drive(); }, "eject_sd", nullptr,
[this]() {return can_eject(); }, this);
@ -838,19 +1019,19 @@ void MainFrame::init_menubar()
#if 0
m_menu_item_repeat = nullptr;
append_menu_item(fileMenu, wxID_ANY, _(L("Quick Slice")) +dots+ "\tCtrl+U", _(L("Slice a file into a G-code")),
append_menu_item(fileMenu, wxID_ANY, _L("Quick Slice") +dots+ "\tCtrl+U", _L("Slice a file into a G-code"),
[this](wxCommandEvent&) {
wxTheApp->CallAfter([this]() {
quick_slice();
m_menu_item_repeat->Enable(is_last_input_file());
}); }, "cog_go.png");
append_menu_item(fileMenu, wxID_ANY, _(L("Quick Slice and Save As")) +dots +"\tCtrl+Alt+U", _(L("Slice a file into a G-code, save as")),
append_menu_item(fileMenu, wxID_ANY, _L("Quick Slice and Save As") +dots +"\tCtrl+Alt+U", _L("Slice a file into a G-code, save as"),
[this](wxCommandEvent&) {
wxTheApp->CallAfter([this]() {
quick_slice(qsSaveAs);
m_menu_item_repeat->Enable(is_last_input_file());
}); }, "cog_go.png");
m_menu_item_repeat = append_menu_item(fileMenu, wxID_ANY, _(L("Repeat Last Quick Slice")) +"\tCtrl+Shift+U", _(L("Repeat last quick slice")),
m_menu_item_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);
@ -858,18 +1039,29 @@ void MainFrame::init_menubar()
m_menu_item_repeat->Enable(false);
fileMenu->AppendSeparator();
#endif
m_menu_item_reslice_now = append_menu_item(fileMenu, wxID_ANY, _(L("(Re)Slice No&w")) + "\tCtrl+R", _(L("Start new slicing process")),
m_menu_item_reslice_now = append_menu_item(fileMenu, wxID_ANY, _L("(Re)Slice No&w") + "\tCtrl+R", _L("Start new slicing process"),
[this](wxCommandEvent&) { reslice_now(); }, "re_slice", nullptr,
[this](){return m_plater != nullptr && can_reslice(); }, this);
[this]() { return m_plater != nullptr && can_reslice(); }, this);
fileMenu->AppendSeparator();
append_menu_item(fileMenu, wxID_ANY, _(L("&Repair STL file")) + dots, _(L("Automatically repair an STL file")),
append_menu_item(fileMenu, wxID_ANY, _L("&Repair STL file") + dots, _L("Automatically repair an STL file"),
[this](wxCommandEvent&) { repair_stl(); }, "wrench", nullptr,
[this]() {return true; }, this);
[this]() { return true; }, this);
#if ENABLE_GCODE_VIEWER
fileMenu->AppendSeparator();
append_menu_item(fileMenu, wxID_EXIT, _(L("&Quit")), wxString::Format(_(L("Quit %s")), SLIC3R_APP_NAME),
append_menu_item(fileMenu, wxID_ANY, _L("&G-code preview"), _L("Switch to G-code preview mode"),
[this](wxCommandEvent&) {
if (m_plater->model().objects.empty() ||
wxMessageDialog((wxWindow*)this, _L("Switching to G-code preview mode will remove all objects, continue?"),
wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION | wxCENTRE).ShowModal() == wxID_YES)
set_mode(EMode::GCodeViewer);
}, "", nullptr);
#endif // ENABLE_GCODE_VIEWER
fileMenu->AppendSeparator();
append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME),
[this](wxCommandEvent&) { Close(false); });
}
#if !ENABLE_GCODE_VIEWER
#ifdef _MSC_VER
// \xA0 is a non-breaking space. It is entered here to spoil the automatic accelerators,
// as the simple numeric accelerators spoil all numeric data entry.
@ -879,6 +1071,7 @@ void MainFrame::init_menubar()
wxString sep = " - ";
wxString sep_space = "";
#endif
#endif // !ENABLE_GCODE_VIEWER
// Edit menu
wxMenu* editMenu = nullptr;
@ -891,44 +1084,44 @@ void MainFrame::init_menubar()
#else
wxString hotkey_delete = "Del";
#endif
append_menu_item(editMenu, wxID_ANY, _(L("&Select all")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "A",
_(L("Selects all objects")), [this](wxCommandEvent&) { m_plater->select_all(); },
append_menu_item(editMenu, wxID_ANY, _L("&Select all") + sep + GUI::shortkey_ctrl_prefix() + sep_space + "A",
_L("Selects all objects"), [this](wxCommandEvent&) { m_plater->select_all(); },
"", nullptr, [this](){return can_select(); }, this);
append_menu_item(editMenu, wxID_ANY, _(L("D&eselect all")) + sep + "Esc",
_(L("Deselects all objects")), [this](wxCommandEvent&) { m_plater->deselect_all(); },
append_menu_item(editMenu, wxID_ANY, _L("D&eselect all") + sep + "Esc",
_L("Deselects all objects"), [this](wxCommandEvent&) { m_plater->deselect_all(); },
"", nullptr, [this](){return can_deselect(); }, this);
editMenu->AppendSeparator();
append_menu_item(editMenu, wxID_ANY, _(L("&Delete selected")) + sep + hotkey_delete,
_(L("Deletes the current selection")),[this](wxCommandEvent&) { m_plater->remove_selected(); },
append_menu_item(editMenu, wxID_ANY, _L("&Delete selected") + sep + hotkey_delete,
_L("Deletes the current selection"),[this](wxCommandEvent&) { m_plater->remove_selected(); },
"remove_menu", nullptr, [this](){return can_delete(); }, this);
append_menu_item(editMenu, wxID_ANY, _(L("Delete &all")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + hotkey_delete,
_(L("Deletes all objects")), [this](wxCommandEvent&) { m_plater->reset_with_confirm(); },
append_menu_item(editMenu, wxID_ANY, _L("Delete &all") + sep + GUI::shortkey_ctrl_prefix() + sep_space + hotkey_delete,
_L("Deletes all objects"), [this](wxCommandEvent&) { m_plater->reset_with_confirm(); },
"delete_all_menu", nullptr, [this](){return can_delete_all(); }, this);
editMenu->AppendSeparator();
append_menu_item(editMenu, wxID_ANY, _(L("&Undo")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Z",
_(L("Undo")), [this](wxCommandEvent&) { m_plater->undo(); },
append_menu_item(editMenu, wxID_ANY, _L("&Undo") + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Z",
_L("Undo"), [this](wxCommandEvent&) { m_plater->undo(); },
"undo_menu", nullptr, [this](){return m_plater->can_undo(); }, this);
append_menu_item(editMenu, wxID_ANY, _(L("&Redo")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Y",
_(L("Redo")), [this](wxCommandEvent&) { m_plater->redo(); },
append_menu_item(editMenu, wxID_ANY, _L("&Redo") + sep + GUI::shortkey_ctrl_prefix() + sep_space + "Y",
_L("Redo"), [this](wxCommandEvent&) { m_plater->redo(); },
"redo_menu", nullptr, [this](){return m_plater->can_redo(); }, this);
editMenu->AppendSeparator();
append_menu_item(editMenu, wxID_ANY, _(L("&Copy")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "C",
_(L("Copy selection to clipboard")), [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); },
append_menu_item(editMenu, wxID_ANY, _L("&Copy") + sep + GUI::shortkey_ctrl_prefix() + sep_space + "C",
_L("Copy selection to clipboard"), [this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); },
"copy_menu", nullptr, [this](){return m_plater->can_copy_to_clipboard(); }, this);
append_menu_item(editMenu, wxID_ANY, _(L("&Paste")) + sep + GUI::shortkey_ctrl_prefix() + sep_space + "V",
_(L("Paste clipboard")), [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); },
append_menu_item(editMenu, wxID_ANY, _L("&Paste") + sep + GUI::shortkey_ctrl_prefix() + sep_space + "V",
_L("Paste clipboard"), [this](wxCommandEvent&) { m_plater->paste_from_clipboard(); },
"paste_menu", nullptr, [this](){return m_plater->can_paste_from_clipboard(); }, this);
editMenu->AppendSeparator();
append_menu_item(editMenu, wxID_ANY, _(L("Re&load from disk")) + sep + "F5",
_(L("Reload the plater from disk")), [this](wxCommandEvent&) { m_plater->reload_all_from_disk(); },
append_menu_item(editMenu, wxID_ANY, _L("Re&load from disk") + sep + "F5",
_L("Reload the plater from disk"), [this](wxCommandEvent&) { m_plater->reload_all_from_disk(); },
"", nullptr, [this]() {return !m_plater->model().objects.empty(); }, this);
editMenu->AppendSeparator();
append_menu_item(editMenu, wxID_ANY, _(L("Searc&h")) + "\tCtrl+F",
_(L("Find option")), [this](wxCommandEvent&) { m_plater->search(/*m_tabpanel->GetCurrentPage() == */m_plater->IsShown()); },
append_menu_item(editMenu, wxID_ANY, _L("Searc&h") + "\tCtrl+F",
_L("Find option"), [this](wxCommandEvent&) { m_plater->search(/*m_tabpanel->GetCurrentPage() == */m_plater->IsShown()); },
"search", nullptr, [this]() {return true; }, this);
}
@ -936,32 +1129,33 @@ void MainFrame::init_menubar()
auto windowMenu = new wxMenu();
{
if (m_plater) {
append_menu_item(windowMenu, wxID_HIGHEST + 1, _(L("&Plater Tab")) + "\tCtrl+1", _(L("Show the plater")),
append_menu_item(windowMenu, wxID_HIGHEST + 1, _L("&Plater Tab") + "\tCtrl+1", _L("Show the plater"),
[this](wxCommandEvent&) { select_tab(0); }, "plater", nullptr,
[this]() {return true; }, this);
windowMenu->AppendSeparator();
}
append_menu_item(windowMenu, wxID_HIGHEST + 2, _(L("P&rint Settings Tab")) + "\tCtrl+2", _(L("Show the print settings")),
append_menu_item(windowMenu, wxID_HIGHEST + 2, _L("P&rint Settings Tab") + "\tCtrl+2", _L("Show the print settings"),
[this/*, tab_offset*/](wxCommandEvent&) { select_tab(1); }, "cog", nullptr,
[this]() {return true; }, this);
wxMenuItem* item_material_tab = append_menu_item(windowMenu, wxID_HIGHEST + 3, _(L("&Filament Settings Tab")) + "\tCtrl+3", _(L("Show the filament settings")),
wxMenuItem* item_material_tab = append_menu_item(windowMenu, wxID_HIGHEST + 3, _L("&Filament Settings Tab") + "\tCtrl+3", _L("Show the filament settings"),
[this/*, tab_offset*/](wxCommandEvent&) { select_tab(2); }, "spool", nullptr,
[this]() {return true; }, this);
m_changeable_menu_items.push_back(item_material_tab);
wxMenuItem* item_printer_tab = append_menu_item(windowMenu, wxID_HIGHEST + 4, _(L("Print&er Settings Tab")) + "\tCtrl+4", _(L("Show the printer settings")),
wxMenuItem* item_printer_tab = append_menu_item(windowMenu, wxID_HIGHEST + 4, _L("Print&er Settings Tab") + "\tCtrl+4", _L("Show the printer settings"),
[this/*, tab_offset*/](wxCommandEvent&) { select_tab(3); }, "printer", nullptr,
[this]() {return true; }, this);
m_changeable_menu_items.push_back(item_printer_tab);
if (m_plater) {
windowMenu->AppendSeparator();
append_menu_item(windowMenu, wxID_HIGHEST + 5, _(L("3&D")) + "\tCtrl+5", _(L("Show the 3D editing view")),
append_menu_item(windowMenu, wxID_HIGHEST + 5, _L("3&D") + "\tCtrl+5", _L("Show the 3D editing view"),
[this](wxCommandEvent&) { m_plater->select_view_3D("3D"); }, "editor_menu", nullptr,
[this](){return can_change_view(); }, this);
append_menu_item(windowMenu, wxID_HIGHEST + 6, _(L("Pre&view")) + "\tCtrl+6", _(L("Show the 3D slices preview")),
append_menu_item(windowMenu, wxID_HIGHEST + 6, _L("Pre&view") + "\tCtrl+6", _L("Show the 3D slices preview"),
[this](wxCommandEvent&) { m_plater->select_view_3D("Preview"); }, "preview_menu", nullptr,
[this](){return can_change_view(); }, this);
}
#if !ENABLE_GCODE_VIEWER
#if _WIN32
// This is needed on Windows to fake the CTRL+# of the window menu when using the numpad
wxAcceleratorEntry entries[6];
@ -974,83 +1168,103 @@ void MainFrame::init_menubar()
wxAcceleratorTable accel(6, entries);
SetAcceleratorTable(accel);
#endif // _WIN32
#endif // !ENABLE_GCODE_VIEWER
windowMenu->AppendSeparator();
append_menu_item(windowMenu, wxID_ANY, _(L("Print &Host Upload Queue")) + "\tCtrl+J", _(L("Display the Print Host Upload Queue window")),
append_menu_item(windowMenu, wxID_ANY, _L("Print &Host Upload Queue") + "\tCtrl+J", _L("Display the Print Host Upload Queue window"),
[this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue", nullptr,
[this]() {return true; }, this);
windowMenu->AppendSeparator();
append_menu_item(windowMenu, wxID_ANY, _(L("Open new instance")) + "\tCtrl+I", _(L("Open a new PrusaSlicer instance")),
[this](wxCommandEvent&) {
wxString path = wxStandardPaths::Get().GetExecutablePath();
#ifdef __APPLE__
boost::process::spawn((const char*)path.c_str());
#else
wxExecute(wxStandardPaths::Get().GetExecutablePath(), wxEXEC_ASYNC | wxEXEC_HIDE_CONSOLE | wxEXEC_MAKE_GROUP_LEADER);
#endif
}, "upload_queue", nullptr,
[this]() {return true; }, this);
}
// View menu
wxMenu* viewMenu = nullptr;
if (m_plater) {
viewMenu = new wxMenu();
#if ENABLE_GCODE_VIEWER
add_common_view_menu_items(viewMenu, this, std::bind(&MainFrame::can_change_view, this));
#else
// The camera control accelerators are captured by GLCanvas3D::on_char().
append_menu_item(viewMenu, wxID_ANY, _(L("Iso")) + sep + "&0", _(L("Iso View")),[this](wxCommandEvent&) { select_view("iso"); },
append_menu_item(viewMenu, wxID_ANY, _L("Iso") + sep + "&0", _L("Iso View"), [this](wxCommandEvent&) { select_view("iso"); },
"", nullptr, [this](){return can_change_view(); }, this);
viewMenu->AppendSeparator();
//TRN To be shown in the main menu View->Top
append_menu_item(viewMenu, wxID_ANY, _(L("Top")) + sep + "&1", _(L("Top View")), [this](wxCommandEvent&) { select_view("top"); },
append_menu_item(viewMenu, wxID_ANY, _L("Top") + sep + "&1", _L("Top View"), [this](wxCommandEvent&) { select_view("top"); },
"", nullptr, [this](){return can_change_view(); }, this);
//TRN To be shown in the main menu View->Bottom
append_menu_item(viewMenu, wxID_ANY, _(L("Bottom")) + sep + "&2", _(L("Bottom View")), [this](wxCommandEvent&) { select_view("bottom"); },
append_menu_item(viewMenu, wxID_ANY, _L("Bottom") + sep + "&2", _L("Bottom View"), [this](wxCommandEvent&) { select_view("bottom"); },
"", nullptr, [this](){return can_change_view(); }, this);
append_menu_item(viewMenu, wxID_ANY, _(L("Front")) + sep + "&3", _(L("Front View")), [this](wxCommandEvent&) { select_view("front"); },
append_menu_item(viewMenu, wxID_ANY, _L("Front") + sep + "&3", _L("Front View"), [this](wxCommandEvent&) { select_view("front"); },
"", nullptr, [this](){return can_change_view(); }, this);
append_menu_item(viewMenu, wxID_ANY, _(L("Rear")) + sep + "&4", _(L("Rear View")), [this](wxCommandEvent&) { select_view("rear"); },
append_menu_item(viewMenu, wxID_ANY, _L("Rear") + sep + "&4", _L("Rear View"), [this](wxCommandEvent&) { select_view("rear"); },
"", nullptr, [this](){return can_change_view(); }, this);
append_menu_item(viewMenu, wxID_ANY, _(L("Left")) + sep + "&5", _(L("Left View")), [this](wxCommandEvent&) { select_view("left"); },
append_menu_item(viewMenu, wxID_ANY, _L("Left") + sep + "&5", _L("Left View"), [this](wxCommandEvent&) { select_view("left"); },
"", nullptr, [this](){return can_change_view(); }, this);
append_menu_item(viewMenu, wxID_ANY, _(L("Right")) + sep + "&6", _(L("Right View")), [this](wxCommandEvent&) { select_view("right"); },
append_menu_item(viewMenu, wxID_ANY, _L("Right") + sep + "&6", _L("Right View"), [this](wxCommandEvent&) { select_view("right"); },
"", nullptr, [this](){return can_change_view(); }, this);
#endif // ENABLE_GCODE_VIEWER
viewMenu->AppendSeparator();
#if ENABLE_SLOPE_RENDERING
wxMenu* options_menu = new wxMenu();
append_menu_check_item(options_menu, wxID_ANY, _(L("Show &labels")) + sep + "E", _(L("Show object/instance labels in 3D scene")),
append_menu_check_item(options_menu, wxID_ANY, _L("Show &labels") + sep + "E", _L("Show object/instance labels in 3D scene"),
[this](wxCommandEvent&) { m_plater->show_view3D_labels(!m_plater->are_view3D_labels_shown()); }, this,
[this]() { return m_plater->is_view3D_shown(); }, [this]() { return m_plater->are_view3D_labels_shown(); }, this);
append_menu_check_item(options_menu, wxID_ANY, _(L("Show &slope")) + sep + "D", _(L("Objects coloring using faces' slope")),
append_menu_check_item(options_menu, wxID_ANY, _L("Show &slope") + sep + "D", _L("Objects coloring using faces' slope"),
[this](wxCommandEvent&) { m_plater->show_view3D_slope(!m_plater->is_view3D_slope_shown()); }, this,
[this]() { return m_plater->is_view3D_shown() && !m_plater->is_view3D_layers_editing_enabled(); }, [this]() { return m_plater->is_view3D_slope_shown(); }, this);
append_submenu(viewMenu, options_menu, wxID_ANY, _(L("&Options")), "");
append_submenu(viewMenu, options_menu, wxID_ANY, _L("&Options"), "");
#else
append_menu_check_item(viewMenu, wxID_ANY, _(L("Show &labels")) + sep + "E", _(L("Show object/instance labels in 3D scene")),
append_menu_check_item(viewMenu, wxID_ANY, _L("Show &labels") + sep + "E", _L("Show object/instance labels in 3D scene"),
[this](wxCommandEvent&) { m_plater->show_view3D_labels(!m_plater->are_view3D_labels_shown()); }, this,
[this]() { return m_plater->is_view3D_shown(); }, [this]() { return m_plater->are_view3D_labels_shown(); }, this);
#endif // ENABLE_SLOPE_RENDERING
append_menu_check_item(viewMenu, wxID_ANY, _(L("&Collapse sidebar")), _(L("Collapse sidebar")),
append_menu_check_item(viewMenu, wxID_ANY, _L("&Collapse sidebar"), _L("Collapse sidebar"),
[this](wxCommandEvent&) { m_plater->collapse_sidebar(!m_plater->is_sidebar_collapsed()); }, this,
[this]() { return true; }, [this]() { return m_plater->is_sidebar_collapsed(); }, this);
}
// Help menu
#if ENABLE_GCODE_VIEWER
auto helpMenu = generate_help_menu();
#else
auto helpMenu = new wxMenu();
{
append_menu_item(helpMenu, wxID_ANY, _(L("Prusa 3D &Drivers")), _(L("Open the Prusa3D drivers download page in your browser")),
append_menu_item(helpMenu, wxID_ANY, _L("Prusa 3D &Drivers"), _L("Open the Prusa3D drivers download page in your browser"),
[this](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/downloads"); });
append_menu_item(helpMenu, wxID_ANY, _(L("Software &Releases")), _(L("Open the software releases page in your browser")),
append_menu_item(helpMenu, wxID_ANY, _L("Software &Releases"), _L("Open the software releases page in your browser"),
[this](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/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, wxString::Format(_(L("%s &Website")), SLIC3R_APP_NAME),
wxString::Format(_(L("Open the %s website in your browser")), SLIC3R_APP_NAME),
append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("%s &Website"), SLIC3R_APP_NAME),
wxString::Format(_L("Open the %s website in your browser"), SLIC3R_APP_NAME),
[this](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/slicerweb"); });
// append_menu_item(helpMenu, wxID_ANY, wxString::Format(_(L("%s &Manual")), SLIC3R_APP_NAME),
// wxString::Format(_(L("Open the %s manual in your browser")), SLIC3R_APP_NAME),
// [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://manual.slic3r.org/"); });
helpMenu->AppendSeparator();
append_menu_item(helpMenu, wxID_ANY, _(L("System &Info")), _(L("Show system information")),
append_menu_item(helpMenu, wxID_ANY, _L("System &Info"), _L("Show system information"),
[this](wxCommandEvent&) { wxGetApp().system_info(); });
append_menu_item(helpMenu, wxID_ANY, _(L("Show &Configuration Folder")), _(L("Show user configuration folder (datadir)")),
append_menu_item(helpMenu, wxID_ANY, _L("Show &Configuration Folder"), _L("Show user configuration folder (datadir)"),
[this](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); });
append_menu_item(helpMenu, wxID_ANY, _(L("Report an I&ssue")), wxString::Format(_(L("Report an issue on %s")), SLIC3R_APP_NAME),
append_menu_item(helpMenu, wxID_ANY, _L("Report an I&ssue"), wxString::Format(_L("Report an issue on %s"), SLIC3R_APP_NAME),
[this](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/slic3r/issues/new"); });
append_menu_item(helpMenu, wxID_ANY, wxString::Format(_(L("&About %s")), SLIC3R_APP_NAME), _(L("Show about dialog")),
[this](wxCommandEvent&) { Slic3r::GUI::about(); });
helpMenu->AppendSeparator();
append_menu_item(helpMenu, wxID_ANY, _(L("Keyboard Shortcuts")) + sep + "&?", _(L("Show the list of the keyboard shortcuts")),
append_menu_item(helpMenu, wxID_ANY, _L("Keyboard Shortcuts") + sep + "&?", _L("Show the list of the keyboard shortcuts"),
[this](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); });
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG
helpMenu->AppendSeparator();
@ -1058,10 +1272,22 @@ void MainFrame::init_menubar()
[this](wxCommandEvent&) { wxGetApp().gcode_thumbnails_debug(); });
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG
}
#endif // ENABLE_GCODE_VIEWER
// menubar
// assign menubar to frame after appending items, otherwise special items
// will not be handled correctly
#if ENABLE_GCODE_VIEWER
m_editor_menubar = new wxMenuBar();
m_editor_menubar->Append(fileMenu, _L("&File"));
if (editMenu) m_editor_menubar->Append(editMenu, _L("&Edit"));
m_editor_menubar->Append(windowMenu, _L("&Window"));
if (viewMenu) m_editor_menubar->Append(viewMenu, _L("&View"));
// Add additional menus from C++
wxGetApp().add_config_menu(m_editor_menubar);
m_editor_menubar->Append(helpMenu, _L("&Help"));
SetMenuBar(m_editor_menubar);
#else
auto menubar = new wxMenuBar();
menubar->Append(fileMenu, _(L("&File")));
if (editMenu) menubar->Append(editMenu, _(L("&Edit")));
@ -1071,11 +1297,16 @@ void MainFrame::init_menubar()
wxGetApp().add_config_menu(menubar);
menubar->Append(helpMenu, _(L("&Help")));
SetMenuBar(menubar);
#endif // ENABLE_GCODE_VIEWER
#ifdef __APPLE__
// This fixes a bug on Mac OS where the quit command doesn't emit window close events
// wx bug: https://trac.wxwidgets.org/ticket/18328
#if ENABLE_GCODE_VIEWER
wxMenu* apple_menu = m_editor_menubar->OSXGetAppleMenu();
#else
wxMenu *apple_menu = menubar->OSXGetAppleMenu();
#endif // ENABLE_GCODE_VIEWER
if (apple_menu != nullptr) {
apple_menu->Bind(wxEVT_MENU, [this](wxCommandEvent &) {
Close();
@ -1084,10 +1315,190 @@ void MainFrame::init_menubar()
#endif
if (plater()->printer_technology() == ptSLA)
#if ENABLE_GCODE_VIEWER
update_editor_menubar();
#else
update_menubar();
#endif // ENABLE_GCODE_VIEWER
}
#if ENABLE_GCODE_VIEWER
void MainFrame::init_gcodeviewer_menubar()
{
wxMenu* fileMenu = new wxMenu;
{
append_menu_item(fileMenu, wxID_ANY, _L("&Open G-code") + dots + "\tCtrl+O", _L("Open a G-code file"),
[this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->load_gcode(); }, "open", nullptr,
[this]() {return m_plater != nullptr; }, this);
fileMenu->AppendSeparator();
append_menu_item(fileMenu, wxID_ANY, _L("Export &toolpaths as OBJ") + dots, _L("Export toolpaths as OBJ"),
[this](wxCommandEvent&) { if (m_plater != nullptr) m_plater->export_toolpaths_to_obj(); }, "export_plater", nullptr,
[this]() {return can_export_toolpaths(); }, this);
fileMenu->AppendSeparator();
append_menu_item(fileMenu, wxID_ANY, _L("Exit &G-code preview"), _L("Switch to editor mode"),
[this](wxCommandEvent&) { set_mode(EMode::Editor); });
fileMenu->AppendSeparator();
append_menu_item(fileMenu, wxID_EXIT, _L("&Quit"), wxString::Format(_L("Quit %s"), SLIC3R_APP_NAME),
[this](wxCommandEvent&) { Close(false); });
}
// View menu
wxMenu* viewMenu = nullptr;
if (m_plater != nullptr) {
viewMenu = new wxMenu();
add_common_view_menu_items(viewMenu, this, std::bind(&MainFrame::can_change_view, this));
}
// helpmenu
auto helpMenu = generate_help_menu();
m_gcodeviewer_menubar = new wxMenuBar();
m_gcodeviewer_menubar->Append(fileMenu, _L("&File"));
if ((viewMenu != nullptr))
m_gcodeviewer_menubar->Append(viewMenu, _L("&View"));
m_gcodeviewer_menubar->Append(helpMenu, _L("&Help"));
}
void MainFrame::set_mode(EMode mode)
{
if (m_mode == mode)
return;
wxBusyCursor busy;
m_mode = mode;
switch (m_mode)
{
default:
case EMode::Editor:
{
update_layout();
select_tab(0);
m_plater->reset();
m_plater->reset_gcode_toolpaths();
m_plater->Freeze();
// reinitialize undo/redo stack
m_plater->clear_undo_redo_stack_main();
m_plater->take_snapshot(_L("New Project"));
// restore sla printer if it was deselected when switching to gcode viewer mode
if (m_restore_from_gcode_viewer.sla_technology) {
m_plater->set_printer_technology(ptSLA);
m_restore_from_gcode_viewer.sla_technology = false;
}
// switch view
m_plater->select_view_3D("3D");
m_plater->select_view("iso");
// switch printbed
m_plater->set_bed_shape();
// switch menubar
SetMenuBar(m_editor_menubar);
// show toolbars
m_plater->enable_view_toolbar(true);
if (m_restore_from_gcode_viewer.collapse_toolbar_enabled) {
m_plater->get_collapse_toolbar().set_enabled(true);
m_restore_from_gcode_viewer.collapse_toolbar_enabled = false;
}
// show sidebar
if (m_restore_from_gcode_viewer.collapsed_sidebar) {
m_plater->collapse_sidebar(false);
m_restore_from_gcode_viewer.collapsed_sidebar = false;
}
m_plater->Thaw();
// SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG));
// 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("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG));
#endif // _WIN32
#if ENABLE_GCODE_VIEWER_TASKBAR_ICON
if (m_taskbar_icon != nullptr) {
m_taskbar_icon->RemoveIcon();
m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer");
}
#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON
break;
}
case EMode::GCodeViewer:
{
update_layout();
m_plater->reset();
m_plater->reset_last_loaded_gcode();
m_plater->reset_gcode_toolpaths();
m_plater->Freeze();
// reinitialize undo/redo stack
m_plater->clear_undo_redo_stack_main();
m_plater->take_snapshot(_L("New Project"));
// switch to FFF printer mode
m_restore_from_gcode_viewer.sla_technology = m_plater->set_printer_technology(ptFFF);
// switch view
m_plater->select_view_3D("Preview");
m_plater->select_view("iso");
// switch printbed
m_plater->set_bed_shape({ { 0.0, 0.0 }, { 200.0, 0.0 }, { 200.0, 200.0 }, { 0.0, 200.0 } }, "", "", true);
// switch menubar
SetMenuBar(m_gcodeviewer_menubar);
// hide toolbars
m_plater->enable_view_toolbar(false);
if (wxGetApp().app_config->get("show_collapse_button") == "1") {
m_plater->get_collapse_toolbar().set_enabled(false);
m_restore_from_gcode_viewer.collapse_toolbar_enabled = true;
}
// hide sidebar
if (wxGetApp().app_config->get("collapsed_sidebar") != "1") {
m_plater->collapse_sidebar(true);
m_restore_from_gcode_viewer.collapsed_sidebar = true;
}
m_plater->Thaw();
SetIcon(wxIcon(Slic3r::var("PrusaSlicerGCodeViewer_128px.png"), wxBITMAP_TYPE_PNG));
#if ENABLE_GCODE_VIEWER_TASKBAR_ICON
if (m_taskbar_icon != nullptr) {
m_taskbar_icon->RemoveIcon();
m_taskbar_icon->SetIcon(wxIcon(Slic3r::var("PrusaSlicerGCodeViewer_128px.png"), wxBITMAP_TYPE_PNG), "PrusaSlicer-GCode viewer");
}
#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON
break;
}
}
}
#endif // ENABLE_GCODE_VIEWER
#if ENABLE_GCODE_VIEWER
void MainFrame::update_editor_menubar()
#else
void MainFrame::update_menubar()
#endif // ENABLE_GCODE_VIEWER
{
const bool is_fff = plater()->printer_technology() == ptFFF;
@ -1580,9 +1991,12 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe)
this->SetFont(wxGetApp().normal_font());
this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
// SetIcon(wxIcon(Slic3r::var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG));
// 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));

View file

@ -7,6 +7,9 @@
#include <wx/settings.h>
#include <wx/string.h>
#include <wx/filehistory.h>
#if ENABLE_GCODE_VIEWER_TASKBAR_ICON
#include <wx/taskbar.h>
#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON
#include <string>
#include <map>
@ -68,6 +71,20 @@ class MainFrame : public DPIFrame
wxString m_qs_last_input_file = wxEmptyString;
wxString m_qs_last_output_file = wxEmptyString;
wxString m_last_config = wxEmptyString;
#if ENABLE_GCODE_VIEWER
wxMenuBar* m_editor_menubar{ nullptr };
wxMenuBar* m_gcodeviewer_menubar{ nullptr };
struct RestoreFromGCodeViewer
{
bool collapsed_sidebar{ false };
bool collapse_toolbar_enabled{ false };
bool sla_technology{ false };
};
RestoreFromGCodeViewer m_restore_from_gcode_viewer;
#endif // ENABLE_GCODE_VIEWER
#if 0
wxMenuItem* m_menu_item_repeat { nullptr }; // doesn't used now
#endif
@ -121,17 +138,36 @@ class MainFrame : public DPIFrame
Old,
New,
Dlg,
#if ENABLE_GCODE_VIEWER
GCodeViewer
#endif // ENABLE_GCODE_VIEWER
};
ESettingsLayout m_layout{ ESettingsLayout::Unknown };
#if ENABLE_GCODE_VIEWER
public:
enum class EMode : unsigned char
{
Editor,
GCodeViewer
};
private:
EMode m_mode{ EMode::Editor };
#endif // ENABLE_GCODE_VIEWER
protected:
virtual void on_dpi_changed(const wxRect &suggested_rect);
virtual void on_sys_color_changed() override;
public:
MainFrame();
#if ENABLE_GCODE_VIEWER_TASKBAR_ICON
~MainFrame();
#else
~MainFrame() = default;
#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON
void update_layout();
@ -145,8 +181,17 @@ public:
void init_tabpanel();
void create_preset_tabs();
void add_created_tab(Tab* panel);
#if ENABLE_GCODE_VIEWER
void init_editor_menubar();
void update_editor_menubar();
void init_gcodeviewer_menubar();
EMode get_mode() const { return m_mode; }
void set_mode(EMode mode);
#else
void init_menubar();
void update_menubar();
#endif // ENABLE_GCODE_VIEWER
void update_ui_from_settings();
bool is_loaded() const { return m_loaded; }
@ -181,6 +226,10 @@ public:
wxProgressDialog* m_progress_dialog { nullptr };
std::shared_ptr<ProgressStatusBar> m_statusbar;
#if ENABLE_GCODE_VIEWER_TASKBAR_ICON
wxTaskBarIcon* m_taskbar_icon{ nullptr };
#endif // ENABLE_GCODE_VIEWER_TASKBAR_ICON
#ifdef _WIN32
void* m_hDeviceNotify { nullptr };
uint32_t m_ulSHChangeNotifyRegister { 0 };

View file

@ -49,13 +49,17 @@ NotificationManager::PopNotification::PopNotification(const NotificationData &n,
, m_text2 (n.text2)
, m_evt_handler (evt_handler)
{
init();
//init();
}
NotificationManager::PopNotification::~PopNotification()
{
}
NotificationManager::PopNotification::RenderResult NotificationManager::PopNotification::render(GLCanvas3D& canvas, const float& initial_y)
{
if (!m_initialized)
{
init();
}
if (m_finished)
return RenderResult::Finished;
if (m_close_pending) {
@ -228,6 +232,7 @@ void NotificationManager::PopNotification::init()
}
m_lines_count++;
}
m_initialized = true;
}
void NotificationManager::PopNotification::set_next_window_size(ImGuiWrapper& imgui)
{
@ -492,12 +497,12 @@ void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper&
//button - if part if treggered
std::string button_text;
button_text = ImGui::CloseIconMarker;
button_text = ImGui::MinimalizeMarker;
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 10.f, win_pos_y + m_window_height - 2 * m_line_height + 1),
ImVec2(win_pos_x, win_pos_y + m_window_height),
true))
{
button_text = ImGui::CloseIconHoverMarker;
button_text = ImGui::MinimalizeHoverMarker;
}
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
@ -730,16 +735,16 @@ void NotificationManager::push_slicing_complete_notification(GLCanvas3D& canvas,
{
std::string hypertext;
int time = 10;
if(large)
{
if (has_error_notification())
return;
if (large) {
hypertext = _u8L("Export G-Code.");
time = 0;
}
NotificationData data{ NotificationType::SlicingComplete, NotificationLevel::RegularNotification, time, _u8L("Slicing finished."), hypertext };
NotificationManager::SlicingCompleteLargeNotification* notification = new NotificationManager::SlicingCompleteLargeNotification(data, m_next_id++, m_evt_handler, large);
if (push_notification_data(notification, canvas, timestamp)) {
} else {
if (!push_notification_data(notification, canvas, timestamp)) {
delete notification;
}
}
@ -909,6 +914,23 @@ bool NotificationManager::find_older(NotificationManager::PopNotification* notif
return false;
}
void NotificationManager::set_in_preview(bool preview)
{
m_in_preview = preview;
for (PopNotification* notification : m_pop_notifications) {
if (notification->get_type() == NotificationType::PlaterWarning)
notification->hide(preview);
}
}
bool NotificationManager::has_error_notification()
{
for (PopNotification* notification : m_pop_notifications) {
if (notification->get_data().level == NotificationLevel::ErrorNotification)
return true;
}
return false;
}
void NotificationManager::dpi_changed()
{

View file

@ -94,6 +94,7 @@ public:
void set_gray(bool g) { m_is_gray = g; }
void set_paused(bool p) { m_paused = p; }
bool compare_text(const std::string& text);
void hide(bool h) { m_hidden = h; }
protected:
// Call after every size change
void init();
@ -120,6 +121,7 @@ public:
const NotificationData m_data;
int m_id;
bool m_initialized { false };
// Main text
std::string m_text1;
// Clickable text
@ -130,12 +132,12 @@ public:
long m_remaining_time;
bool m_counting_down;
long m_last_remaining_time;
bool m_paused{ false };
int m_countdown_frame{ 0 };
bool m_fading_out{ false };
bool m_paused { false };
int m_countdown_frame { 0 };
bool m_fading_out { false };
// total time left when fading beggins
float m_fading_time{ 0.0f };
float m_current_fade_opacity{ 1.f };
float m_fading_time { 0.0f };
float m_current_fade_opacity { 1.f };
// If hidden the notif is alive but not visible to user
bool m_hidden { false };
// m_finished = true - does not render, marked to delete
@ -230,6 +232,7 @@ public:
// finds and closes all notifications of given type
void close_notification_of_type(const NotificationType type);
void dpi_changed();
void set_in_preview(bool preview);
private:
//pushes notification into the queue of notifications that are rendered
//can be used to create custom notification
@ -238,6 +241,7 @@ private:
//finds older notification of same type and moves it to the end of queue. returns true if found
bool find_older(NotificationManager::PopNotification* notification);
void sort_notifications();
bool has_error_notification();
wxEvtHandler* m_evt_handler;
std::deque<PopNotification*> m_pop_notifications;
@ -246,6 +250,7 @@ private:
bool m_hovered { false };
//timestamps used for slining finished - notification could be gone so it needs to be stored here
std::unordered_set<int> m_used_timestamps;
bool m_in_preview;
//prepared (basic) notifications
const std::vector<NotificationData> basic_notifications = {

View file

@ -28,6 +28,13 @@
namespace Slic3r {
namespace GUI {
// 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 std::string& default_value)
{
const char* value = (const char*)::glGetString(param);
return std::string((value != nullptr) ? value : default_value);
}
const std::string& OpenGLManager::GLInfo::get_version() const
{
if (!m_detected)
@ -85,21 +92,10 @@ float OpenGLManager::GLInfo::get_max_anisotropy() const
void OpenGLManager::GLInfo::detect() const
{
const char* data = (const char*)::glGetString(GL_VERSION);
if (data != nullptr)
m_version = data;
data = (const char*)::glGetString(GL_SHADING_LANGUAGE_VERSION);
if (data != nullptr)
m_glsl_version = data;
data = (const char*)::glGetString(GL_VENDOR);
if (data != nullptr)
m_vendor = data;
data = (const char*)::glGetString(GL_RENDERER);
if (data != nullptr)
m_renderer = data;
m_version = gl_get_string_safe(GL_VERSION, "N/A");
m_glsl_version = gl_get_string_safe(GL_SHADING_LANGUAGE_VERSION, "N/A");
m_vendor = gl_get_string_safe(GL_VENDOR, "N/A");
m_renderer = gl_get_string_safe(GL_RENDERER, "N/A");
glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &m_max_tex_size));
@ -114,13 +110,13 @@ void OpenGLManager::GLInfo::detect() const
m_detected = true;
}
bool OpenGLManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const
static bool version_greater_or_equal_to(const std::string& version, unsigned int major, unsigned int minor)
{
if (!m_detected)
detect();
if (version == "N/A")
return false;
std::vector<std::string> tokens;
boost::split(tokens, m_version, boost::is_any_of(" "), boost::token_compress_on);
boost::split(tokens, version, boost::is_any_of(" "), boost::token_compress_on);
if (tokens.empty())
return false;
@ -145,6 +141,22 @@ bool OpenGLManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, u
return gl_minor >= minor;
}
bool OpenGLManager::GLInfo::is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const
{
if (!m_detected)
detect();
return version_greater_or_equal_to(m_version, major, minor);
}
bool OpenGLManager::GLInfo::is_glsl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const
{
if (!m_detected)
detect();
return version_greater_or_equal_to(m_glsl_version, major, minor);
}
std::string OpenGLManager::GLInfo::to_string(bool format_as_html, bool extensions) const
{
if (!m_detected)
@ -159,15 +171,15 @@ std::string OpenGLManager::GLInfo::to_string(bool format_as_html, bool extension
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 << (m_version.empty() ? "N/A" : m_version) << line_end;
out << b_start << "Vendor: " << b_end << (m_vendor.empty() ? "N/A" : m_vendor) << line_end;
out << b_start << "Renderer: " << b_end << (m_renderer.empty() ? "N/A" : m_renderer) << line_end;
out << b_start << "GLSL version: " << b_end << (m_glsl_version.empty() ? "N/A" : m_glsl_version) << line_end;
out << b_start << "GL version: " << b_end << m_version << line_end;
out << b_start << "Vendor: " << b_end << m_vendor << line_end;
out << b_start << "Renderer: " << b_end << m_renderer << line_end;
out << b_start << "GLSL version: " << b_end << m_glsl_version << line_end;
if (extensions)
{
std::vector<std::string> extensions_list;
std::string extensions_str = (const char*)::glGetString(GL_EXTENSIONS);
std::string extensions_str = gl_get_string_safe(GL_EXTENSIONS, "");
boost::split(extensions_list, extensions_str, boost::is_any_of(" "), boost::token_compress_off);
if (!extensions_list.empty())
@ -199,6 +211,8 @@ OpenGLManager::OSInfo OpenGLManager::s_os_info;
OpenGLManager::~OpenGLManager()
{
m_shaders_manager.shutdown();
#if ENABLE_HACK_CLOSING_ON_OSX_10_9_5
#ifdef __APPLE__
// This is an ugly hack needed to solve the crash happening when closing the application on OSX 10.9.5 with newer wxWidgets
@ -240,19 +254,30 @@ bool OpenGLManager::init_gl()
else
s_framebuffers_type = EFramebufferType::Unknown;
if (! s_gl_info.is_version_greater_or_equal_to(2, 0)) {
// Complain about the OpenGL version.
bool valid_version = s_gl_info.is_version_greater_or_equal_to(2, 0);
if (!valid_version) {
// Complain about the OpenGL version.
wxString message = from_u8((boost::format(
_utf8(L("PrusaSlicer requires OpenGL 2.0 capable graphics driver to run correctly, \n"
"while OpenGL version %s, render %s, vendor %s was detected."))) % s_gl_info.get_version() % s_gl_info.get_renderer() % s_gl_info.get_vendor()).str());
message += "\n";
message += "\n";
message += _L("You may need to update your graphics card driver.");
#ifdef _WIN32
message += "\n";
message += "\n";
message += _L("As a workaround, you may run PrusaSlicer with a software rendered 3D graphics by running prusa-slicer.exe with the --sw_renderer parameter.");
#endif
wxMessageBox(message, wxString("PrusaSlicer - ") + _L("Unsupported OpenGL version"), wxOK | wxICON_ERROR);
}
if (valid_version) {
// load shaders
auto [result, error] = m_shaders_manager.init();
if (!result) {
wxString message = from_u8((boost::format(
_utf8(L("Unable to load the following shaders:\n%s"))) % error).str());
wxMessageBox(message, wxString("PrusaSlicer - ") + _L("Error loading shaders"), wxOK | wxICON_ERROR);
}
}
}
return true;
@ -260,8 +285,7 @@ bool OpenGLManager::init_gl()
wxGLContext* OpenGLManager::init_glcontext(wxGLCanvas& canvas)
{
if (m_context == nullptr)
{
if (m_context == nullptr) {
m_context = new wxGLContext(&canvas);
#if ENABLE_HACK_CLOSING_ON_OSX_10_9_5

View file

@ -1,6 +1,8 @@
#ifndef slic3r_OpenGLManager_hpp_
#define slic3r_OpenGLManager_hpp_
#include "GLShadersManager.hpp"
class wxWindow;
class wxGLCanvas;
class wxGLContext;
@ -41,6 +43,7 @@ public:
float get_max_anisotropy() const;
bool is_version_greater_or_equal_to(unsigned int major, unsigned int minor) const;
bool is_glsl_version_greater_or_equal_to(unsigned int major, unsigned int minor) const;
std::string to_string(bool format_as_html, bool extensions) const;
@ -70,6 +73,7 @@ private:
bool m_gl_initialized{ false };
wxGLContext* m_context{ nullptr };
GLShadersManager m_shaders_manager;
static GLInfo s_gl_info;
#if ENABLE_HACK_CLOSING_ON_OSX_10_9_5
#ifdef __APPLE__
@ -86,9 +90,11 @@ public:
~OpenGLManager();
bool init_gl();
wxGLContext* init_glcontext(wxGLCanvas& canvas);
GLShaderProgram* get_shader(const std::string& shader_name) { return m_shaders_manager.get_shader(shader_name); }
GLShaderProgram* get_current_shader() { return m_shaders_manager.get_current_shader(); }
static bool are_compressed_textures_supported() { return s_compressed_textures_supported; }
static bool can_multisample() { return s_multisample == EMultisampleState::Enabled; }
static bool are_framebuffers_supported() { return (s_framebuffers_type != EFramebufferType::Unknown); }

View file

@ -32,7 +32,11 @@
#include "libslic3r/Format/STL.hpp"
#include "libslic3r/Format/AMF.hpp"
#include "libslic3r/Format/3mf.hpp"
#if ENABLE_GCODE_VIEWER
#include "libslic3r/GCode/GCodeProcessor.hpp"
#else
#include "libslic3r/GCode/PreviewData.hpp"
#endif // ENABLE_GCODE_VIEWER
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/SLA/Hollowing.hpp"
@ -1161,35 +1165,39 @@ void Sidebar::update_sliced_info_sizer()
wxString::Format("%.2f", ps.total_cost);
p->sliced_info->SetTextAndShow(siCost, info_text, new_label);
#if ENABLE_GCODE_VIEWER
// hide the estimate time
p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A");
#else
if (ps.estimated_normal_print_time == "N/A" && ps.estimated_silent_print_time == "N/A")
p->sliced_info->SetTextAndShow(siEstimatedTime, "N/A");
else {
new_label = _L("Estimated printing time") +":";
new_label = _L("Estimated printing time") + ":";
info_text = "";
wxString str_color = _L("Color");
wxString str_pause = _L("Pause");
auto fill_labels = [str_color, str_pause](const std::vector<std::pair<CustomGCode::Type, std::string>>& times,
wxString& new_label, wxString& info_text)
{
int color_change_count = 0;
for (auto time : times)
if (time.first == CustomGCode::ColorChange)
color_change_count++;
for (int i = (int)times.size() - 1; i >= 0; --i)
auto fill_labels = [str_color, str_pause](const std::vector<std::pair<CustomGCode::Type, std::string>>& times,
wxString& new_label, wxString& info_text)
{
if (i == 0 || times[i - 1].first == CustomGCode::PausePrint)
new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count);
else if (times[i - 1].first == CustomGCode::ColorChange)
new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count--);
int color_change_count = 0;
for (auto time : times)
if (time.first == CustomGCode::ColorChange)
color_change_count++;
if (i != (int)times.size() - 1 && times[i].first == CustomGCode::PausePrint)
new_label += format_wxstr(" -> %1%", str_pause);
for (int i = (int)times.size() - 1; i >= 0; --i)
{
if (i == 0 || times[i - 1].first == CustomGCode::PausePrint)
new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count);
else if (times[i - 1].first == CustomGCode::ColorChange)
new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count--);
info_text += format_wxstr("\n%1%", times[i].second);
}
};
if (i != (int)times.size() - 1 && times[i].first == CustomGCode::PausePrint)
new_label += format_wxstr(" -> %1%", str_pause);
info_text += format_wxstr("\n%1%", times[i].second);
}
};
if (ps.estimated_normal_print_time != "N/A") {
new_label += format_wxstr("\n - %1%", _L("normal mode"));
@ -1207,8 +1215,9 @@ void Sidebar::update_sliced_info_sizer()
info_text += format_wxstr("\n%1%", ps.estimated_silent_print_time);
fill_labels(ps.estimated_silent_custom_gcode_print_times, new_label, info_text);
}
p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label);
p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label);
}
#endif // !ENABLE_GCODE_VIEWER
// if there is a wipe tower, insert number of toolchanges info into the array:
p->sliced_info->SetTextAndShow(siWTNumbetOfToolchanges, is_wipe_tower ? wxString::Format("%.d", ps.total_toolchanges) : "N/A");
@ -1217,6 +1226,8 @@ void Sidebar::update_sliced_info_sizer()
p->sliced_info->SetTextAndShow(siMateril_unit, "N/A");
}
}
Layout();
}
void Sidebar::show_sliced_info_sizer(const bool show)
@ -1338,21 +1349,78 @@ private:
Plater *plater;
static const std::regex pattern_drop;
#if ENABLE_GCODE_VIEWER
static const std::regex pattern_gcode_drop;
#endif // ENABLE_GCODE_VIEWER
};
const std::regex PlaterDropTarget::pattern_drop(".*[.](stl|obj|amf|3mf|prusa)", std::regex::icase);
#if ENABLE_GCODE_VIEWER
const std::regex PlaterDropTarget::pattern_gcode_drop(".*[.](gcode)", std::regex::icase);
#endif // ENABLE_GCODE_VIEWER
bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames)
{
std::vector<fs::path> paths;
for (const auto &filename : filenames) {
#if ENABLE_GCODE_VIEWER
#ifdef WIN32
// hides the system icon
this->MSWUpdateDragImageOnLeave();
#endif // WIN32
// gcode section
for (const auto& filename : filenames) {
fs::path path(into_path(filename));
if (std::regex_match(path.string(), pattern_drop)) {
if (std::regex_match(path.string(), pattern_gcode_drop))
paths.push_back(std::move(path));
} else {
}
if (paths.size() > 1) {
wxMessageDialog((wxWindow*)plater, _L("You can open only one .gcode file at a time."),
wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop G-code file"), wxCLOSE | wxICON_WARNING | wxCENTRE).ShowModal();
return false;
}
else if (paths.size() == 1) {
if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) {
plater->load_gcode(from_path(paths.front()));
return true;
}
else {
if (wxMessageDialog((wxWindow*)plater, _L("Do you want to switch to G-code preview ?"),
wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop G-code file"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) {
if (plater->model().objects.empty() ||
wxMessageDialog((wxWindow*)plater, _L("Switching to G-code preview mode will remove all objects, continue?"),
wxString(SLIC3R_APP_NAME) + " - " + _L("Switch to G-code preview mode"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES) {
wxGetApp().mainframe->set_mode(MainFrame::EMode::GCodeViewer);
plater->load_gcode(from_path(paths.front()));
return true;
}
}
return false;
}
}
#endif // ENABLE_GCODE_VIEWER
// model section
for (const auto &filename : filenames) {
fs::path path(into_path(filename));
if (std::regex_match(path.string(), pattern_drop))
paths.push_back(std::move(path));
else
return false;
}
#if ENABLE_GCODE_VIEWER
if (wxGetApp().mainframe->get_mode() == MainFrame::EMode::GCodeViewer) {
if (wxMessageDialog((wxWindow*)plater, _L("Do you want to exit G-code preview ?"),
wxString(SLIC3R_APP_NAME) + " - " + _L("Drag and drop model file"), wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT | wxCENTRE).ShowModal() == wxID_YES)
wxGetApp().mainframe->set_mode(MainFrame::EMode::Editor);
else
return false;
}
#endif // ENABLE_GCODE_VIEWER
wxString snapshot_label;
assert(! paths.empty());
@ -1379,13 +1447,10 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi
// because right now the plater is not cleared, we set the project file (from the latest imported .3mf or .amf file)
// only if not set yet
// if res is empty no data has been loaded
if (!res.empty() && plater->get_project_filename().empty())
{
for (std::vector<fs::path>::const_reverse_iterator it = paths.rbegin(); it != paths.rend(); ++it)
{
if (!res.empty() && plater->get_project_filename().empty()) {
for (std::vector<fs::path>::const_reverse_iterator it = paths.rbegin(); it != paths.rend(); ++it) {
std::string filename = (*it).filename().string();
if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf"))
{
if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf")) {
plater->set_project_filename(from_path(*it));
break;
}
@ -1426,7 +1491,11 @@ struct Plater::priv
Slic3r::SLAPrint sla_print;
Slic3r::Model model;
PrinterTechnology printer_technology = ptFFF;
#if ENABLE_GCODE_VIEWER
Slic3r::GCodeProcessor::Result gcode_result;
#else
Slic3r::GCodePreviewData gcode_preview_data;
#endif // ENABLE_GCODE_VIEWER
// GUI elements
wxSizer* panel_sizer{ nullptr };
@ -1443,7 +1512,7 @@ struct Plater::priv
GLToolbar view_toolbar;
GLToolbar collapse_toolbar;
Preview *preview;
NotificationManager* notification_manager;
NotificationManager* notification_manager { nullptr };
BackgroundSlicingProcess background_process;
bool suppressed_backround_processing_update { false };
@ -1539,6 +1608,15 @@ struct Plater::priv
bool init_view_toolbar();
bool init_collapse_toolbar();
#if ENABLE_GCODE_VIEWER
void update_preview_bottom_toolbar();
void update_preview_moves_slider();
#endif // ENABLE_GCODE_VIEWER
#if ENABLE_GCODE_VIEWER
void reset_gcode_toolpaths();
#endif // ENABLE_GCODE_VIEWER
void reset_all_gizmos();
void update_ui_from_settings();
void update_main_toolbar_tooltips();
@ -1659,7 +1737,7 @@ struct Plater::priv
// triangulate the bed and store the triangles into m_bed.m_triangles,
// fills the m_bed.m_grid_lines and sets m_bed.m_origin.
// Sets m_bed.m_polygon to limit the object placement.
void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model);
void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false);
bool can_delete() const;
bool can_delete_all() const;
@ -1755,7 +1833,11 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
background_process.set_fff_print(&fff_print);
background_process.set_sla_print(&sla_print);
#if ENABLE_GCODE_VIEWER
background_process.set_gcode_result(&gcode_result);
#else
background_process.set_gcode_preview_data(&gcode_preview_data);
#endif // ENABLE_GCODE_VIEWER
background_process.set_thumbnail_cb([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
{
std::packaged_task<void(ThumbnailsList&, const Vec2ds&, bool, bool, bool, bool)> task([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) {
@ -1780,7 +1862,11 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
this->q->Bind(EVT_SLICING_UPDATE, &priv::on_slicing_update, this);
view3D = new View3D(q, &model, config, &background_process);
#if ENABLE_GCODE_VIEWER
preview = new Preview(q, &model, config, &background_process, &gcode_result, [this]() { schedule_background_process(); });
#else
preview = new Preview(q, &model, config, &background_process, &gcode_preview_data, [this]() { schedule_background_process(); });
#endif // ENABLE_GCODE_VIEWER
#ifdef __APPLE__
// set default view_toolbar icons size equal to GLGizmosManager::Default_Icons_Size
@ -1860,24 +1946,19 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this);
view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this);
view3D_canvas->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this);
view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&)
{
set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values,
config->option<ConfigOptionString>("bed_custom_texture")->value,
config->option<ConfigOptionString>("bed_custom_model")->value);
});
view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [q](SimpleEvent&) { q->set_bed_shape(); });
// Preview events:
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&)
{
set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values,
config->option<ConfigOptionString>("bed_custom_texture")->value,
config->option<ConfigOptionString>("bed_custom_model")->value);
});
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [q](SimpleEvent&) { q->set_bed_shape(); });
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); });
#if ENABLE_GCODE_VIEWER
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_LAYERS_SLIDER, [this](wxKeyEvent& evt) { preview->move_layers_slider(evt); });
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_layers_slider(evt); });
#else
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); });
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); });
#endif // ENABLE_GCODE_VIEWER
q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this);
q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this);
@ -2015,6 +2096,10 @@ void Plater::priv::select_view_3D(const std::string& name)
set_current_panel(view3D);
else if (name == "Preview")
set_current_panel(preview);
#if ENABLE_GCODE_VIEWER
wxGetApp().update_ui_from_settings();
#endif // ENABLE_GCODE_VIEWER
}
void Plater::priv::select_next_view_3D()
@ -2529,8 +2614,10 @@ void Plater::priv::deselect_all()
void Plater::priv::remove(size_t obj_idx)
{
#if !ENABLE_GCODE_VIEWER
// Prevent toolpaths preview from rendering while we modify the Print object
preview->set_enabled(false);
#endif // !ENABLE_GCODE_VIEWER
if (view3D->is_layers_editing_enabled())
view3D->enable_layers_editing(false);
@ -2562,12 +2649,19 @@ void Plater::priv::reset()
set_project_filename(wxEmptyString);
#if !ENABLE_GCODE_VIEWER
// Prevent toolpaths preview from rendering while we modify the Print object
preview->set_enabled(false);
#endif // !ENABLE_GCODE_VIEWER
if (view3D->is_layers_editing_enabled())
view3D->enable_layers_editing(false);
#if ENABLE_GCODE_VIEWER
reset_gcode_toolpaths();
gcode_result.reset();
#endif // ENABLE_GCODE_VIEWER
// Stop and reset the Print content.
this->background_process.reset();
model.clear_objects();
@ -2713,10 +2807,19 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
this->sidebar->show_sliced_info_sizer(false);
// Reset preview canvases. If the print has been invalidated, the preview canvases will be cleared.
// Otherwise they will be just refreshed.
#if ENABLE_GCODE_VIEWER
if (this->preview != nullptr) {
// If the preview is not visible, the following line just invalidates the preview,
// but the G-code paths or SLA preview are calculated first once the preview is made visible.
this->preview->get_canvas3d()->reset_gcode_toolpaths();
this->preview->reload_print();
}
#else
if (this->preview != nullptr)
// If the preview is not visible, the following line just invalidates the preview,
// but the G-code paths or SLA preview are calculated first once the preview is made visible.
this->preview->reload_print();
#endif // ENABLE_GCODE_VIEWER
// In FDM mode, we need to reload the 3D scene because of the wipe tower preview box.
// In SLA mode, we need to reload the 3D scene every time to show the support structures.
if (this->printer_technology == ptSLA || (this->printer_technology == ptFFF && this->config->opt_bool("wipe_tower")))
@ -2841,7 +2944,7 @@ void Plater::priv::export_gcode(fs::path output_path, bool output_path_on_remova
if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
return;
show_warning_dialog = true;
show_warning_dialog = true;
if (! output_path.empty()) {
background_process.schedule_export(output_path.string(), output_path_on_removable_media);
} else {
@ -3201,6 +3304,8 @@ void Plater::priv::set_current_panel(wxPanel* panel)
// sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably)
view3D->set_as_dirty();
view_toolbar.select_item("3D");
if(notification_manager != nullptr)
notification_manager->set_in_preview(false);
}
else if (current_panel == preview)
{
@ -3215,6 +3320,8 @@ void Plater::priv::set_current_panel(wxPanel* panel)
preview->set_as_dirty();
view_toolbar.select_item("Preview");
if (notification_manager != nullptr)
notification_manager->set_in_preview(true);
}
current_panel->SetFocusFromKbd();
@ -3378,6 +3485,10 @@ void Plater::priv::add_warning(const Slic3r::PrintStateBase::Warning& warning, s
}
void Plater::priv::actualizate_warnings(const Model& model, size_t print_oid)
{
if (model.objects.size() == 0) {
clear_warnings();
return;
}
std::vector<size_t> living_oids;
living_oids.push_back(model.id().id);
living_oids.push_back(print_oid);
@ -3952,6 +4063,25 @@ bool Plater::priv::init_collapse_toolbar()
return true;
}
#if ENABLE_GCODE_VIEWER
void Plater::priv::update_preview_bottom_toolbar()
{
preview->update_bottom_toolbar();
}
void Plater::priv::update_preview_moves_slider()
{
preview->update_moves_slider();
}
#endif // ENABLE_GCODE_VIEWER
#if ENABLE_GCODE_VIEWER
void Plater::priv::reset_gcode_toolpaths()
{
preview->get_canvas3d()->reset_gcode_toolpaths();
}
#endif // ENABLE_GCODE_VIEWER
bool Plater::priv::can_set_instance_to_object() const
{
const int obj_idx = get_selected_object_idx();
@ -4026,11 +4156,10 @@ bool Plater::priv::can_reload_from_disk() const
return !paths.empty();
}
void Plater::priv::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model)
void Plater::priv::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom)
{
bool new_shape = bed.set_shape(shape, custom_texture, custom_model);
if (new_shape)
{
bool new_shape = bed.set_shape(shape, custom_texture, custom_model, force_as_custom);
if (new_shape) {
if (view3D) view3D->bed_shape_changed();
if (preview) preview->bed_shape_changed();
}
@ -4483,6 +4612,44 @@ void Plater::extract_config_from_project()
load_files(input_paths, false, true);
}
#if ENABLE_GCODE_VIEWER
void Plater::load_gcode()
{
// Ask user for a gcode file name.
wxString input_file;
wxGetApp().load_gcode(this, input_file);
// And finally load the gcode file.
load_gcode(input_file);
}
void Plater::load_gcode(const wxString& filename)
{
if (filename.empty() || m_last_loaded_gcode == filename)
return;
m_last_loaded_gcode = filename;
// cleanup view before to start loading/processing
p->gcode_result.reset();
reset_gcode_toolpaths();
p->preview->reload_print(false);
p->get_current_canvas3D()->render();
wxBusyCursor wait;
// process gcode
GCodeProcessor processor;
processor.enable_producers(true);
processor.enable_machine_envelope_processing(true);
processor.process_file(filename.ToUTF8().data());
p->gcode_result = std::move(processor.extract_result());
// show results
p->preview->reload_print(false);
p->preview->get_canvas3d()->zoom_to_gcode();
}
#endif // ENABLE_GCODE_VIEWER
std::vector<size_t> Plater::load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool imperial_units /*= false*/) { return p->load_files(input_files, load_model, load_config, imperial_units); }
// To be called when providing a list of files to the GUI slic3r on command line.
@ -4697,8 +4864,8 @@ void Plater::export_gcode(bool prefer_removable)
if (p->model.objects.empty())
return;
if (p->process_completed_with_error)//here
return;
if (p->process_completed_with_error)//here
return;
// If possible, remove accents from accented latin characters.
// This function is useful for generating file names to be processed by legacy firmwares.
@ -4983,6 +5150,9 @@ void Plater::reslice()
if ((state & priv::UPDATE_BACKGROUND_PROCESS_INVALID) != 0)
return;
#if ENABLE_GCODE_VIEWER
bool clean_gcode_toolpaths = true;
#endif // ENABLE_GCODE_VIEWER
if (p->background_process.running())
{
if (wxGetApp().get_mode() == comSimple)
@ -4995,9 +5165,19 @@ void Plater::reslice()
}
else if (!p->background_process.empty() && !p->background_process.idle())
p->show_action_buttons(true);
#if ENABLE_GCODE_VIEWER
else
clean_gcode_toolpaths = false;
if (clean_gcode_toolpaths)
reset_gcode_toolpaths();
// update type of preview
p->preview->update_view_type(!clean_gcode_toolpaths);
#else
// update type of preview
p->preview->update_view_type(true);
#endif // ENABLE_GCODE_VIEWER
}
void Plater::reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages)
@ -5235,9 +5415,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
}
if (bed_shape_changed)
p->set_bed_shape(p->config->option<ConfigOptionPoints>("bed_shape")->values,
p->config->option<ConfigOptionString>("bed_custom_texture")->value,
p->config->option<ConfigOptionString>("bed_custom_model")->value);
set_bed_shape();
if (update_scheduled)
update();
@ -5248,11 +5426,24 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
void Plater::set_bed_shape() const
{
p->set_bed_shape(p->config->option<ConfigOptionPoints>("bed_shape")->values,
#if ENABLE_GCODE_VIEWER
set_bed_shape(p->config->option<ConfigOptionPoints>("bed_shape")->values,
p->config->option<ConfigOptionString>("bed_custom_texture")->value,
p->config->option<ConfigOptionString>("bed_custom_model")->value);
#else
p->set_bed_shape(p->config->option<ConfigOptionPoints>("bed_shape")->values,
p->config->option<ConfigOptionString>("bed_custom_texture")->value,
p->config->option<ConfigOptionString>("bed_custom_model")->value);
#endif // ENABLE_GCODE_VIEWER
}
#if ENABLE_GCODE_VIEWER
void Plater::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom) const
{
p->set_bed_shape(shape, custom_texture, custom_model, force_as_custom);
}
#endif // ENABLE_GCODE_VIEWER
void Plater::force_filament_colors_update()
{
bool update_scheduled = false;
@ -5413,24 +5604,43 @@ PrinterTechnology Plater::printer_technology() const
const DynamicPrintConfig * Plater::config() const { return p->config; }
#if ENABLE_GCODE_VIEWER
bool Plater::set_printer_technology(PrinterTechnology printer_technology)
#else
void Plater::set_printer_technology(PrinterTechnology printer_technology)
#endif // ENABLE_GCODE_VIEWER
{
p->printer_technology = printer_technology;
#if ENABLE_GCODE_VIEWER
bool ret = p->background_process.select_technology(printer_technology);
if (ret) {
// Update the active presets.
}
#else
if (p->background_process.select_technology(printer_technology)) {
// Update the active presets.
}
#endif // ENABLE_GCODE_VIEWER
//FIXME for SLA synchronize
//p->background_process.apply(Model)!
p->label_btn_export = printer_technology == ptFFF ? L("Export G-code") : L("Export");
p->label_btn_send = printer_technology == ptFFF ? L("Send G-code") : L("Send to printer");
if (wxGetApp().mainframe)
if (wxGetApp().mainframe != nullptr)
#if ENABLE_GCODE_VIEWER
wxGetApp().mainframe->update_editor_menubar();
#else
wxGetApp().mainframe->update_menubar();
#endif // ENABLE_GCODE_VIEWER
p->update_main_toolbar_tooltips();
p->sidebar->get_searcher().set_printer_technology(printer_technology);
#if ENABLE_GCODE_VIEWER
return ret;
#endif // ENABLE_GCODE_VIEWER
}
void Plater::changed_object(int obj_idx)
@ -5578,11 +5788,25 @@ bool Plater::init_view_toolbar()
return p->init_view_toolbar();
}
#if ENABLE_GCODE_VIEWER
void Plater::enable_view_toolbar(bool enable)
{
p->view_toolbar.set_enabled(enable);
}
#endif // ENABLE_GCODE_VIEWER
bool Plater::init_collapse_toolbar()
{
return p->init_collapse_toolbar();
}
#if ENABLE_GCODE_VIEWER
void Plater::enable_collapse_toolbar(bool enable)
{
p->collapse_toolbar.set_enabled(enable);
}
#endif // ENABLE_GCODE_VIEWER
const Camera& Plater::get_camera() const
{
return p->camera;
@ -5636,6 +5860,23 @@ GLToolbar& Plater::get_collapse_toolbar()
return p->collapse_toolbar;
}
#if ENABLE_GCODE_VIEWER
void Plater::update_preview_bottom_toolbar()
{
p->update_preview_bottom_toolbar();
}
void Plater::update_preview_moves_slider()
{
p->update_preview_moves_slider();
}
void Plater::reset_gcode_toolpaths()
{
p->reset_gcode_toolpaths();
}
#endif // ENABLE_GCODE_VIEWER
const Mouse3DController& Plater::get_mouse3d_controller() const
{
return p->mouse3d_controller;
@ -5703,6 +5944,9 @@ bool Plater::can_undo() const { return p->undo_redo_stack().has_undo_snapshot();
bool Plater::can_redo() const { return p->undo_redo_stack().has_redo_snapshot(); }
bool Plater::can_reload_from_disk() const { return p->can_reload_from_disk(); }
const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); }
#if ENABLE_GCODE_VIEWER
void Plater::clear_undo_redo_stack_main() { p->undo_redo_stack_main().clear(); }
#endif // ENABLE_GCODE_VIEWER
void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); }
void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); }
bool Plater::inside_snapshot_capture() { return p->inside_snapshot_capture(); }

View file

@ -140,6 +140,10 @@ public:
void add_model(bool imperial_units = false);
void import_sl1_archive();
void extract_config_from_project();
#if ENABLE_GCODE_VIEWER
void load_gcode();
void load_gcode(const wxString& filename);
#endif // ENABLE_GCODE_VIEWER
std::vector<size_t> load_files(const std::vector<boost::filesystem::path>& input_files, bool load_model = true, bool load_config = true, bool imperial_units = false);
// To be called when providing a list of files to the GUI slic3r on command line.
@ -219,6 +223,9 @@ public:
bool search_string_getter(int idx, const char** label, const char** tooltip);
// For the memory statistics.
const Slic3r::UndoRedo::Stack& undo_redo_stack_main() const;
#if ENABLE_GCODE_VIEWER
void clear_undo_redo_stack_main();
#endif // ENABLE_GCODE_VIEWER
// Enter / leave the Gizmos specific Undo / Redo stack. To be used by the SLA support point editing gizmo.
void enter_gizmos_stack();
void leave_gizmos_stack();
@ -256,7 +263,11 @@ public:
PrinterTechnology printer_technology() const;
const DynamicPrintConfig * config() const;
#if ENABLE_GCODE_VIEWER
bool set_printer_technology(PrinterTechnology printer_technology);
#else
void set_printer_technology(PrinterTechnology printer_technology);
#endif // ENABLE_GCODE_VIEWER
void copy_selection_to_clipboard();
void paste_from_clipboard();
@ -282,7 +293,13 @@ public:
void sys_color_changed();
bool init_view_toolbar();
#if ENABLE_GCODE_VIEWER
void enable_view_toolbar(bool enable);
#endif // ENABLE_GCODE_VIEWER
bool init_collapse_toolbar();
#if ENABLE_GCODE_VIEWER
void enable_collapse_toolbar(bool enable);
#endif // ENABLE_GCODE_VIEWER
const Camera& get_camera() const;
Camera& get_camera();
@ -301,10 +318,21 @@ public:
const GLToolbar& get_collapse_toolbar() const;
GLToolbar& get_collapse_toolbar();
#if ENABLE_GCODE_VIEWER
void update_preview_bottom_toolbar();
void update_preview_moves_slider();
void reset_gcode_toolpaths();
void reset_last_loaded_gcode() { m_last_loaded_gcode = ""; }
#endif // ENABLE_GCODE_VIEWER
const Mouse3DController& get_mouse3d_controller() const;
Mouse3DController& get_mouse3d_controller();
void set_bed_shape() const;
#if ENABLE_GCODE_VIEWER
void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false) const;
#endif // ENABLE_GCODE_VIEWER
const NotificationManager* get_notification_manager() const;
NotificationManager* get_notification_manager();
@ -358,6 +386,10 @@ private:
bool m_tracking_popup_menu = false;
wxString m_tracking_popup_menu_error_message;
#if ENABLE_GCODE_VIEWER
wxString m_last_loaded_gcode;
#endif // ENABLE_GCODE_VIEWER
void suppress_snapshots();
void allow_snapshots();

View file

@ -16,8 +16,11 @@
#include <GL/glew.h>
#include <boost/algorithm/string/predicate.hpp>
#if ENABLE_GCODE_VIEWER
#include <boost/log/trivial.hpp>
#endif // ENABLE_GCODE_VIEWER
static const float UNIFORM_SCALE_COLOR[3] = { 1.0f, 0.38f, 0.0f };
static const float UNIFORM_SCALE_COLOR[4] = { 0.923f, 0.504f, 0.264f, 1.0f };
namespace Slic3r {
namespace GUI {
@ -110,8 +113,10 @@ Selection::Selection()
, m_valid(false)
, m_scale_factor(1.0f)
{
#if !ENABLE_GCODE_VIEWER
m_arrow.reset(new GLArrow);
m_curved_arrow.reset(new GLCurvedArrow(16));
#endif // !ENABLE_GCODE_VIEWER
this->set_bounding_boxes_dirty();
#if ENABLE_RENDER_SELECTION_CENTER
@ -138,6 +143,10 @@ void Selection::set_volumes(GLVolumePtrs* volumes)
// Init shall be called from the OpenGL render function, so that the OpenGL context is initialized!
bool Selection::init()
{
#if ENABLE_GCODE_VIEWER
m_arrow.init_from(straight_arrow(10.0f, 5.0f, 5.0f, 10.0f, 1.0f));
m_curved_arrow.init_from(circular_arrow(16, 10.0f, 5.0f, 10.0f, 5.0f, 1.0f));
#else
if (!m_arrow->init())
return false;
@ -147,6 +156,7 @@ bool Selection::init()
return false;
m_curved_arrow->set_scale(5.0 * Vec3d::Ones());
#endif //ENABLE_GCODE_VIEWER
return true;
}
@ -1261,40 +1271,40 @@ void Selection::render_center(bool gizmo_is_dragging) const
}
#endif // ENABLE_RENDER_SELECTION_CENTER
void Selection::render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const
void Selection::render_sidebar_hints(const std::string& sidebar_field) const
{
if (sidebar_field.empty())
return;
GLShaderProgram* shader = nullptr;
if (!boost::starts_with(sidebar_field, "layer"))
{
shader.start_using();
shader = wxGetApp().get_shader("gouraud_light");
if (shader == nullptr)
return;
shader->start_using();
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_LIGHTING));
}
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glPushMatrix());
if (!boost::starts_with(sidebar_field, "layer"))
{
if (!boost::starts_with(sidebar_field, "layer")) {
const Vec3d& center = get_bounding_box().center();
if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates())
{
if (is_single_full_instance() && !wxGetApp().obj_manipul()->get_world_coordinates()) {
glsafe(::glTranslated(center(0), center(1), center(2)));
if (!boost::starts_with(sidebar_field, "position"))
{
if (!boost::starts_with(sidebar_field, "position")) {
Transform3d orient_matrix = Transform3d::Identity();
if (boost::starts_with(sidebar_field, "scale"))
orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
else if (boost::starts_with(sidebar_field, "rotation"))
{
else if (boost::starts_with(sidebar_field, "rotation")) {
if (boost::ends_with(sidebar_field, "x"))
orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
else if (boost::ends_with(sidebar_field, "y"))
{
else if (boost::ends_with(sidebar_field, "y")) {
const Vec3d& rotation = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_rotation();
if (rotation(0) == 0.0)
orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
@ -1305,21 +1315,16 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field, const Sha
glsafe(::glMultMatrixd(orient_matrix.data()));
}
}
else if (is_single_volume() || is_single_modifier())
{
} else if (is_single_volume() || is_single_modifier()) {
glsafe(::glTranslated(center(0), center(1), center(2)));
Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
if (!boost::starts_with(sidebar_field, "position"))
orient_matrix = orient_matrix * (*m_volumes)[*m_list.begin()]->get_volume_transformation().get_matrix(true, false, true, true);
glsafe(::glMultMatrixd(orient_matrix.data()));
}
else
{
} else {
glsafe(::glTranslated(center(0), center(1), center(2)));
if (requires_local_axes())
{
if (requires_local_axes()) {
Transform3d orient_matrix = (*m_volumes)[*m_list.begin()]->get_instance_transformation().get_matrix(true, false, true, true);
glsafe(::glMultMatrixd(orient_matrix.data()));
}
@ -1330,20 +1335,15 @@ void Selection::render_sidebar_hints(const std::string& sidebar_field, const Sha
render_sidebar_position_hints(sidebar_field);
else if (boost::starts_with(sidebar_field, "rotation"))
render_sidebar_rotation_hints(sidebar_field);
else if (boost::starts_with(sidebar_field, "scale"))
else if (boost::starts_with(sidebar_field, "scale") || boost::starts_with(sidebar_field, "size"))
render_sidebar_scale_hints(sidebar_field);
else if (boost::starts_with(sidebar_field, "size"))
render_sidebar_size_hints(sidebar_field);
else if (boost::starts_with(sidebar_field, "layer"))
render_sidebar_layers_hints(sidebar_field);
glsafe(::glPopMatrix());
if (!boost::starts_with(sidebar_field, "layer"))
{
glsafe(::glDisable(GL_LIGHTING));
shader.stop_using();
}
shader->stop_using();
}
bool Selection::requires_local_axes() const
@ -1944,6 +1944,29 @@ void Selection::render_bounding_box(const BoundingBoxf3& box, float* color) cons
glsafe(::glEnd());
}
#if ENABLE_GCODE_VIEWER
void Selection::render_sidebar_position_hints(const std::string& sidebar_field) const
{
auto set_color = [](Axis axis) {
GLShaderProgram* shader = wxGetApp().get_current_shader();
if (shader != nullptr)
shader->set_uniform("uniform_color", AXES_COLOR[axis], 4);
};
if (boost::ends_with(sidebar_field, "x")) {
set_color(X);
glsafe(::glRotated(-90.0, 0.0, 0.0, 1.0));
m_arrow.render();
} else if (boost::ends_with(sidebar_field, "y")) {
set_color(Y);
m_arrow.render();
} else if (boost::ends_with(sidebar_field, "z")) {
set_color(Z);
glsafe(::glRotated(90.0, 1.0, 0.0, 0.0));
m_arrow.render();
}
}
#else
void Selection::render_sidebar_position_hints(const std::string& sidebar_field) const
{
if (boost::ends_with(sidebar_field, "x"))
@ -1959,8 +1982,38 @@ void Selection::render_sidebar_position_hints(const std::string& sidebar_field)
render_sidebar_position_hint(Z);
}
}
#endif // ENABLE_GCODE_VIEWER
#if ENABLE_GCODE_VIEWER
void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field) const
{
auto set_color = [](Axis axis) {
GLShaderProgram* shader = wxGetApp().get_current_shader();
if (shader != nullptr)
shader->set_uniform("uniform_color", AXES_COLOR[axis], 4);
};
auto render_sidebar_rotation_hint = [this]() {
m_curved_arrow.render();
glsafe(::glRotated(180.0, 0.0, 0.0, 1.0));
m_curved_arrow.render();
};
if (boost::ends_with(sidebar_field, "x")) {
set_color(X);
glsafe(::glRotated(90.0, 0.0, 1.0, 0.0));
render_sidebar_rotation_hint();
} else if (boost::ends_with(sidebar_field, "y")) {
set_color(Y);
glsafe(::glRotated(-90.0, 1.0, 0.0, 0.0));
render_sidebar_rotation_hint();
} else if (boost::ends_with(sidebar_field, "z")) {
set_color(Z);
render_sidebar_rotation_hint();
}
}
#else
void Selection::render_sidebar_rotation_hints(const std::string & sidebar_field) const
{
if (boost::ends_with(sidebar_field, "x"))
{
@ -1975,11 +2028,33 @@ void Selection::render_sidebar_rotation_hints(const std::string& sidebar_field)
else if (boost::ends_with(sidebar_field, "z"))
render_sidebar_rotation_hint(Z);
}
#endif // ENABLE_GCODE_VIEWER
void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) const
{
bool uniform_scale = requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling();
auto render_sidebar_scale_hint = [this, uniform_scale](Axis axis) {
GLShaderProgram* shader = wxGetApp().get_current_shader();
if (shader != nullptr)
shader->set_uniform("uniform_color", uniform_scale ? UNIFORM_SCALE_COLOR : AXES_COLOR[axis], 4);
glsafe(::glTranslated(0.0, 5.0, 0.0));
#if ENABLE_GCODE_VIEWER
m_arrow.render();
#else
m_arrow->render();
#endif // ENABLE_GCODE_VIEWER
glsafe(::glTranslated(0.0, -10.0, 0.0));
glsafe(::glRotated(180.0, 0.0, 0.0, 1.0));
#if ENABLE_GCODE_VIEWER
m_arrow.render();
#else
m_arrow->render();
#endif // ENABLE_GCODE_VIEWER
};
if (boost::ends_with(sidebar_field, "x") || uniform_scale)
{
glsafe(::glPushMatrix());
@ -2004,11 +2079,6 @@ void Selection::render_sidebar_scale_hints(const std::string& sidebar_field) con
}
}
void Selection::render_sidebar_size_hints(const std::string& sidebar_field) const
{
render_sidebar_scale_hints(sidebar_field);
}
void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) const
{
static const double Margin = 10.0;
@ -2081,6 +2151,7 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co
glsafe(::glDisable(GL_BLEND));
}
#if !ENABLE_GCODE_VIEWER
void Selection::render_sidebar_position_hint(Axis axis) const
{
m_arrow->set_color(AXES_COLOR[axis], 3);
@ -2107,10 +2178,7 @@ void Selection::render_sidebar_scale_hint(Axis axis) const
glsafe(::glRotated(180.0, 0.0, 0.0, 1.0));
m_arrow->render();
}
void Selection::render_sidebar_size_hint(Axis axis, double length) const
{
}
#endif // !ENABLE_GCODE_VIEWER
#ifndef NDEBUG
static bool is_rotation_xy_synchronized(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to)

View file

@ -3,7 +3,9 @@
#include <set>
#include "libslic3r/Geometry.hpp"
#if ENABLE_GCODE_VIEWER
#include "GLModel.hpp"
#endif // ENABLE_GCODE_VIEWER
#if ENABLE_RENDER_SELECTION_CENTER
class GLUquadric;
@ -19,6 +21,7 @@ class GLVolume;
class GLArrow;
class GLCurvedArrow;
class DynamicPrintConfig;
class GLShaderProgram;
using GLVolumePtrs = std::vector<GLVolume*>;
using ModelObjectPtrs = std::vector<ModelObject*>;
@ -218,10 +221,15 @@ private:
GLUquadricObj* m_quadric;
#endif // ENABLE_RENDER_SELECTION_CENTER
#if ENABLE_GCODE_VIEWER
GLModel m_arrow;
GLModel m_curved_arrow;
#else
// Arrows are saved through pointers to avoid including 3DScene.hpp.
// It also allows mutability.
std::unique_ptr<GLArrow> m_arrow;
std::unique_ptr<GLCurvedArrow> m_curved_arrow;
#endif // ENABLE_GCODE_VIEWER
mutable float m_scale_factor;
@ -336,7 +344,7 @@ public:
#if ENABLE_RENDER_SELECTION_CENTER
void render_center(bool gizmo_is_dragging) const;
#endif // ENABLE_RENDER_SELECTION_CENTER
void render_sidebar_hints(const std::string& sidebar_field, const Shader& shader) const;
void render_sidebar_hints(const std::string& sidebar_field) const;
bool requires_local_axes() const;
@ -377,12 +385,12 @@ private:
void render_sidebar_position_hints(const std::string& sidebar_field) const;
void render_sidebar_rotation_hints(const std::string& sidebar_field) const;
void render_sidebar_scale_hints(const std::string& sidebar_field) const;
void render_sidebar_size_hints(const std::string& sidebar_field) const;
void render_sidebar_layers_hints(const std::string& sidebar_field) const;
#if !ENABLE_GCODE_VIEWER
void render_sidebar_position_hint(Axis axis) const;
void render_sidebar_rotation_hint(Axis axis) const;
void render_sidebar_scale_hint(Axis axis) const;
void render_sidebar_size_hint(Axis axis, double length) const;
#endif // !ENABLE_GCODE_VIEWER
public:
enum SyncRotationType {

View file

@ -174,7 +174,6 @@ wxMenuItem* append_menu_check_item(wxMenu* menu, int id, const wxString& string,
const unsigned int wxCheckListBoxComboPopup::DefaultWidth = 200;
const unsigned int wxCheckListBoxComboPopup::DefaultHeight = 200;
const unsigned int wxCheckListBoxComboPopup::DefaultItemHeight = 18;
bool wxCheckListBoxComboPopup::Create(wxWindow* parent)
{
@ -198,17 +197,22 @@ wxString wxCheckListBoxComboPopup::GetStringValue() const
wxSize wxCheckListBoxComboPopup::GetAdjustedSize(int minWidth, int prefHeight, int maxHeight)
{
// matches owner wxComboCtrl's width
// and sets height dinamically in dependence of contained items count
// set width dinamically in dependence of items text
// and set height dinamically in dependence of items count
wxComboCtrl* cmb = GetComboCtrl();
if (cmb != nullptr)
{
if (cmb != nullptr) {
wxSize size = GetComboCtrl()->GetSize();
unsigned int count = GetCount();
if (count > 0)
size.SetHeight(count * DefaultItemHeight);
if (count > 0) {
int max_width = size.x;
for (unsigned int i = 0; i < count; ++i) {
max_width = std::max(max_width, 60 + GetTextExtent(GetString(i)).x);
}
size.SetWidth(max_width);
size.SetHeight(count * cmb->GetCharHeight());
}
else
size.SetHeight(DefaultHeight);

View file

@ -63,7 +63,6 @@ class wxCheckListBoxComboPopup : public wxCheckListBox, public wxComboPopup
{
static const unsigned int DefaultWidth;
static const unsigned int DefaultHeight;
static const unsigned int DefaultItemHeight;
wxString m_text;

View file

@ -0,0 +1,19 @@
#ifndef slic3r_GUI_Profile_hpp_
#define slic3r_GUI_Profile_hpp_
// Profiling support using the Shiny intrusive profiler
//#define SLIC3R_PROFILE_GUI
#if defined(SLIC3R_PROFILE) && defined(SLIC3R_PROFILE_GUI)
#include <Shiny/Shiny.h>
#define SLIC3R_GUI_PROFILE_FUNC() PROFILE_FUNC()
#define SLIC3R_GUI_PROFILE_BLOCK(name) PROFILE_BLOCK(name)
#define SLIC3R_GUI_PROFILE_UPDATE() PROFILE_UPDATE()
#define SLIC3R_GUI_PROFILE_OUTPUT(x) PROFILE_OUTPUT(x)
#else
#define SLIC3R_GUI_PROFILE_FUNC()
#define SLIC3R_GUI_PROFILE_BLOCK(name)
#define SLIC3R_GUI_PROFILE_UPDATE()
#define SLIC3R_GUI_PROFILE_OUTPUT(x)
#endif
#endif // slic3r_GUI_Profile_hpp_

View file

@ -1,314 +0,0 @@
#include "SLAImport.hpp"
#include <sstream>
#include "libslic3r/SlicesToTriangleMesh.hpp"
#include "libslic3r/MarchingSquares.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/MTUtils.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/SLA/RasterBase.hpp"
#include "libslic3r/miniz_extension.hpp"
#include <boost/property_tree/ini_parser.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/algorithm/string.hpp>
#include <wx/image.h>
#include <wx/mstream.h>
namespace marchsq {
// Specialize this struct to register a raster type for the Marching squares alg
template<> struct _RasterTraits<wxImage> {
using Rst = wxImage;
// The type of pixel cell in the raster
using ValueType = uint8_t;
// Value at a given position
static uint8_t get(const Rst &rst, size_t row, size_t col)
{
return rst.GetRed(col, row);
}
// Number of rows and cols of the raster
static size_t rows(const Rst &rst) { return rst.GetHeight(); }
static size_t cols(const Rst &rst) { return rst.GetWidth(); }
};
} // namespace marchsq
namespace Slic3r {
namespace {
struct ArchiveData {
boost::property_tree::ptree profile, config;
std::vector<sla::EncodedRaster> images;
};
static const constexpr char *CONFIG_FNAME = "config.ini";
static const constexpr char *PROFILE_FNAME = "prusaslicer.ini";
boost::property_tree::ptree read_ini(const mz_zip_archive_file_stat &entry,
MZ_Archive & zip)
{
std::string buf(size_t(entry.m_uncomp_size), '\0');
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
buf.data(), buf.size(), 0))
throw std::runtime_error(zip.get_errorstr());
boost::property_tree::ptree tree;
std::stringstream ss(buf);
boost::property_tree::read_ini(ss, tree);
return tree;
}
sla::EncodedRaster read_png(const mz_zip_archive_file_stat &entry,
MZ_Archive & zip,
const std::string & name)
{
std::vector<uint8_t> buf(entry.m_uncomp_size);
if (!mz_zip_reader_extract_file_to_mem(&zip.arch, entry.m_filename,
buf.data(), buf.size(), 0))
throw std::runtime_error(zip.get_errorstr());
return sla::EncodedRaster(std::move(buf),
name.empty() ? entry.m_filename : name);
}
ArchiveData extract_sla_archive(const std::string &zipfname,
const std::string &exclude)
{
ArchiveData arch;
// Little RAII
struct Arch: public MZ_Archive {
Arch(const std::string &fname) {
if (!open_zip_reader(&arch, fname))
throw std::runtime_error(get_errorstr());
}
~Arch() { close_zip_reader(&arch); }
} zip (zipfname);
mz_uint num_entries = mz_zip_reader_get_num_files(&zip.arch);
for (mz_uint i = 0; i < num_entries; ++i)
{
mz_zip_archive_file_stat entry;
if (mz_zip_reader_file_stat(&zip.arch, i, &entry))
{
std::string name = entry.m_filename;
boost::algorithm::to_lower(name);
if (boost::algorithm::contains(name, exclude)) continue;
if (name == CONFIG_FNAME) arch.config = read_ini(entry, zip);
if (name == PROFILE_FNAME) arch.profile = read_ini(entry, zip);
if (boost::filesystem::path(name).extension().string() == ".png") {
auto it = std::lower_bound(
arch.images.begin(), arch.images.end(), sla::EncodedRaster({}, name),
[](const sla::EncodedRaster &r1, const sla::EncodedRaster &r2) {
return std::less<std::string>()(r1.extension(), r2.extension());
});
arch.images.insert(it, read_png(entry, zip, name));
}
}
}
return arch;
}
ExPolygons rings_to_expolygons(const std::vector<marchsq::Ring> &rings,
double px_w, double px_h)
{
ExPolygons polys; polys.reserve(rings.size());
for (const marchsq::Ring &ring : rings) {
Polygon poly; Points &pts = poly.points;
pts.reserve(ring.size());
for (const marchsq::Coord &crd : ring)
pts.emplace_back(scaled(crd.c * px_w), scaled(crd.r * px_h));
polys.emplace_back(poly);
}
// reverse the raster transformations
return union_ex(polys);
}
template<class Fn> void foreach_vertex(ExPolygon &poly, Fn &&fn)
{
for (auto &p : poly.contour.points) fn(p);
for (auto &h : poly.holes)
for (auto &p : h.points) fn(p);
}
void invert_raster_trafo(ExPolygons & expolys,
const sla::RasterBase::Trafo &trafo,
coord_t width,
coord_t height)
{
for (auto &expoly : expolys) {
if (trafo.mirror_y)
foreach_vertex(expoly, [height](Point &p) {p.y() = height - p.y(); });
if (trafo.mirror_x)
foreach_vertex(expoly, [width](Point &p) {p.x() = width - p.x(); });
expoly.translate(-trafo.center_x, -trafo.center_y);
if (trafo.flipXY)
foreach_vertex(expoly, [](Point &p) { std::swap(p.x(), p.y()); });
if ((trafo.mirror_x + trafo.mirror_y + trafo.flipXY) % 2) {
expoly.contour.reverse();
for (auto &h : expoly.holes) h.reverse();
}
}
}
struct RasterParams {
sla::RasterBase::Trafo trafo; // Raster transformations
coord_t width, height; // scaled raster dimensions (not resolution)
double px_h, px_w; // pixel dimesions
marchsq::Coord win; // marching squares window size
};
RasterParams get_raster_params(const DynamicPrintConfig &cfg)
{
auto *opt_disp_cols = cfg.option<ConfigOptionInt>("display_pixels_x");
auto *opt_disp_rows = cfg.option<ConfigOptionInt>("display_pixels_y");
auto *opt_disp_w = cfg.option<ConfigOptionFloat>("display_width");
auto *opt_disp_h = cfg.option<ConfigOptionFloat>("display_height");
auto *opt_mirror_x = cfg.option<ConfigOptionBool>("display_mirror_x");
auto *opt_mirror_y = cfg.option<ConfigOptionBool>("display_mirror_y");
auto *opt_orient = cfg.option<ConfigOptionEnum<SLADisplayOrientation>>("display_orientation");
if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h ||
!opt_mirror_x || !opt_mirror_y || !opt_orient)
throw std::runtime_error("Invalid SL1 file");
RasterParams rstp;
rstp.px_w = opt_disp_w->value / (opt_disp_cols->value - 1);
rstp.px_h = opt_disp_h->value / (opt_disp_rows->value - 1);
sla::RasterBase::Trafo trafo{opt_orient->value == sladoLandscape ?
sla::RasterBase::roLandscape :
sla::RasterBase::roPortrait,
{opt_mirror_x->value, opt_mirror_y->value}};
rstp.height = scaled(opt_disp_h->value);
rstp.width = scaled(opt_disp_w->value);
return rstp;
}
struct SliceParams { double layerh = 0., initial_layerh = 0.; };
SliceParams get_slice_params(const DynamicPrintConfig &cfg)
{
auto *opt_layerh = cfg.option<ConfigOptionFloat>("layer_height");
auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
if (!opt_layerh || !opt_init_layerh)
throw std::runtime_error("Invalid SL1 file");
return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
}
std::vector<ExPolygons> extract_slices_from_sla_archive(
ArchiveData & arch,
const RasterParams & rstp,
std::function<bool(int)> progr)
{
auto jobdir = arch.config.get<std::string>("jobDir");
for (auto &c : jobdir) c = std::tolower(c);
std::vector<ExPolygons> slices(arch.images.size());
struct Status
{
double incr, val, prev;
bool stop = false;
tbb::spin_mutex mutex;
} st {100. / slices.size(), 0., 0.};
tbb::parallel_for(size_t(0), arch.images.size(),
[&arch, &slices, &st, &rstp, progr](size_t i) {
// Status indication guarded with the spinlock
{
std::lock_guard<tbb::spin_mutex> lck(st.mutex);
if (st.stop) return;
st.val += st.incr;
double curr = std::round(st.val);
if (curr > st.prev) {
st.prev = curr;
st.stop = !progr(int(curr));
}
}
auto &buf = arch.images[i];
wxMemoryInputStream stream{buf.data(), buf.size()};
wxImage img{stream};
auto rings = marchsq::execute(img, 128, rstp.win);
ExPolygons expolys = rings_to_expolygons(rings, rstp.px_w, rstp.px_h);
// Invert the raster transformations indicated in
// the profile metadata
invert_raster_trafo(expolys, rstp.trafo, rstp.width, rstp.height);
slices[i] = std::move(expolys);
});
if (st.stop) slices = {};
return slices;
}
} // namespace
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out)
{
ArchiveData arch = extract_sla_archive(zipfname, "png");
out.load(arch.profile);
}
void import_sla_archive(
const std::string & zipfname,
Vec2i windowsize,
TriangleMesh & out,
DynamicPrintConfig & profile,
std::function<bool(int)> progr)
{
// Ensure minimum window size for marching squares
windowsize.x() = std::max(2, windowsize.x());
windowsize.y() = std::max(2, windowsize.y());
ArchiveData arch = extract_sla_archive(zipfname, "thumbnail");
profile.load(arch.profile);
RasterParams rstp = get_raster_params(profile);
rstp.win = {windowsize.y(), windowsize.x()};
SliceParams slicp = get_slice_params(profile);
std::vector<ExPolygons> slices =
extract_slices_from_sla_archive(arch, rstp, progr);
if (!slices.empty())
out = slices_to_triangle_mesh(slices, 0, slicp.layerh, slicp.initial_layerh);
}
} // namespace Slic3r

View file

@ -1,35 +0,0 @@
#ifndef SLAIMPORT_HPP
#define SLAIMPORT_HPP
#include <functional>
#include <libslic3r/Point.hpp>
#include <libslic3r/PrintConfig.hpp>
namespace Slic3r {
class TriangleMesh;
class DynamicPrintConfig;
void import_sla_archive(const std::string &zipfname, DynamicPrintConfig &out);
void import_sla_archive(
const std::string & zipfname,
Vec2i windowsize,
TriangleMesh & out,
DynamicPrintConfig & profile,
std::function<bool(int)> progr = [](int) { return true; });
inline void import_sla_archive(
const std::string & zipfname,
Vec2i windowsize,
TriangleMesh & out,
std::function<bool(int)> progr = [](int) { return true; })
{
DynamicPrintConfig profile;
import_sla_archive(zipfname, windowsize, out, profile, progr);
}
}
#endif // SLAIMPORT_HPP