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

This commit is contained in:
YuSanka 2019-04-14 13:49:22 +02:00
commit df7ada0199
104 changed files with 2162 additions and 410 deletions

View file

@ -721,32 +721,37 @@ int GLVolumeCollection::load_wipe_tower_preview(
return int(this->volumes.size() - 1);
}
typedef std::pair<GLVolume*, double> GLVolumeWithZ;
typedef std::vector<GLVolumeWithZ> GLVolumesWithZList;
static GLVolumesWithZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func)
GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func)
{
GLVolumesWithZList list;
GLVolumeWithIdAndZList list;
list.reserve(volumes.size());
for (GLVolume* volume : volumes)
for (unsigned int i = 0; i < (unsigned int)volumes.size(); ++i)
{
GLVolume* volume = volumes[i];
bool is_transparent = (volume->render_color[3] < 1.0f);
if ((((type == GLVolumeCollection::Opaque) && !is_transparent) ||
((type == GLVolumeCollection::Transparent) && is_transparent) ||
(type == GLVolumeCollection::All)) &&
(! filter_func || filter_func(*volume)))
list.emplace_back(std::make_pair(volume, 0.0));
list.emplace_back(std::make_pair(volume, std::make_pair(i, 0.0)));
}
if ((type == GLVolumeCollection::Transparent) && (list.size() > 1))
{
for (GLVolumeWithZ& volume : list)
for (GLVolumeWithIdAndZ& volume : list)
{
volume.second = volume.first->bounding_box.transformed(view_matrix * volume.first->world_matrix()).max(2);
volume.second.second = volume.first->bounding_box.transformed(view_matrix * volume.first->world_matrix()).max(2);
}
std::sort(list.begin(), list.end(),
[](const GLVolumeWithZ& v1, const GLVolumeWithZ& v2) -> bool { return v1.second < v2.second; }
[](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool { return v1.second.second < v2.second.second; }
);
}
else if ((type == GLVolumeCollection::Opaque) && (list.size() > 1))
{
std::sort(list.begin(), list.end(),
[](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool { return v1.first->selected && !v2.first->selected; }
);
}
@ -769,6 +774,7 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool
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_detection_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.volume_detection") : -1;
@ -784,8 +790,11 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool
if (z_range_id != -1)
glsafe(::glUniform2fv(z_range_id, 1, (const GLfloat*)z_range));
GLVolumesWithZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func);
for (GLVolumeWithZ& volume : to_render) {
if (clipping_plane_id != -1)
glsafe(::glUniform4fv(clipping_plane_id, 1, (const GLfloat*)clipping_plane));
GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func);
for (GLVolumeWithIdAndZ& volume : to_render) {
volume.first->set_render_color();
volume.first->render_VBOs(color_id, print_box_detection_id, print_box_worldmatrix_id);
}
@ -814,8 +823,8 @@ void GLVolumeCollection::render_legacy(ERenderType type, bool disable_cullface,
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
GLVolumesWithZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func);
for (GLVolumeWithZ& volume : to_render)
GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func);
for (GLVolumeWithIdAndZ& volume : to_render)
{
volume.first->set_render_color();
volume.first->render_legacy();

View file

@ -412,6 +412,8 @@ public:
};
typedef std::vector<GLVolume*> GLVolumePtrs;
typedef std::pair<GLVolume*, std::pair<unsigned int, double>> GLVolumeWithIdAndZ;
typedef std::vector<GLVolumeWithIdAndZ> GLVolumeWithIdAndZList;
class GLVolumeCollection
{
@ -431,6 +433,9 @@ private:
// z range for clipping in shaders
float z_range[2];
// plane coeffs for clipping in shaders
float clipping_plane[4];
public:
GLVolumePtrs volumes;
@ -489,6 +494,7 @@ public:
}
void set_z_range(float min_z, float max_z) { z_range[0] = min_z; z_range[1] = max_z; }
void set_clipping_plane(const double* coeffs) { clipping_plane[0] = coeffs[0]; clipping_plane[1] = coeffs[1]; clipping_plane[2] = coeffs[2]; clipping_plane[3] = coeffs[3]; }
// returns true if all the volumes are completely contained in the print volume
// returns the containment state in the given out_state, if non-null
@ -505,6 +511,8 @@ private:
GLVolumeCollection& operator=(const GLVolumeCollection &);
};
GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func = nullptr);
class GLModel
{
protected:

View file

@ -255,16 +255,21 @@ void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config)
void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id)
{
const ModelObject *model_object_new = (object_id >= 0) ? model.objects[object_id] : nullptr;
if (model_object_new == nullptr || this->last_object_id != object_id || m_model_object != model_object_new || m_model_object->id() != model_object_new->id()) {
// Maximum height of an object changes when the object gets rotated or scaled.
// Changing maximum height of an object will invalidate the layer heigth editing profile.
// m_model_object->raw_bounding_box() is cached, therefore it is cheap even if this method is called frequently.
float new_max_z = (m_model_object == nullptr) ? 0.f : m_model_object->raw_bounding_box().size().z();
if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z ||
(model_object_new != nullptr && m_model_object->id() != model_object_new->id())) {
m_layer_height_profile.clear();
m_layer_height_profile_modified = false;
delete m_slicing_parameters;
m_slicing_parameters = nullptr;
m_slicing_parameters = nullptr;
m_layers_texture.valid = false;
this->last_object_id = object_id;
m_model_object = model_object_new;
m_object_max_z = new_max_z;
}
this->last_object_id = object_id;
m_model_object = model_object_new;
m_object_max_z = (m_model_object == nullptr) ? 0.f : m_model_object->bounding_box().max.z();
}
bool GLCanvas3D::LayersEditing::is_allowed() const
@ -623,7 +628,7 @@ void GLCanvas3D::LayersEditing::update_slicing_parameters()
{
if (m_slicing_parameters == nullptr) {
m_slicing_parameters = new SlicingParameters();
*m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object);
*m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z);
}
}
@ -2204,6 +2209,9 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
void GLCanvas3D::on_char(wxKeyEvent& evt)
{
if (!m_initialized)
return;
// see include/wx/defs.h enum wxKeyCode
int keyCode = evt.GetKeyCode();
int ctrlMask = wxMOD_CONTROL;
@ -2222,9 +2230,12 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
//#endif /* __APPLE__ */
if ((evt.GetModifiers() & ctrlMask) != 0) {
switch (keyCode) {
#ifdef __APPLE__
case 'a':
case 'A':
#else /* __APPLE__ */
case WXK_CONTROL_A:
#endif /* __APPLE__ */
post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL));
break;
#ifdef __APPLE__
@ -3134,6 +3145,11 @@ Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos)
return Linef3(_mouse_to_3d(mouse_pos, &z0), _mouse_to_3d(mouse_pos, &z1));
}
double GLCanvas3D::get_size_proportional_to_max_bed_size(double factor) const
{
return factor * m_bed.get_bounding_box().max_size();
}
bool GLCanvas3D::_is_shown_on_screen() const
{
return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false;
@ -3226,12 +3242,37 @@ bool GLCanvas3D::_init_toolbar()
if (!m_toolbar.add_separator())
return false;
item.name = "copy";
#if ENABLE_SVG_ICONS
item.icon_filename = "copy.svg";
#endif // ENABLE_SVG_ICONS
item.tooltip = GUI::L_str("Copy") + " [" + GUI::shortkey_ctrl_prefix() + "C]";
item.sprite_id = 4;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_copy(); };
if (!m_toolbar.add_item(item))
return false;
item.name = "paste";
#if ENABLE_SVG_ICONS
item.icon_filename = "paste.svg";
#endif // ENABLE_SVG_ICONS
item.tooltip = GUI::L_str("Paste") + " [" + GUI::shortkey_ctrl_prefix() + "V]";
item.sprite_id = 5;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_paste(); };
if (!m_toolbar.add_item(item))
return false;
if (!m_toolbar.add_separator())
return false;
item.name = "more";
#if ENABLE_SVG_ICONS
item.icon_filename = "instance_add.svg";
#endif // ENABLE_SVG_ICONS
item.tooltip = GUI::L_str("Add instance [+]");
item.sprite_id = 4;
item.sprite_id = 6;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); };
item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); };
@ -3243,7 +3284,7 @@ bool GLCanvas3D::_init_toolbar()
item.icon_filename = "instance_remove.svg";
#endif // ENABLE_SVG_ICONS
item.tooltip = GUI::L_str("Remove instance [-]");
item.sprite_id = 5;
item.sprite_id = 7;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); };
item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); };
@ -3258,7 +3299,7 @@ bool GLCanvas3D::_init_toolbar()
item.icon_filename = "split_objects.svg";
#endif // ENABLE_SVG_ICONS
item.tooltip = GUI::L_str("Split to objects");
item.sprite_id = 6;
item.sprite_id = 8;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); };
item.visibility_callback = GLToolbarItem::Default_Visibility_Callback;
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); };
@ -3270,7 +3311,7 @@ bool GLCanvas3D::_init_toolbar()
item.icon_filename = "split_parts.svg";
#endif // ENABLE_SVG_ICONS
item.tooltip = GUI::L_str("Split to parts");
item.sprite_id = 7;
item.sprite_id = 9;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); };
item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; };
item.enabled_state_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); };
@ -3285,7 +3326,7 @@ bool GLCanvas3D::_init_toolbar()
item.icon_filename = "layers.svg";
#endif // ENABLE_SVG_ICONS
item.tooltip = GUI::L_str("Layers editing");
item.sprite_id = 8;
item.sprite_id = 10;
item.is_toggable = true;
item.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); };
item.visibility_callback = [this]()->bool { return m_process->current_printer_technology() == ptFFF; };
@ -3490,7 +3531,15 @@ void GLCanvas3D::_picking_pass() const
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
_render_volumes(true);
m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane();
if (m_camera_clipping_plane.is_active()) {
::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data());
::glEnable(GL_CLIP_PLANE0);
}
_render_volumes_for_picking();
if (m_camera_clipping_plane.is_active())
::glDisable(GL_CLIP_PLANE0);
m_gizmos.render_current_gizmo_for_picking_pass(m_selection);
if (m_multisample_allowed)
@ -3571,6 +3620,8 @@ void GLCanvas3D::_render_axes() const
m_bed.render_axes();
}
void GLCanvas3D::_render_objects() const
{
if (m_volumes.empty())
@ -3579,6 +3630,8 @@ void GLCanvas3D::_render_objects() const
glsafe(::glEnable(GL_LIGHTING));
glsafe(::glEnable(GL_DEPTH_TEST));
m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane();
if (m_use_VBOs)
{
if (m_picking_enabled)
@ -3599,6 +3652,8 @@ void GLCanvas3D::_render_objects() const
else
m_volumes.set_z_range(-FLT_MAX, FLT_MAX);
m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data());
m_shader.start_using();
if (m_picking_enabled && m_layers_editing.is_enabled() && m_layers_editing.last_object_id != -1) {
int object_id = m_layers_editing.last_object_id;
@ -3619,13 +3674,17 @@ void GLCanvas3D::_render_objects() const
}
else
{
::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data());
::glEnable(GL_CLIP_PLANE0);
if (m_use_clipping_planes)
{
glsafe(::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_clipping_planes[0].get_data()));
glsafe(::glEnable(GL_CLIP_PLANE0));
glsafe(::glClipPlane(GL_CLIP_PLANE1, (GLdouble*)m_clipping_planes[1].get_data()));
glsafe(::glClipPlane(GL_CLIP_PLANE1, (GLdouble*)m_clipping_planes[0].get_data()));
glsafe(::glEnable(GL_CLIP_PLANE1));
glsafe(::glClipPlane(GL_CLIP_PLANE2, (GLdouble*)m_clipping_planes[1].get_data()));
glsafe(::glEnable(GL_CLIP_PLANE2));
}
// do not cull backfaces to show broken geometry, if any
m_volumes.render_legacy(GLVolumeCollection::Opaque, m_picking_enabled, m_camera.get_view_matrix(), [this](const GLVolume& volume) {
@ -3633,13 +3692,16 @@ void GLCanvas3D::_render_objects() const
});
m_volumes.render_legacy(GLVolumeCollection::Transparent, false, m_camera.get_view_matrix());
::glDisable(GL_CLIP_PLANE0);
if (m_use_clipping_planes)
{
glsafe(::glDisable(GL_CLIP_PLANE0));
glsafe(::glDisable(GL_CLIP_PLANE1));
glsafe(::glDisable(GL_CLIP_PLANE2));
}
}
m_camera_clipping_plane = ClippingPlane::ClipsNothing();
glsafe(::glDisable(GL_LIGHTING));
}
@ -3675,13 +3737,10 @@ void GLCanvas3D::_render_legend_texture() const
m_legend_texture.render(*this);
}
void GLCanvas3D::_render_volumes(bool fake_colors) const
void GLCanvas3D::_render_volumes_for_picking() const
{
static const GLfloat INV_255 = 1.0f / 255.0f;
if (!fake_colors)
glsafe(::glEnable(GL_LIGHTING));
// do not cull backfaces to show broken geometry, if any
glsafe(::glDisable(GL_CULL_FACE));
@ -3691,27 +3750,31 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
unsigned int volume_id = 0;
for (GLVolume* vol : m_volumes.volumes)
const Transform3d& view_matrix = m_camera.get_view_matrix();
GLVolumeWithIdAndZList to_render = volumes_to_render(m_volumes.volumes, GLVolumeCollection::Opaque, view_matrix);
for (const GLVolumeWithIdAndZ& volume : to_render)
{
if (fake_colors)
{
// Object picking mode. Render the object with a color encoding the object index.
unsigned int r = (volume_id & 0x000000FF) >> 0;
unsigned int g = (volume_id & 0x0000FF00) >> 8;
unsigned int b = (volume_id & 0x00FF0000) >> 16;
glsafe(::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255));
}
else
{
vol->set_render_color();
glsafe(::glColor4fv(vol->render_color));
}
// Object picking mode. Render the object with a color encoding the object index.
unsigned int r = (volume.second.first & 0x000000FF) >> 0;
unsigned int g = (volume.second.first & 0x0000FF00) >> 8;
unsigned int b = (volume.second.first & 0x00FF0000) >> 16;
glsafe(::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255));
if ((!fake_colors || !vol->disabled) && (vol->composite_id.volume_id >= 0 || m_render_sla_auxiliaries))
vol->render();
if (!volume.first->disabled && ((volume.first->composite_id.volume_id >= 0) || m_render_sla_auxiliaries))
volume.first->render();
}
++volume_id;
to_render = volumes_to_render(m_volumes.volumes, GLVolumeCollection::Transparent, view_matrix);
for (const GLVolumeWithIdAndZ& volume : to_render)
{
// Object picking mode. Render the object with a color encoding the object index.
unsigned int r = (volume.second.first & 0x000000FF) >> 0;
unsigned int g = (volume.second.first & 0x0000FF00) >> 8;
unsigned int b = (volume.second.first & 0x00FF0000) >> 16;
glsafe(::glColor3f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255));
if (!volume.first->disabled && ((volume.first->composite_id.volume_id >= 0) || m_render_sla_auxiliaries))
volume.first->render();
}
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
@ -3719,9 +3782,6 @@ void GLCanvas3D::_render_volumes(bool fake_colors) const
glsafe(::glDisable(GL_BLEND));
glsafe(::glEnable(GL_CULL_FACE));
if (!fake_colors)
glsafe(::glDisable(GL_LIGHTING));
}
void GLCanvas3D::_render_current_gizmo() const
@ -3999,15 +4059,9 @@ void GLCanvas3D::_update_volumes_hover_state() const
return;
GLVolume* volume = m_volumes.volumes[m_hover_volume_id];
switch (m_selection.get_mode())
{
case Selection::Volume:
{
if (volume->is_modifier)
volume->hover = true;
break;
}
case Selection::Instance:
else
{
int object_idx = volume->object_idx();
int instance_idx = volume->instance_idx();
@ -4017,9 +4071,6 @@ void GLCanvas3D::_update_volumes_hover_state() const
if ((v->object_idx() == object_idx) && (v->instance_idx() == instance_idx))
v->hover = true;
}
break;
}
}
}

View file

@ -65,6 +65,37 @@ public:
void set_scale_factor(int height);
};
class ClippingPlane
{
double m_data[4];
public:
ClippingPlane()
{
m_data[0] = 0.0;
m_data[1] = 0.0;
m_data[2] = 1.0;
m_data[3] = 0.0;
}
ClippingPlane(const Vec3d& direction, double offset)
{
Vec3d norm_dir = direction.normalized();
m_data[0] = norm_dir(0);
m_data[1] = norm_dir(1);
m_data[2] = norm_dir(2);
m_data[3] = offset;
}
bool is_active() const { return m_data[3] != DBL_MAX; }
static ClippingPlane ClipsNothing() { return ClippingPlane(Vec3d(0., 0., 1.), DBL_MAX); }
const double* get_data() const { return m_data; }
};
wxDECLARE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent);
using Vec2dEvent = Event<Vec2d>;
@ -288,32 +319,6 @@ class GLCanvas3D
}
};
public:
class ClippingPlane
{
double m_data[4];
public:
ClippingPlane()
{
m_data[0] = 0.0;
m_data[1] = 0.0;
m_data[2] = 1.0;
m_data[3] = 0.0;
}
ClippingPlane(const Vec3d& direction, double offset)
{
Vec3d norm_dir = direction.normalized();
m_data[0] = norm_dir(0);
m_data[1] = norm_dir(1);
m_data[2] = norm_dir(2);
m_data[3] = offset;
}
const double* get_data() const { return m_data; }
};
private:
struct SlaCap
{
@ -405,6 +410,7 @@ private:
mutable GLGizmosManager m_gizmos;
mutable GLToolbar m_toolbar;
ClippingPlane m_clipping_planes[2];
mutable ClippingPlane m_camera_clipping_plane;
bool m_use_clipping_planes;
mutable SlaCap m_sla_caps[2];
std::string m_sidebar_field;
@ -579,6 +585,8 @@ public:
void refresh_camera_scene_box() { m_camera.set_scene_box(scene_bounding_box()); }
bool is_mouse_dragging() const { return m_mouse.dragging; }
double get_size_proportional_to_max_bed_size(double factor) const;
private:
bool _is_shown_on_screen() const;
@ -605,7 +613,7 @@ private:
#endif // ENABLE_RENDER_SELECTION_CENTER
void _render_warning_texture() const;
void _render_legend_texture() const;
void _render_volumes(bool fake_colors) const;
void _render_volumes_for_picking() const;
void _render_current_gizmo() const;
void _render_gizmos_overlay() const;
void _render_toolbar() const;

View file

@ -21,6 +21,8 @@ wxDEFINE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_COPY, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_PASTE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_FEWER, SimpleEvent);
wxDEFINE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent);

View file

@ -20,6 +20,8 @@ wxDECLARE_EVENT(EVT_GLTOOLBAR_ADD, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_DELETE_ALL, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_ARRANGE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_COPY, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_PASTE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_MORE, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_FEWER, SimpleEvent);
wxDECLARE_EVENT(EVT_GLTOOLBAR_SPLIT_OBJECTS, SimpleEvent);

View file

@ -61,19 +61,22 @@ ObjectList::ObjectList(wxWindow* parent) :
{
// Fill CATEGORY_ICON
{
// Note: `this` isn't passed to create_scaled_bitmap() here because of bugs in the widget,
// see note in PresetBundle::load_compatible_bitmaps()
// ptFFF
CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap(this, "layers");
CATEGORY_ICON[L("Infill")] = create_scaled_bitmap(this, "infill");
CATEGORY_ICON[L("Support material")] = create_scaled_bitmap(this, "support");
CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(this, "time");
CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(this, "funnel");
CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(this, "funnel");
// CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(this, "skirt+brim");
// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(this, "time");
CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(this, "wrench");
// ptSLA
CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(this, "sla_supports");
CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(this, "brick.png");
CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap(nullptr, "layers");
CATEGORY_ICON[L("Infill")] = create_scaled_bitmap(nullptr, "infill");
CATEGORY_ICON[L("Support material")] = create_scaled_bitmap(nullptr, "support");
CATEGORY_ICON[L("Speed")] = create_scaled_bitmap(nullptr, "time");
CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap(nullptr, "funnel");
CATEGORY_ICON[L("Extrusion Width")] = create_scaled_bitmap(nullptr, "funnel");
// CATEGORY_ICON[L("Skirt and brim")] = create_scaled_bitmap(nullptr, "skirt+brim");
// CATEGORY_ICON[L("Speed > Acceleration")] = create_scaled_bitmap(nullptr, "time");
CATEGORY_ICON[L("Advanced")] = create_scaled_bitmap(nullptr, "wrench");
// ptSLA
CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(nullptr, "support"/*"sla_supports"*/);
CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(nullptr, "pad");
}
// create control
@ -392,10 +395,10 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const
void ObjectList::init_icons()
{
m_bmp_modifiermesh = create_scaled_bitmap(this, "lambda.png");
m_bmp_solidmesh = create_scaled_bitmap(this, "object.png");
m_bmp_support_enforcer = create_scaled_bitmap(this, "support_enforcer_.png");
m_bmp_support_blocker = create_scaled_bitmap(this, "support_blocker_.png");
m_bmp_modifiermesh = create_scaled_bitmap(nullptr, "add_modifier");
m_bmp_solidmesh = create_scaled_bitmap(nullptr, "add_part");
m_bmp_support_enforcer = create_scaled_bitmap(nullptr, "support_enforcer");
m_bmp_support_blocker = create_scaled_bitmap(nullptr, "support_blocker");
m_bmp_vector.reserve(4); // bitmaps for different types of parts
@ -406,13 +409,13 @@ void ObjectList::init_icons()
m_objects_model->SetVolumeBitmaps(m_bmp_vector);
// init icon for manifold warning
m_bmp_manifold_warning = create_scaled_bitmap(this, "exclamation_mark_.png");
m_bmp_manifold_warning = create_scaled_bitmap(nullptr, "exclamation");
// init bitmap for "Split to sub-objects" context menu
m_bmp_split = create_scaled_bitmap(this, "split_parts");
m_bmp_split = create_scaled_bitmap(nullptr, "split_parts_SMALL");
// init bitmap for "Add Settings" context menu
m_bmp_cog = create_scaled_bitmap(this, "cog");
m_bmp_cog = create_scaled_bitmap(nullptr, "cog");
}
@ -436,6 +439,66 @@ void ObjectList::selection_changed()
part_selection_changed();
}
void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes)
{
if ((obj_idx < 0) || ((int)m_objects->size() <= obj_idx))
return;
if (volumes.empty())
return;
ModelObject& model_object = *(*m_objects)[obj_idx];
const auto object_item = m_objects_model->GetItemById(obj_idx);
wxDataViewItemArray items;
for (const ModelVolume* volume : volumes)
{
auto vol_item = m_objects_model->AddVolumeChild(object_item, volume->name, volume->type(),
volume->config.has("extruder") ? volume->config.option<ConfigOptionInt>("extruder")->value : 0);
auto opt_keys = volume->config.keys();
if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder")))
select_item(m_objects_model->AddSettingsChild(vol_item));
items.Add(vol_item);
}
m_parts_changed = true;
parts_changed(obj_idx);
if (items.size() > 1)
{
m_selection_mode = smVolume;
m_last_selected_item = wxDataViewItem(0);
}
select_items(items);
#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
selection_changed();
#endif //no __WXOSX__ //__WXMSW__
}
void ObjectList::paste_objects_into_list(const std::vector<size_t>& object_idxs)
{
if (object_idxs.empty())
return;
wxDataViewItemArray items;
for (const size_t object : object_idxs)
{
add_object_to_list(object);
m_parts_changed = true;
parts_changed(object);
items.Add(m_objects_model->GetItemById(object));
}
select_items(items);
#ifndef __WXOSX__ //#ifdef __WXMSW__ // #ys_FIXME
selection_changed();
#endif //no __WXOSX__ //__WXMSW__
}
void ObjectList::OnChar(wxKeyEvent& event)
{
if (event.GetKeyCode() == WXK_BACK){
@ -1374,9 +1437,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
const wxString name = _(L("Generic")) + "-" + _(type_name);
TriangleMesh mesh;
auto& bed_shape = printer_config().option<ConfigOptionPoints>("bed_shape")->values;
const auto& sz = BoundingBoxf(bed_shape).size();
const auto side = 0.1 * std::max(sz(0), sz(1));
double side = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.1);
if (type_name == "Box")
// Sitting on the print bed, left front front corner at (0, 0).
@ -1573,11 +1634,11 @@ void ObjectList::split()
for (auto id = 0; id < model_object->volumes.size(); id++) {
const auto vol_item = m_objects_model->AddVolumeChild(parent, from_u8(model_object->volumes[id]->name),
model_object->volumes[id]->is_modifier() ?
ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART,
model_object->volumes[id]->config.has("extruder") ?
model_object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value : 0,
false);
model_object->volumes[id]->is_modifier() ?
ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART,
model_object->volumes[id]->config.has("extruder") ?
model_object->volumes[id]->config.option<ConfigOptionInt>("extruder")->value : 0,
false);
// add settings to the part, if it has those
auto opt_keys = model_object->volumes[id]->config.keys();
if ( !(opt_keys.size() == 1 && opt_keys[0] == "extruder") ) {
@ -2132,6 +2193,7 @@ void ObjectList::update_selections_on_canvas()
add_to_selection(item, selection, instance_idx, false);
wxGetApp().plater()->canvas3D()->update_gizmos_on_off_state();
wxGetApp().plater()->canvas3D()->render();
}
void ObjectList::select_item(const wxDataViewItem& item)

View file

@ -31,6 +31,8 @@ typedef std::map<std::string, std::vector<std::string>> FreqSettingsBundle;
// category -> vector ( option ; label )
typedef std::map< std::string, std::vector< std::pair<std::string, std::string> > > settings_menu_hierarchy;
typedef std::vector<ModelVolume*> ModelVolumePtrs;
namespace GUI {
wxDECLARE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent);
@ -286,6 +288,9 @@ public:
void fix_through_netfabb() const;
void update_item_error_icon(const int obj_idx, int vol_idx) const ;
void paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes);
void paste_objects_into_list(const std::vector<size_t>& object_idxs);
void rescale();
private:

View file

@ -818,8 +818,8 @@ void Preview::on_sliders_scroll_changed(wxEvent& event)
}
else if (tech == ptSLA)
{
m_canvas->set_clipping_plane(0, GLCanvas3D::ClippingPlane(Vec3d::UnitZ(), -m_slider->GetLowerValueD()));
m_canvas->set_clipping_plane(1, GLCanvas3D::ClippingPlane(-Vec3d::UnitZ(), m_slider->GetHigherValueD()));
m_canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -m_slider->GetLowerValueD()));
m_canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), m_slider->GetHigherValueD()));
m_canvas->set_use_clipping_planes(m_slider->GetHigherValue() != 0);
m_canvas_widget->Refresh();
}

View file

@ -10,6 +10,7 @@
#include "slic3r/GUI/GUI_ObjectSettings.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/PresetBundle.hpp"
#include "libslic3r/Tesselate.hpp"
namespace Slic3r {
@ -47,24 +48,24 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S
{
if (selection.is_empty()) {
m_model_object = nullptr;
m_old_model_object = nullptr;
return;
}
m_old_model_object = m_model_object;
m_model_object = model_object;
m_active_instance = selection.get_instance_idx();
if (model_object && selection.is_from_single_instance())
{
// Cache the bb - it's needed for dealing with the clipping plane quite often
// It could be done inside update_mesh but one has to account for scaling of the instance.
//FIXME calling ModelObject::instance_bounding_box() is expensive!
m_active_instance_bb_radius = m_model_object->instance_bounding_box(m_active_instance).radius();
if (is_mesh_update_necessary()) {
update_mesh();
editing_mode_reload_cache();
}
if (m_model_object != m_old_model_object)
m_editing_mode = false;
if (m_editing_mode_cache.empty() && m_model_object->sla_points_status != sla::PointsStatus::UserModified)
get_data_from_backend();
@ -88,12 +89,75 @@ void GLGizmoSlaSupports::on_render(const Selection& selection) const
glsafe(::glEnable(GL_BLEND));
glsafe(::glEnable(GL_DEPTH_TEST));
render_points(selection, false);
// we'll recover current look direction from the modelview matrix (in world coords):
Eigen::Matrix<double, 4, 4, Eigen::DontAlign> modelview_matrix;
::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data());
Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]);
m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z();
if (m_quadric != nullptr && selection.is_from_single_instance())
render_points(selection, direction_to_camera, false);
render_selection_rectangle();
render_clipping_plane(selection, direction_to_camera);
glsafe(::glDisable(GL_BLEND));
}
void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection, const Vec3d& direction_to_camera) const
{
if (m_clipping_plane_distance == 0.f)
return;
const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin());
Transform3f instance_matrix = vol->get_instance_transformation().get_matrix().cast<float>();
Transform3f instance_matrix_no_translation_no_scaling = vol->get_instance_transformation().get_matrix(true,false,true).cast<float>();
Vec3f scaling = vol->get_instance_scaling_factor().cast<float>();
Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera.cast<float>();
Vec3f up = Vec3f(up_noscale(0)*scaling(0), up_noscale(1)*scaling(1), up_noscale(2)*scaling(2));
float height_mesh = (m_active_instance_bb_radius - m_clipping_plane_distance * 2*m_active_instance_bb_radius) * (up_noscale.norm()/up.norm());
if (m_clipping_plane_distance != m_old_clipping_plane_distance
|| m_old_direction_to_camera != direction_to_camera) {
std::vector<ExPolygons> list_of_expolys;
if (! m_tms) {
m_tms.reset(new TriangleMeshSlicer);
m_tms->init(const_cast<TriangleMesh*>(&m_mesh), [](){});
}
m_tms->set_up_direction(up);
m_tms->slice(std::vector<float>{height_mesh}, 0.f, &list_of_expolys, [](){});
m_triangles = triangulate_expolygons_2f(list_of_expolys[0]);
m_old_direction_to_camera = direction_to_camera;
m_old_clipping_plane_distance = m_clipping_plane_distance;
}
if (! m_triangles.empty()) {
::glPushMatrix();
::glTranslated(0.0, 0.0, m_z_shift);
::glMultMatrixf(instance_matrix.data());
Eigen::Quaternionf q;
q.setFromTwoVectors(Vec3f::UnitZ(), up);
Eigen::AngleAxisf aa(q);
::glRotatef(aa.angle() * (180./M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2));
::glTranslatef(0.f, 0.f, -0.001f); // to make sure the cut is safely beyond the near clipping plane
::glColor3f(1.0f, 0.37f, 0.0f);
::glBegin(GL_TRIANGLES);
::glColor3f(1.0f, 0.37f, 0.0f);
for (const Vec2f& point : m_triangles)
::glVertex3f(point(0), point(1), height_mesh);
::glEnd();
::glPopMatrix();
}
}
void GLGizmoSlaSupports::render_selection_rectangle() const
{
if (m_selection_rectangle_status == srOff)
@ -143,24 +207,25 @@ void GLGizmoSlaSupports::on_render_for_picking(const Selection& selection) const
{
glsafe(::glEnable(GL_DEPTH_TEST));
render_points(selection, true);
// we'll recover current look direction from the modelview matrix (in world coords):
Eigen::Matrix<double, 4, 4, Eigen::DontAlign> modelview_matrix;
::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data());
Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]);
render_points(selection, direction_to_camera, true);
}
void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const
void GLGizmoSlaSupports::render_points(const Selection& selection, const Vec3d& direction_to_camera, bool picking) const
{
if (m_quadric == nullptr || !selection.is_from_single_instance())
return;
if (!picking)
glsafe(::glEnable(GL_LIGHTING));
const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin());
double z_shift = vol->get_sla_shift_z();
const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse();
const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix();
glsafe(::glPushMatrix());
glsafe(::glTranslated(0.0, 0.0, z_shift));
glsafe(::glTranslated(0.0, 0.0, m_z_shift));
glsafe(::glMultMatrixd(instance_matrix.data()));
float render_color[3];
@ -169,6 +234,9 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
const sla::SupportPoint& support_point = m_editing_mode_cache[i].support_point;
const bool& point_selected = m_editing_mode_cache[i].selected;
if (is_point_clipped(support_point.pos.cast<double>(), direction_to_camera))
continue;
// First decide about the color of the point.
if (picking) {
std::array<float, 3> color = picking_color_component(i);
@ -238,13 +306,25 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
glsafe(::glPopMatrix());
}
bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point, const Vec3d& direction_to_camera) const
{
if (m_clipping_plane_distance == 0.f)
return false;
Vec3d transformed_point = m_model_object->instances.front()->get_transformation().get_matrix() * point;
transformed_point(2) += m_z_shift;
return direction_to_camera.dot(m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift)) + m_active_instance_bb_radius
- m_clipping_plane_distance * 2*m_active_instance_bb_radius < direction_to_camera.dot(transformed_point);
}
bool GLGizmoSlaSupports::is_mesh_update_necessary() const
{
return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty())
&& ((m_model_object != m_old_model_object) || m_V.size()==0);
//if (m_state != On || !m_model_object || m_model_object->instances.empty() || ! m_instance_matrix.isApprox(m_source_data.matrix))
// return false;
&& ((m_model_object->id() != m_current_mesh_model_id) || m_V.size()==0);
}
void GLGizmoSlaSupports::update_mesh()
@ -252,10 +332,9 @@ void GLGizmoSlaSupports::update_mesh()
wxBusyCursor wait;
Eigen::MatrixXf& V = m_V;
Eigen::MatrixXi& F = m_F;
// Composite mesh of all instances in the world coordinate system.
// This mesh does not account for the possible Z up SLA offset.
TriangleMesh mesh = m_model_object->raw_mesh();
const stl_file& stl = mesh.stl;
m_mesh = m_model_object->raw_mesh();
const stl_file& stl = m_mesh.stl;
V.resize(3 * stl.stats.number_of_facets, 3);
F.resize(stl.stats.number_of_facets, 3);
for (unsigned int i=0; i<stl.stats.number_of_facets; ++i) {
@ -267,11 +346,15 @@ void GLGizmoSlaSupports::update_mesh()
F(i, 1) = 3*i+1;
F(i, 2) = 3*i+2;
}
m_current_mesh_model_id = m_model_object->id();
m_editing_mode = false;
m_AABB = igl::AABB<Eigen::MatrixXf,3>();
m_AABB.init(m_V, m_F);
}
// Unprojects the mouse position on the mesh and return the hit point and normal of the facet.
// The function throws if no intersection if found.
std::pair<Vec3f, Vec3f> GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos)
{
// if the gizmo doesn't have the V, F structures for igl, calculate them first:
@ -288,31 +371,53 @@ std::pair<Vec3f, Vec3f> GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse
::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 0.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point1(0), &point1(1), &point1(2));
::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 1.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point2(0), &point2(1), &point2(2));
igl::Hit hit;
std::vector<igl::Hit> hits;
const Selection& selection = m_parent.get_selection();
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
double z_offset = volume->get_sla_shift_z();
point1(2) -= z_offset;
point2(2) -= z_offset;
// we'll recover current look direction from the modelview matrix (in world coords):
Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]);
point1(2) -= m_z_shift;
point2(2) -= m_z_shift;
Transform3d inv = volume->get_instance_transformation().get_matrix().inverse();
point1 = inv * point1;
point2 = inv * point2;
if (!m_AABB.intersect_ray(m_V, m_F, point1.cast<float>(), (point2-point1).cast<float>(), hit))
if (!m_AABB.intersect_ray(m_V, m_F, point1.cast<float>(), (point2-point1).cast<float>(), hits))
throw std::invalid_argument("unproject_on_mesh(): No intersection found.");
int fid = hit.id; // facet id
Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit
Vec3f a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0)));
Vec3f b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0)));
std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
// Now let's iterate through the points and find the first that is not clipped:
unsigned int i=0;
Vec3f bc;
Vec3f a;
Vec3f b;
Vec3f result;
for (i=0; i<hits.size(); ++i) {
igl::Hit& hit = hits[i];
int fid = hit.id; // facet id
bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit
a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0)));
b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0)));
result = bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2));
if (m_clipping_plane_distance == 0.f || !is_point_clipped(result.cast<double>(), direction_to_camera))
break;
}
if (i==hits.size() || (hits.size()-i) % 2 != 0) {
// All hits are either clipped, or there is an odd number of unclipped
// hits - meaning the nearest must be from inside the mesh.
throw std::invalid_argument("unproject_on_mesh(): No intersection found.");
}
// Calculate and return both the point and the facet normal.
return std::make_pair(
bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)),
result,
a.cross(b)
);
}
@ -383,36 +488,64 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
const Selection& selection = m_parent.get_selection();
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
double z_offset = volume->get_sla_shift_z();
// bounding box created from the rectangle corners - will take care of order of the corners
BoundingBox rectangle(Points{Point(m_selection_rectangle_start_corner.cast<int>()), Point(m_selection_rectangle_end_corner.cast<int>())});
const Transform3d& instance_matrix_no_translation = volume->get_instance_transformation().get_matrix(true);
const Transform3d& instance_matrix_no_translation_no_scaling = volume->get_instance_transformation().get_matrix(true,false,true);
// we'll recover current look direction from the modelview matrix (in world coords)...
Vec3f direction_to_camera = camera.get_dir_forward().cast<float>();
// ...and transform it to model coords.
direction_to_camera = (instance_matrix_no_translation.inverse().cast<float>() * direction_to_camera).normalized().eval();
Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast<float>() * direction_to_camera).normalized().eval();
Vec3f scaling = volume->get_instance_scaling_factor().cast<float>();
direction_to_camera_mesh = Vec3f(direction_to_camera_mesh(0)*scaling(0), direction_to_camera_mesh(1)*scaling(1), direction_to_camera_mesh(2)*scaling(2));
// Iterate over all points, check if they're in the rectangle and if so, check that they are not obscured by the mesh:
for (unsigned int i=0; i<m_editing_mode_cache.size(); ++i) {
const sla::SupportPoint &support_point = m_editing_mode_cache[i].support_point;
Vec3f pos = instance_matrix.cast<float>() * support_point.pos;
pos(2) += z_offset;
pos(2) += m_z_shift;
GLdouble out_x, out_y, out_z;
::gluProject((GLdouble)pos(0), (GLdouble)pos(1), (GLdouble)pos(2), (GLdouble*)modelview_matrix.data(), (GLdouble*)projection_matrix.data(), (GLint*)viewport.data(), &out_x, &out_y, &out_z);
out_y = m_canvas_height - out_y;
if (rectangle.contains(Point(out_x, out_y))) {
if (rectangle.contains(Point(out_x, out_y)) && !is_point_clipped(support_point.pos.cast<double>(), direction_to_camera.cast<double>())) {
bool is_obscured = false;
// Cast a ray in the direction of the camera and look for intersection with the mesh:
std::vector<igl::Hit> hits;
// Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies.
if (m_AABB.intersect_ray(m_V, m_F, support_point.pos + direction_to_camera * (support_point.head_front_radius + EPSILON), direction_to_camera, hits))
if (m_AABB.intersect_ray(m_V, m_F, support_point.pos + direction_to_camera_mesh * (support_point.head_front_radius + EPSILON), direction_to_camera_mesh, hits)) {
std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; });
if (m_clipping_plane_distance != 0.f) {
// If the closest hit facet normal points in the same direction as the ray,
// we are looking through the mesh and should therefore discard the point:
int fid = hits.front().id; // facet id
Vec3f a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0)));
Vec3f b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0)));
if ((a.cross(b)).dot(direction_to_camera_mesh) > 0.f)
is_obscured = true;
// Eradicate all hits that are on clipped surfaces:
for (unsigned int j=0; j<hits.size(); ++j) {
const igl::Hit& hit = hits[j];
int fid = hit.id; // facet id
Vec3f bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit
Vec3f hit_pos = bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2));
if (is_point_clipped(hit_pos.cast<double>(), direction_to_camera.cast<double>())) {
hits.erase(hits.begin()+j);
--j;
}
}
}
// FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction.
// Also, the threshold is in mesh coordinates, not in actual dimensions.
if (hits.size() > 1 || hits.front().t > 0.001f)
if (!hits.empty())
is_obscured = true;
}
if (!is_obscured) {
if (m_selection_rectangle_status == srDeselect)
@ -570,6 +703,64 @@ void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const
ClippingPlane GLGizmoSlaSupports::get_sla_clipping_plane() const
{
if (!m_model_object || m_state == Off)
return ClippingPlane::ClipsNothing();
Eigen::Matrix<GLdouble, 4, 4, Eigen::DontAlign> modelview_matrix;
::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data());
// we'll recover current look direction from the modelview matrix (in world coords):
Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]);
float dist = direction_to_camera.dot(m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift));
return ClippingPlane(-direction_to_camera.normalized(),(dist - (-m_active_instance_bb_radius) - m_clipping_plane_distance * 2*m_active_instance_bb_radius));
}
/*
void GLGizmoSlaSupports::find_intersecting_facets(const igl::AABB<Eigen::MatrixXf, 3>* aabb, const Vec3f& normal, double offset, std::vector<unsigned int>& idxs) const
{
if (aabb->is_leaf()) { // this is a facet
// corner.dot(normal) - offset
idxs.push_back(aabb->m_primitive);
}
else { // not a leaf
using CornerType = Eigen::AlignedBox<float, 3>::CornerType;
bool sign = std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(0))));
for (unsigned int i=1; i<8; ++i)
if (std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(i)))) != sign) {
find_intersecting_facets(aabb->m_left, normal, offset, idxs);
find_intersecting_facets(aabb->m_right, normal, offset, idxs);
}
}
}
void GLGizmoSlaSupports::make_line_segments() const
{
TriangleMeshSlicer tms(&m_model_object->volumes.front()->mesh);
Vec3f normal(0.f, 1.f, 1.f);
double d = 0.;
std::vector<IntersectionLine> lines;
find_intersections(&m_AABB, normal, d, lines);
ExPolygons expolys;
tms.make_expolygons_simple(lines, &expolys);
SVG svg("slice_loops.svg", get_extents(expolys));
svg.draw(expolys);
//for (const IntersectionLine &l : lines[i])
// svg.draw(l, "red", 0);
//svg.draw_outline(expolygons, "black", "blue", 0);
svg.Close();
}
*/
void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection)
{
if (!m_model_object)
@ -580,7 +771,7 @@ void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_l
RENDER_AGAIN:
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
const ImVec2 window_size(m_imgui->scaled(17.f, 18.f));
const ImVec2 window_size(m_imgui->scaled(17.f, 20.f));
ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) ));
ImGui::SetNextWindowSize(ImVec2(window_size));
@ -687,6 +878,13 @@ RENDER_AGAIN:
(m_model_object->sla_points_status == sla::PointsStatus::Generating ? "Generation in progress..." : "UNKNOWN STATUS"))));
}
// Following is rendered in both editing and non-editing mode:
m_imgui->text("Clipping of view: ");
ImGui::SameLine();
ImGui::PushItemWidth(150.0f);
bool value_changed = ImGui::SliderFloat(" ", &m_clipping_plane_distance, 0.f, 1.f, "%.2f");
m_imgui->end();
if (m_editing_mode != m_old_editing_state) { // user toggled between editing/non-editing mode
@ -740,12 +938,7 @@ std::string GLGizmoSlaSupports::on_get_name() const
void GLGizmoSlaSupports::on_set_state()
{
// Following is called through CallAfter, because otherwise there was a problem
// on OSX with the wxMessageDialog being shown several times when clicked into.
wxGetApp().CallAfter([this]() {
if (m_state == On && m_old_state != On) { // the gizmo was just turned on
if (is_mesh_update_necessary())
update_mesh();
@ -762,23 +955,32 @@ void GLGizmoSlaSupports::on_set_state()
m_new_point_head_diameter = static_cast<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value;
}
if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
if (m_model_object) {
if (m_unsaved_changes) {
wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually edited support points ?\n")),
_(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO);
if (dlg.ShowModal() == wxID_YES)
editing_mode_apply_changes();
else
editing_mode_discard_changes();
wxGetApp().CallAfter([this]() {
// Following is called through CallAfter, because otherwise there was a problem
// on OSX with the wxMessageDialog being shown several times when clicked into.
if (m_model_object) {
if (m_unsaved_changes) {
wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually edited support points ?\n")),
_(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO);
if (dlg.ShowModal() == wxID_YES)
editing_mode_apply_changes();
else
editing_mode_discard_changes();
}
}
}
m_parent.toggle_model_objects_visibility(true);
m_editing_mode = false; // so it is not active next time the gizmo opens
m_editing_mode_cache.clear();
m_parent.toggle_model_objects_visibility(true);
m_editing_mode = false; // so it is not active next time the gizmo opens
m_editing_mode_cache.clear();
m_clipping_plane_distance = 0.f;
// Release copy of the mesh, triangle slicer and the AABB spatial search structure.
m_mesh.clear();
m_AABB.deinit();
m_V = Eigen::MatrixXf();
m_F = Eigen::MatrixXi();
m_tms.reset(nullptr);
});
}
m_old_state = m_state;
});
}
@ -827,11 +1029,19 @@ void GLGizmoSlaSupports::unselect_point(int i)
void GLGizmoSlaSupports::editing_mode_discard_changes()
{
m_editing_mode_cache.clear();
for (const sla::SupportPoint& point : m_model_object->sla_support_points)
m_editing_mode_cache.emplace_back(point, false);
m_editing_mode = false;
m_unsaved_changes = false;
// If the points were autogenerated, they may not be on the ModelObject yet.
// Because the user probably messed with the cache, we will get the data
// from the backend again.
if (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated)
get_data_from_backend();
else {
m_editing_mode_cache.clear();
for (const sla::SupportPoint& point : m_model_object->sla_support_points)
m_editing_mode_cache.emplace_back(point, false);
}
m_editing_mode = false;
m_unsaved_changes = false;
}

View file

@ -17,12 +17,17 @@ namespace Slic3r {
namespace GUI {
class ClippingPlane;
class GLGizmoSlaSupports : public GLGizmoBase
{
private:
ModelObject* m_model_object = nullptr;
ModelObject* m_old_model_object = nullptr;
ModelID m_current_mesh_model_id = 0;
int m_active_instance = -1;
float m_active_instance_bb_radius; // to cache the bb
mutable float m_z_shift = 0.f;
std::pair<Vec3f, Vec3f> unproject_on_mesh(const Vec2d& mouse_pos);
const float RenderPointScale = 1.f;
@ -31,6 +36,8 @@ private:
Eigen::MatrixXf m_V; // vertices
Eigen::MatrixXi m_F; // facets indices
igl::AABB<Eigen::MatrixXf,3> m_AABB;
TriangleMesh m_mesh;
mutable std::vector<Vec2f> m_triangles;
class CacheEntry {
public:
@ -52,7 +59,7 @@ public:
void set_sla_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);
void delete_selected_points(bool force = false);
std::pair<float, float> get_sla_clipping_plane() const;
ClippingPlane get_sla_clipping_plane() const;
private:
bool on_init();
@ -61,7 +68,8 @@ private:
virtual void on_render_for_picking(const Selection& selection) const;
void render_selection_rectangle() const;
void render_points(const Selection& selection, bool picking = false) const;
void render_points(const Selection& selection, const Vec3d& direction_to_camera, bool picking = false) const;
void render_clipping_plane(const Selection& selection, const Vec3d& direction_to_camera) const;
bool is_mesh_update_necessary() const;
void update_mesh();
void update_cache_entry_normal(unsigned int i) const;
@ -74,6 +82,8 @@ private:
float m_density = 100.f;
mutable std::vector<CacheEntry> m_editing_mode_cache; // a support point and whether it is currently selected
float m_clipping_plane_distance = 0.f;
mutable float m_old_clipping_plane_distance = 0.f;
mutable Vec3d m_old_direction_to_camera;
enum SelectionRectangleStatus {
srOff = 0,
@ -90,7 +100,11 @@ private:
int m_canvas_width;
int m_canvas_height;
mutable std::unique_ptr<TriangleMeshSlicer> m_tms;
std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const;
bool is_point_clipped(const Vec3d& point, const Vec3d& direction_to_camera) const;
void find_intersecting_facets(const igl::AABB<Eigen::MatrixXf, 3>* aabb, const Vec3f& normal, double offset, std::vector<unsigned int>& out) const;
// Methods that do the model_object and editing cache synchronization,
// editing mode selection, etc:

View file

@ -467,6 +467,19 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
return false;
}
ClippingPlane GLGizmosManager::get_sla_clipping_plane() const
{
if (!m_enabled || m_current != SlaSupports)
return ClippingPlane::ClipsNothing();
GizmosMap::const_iterator it = m_gizmos.find(SlaSupports);
if (it != m_gizmos.end())
return reinterpret_cast<GLGizmoSlaSupports*>(it->second)->get_sla_clipping_plane();
return ClippingPlane::ClipsNothing();
}
void GLGizmosManager::render_current_gizmo(const Selection& selection) const
{
if (!m_enabled)
@ -713,7 +726,12 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt, GLCanvas3D& canvas)
{
switch (keyCode)
{
#ifdef __APPLE__
case 'a':
case 'A':
#else /* __APPLE__ */
case WXK_CONTROL_A:
#endif /* __APPLE__ */
{
// Sla gizmo selects all support points
if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::SelectAll))

View file

@ -13,6 +13,7 @@ namespace GUI {
class Selection;
class GLGizmoBase;
class GLCanvas3D;
class ClippingPlane;
class Rect
{
@ -146,7 +147,7 @@ public:
void set_sla_support_data(ModelObject* model_object, const Selection& selection);
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_sla_clipping_plane() const;
void render_current_gizmo(const Selection& selection) const;
void render_current_gizmo_for_picking_pass(const Selection& selection) const;

View file

@ -321,7 +321,7 @@ void MainFrame::init_menubar()
wxMenu* fileMenu = new wxMenu;
{
wxMenuItem* item_open = 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(); }, "brick_add.png");
[this](wxCommandEvent&) { if (m_plater) m_plater->load_project(); }, "open");
wxMenuItem* item_save = 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())); }, "save");
wxMenuItem* item_save_as = append_menu_item(fileMenu, wxID_ANY, _(L("Save Project &as")) + dots + "\tCtrl+Alt+S", _(L("Save current project file as")),
@ -331,30 +331,30 @@ void MainFrame::init_menubar()
wxMenu* import_menu = new wxMenu();
wxMenuItem* item_import_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(); }, "brick_add.png");
[this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "import_plater");
import_menu->AppendSeparator();
append_menu_item(import_menu, wxID_ANY, _(L("Import &Config")) + dots + "\tCtrl+L", _(L("Load exported configuration file")),
[this](wxCommandEvent&) { load_config_file(); }, "plugin_add.png");
[this](wxCommandEvent&) { load_config_file(); }, "import_config");
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(); }, "plugin_add.png");
[this](wxCommandEvent&) { if (m_plater) m_plater->extract_config_from_project(); }, "import_config");
import_menu->AppendSeparator();
append_menu_item(import_menu, wxID_ANY, _(L("Import Config &Bundle")) + dots, _(L("Load presets from a bundle")),
[this](wxCommandEvent&) { load_configbundle(); }, "lorry_add.png");
[this](wxCommandEvent&) { load_configbundle(); }, "import_config_bundle");
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")),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, "cog_go.png");
[this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, "export_gcode");
export_menu->AppendSeparator();
wxMenuItem* item_export_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(); }, "brick_go.png");
[this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(); }, "export_plater");
wxMenuItem* item_export_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(); }, "brick_go.png");
[this](wxCommandEvent&) { if (m_plater) m_plater->export_amf(); }, "export_plater");
export_menu->AppendSeparator();
append_menu_item(export_menu, wxID_ANY, _(L("Export &Config")) +dots +"\tCtrl+E", _(L("Export current configuration to file")),
[this](wxCommandEvent&) { export_config(); }, "plugin_go.png");
[this](wxCommandEvent&) { export_config(); }, "export_config");
append_menu_item(export_menu, wxID_ANY, _(L("Export Config &Bundle")) + dots, _(L("Export all presets to file")),
[this](wxCommandEvent&) { export_configbundle(); }, "lorry_go.png");
[this](wxCommandEvent&) { export_configbundle(); }, "export_config_bundle");
append_submenu(fileMenu, export_menu, wxID_ANY, _(L("&Export")), "");
fileMenu->AppendSeparator();
@ -382,10 +382,10 @@ void MainFrame::init_menubar()
fileMenu->AppendSeparator();
#endif
m_menu_item_reslice_now = append_menu_item(fileMenu, wxID_ANY, _(L("(Re)Slice &Now")) + "\tCtrl+R", _(L("Start new slicing process")),
[this](wxCommandEvent&) { reslice_now(); }, "shape_handles.png");
[this](wxCommandEvent&) { reslice_now(); }, "re_slice");
fileMenu->AppendSeparator();
append_menu_item(fileMenu, wxID_ANY, _(L("&Repair STL file")) + dots, _(L("Automatically repair an STL file")),
[this](wxCommandEvent&) { repair_stl(); }, "wrench.png");
[this](wxCommandEvent&) { repair_stl(); }, "wrench");
fileMenu->AppendSeparator();
append_menu_item(fileMenu, wxID_EXIT, _(L("&Quit")), _(L("Quit Slic3r")),
[this](wxCommandEvent&) { Close(false); });
@ -425,13 +425,22 @@ void MainFrame::init_menubar()
[this](wxCommandEvent&) { m_plater->select_all(); }, "");
editMenu->AppendSeparator();
wxMenuItem* item_delete_sel = append_menu_item(editMenu, wxID_ANY, _(L("&Delete selected")) + sep + hotkey_delete, _(L("Deletes the current selection")),
[this](wxCommandEvent&) { m_plater->remove_selected(); }, "");
[this](wxCommandEvent&) { m_plater->remove_selected(); }, "remove_menu");
wxMenuItem* item_delete_all = 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(); }, "");
[this](wxCommandEvent&) { m_plater->reset(); }, "delete_all_menu");
editMenu->AppendSeparator();
wxMenuItem* item_copy = append_menu_item(editMenu, wxID_ANY, _(L("&Copy")) + "\tCtrl+C", _(L("Copy selection to clipboard")),
[this](wxCommandEvent&) { m_plater->copy_selection_to_clipboard(); }, "copy_menu");
wxMenuItem* item_paste = append_menu_item(editMenu, wxID_ANY, _(L("&Paste")) + "\tCtrl+V", _(L("Paste clipboard")),
[this](wxCommandEvent&) { m_plater->paste_from_clipboard(); }, "paste_menu");
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_select()); }, item_select_all->GetId());
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete()); }, item_delete_sel->GetId());
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete_all()); }, item_delete_all->GetId());
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_plater->can_copy()); }, item_copy->GetId());
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_plater->can_paste()); }, item_paste->GetId());
}
// Window menu
@ -440,7 +449,7 @@ void MainFrame::init_menubar()
size_t tab_offset = 0;
if (m_plater) {
append_menu_item(windowMenu, wxID_HIGHEST + 1, _(L("&Plater Tab")) + "\tCtrl+1", _(L("Show the plater")),
[this](wxCommandEvent&) { select_tab(0); }, "application_view_tile.png");
[this](wxCommandEvent&) { select_tab(0); }, "plater");
tab_offset += 1;
}
if (tab_offset > 0) {
@ -455,9 +464,9 @@ void MainFrame::init_menubar()
if (m_plater) {
windowMenu->AppendSeparator();
wxMenuItem* item_3d = 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"); }, "");
[this](wxCommandEvent&) { m_plater->select_view_3D("3D"); }, "editor_menu");
wxMenuItem* item_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"); }, "");
[this](wxCommandEvent&) { m_plater->select_view_3D("Preview"); }, "preview_menu");
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_3d->GetId());
Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_change_view()); }, item_preview->GetId());
@ -478,7 +487,7 @@ void MainFrame::init_menubar()
windowMenu->AppendSeparator();
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(); }, "arrow_up.png");
[this](wxCommandEvent&) { m_printhost_queue_dlg->Show(); }, "upload_queue");
}
// View menu
@ -590,7 +599,7 @@ void MainFrame::quick_slice(const int qs)
dlg->ShowModal();
return;
}
if (std::ifstream(m_qs_last_input_file.char_str())) {
if (std::ifstream(m_qs_last_input_file.ToUTF8().data())) {
auto dlg = new wxMessageDialog(this, _(L("Previously sliced file ("))+m_qs_last_input_file+_(L(") not found.")),
_(L("File Not Found")), wxICON_ERROR | wxOK);
dlg->ShowModal();
@ -705,24 +714,23 @@ void MainFrame::repair_stl()
dlg->Destroy();
}
auto output_file = input_file;
wxString output_file = input_file;
{
// output_file = ~s / \.[sS][tT][lL]$ / _fixed.obj / ;
auto dlg = new wxFileDialog( this, L("Save OBJ file (less prone to coordinate errors than STL) as:"),
get_dir_name(output_file), get_base_name(output_file),
get_dir_name(output_file), get_base_name(output_file, ".obj"),
file_wildcards(FT_OBJ), wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
if (dlg->ShowModal() != wxID_OK) {
dlg->Destroy();
return /*undef*/;
return;
}
output_file = dlg->GetPath();
dlg->Destroy();
}
auto tmesh = new Slic3r::TriangleMesh();
tmesh->ReadSTLFile(input_file.char_str());
tmesh->ReadSTLFile(input_file.ToUTF8().data());
tmesh->repair();
tmesh->WriteOBJFile(output_file.char_str());
tmesh->WriteOBJFile(output_file.ToUTF8().data());
Slic3r::GUI::show_info(this, L("Your file was repaired."), L("Repair"));
}
@ -962,9 +970,12 @@ void MainFrame::update_ui_from_settings()
tab->update_ui_from_settings();
}
std::string MainFrame::get_base_name(const wxString &full_name) const
std::string MainFrame::get_base_name(const wxString &full_name, const char *extension) const
{
return boost::filesystem::path(full_name.wx_str()).filename().string();
boost::filesystem::path filename = boost::filesystem::path(full_name.wx_str()).filename();
if (extension != nullptr)
filename = filename.replace_extension(extension);
return filename.string();
}
std::string MainFrame::get_dir_name(const wxString &full_name) const

View file

@ -54,7 +54,7 @@ class MainFrame : public DPIFrame
PrintHostQueueDialog *m_printhost_queue_dlg;
std::string get_base_name(const wxString &full_name) const;
std::string get_base_name(const wxString &full_name, const char *extension = nullptr) const;
std::string get_dir_name(const wxString &full_name) const;
void on_presets_changed(SimpleEvent&);

View file

@ -144,8 +144,7 @@ ObjectInfo::ObjectInfo(wxWindow *parent) :
info_manifold_text->SetFont(wxGetApp().small_font());
info_manifold = new wxStaticText(parent, wxID_ANY, "");
info_manifold->SetFont(wxGetApp().small_font());
wxBitmap bitmap(GUI::from_u8(Slic3r::var("error.png")), wxBITMAP_TYPE_PNG);
manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, bitmap);
manifold_warning_icon = new wxStaticBitmap(parent, wxID_ANY, create_scaled_bitmap(parent, "exclamation"));
auto *sizer_manifold = new wxBoxSizer(wxHORIZONTAL);
sizer_manifold->Add(info_manifold_text, 0);
sizer_manifold->Add(manifold_warning_icon, 0, wxLEFT, 2);
@ -1477,6 +1476,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE_ALL, [this](SimpleEvent&) { reset(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_ARRANGE, [this](SimpleEvent&) { arrange(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_COPY, [q](SimpleEvent&) { q->copy_selection_to_clipboard(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_PASTE, [q](SimpleEvent&) { q->paste_from_clipboard(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_MORE, [q](SimpleEvent&) { q->increase_instances(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_FEWER, [q](SimpleEvent&) { q->decrease_instances(); });
view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_OBJECTS, &priv::on_action_split_objects, this);
@ -1719,11 +1720,11 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
if (advanced)
{
wxMessageDialog dlg(q, _(L("This file cannot be loaded in simple mode. Do you want to switch to expert mode?\n")),
wxMessageDialog dlg(q, _(L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?\n")),
_(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO);
if (dlg.ShowModal() == wxID_YES)
{
Slic3r::GUI::wxGetApp().save_mode(comExpert);
Slic3r::GUI::wxGetApp().save_mode(comAdvanced);
view3D->set_as_dirty();
}
else
@ -2669,25 +2670,21 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
this->statusbar()->set_progress(evt.status.percent);
this->statusbar()->set_status_text(_(L(evt.status.text)) + wxString::FromUTF8(""));
}
if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SCENE) {
if (evt.status.flags & (PrintBase::SlicingStatus::RELOAD_SCENE || PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS)) {
switch (this->printer_technology) {
case ptFFF:
this->update_fff_scene();
break;
case ptSLA:
// If RELOAD_SLA_SUPPORT_POINTS, then the SLA gizmo is updated (reload_scene calls update_gizmos_data)
if (view3D->is_dragging())
delayed_scene_refresh = true;
else
this->update_sla_scene();
break;
}
}
if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_SUPPORT_POINTS) {
// Update SLA gizmo (reload_scene calls update_gizmos_data)
q->canvas3D()->reload_scene(true);
}
if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_PREVIEW) {
// Update the SLA preview
} else if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_PREVIEW) {
// Update the SLA preview. Only called if not RELOAD_SLA_SUPPORT_POINTS, as the block above will refresh the preview anyways.
this->preview->reload_print();
}
}
@ -2895,17 +2892,17 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/
wxMenuItem* item_delete = nullptr;
if (is_part) {
item_delete = append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")),
[this](wxCommandEvent&) { q->remove_selected(); }, "remove");
[this](wxCommandEvent&) { q->remove_selected(); }, "delete");
sidebar->obj_list()->append_menu_item_export_stl(menu);
}
else {
wxMenuItem* item_increase = append_menu_item(menu, wxID_ANY, _(L("Increase copies")) + "\t+", _(L("Place one more copy of the selected object")),
[this](wxCommandEvent&) { q->increase_instances(); }, "instance_add");
[this](wxCommandEvent&) { q->increase_instances(); }, "add_copies");
wxMenuItem* item_decrease = append_menu_item(menu, wxID_ANY, _(L("Decrease copies")) + "\t-", _(L("Remove one copy of the selected object")),
[this](wxCommandEvent&) { q->decrease_instances(); }, "instance_remove");
[this](wxCommandEvent&) { q->decrease_instances(); }, "remove_copies");
wxMenuItem* item_set_number_of_copies = append_menu_item(menu, wxID_ANY, _(L("Set number of copies")) + dots, _(L("Change the number of copies of the selected object")),
[this](wxCommandEvent&) { q->set_number_of_copies(); }, "textfield.png");
[this](wxCommandEvent&) { q->set_number_of_copies(); }, "number_of_copies");
items_increase.push_back(item_increase);
items_decrease.push_back(item_decrease);
@ -2913,7 +2910,7 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/
// Delete menu was moved to be after +/- instace to make it more difficult to be selected by mistake.
item_delete = append_menu_item(menu, wxID_ANY, _(L("Delete")) + "\tDel", _(L("Remove the selected object")),
[this](wxCommandEvent&) { q->remove_selected(); }, "remove");
[this](wxCommandEvent&) { q->remove_selected(); }, "delete");
menu->AppendSeparator();
wxMenuItem* item_instance_to_object = sidebar->obj_list()->append_menu_item_instance_to_object(menu);
@ -2943,11 +2940,11 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/
return false;
append_menu_item(mirror_menu, wxID_ANY, _(L("Along X axis")), _(L("Mirror the selected object along the X axis")),
[this](wxCommandEvent&) { mirror(X); }, "bullet_red.png", menu);
[this](wxCommandEvent&) { mirror(X); }, "mark_X", menu);
append_menu_item(mirror_menu, wxID_ANY, _(L("Along Y axis")), _(L("Mirror the selected object along the Y axis")),
[this](wxCommandEvent&) { mirror(Y); }, "bullet_green.png", menu);
[this](wxCommandEvent&) { mirror(Y); }, "mark_Y", menu);
append_menu_item(mirror_menu, wxID_ANY, _(L("Along Z axis")), _(L("Mirror the selected object along the Z axis")),
[this](wxCommandEvent&) { mirror(Z); }, "bullet_blue.png", menu);
[this](wxCommandEvent&) { mirror(Z); }, "mark_Z", menu);
wxMenuItem* item_mirror = append_submenu(menu, mirror_menu, wxID_ANY, _(L("Mirror")), _(L("Mirror the selected object")));
@ -2968,9 +2965,9 @@ bool Plater::priv::complit_init_object_menu()
return false;
wxMenuItem* item_split_objects = append_menu_item(split_menu, wxID_ANY, _(L("To objects")), _(L("Split the selected object into individual objects")),
[this](wxCommandEvent&) { split_object(); }, "split_objects.png", &object_menu);
[this](wxCommandEvent&) { split_object(); }, "split_object_SMALL", &object_menu);
wxMenuItem* item_split_volumes = append_menu_item(split_menu, wxID_ANY, _(L("To parts")), _(L("Split the selected object into individual sub-parts")),
[this](wxCommandEvent&) { split_volume(); }, "split_parts.png", &object_menu);
[this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL", &object_menu);
wxMenuItem* item_split = append_submenu(&object_menu, split_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object"))/*, "shape_ungroup.png"*/);
object_menu.AppendSeparator();
@ -2990,7 +2987,7 @@ bool Plater::priv::complit_init_object_menu()
bool Plater::priv::complit_init_sla_object_menu()
{
wxMenuItem* item_split = append_menu_item(&sla_object_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object into individual objects")),
[this](wxCommandEvent&) { split_object(); }, "shape_ungroup_o.png");
[this](wxCommandEvent&) { split_object(); }, "split_object_SMALL");
sla_object_menu.AppendSeparator();
@ -3010,7 +3007,7 @@ bool Plater::priv::complit_init_sla_object_menu()
bool Plater::priv::complit_init_part_menu()
{
wxMenuItem* item_split = append_menu_item(&part_menu, wxID_ANY, _(L("Split")), _(L("Split the selected object into individual sub-parts")),
[this](wxCommandEvent&) { split_volume(); }, "shape_ungroup_p.png");
[this](wxCommandEvent&) { split_volume(); }, "split_parts_SMALL");
part_menu.AppendSeparator();
@ -3117,7 +3114,7 @@ void Plater::priv::set_bed_shape(const Pointfs& shape)
bool Plater::priv::can_delete() const
{
return !get_selection().is_empty();
return !get_selection().is_empty() && !get_selection().is_wipe_tower();
}
bool Plater::priv::can_delete_all() const
@ -3299,8 +3296,9 @@ void Plater::increase_instances(size_t num)
bool was_one_instance = model_object->instances.size()==1;
float offset = 10.0;
for (size_t i = 0; i < num; i++, offset += 10.0) {
double offset_base = canvas3D()->get_size_proportional_to_max_bed_size(0.05);
double offset = offset_base;
for (size_t i = 0; i < num; i++, offset += offset_base) {
Vec3d offset_vec = model_instance->get_offset() + Vec3d(offset, offset, 0.0);
model_object->add_instance(offset_vec, model_instance->get_scaling_factor(), model_instance->get_rotation(), model_instance->get_mirror());
// p->print.get_object(obj_idx)->add_copy(Slic3r::to_2d(offset_vec));
@ -3368,7 +3366,7 @@ void Plater::set_number_of_copies(/*size_t num*/)
bool Plater::is_selection_empty() const
{
return p->get_selection().is_empty();
return p->get_selection().is_empty() || p->get_selection().is_wipe_tower();
}
void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower)
@ -3771,10 +3769,43 @@ void Plater::changed_object(int obj_idx)
this->p->schedule_background_process();
}
void Plater::schedule_background_process()
{
this->p->schedule_background_process();
}
void Plater::fix_through_netfabb(const int obj_idx, const int vol_idx/* = -1*/) { p->fix_through_netfabb(obj_idx, vol_idx); }
void Plater::update_object_menu() { p->update_object_menu(); }
void Plater::copy_selection_to_clipboard()
{
p->view3D->get_canvas3d()->get_selection().copy_to_clipboard();
}
void Plater::paste_from_clipboard()
{
p->view3D->get_canvas3d()->get_selection().paste_from_clipboard();
}
bool Plater::can_paste_from_clipboard() const
{
const Selection& selection = p->view3D->get_canvas3d()->get_selection();
const Selection::Clipboard& clipboard = selection.get_clipboard();
Selection::EMode mode = clipboard.get_mode();
if (clipboard.is_empty())
return false;
if ((mode == Selection::Volume) && !selection.is_from_single_instance())
return false;
if ((mode == Selection::Instance) && (selection.get_mode() != Selection::Instance))
return false;
return true;
}
bool Plater::can_delete() const { return p->can_delete(); }
bool Plater::can_delete_all() const { return p->can_delete_all(); }
bool Plater::can_increase_instances() const { return p->can_increase_instances(); }
@ -3783,5 +3814,7 @@ bool Plater::can_split_to_objects() const { return p->can_split_to_objects(); }
bool Plater::can_split_to_volumes() const { return p->can_split_to_volumes(); }
bool Plater::can_arrange() const { return p->can_arrange(); }
bool Plater::can_layers_editing() const { return p->can_layers_editing(); }
bool Plater::can_copy() const { return !is_selection_empty(); }
bool Plater::can_paste() const { return can_paste_from_clipboard(); }
}} // namespace Slic3r::GUI

View file

@ -167,6 +167,7 @@ public:
void reslice();
void reslice_SLA_supports(const ModelObject &object);
void changed_object(int obj_idx);
void schedule_background_process();
void fix_through_netfabb(const int obj_idx, const int vol_idx = -1);
void send_gcode();
@ -187,6 +188,10 @@ public:
PrinterTechnology printer_technology() const;
void set_printer_technology(PrinterTechnology printer_technology);
void copy_selection_to_clipboard();
void paste_from_clipboard();
bool can_paste_from_clipboard() const;
bool can_delete() const;
bool can_delete_all() const;
bool can_increase_instances() const;
@ -195,6 +200,8 @@ public:
bool can_split_to_volumes() const;
bool can_arrange() const;
bool can_layers_editing() const;
bool can_copy() const;
bool can_paste() const;
private:
struct priv;

View file

@ -801,12 +801,16 @@ bool PresetCollection::delete_current_preset()
void PresetCollection::load_bitmap_default(wxWindow *window, const std::string &file_name)
{
*m_bitmap_main_frame = create_scaled_bitmap(window, file_name);
// XXX: See note in PresetBundle::load_compatible_bitmaps()
(void)window;
*m_bitmap_main_frame = create_scaled_bitmap(nullptr, file_name);
}
void PresetCollection::load_bitmap_add(wxWindow *window, const std::string &file_name)
{
*m_bitmap_add = create_scaled_bitmap(window, file_name);
// XXX: See note in PresetBundle::load_compatible_bitmaps()
(void)window;
*m_bitmap_add = create_scaled_bitmap(nullptr, file_name);
}
const Preset* PresetCollection::get_selected_preset_parent() const

View file

@ -398,22 +398,27 @@ void PresetBundle::export_selections(AppConfig &config)
void PresetBundle::load_compatible_bitmaps(wxWindow *window)
{
*m_bitmapCompatible = create_scaled_bitmap(window, "flag_green");
*m_bitmapIncompatible = create_scaled_bitmap(window, "flag_red");
*m_bitmapLock = create_scaled_bitmap(window, "lock_closed");
*m_bitmapLockOpen = create_scaled_bitmap(window, "sys_unlock.png");
// We don't actually pass the window pointer here and instead generate
// a low DPI bitmap, because the wxBitmapComboBox and wxDataViewCtrl don't support
// high DPI bitmaps very well, they compute their dimensions wrong.
// TODO: Update this when fixed in wxWidgets
// See also PresetCollection::load_bitmap_default() and PresetCollection::load_bitmap_add()
(void)window;
*m_bitmapCompatible = create_scaled_bitmap(nullptr, "flag_green");
*m_bitmapIncompatible = create_scaled_bitmap(nullptr, "flag_red");
*m_bitmapLock = create_scaled_bitmap(nullptr, "lock_closed");
*m_bitmapLockOpen = create_scaled_bitmap(nullptr, "sys_unlock.png");
prints .set_bitmap_compatible(m_bitmapCompatible);
filaments .set_bitmap_compatible(m_bitmapCompatible);
sla_prints .set_bitmap_compatible(m_bitmapCompatible);
sla_materials.set_bitmap_compatible(m_bitmapCompatible);
printers .set_bitmap_compatible(m_bitmapCompatible);
prints .set_bitmap_incompatible(m_bitmapIncompatible);
filaments .set_bitmap_incompatible(m_bitmapIncompatible);
sla_prints .set_bitmap_incompatible(m_bitmapIncompatible);
sla_materials.set_bitmap_incompatible(m_bitmapIncompatible);
printers .set_bitmap_incompatible(m_bitmapIncompatible);
prints .set_bitmap_lock(m_bitmapLock);
filaments .set_bitmap_lock(m_bitmapLock);

View file

@ -1022,6 +1022,77 @@ bool Selection::requires_local_axes() const
return (m_mode == Volume) && is_from_single_instance();
}
void Selection::copy_to_clipboard()
{
if (!m_valid)
return;
m_clipboard.reset();
for (const ObjectIdxsToInstanceIdxsMap::value_type& object : m_cache.content)
{
ModelObject* src_object = m_model->objects[object.first];
ModelObject* dst_object = m_clipboard.add_object();
dst_object->name = src_object->name;
dst_object->input_file = src_object->input_file;
dst_object->config = src_object->config;
dst_object->sla_support_points = src_object->sla_support_points;
dst_object->sla_points_status = src_object->sla_points_status;
dst_object->layer_height_ranges = src_object->layer_height_ranges;
dst_object->layer_height_profile = src_object->layer_height_profile;
dst_object->origin_translation = src_object->origin_translation;
for (int i : object.second)
{
dst_object->add_instance(*src_object->instances[i]);
}
for (unsigned int i : m_list)
{
// Copy the ModelVolumes only for the selected GLVolumes of the 1st selected instance.
const GLVolume* volume = (*m_volumes)[i];
if ((volume->object_idx() == object.first) && (volume->instance_idx() == *object.second.begin()))
{
int volume_idx = volume->volume_idx();
if ((0 <= volume_idx) && (volume_idx < (int)src_object->volumes.size()))
{
ModelVolume* src_volume = src_object->volumes[volume_idx];
ModelVolume* dst_volume = dst_object->add_volume(*src_volume);
dst_volume->set_new_unique_id();
} else {
assert(false);
}
}
}
}
m_clipboard.set_mode(m_mode);
}
void Selection::paste_from_clipboard()
{
if (!m_valid || m_clipboard.is_empty())
return;
switch (m_clipboard.get_mode())
{
case Volume:
{
if (is_from_single_instance())
paste_volumes_from_clipboard();
break;
}
case Instance:
{
if (m_mode == Instance)
paste_objects_from_clipboard();
break;
}
}
}
void Selection::update_valid()
{
m_valid = (m_volumes != nullptr) && (m_model != nullptr);
@ -1697,5 +1768,44 @@ bool Selection::is_from_fully_selected_instance(unsigned int volume_idx) const
return count == (unsigned int)m_model->objects[object_idx]->volumes.size();
}
void Selection::paste_volumes_from_clipboard()
{
int obj_idx = get_object_idx();
if ((obj_idx < 0) || ((int)m_model->objects.size() <= obj_idx))
return;
ModelObject* src_object = m_clipboard.get_object(0);
if (src_object != nullptr)
{
ModelObject* dst_object = m_model->objects[obj_idx];
ModelVolumePtrs volumes;
for (ModelVolume* src_volume : src_object->volumes)
{
ModelVolume* dst_volume = dst_object->add_volume(*src_volume);
dst_volume->set_new_unique_id();
double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05);
dst_volume->translate(offset, offset, 0.0);
volumes.push_back(dst_volume);
}
wxGetApp().obj_list()->paste_volumes_into_list(obj_idx, volumes);
}
}
void Selection::paste_objects_from_clipboard()
{
std::vector<size_t> object_idxs;
const ModelObjectPtrs& src_objects = m_clipboard.get_objects();
for (const ModelObject* src_object : src_objects)
{
ModelObject* dst_object = m_model->add_object(*src_object);
double offset = wxGetApp().plater()->canvas3D()->get_size_proportional_to_max_bed_size(0.05);
dst_object->translate(offset, offset, 0.0);
object_idxs.push_back(m_model->objects.size() - 1);
}
wxGetApp().obj_list()->paste_objects_into_list(object_idxs);
}
} // namespace GUI
} // namespace Slic3r

View file

@ -138,6 +138,23 @@ public:
typedef std::set<int> InstanceIdxsList;
typedef std::map<int, InstanceIdxsList> ObjectIdxsToInstanceIdxsMap;
class Clipboard
{
Model m_model;
Selection::EMode m_mode;
public:
void reset() { m_model.clear_objects(); }
bool is_empty() const { return m_model.objects.empty(); }
ModelObject* add_object() { return m_model.add_object(); }
ModelObject* get_object(unsigned int id) { return (id < (unsigned int)m_model.objects.size()) ? m_model.objects[id] : nullptr; }
const ModelObjectPtrs& get_objects() const { return m_model.objects; }
Selection::EMode get_mode() const { return m_mode; }
void set_mode(Selection::EMode mode) { m_mode = mode; }
};
private:
struct Cache
{
@ -163,6 +180,7 @@ private:
// set of indices to m_volumes
IndicesList m_list;
Cache m_cache;
Clipboard m_clipboard;
mutable BoundingBoxf3 m_bounding_box;
mutable bool m_bounding_box_dirty;
@ -267,6 +285,11 @@ public:
bool requires_local_axes() const;
void copy_to_clipboard();
void paste_from_clipboard();
const Clipboard& get_clipboard() const { return m_clipboard; }
private:
void update_valid();
void update_type();
@ -301,6 +324,9 @@ private:
void synchronize_unselected_volumes();
void ensure_on_bed();
bool is_from_fully_selected_instance(unsigned int volume_idx) const;
void paste_volumes_from_clipboard();
void paste_objects_from_clipboard();
};
} // namespace GUI

View file

@ -165,10 +165,13 @@ void Tab::create_preset_tab()
add_scaled_bitmap(this, m_bmp_value_unlock, "lock_open");
m_bmp_non_system = &m_bmp_white_bullet;
// Bitmaps to be shown on the "Undo user changes" button next to each input field.
// m_bmp_value_revert = create_scaled_bitmap(this, "undo");
// m_bmp_white_bullet = create_scaled_bitmap(this, luma >= 128 ? "dot" : "dot_white"/*"bullet_white.png"*/);
// m_bmp_question = create_scaled_bitmap(this, "question");
// m_bmp_value_revert = create_scaled_bitmap(this, "undo");
// m_bmp_white_bullet = create_scaled_bitmap(this, "bullet_white.png");
add_scaled_bitmap(this, m_bmp_value_revert, "undo");
add_scaled_bitmap(this, m_bmp_white_bullet, "bullet_white.png");
add_scaled_bitmap(this, m_bmp_white_bullet, luma >= 128 ? "dot" : "dot_white"/*"bullet_white.png"*/);
fill_icon_descriptions();
set_tooltips_text();
@ -1163,7 +1166,7 @@ void TabPrint::build()
optgroup = page->new_optgroup(_(L("Advanced")));
optgroup->append_single_option_line("interface_shells");
page = add_options_page(_(L("Advanced")), "wrench.png");
page = add_options_page(_(L("Advanced")), "wrench");
optgroup = page->new_optgroup(_(L("Extrusion width")));
optgroup->append_single_option_line("extrusion_width");
optgroup->append_single_option_line("first_layer_extrusion_width");
@ -1536,7 +1539,7 @@ void TabFilament::build()
optgroup->append_single_option_line("slowdown_below_layer_time");
optgroup->append_single_option_line("min_print_speed");
page = add_options_page(_(L("Advanced")), "wrench.png");
page = add_options_page(_(L("Advanced")), "wrench");
optgroup = page->new_optgroup(_(L("Filament properties")));
optgroup->append_single_option_line("filament_type");
optgroup->append_single_option_line("filament_soluble");
@ -1702,12 +1705,17 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup)
}
auto printhost_browse = [=](wxWindow* parent) {
// auto btn = m_printhost_browse_btn = new wxButton(parent, wxID_ANY, _(L(" Browse ")) + dots,
// wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
// btn->SetFont(Slic3r::GUI::wxGetApp().normal_font());
// btn->SetBitmap(create_scaled_bitmap(this, "browse"));
// auto btn = m_printhost_browse_btn = new wxButton(parent, wxID_ANY, _(L(" Browse ")) + dots,
// wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
// btn->SetBitmap(create_scaled_bitmap(this, "zoom.png"));
add_scaled_button(parent, &m_printhost_browse_btn, "zoom.png", _(L(" Browse ")) + dots, wxBU_LEFT | wxBU_EXACTFIT);
add_scaled_button(parent, &m_printhost_browse_btn, "browse", _(L(" Browse ")) + dots, wxBU_LEFT | wxBU_EXACTFIT);
PrusaButton* btn = m_printhost_browse_btn;
btn->SetFont(Slic3r::GUI::wxGetApp().normal_font());
btn->SetFont(Slic3r::GUI::wxGetApp().normal_font());
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn);
@ -1722,6 +1730,11 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup)
return sizer;
};
auto print_host_test = [this](wxWindow* parent) {
auto btn = m_print_host_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")),
wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
btn->SetFont(Slic3r::GUI::wxGetApp().normal_font());
btn->SetBitmap(create_scaled_bitmap(this, "test"));
auto print_host_test = [this](wxWindow* parent) {
// auto btn = m_print_host_test_btn = new wxButton(parent, wxID_ANY, _(L("Test")),
// wxDefaultPosition, wxDefaultSize, wxBU_LEFT | wxBU_EXACTFIT);
@ -1765,7 +1778,7 @@ void TabPrinter::build_printhost(ConfigOptionsGroup *optgroup)
auto printhost_cafile_browse = [this, optgroup] (wxWindow* parent) {
auto btn = new wxButton(parent, wxID_ANY, _(L(" Browse "))+dots, wxDefaultPosition, wxDefaultSize, wxBU_LEFT);
btn->SetFont(Slic3r::GUI::wxGetApp().normal_font());
btn->SetBitmap(create_scaled_bitmap(this, "zoom.png"));
btn->SetBitmap(create_scaled_bitmap(this, "browse"));
auto sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(btn);
@ -3330,7 +3343,8 @@ void TabSLAMaterial::build()
optgroup = page->new_optgroup(_(L("Corrections")));
optgroup->label_width = 19;//190;
std::vector<std::string> corrections = {"material_correction"};
std::vector<std::string> axes{ "X", "Y", "Z" };
// std::vector<std::string> axes{ "X", "Y", "Z" };
std::vector<std::string> axes{ "XY", "Z" };
for (auto& opt_key : corrections) {
auto line = Line{ m_config->def()->get(opt_key)->full_label, "" };
int id = 0;
@ -3413,7 +3427,7 @@ void TabSLAPrint::build()
optgroup->append_single_option_line("layer_height");
optgroup->append_single_option_line("faded_layers");
page = add_options_page(_(L("Supports")), "sla_supports");
page = add_options_page(_(L("Supports")), "support"/*"sla_supports"*/);
optgroup = page->new_optgroup(_(L("Supports")));
optgroup->append_single_option_line("supports_enable");
@ -3441,7 +3455,7 @@ void TabSLAPrint::build()
optgroup->append_single_option_line("support_points_density_relative");
optgroup->append_single_option_line("support_points_minimal_distance");
page = add_options_page(_(L("Pad")), "brick.png");
page = add_options_page(_(L("Pad")), "pad");
optgroup = page->new_optgroup(_(L("Pad")));
optgroup->append_single_option_line("pad_enable");
optgroup->append_single_option_line("pad_wall_thickness");

View file

@ -461,10 +461,10 @@ wxBitmap create_scaled_bitmap(wxWindow *win, const std::string& bmp_name_in, con
// ----------------------------------------------------------------------------
void PrusaObjectDataViewModelNode::set_object_action_icon() {
m_action_icon = create_scaled_bitmap(nullptr, "add_object.png"); // FIXME: pass window ptr
m_action_icon = create_scaled_bitmap(nullptr, "advanced_plus"); // FIXME: pass window ptr
}
void PrusaObjectDataViewModelNode::set_part_action_icon() {
m_action_icon = create_scaled_bitmap(nullptr, m_type == itVolume ? "cog.png" : "brick_go.png"); // FIXME: pass window ptr
m_action_icon = create_scaled_bitmap(nullptr, m_type == itVolume ? "cog" : "set_separate_obj"); // FIXME: pass window ptr
}
Slic3r::GUI::BitmapCache *m_bitmap_cache = nullptr;
@ -529,10 +529,10 @@ wxDataViewItem PrusaObjectDataViewModel::Add(const wxString &name, const int ext
}
wxDataViewItem PrusaObjectDataViewModel::AddVolumeChild(const wxDataViewItem &parent_item,
const wxString &name,
const Slic3r::ModelVolumeType volume_type,
const int extruder/* = 0*/,
const bool create_frst_child/* = true*/)
const wxString &name,
const Slic3r::ModelVolumeType volume_type,
const int extruder/* = 0*/,
const bool create_frst_child/* = true*/)
{
PrusaObjectDataViewModelNode *root = (PrusaObjectDataViewModelNode*)parent_item.GetID();
if (!root) return wxDataViewItem(0);
@ -545,7 +545,7 @@ wxDataViewItem PrusaObjectDataViewModel::AddVolumeChild(const wxDataViewItem &pa
insert_position = -1;
if (create_frst_child && root->m_volumes_cnt == 0)
{
{
const auto node = new PrusaObjectDataViewModelNode(root, root->m_name, *m_volume_bmps[0], extruder_str, 0);
insert_position < 0 ? root->Append(node) : root->Insert(node, insert_position);
// notify control

View file

@ -454,12 +454,12 @@ public:
~PrusaObjectDataViewModel();
wxDataViewItem Add(const wxString &name, const int extruder);
wxDataViewItem AddVolumeChild(const wxDataViewItem &parent_item,
const wxString &name,
const Slic3r::ModelVolumeType volume_type,
const int extruder = 0,
const bool create_frst_child = true);
wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item);
wxDataViewItem AddVolumeChild(const wxDataViewItem &parent_item,
const wxString &name,
const Slic3r::ModelVolumeType volume_type,
const int extruder = 0,
const bool create_frst_child = true);
wxDataViewItem AddSettingsChild(const wxDataViewItem &parent_item);
wxDataViewItem AddInstanceChild(const wxDataViewItem &parent_item, size_t num);
wxDataViewItem Delete(const wxDataViewItem &item);
wxDataViewItem DeleteLastInstance(const wxDataViewItem &parent_item, size_t num);