Merge branch 'master' into fs_QuadricEdgeCollapse

This commit is contained in:
Filip Sykala 2021-07-19 09:18:55 +02:00
commit 86a3fd00a5
98 changed files with 21002 additions and 7709 deletions

View file

@ -103,6 +103,8 @@ set(SLIC3R_GUI_SOURCES
GUI/GUI_Factories.hpp
GUI/GUI_ObjectList.cpp
GUI/GUI_ObjectList.hpp
GUI/GalleryDialog.cpp
GUI/GalleryDialog.hpp
GUI/GUI_ObjectManipulation.cpp
GUI/GUI_ObjectManipulation.hpp
GUI/GUI_ObjectSettings.cpp

View file

@ -748,21 +748,18 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo
GLVolumeWithIdAndZList list;
list.reserve(volumes.size());
for (unsigned int i = 0; i < (unsigned int)volumes.size(); ++i)
{
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)) &&
if (((type == GLVolumeCollection::ERenderType::Opaque && !is_transparent) ||
(type == GLVolumeCollection::ERenderType::Transparent && is_transparent) ||
type == GLVolumeCollection::ERenderType::All) &&
(! filter_func || filter_func(*volume)))
list.emplace_back(std::make_pair(volume, std::make_pair(i, 0.0)));
}
if ((type == GLVolumeCollection::Transparent) && (list.size() > 1))
{
for (GLVolumeWithIdAndZ& volume : list)
{
if (type == GLVolumeCollection::ERenderType::Transparent && list.size() > 1) {
for (GLVolumeWithIdAndZ& volume : list) {
volume.second.second = volume.first->bounding_box().transformed(view_matrix * volume.first->world_matrix()).max(2);
}
@ -770,8 +767,7 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo
[](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool { return v1.second.second < v2.second.second; }
);
}
else if ((type == GLVolumeCollection::Opaque) && (list.size() > 1))
{
else if (type == GLVolumeCollection::ERenderType::Opaque && list.size() > 1) {
std::sort(list.begin(), list.end(),
[](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool { return v1.first->selected && !v2.first->selected; }
);
@ -786,8 +782,10 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
if (shader == nullptr)
return;
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
if (type == ERenderType::Transparent) {
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
}
glsafe(::glCullFace(GL_BACK));
if (disable_cullface)
@ -840,7 +838,8 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
if (disable_cullface)
glsafe(::glEnable(GL_CULL_FACE));
glsafe(::glDisable(GL_BLEND));
if (type == ERenderType::Transparent)
glsafe(::glDisable(GL_BLEND));
}
bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state) const

View file

@ -39,26 +39,22 @@ enum ModelInstanceEPrintVolumeState : unsigned char;
// possibly indexed by triangles and / or quads.
class GLIndexedVertexArray {
public:
GLIndexedVertexArray() :
vertices_and_normals_interleaved_VBO_id(0),
triangle_indices_VBO_id(0),
quad_indices_VBO_id(0)
{}
// Only Eigen types of Nx16 size are vectorized. This bounding box will not be vectorized.
static_assert(sizeof(Eigen::AlignedBox<float, 3>) == 24, "Eigen::AlignedBox<float, 3> is not being vectorized, thus it does not need to be aligned");
using BoundingBox = Eigen::AlignedBox<float, 3>;
GLIndexedVertexArray() { m_bounding_box.setEmpty(); }
GLIndexedVertexArray(const GLIndexedVertexArray &rhs) :
vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved),
triangle_indices(rhs.triangle_indices),
quad_indices(rhs.quad_indices),
vertices_and_normals_interleaved_VBO_id(0),
triangle_indices_VBO_id(0),
quad_indices_VBO_id(0)
{ assert(! rhs.has_VBOs()); }
m_bounding_box(rhs.m_bounding_box)
{ assert(! rhs.has_VBOs()); m_bounding_box.setEmpty(); }
GLIndexedVertexArray(GLIndexedVertexArray &&rhs) :
vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)),
triangle_indices(std::move(rhs.triangle_indices)),
quad_indices(std::move(rhs.quad_indices)),
vertices_and_normals_interleaved_VBO_id(0),
triangle_indices_VBO_id(0),
quad_indices_VBO_id(0)
m_bounding_box(rhs.m_bounding_box)
{ assert(! rhs.has_VBOs()); }
~GLIndexedVertexArray() { release_geometry(); }
@ -92,7 +88,7 @@ public:
this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved);
this->triangle_indices = std::move(rhs.triangle_indices);
this->quad_indices = std::move(rhs.quad_indices);
this->m_bounding_box = std::move(rhs.m_bounding_box);
this->m_bounding_box = rhs.m_bounding_box;
this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size;
this->triangle_indices_size = rhs.triangle_indices_size;
this->quad_indices_size = rhs.quad_indices_size;
@ -147,7 +143,7 @@ public:
this->vertices_and_normals_interleaved.emplace_back(z);
this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size();
m_bounding_box.merge(Vec3f(x, y, z).cast<double>());
m_bounding_box.extend(Vec3f(x, y, z));
};
inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) {
@ -203,10 +199,10 @@ public:
this->vertices_and_normals_interleaved.clear();
this->triangle_indices.clear();
this->quad_indices.clear();
this->m_bounding_box.reset();
vertices_and_normals_interleaved_size = 0;
triangle_indices_size = 0;
quad_indices_size = 0;
m_bounding_box.setEmpty();
}
// Shrink the internal storage to tighly fit the data stored.
@ -216,7 +212,7 @@ public:
this->quad_indices.shrink_to_fit();
}
const BoundingBoxf3& bounding_box() const { return m_bounding_box; }
const BoundingBox& bounding_box() const { return m_bounding_box; }
// Return an estimate of the memory consumed by this class.
size_t cpu_memory_used() const { return sizeof(*this) + vertices_and_normals_interleaved.capacity() * sizeof(float) + triangle_indices.capacity() * sizeof(int) + quad_indices.capacity() * sizeof(int); }
@ -235,7 +231,7 @@ public:
size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); }
private:
BoundingBoxf3 m_bounding_box;
BoundingBox m_bounding_box;
};
class GLVolume {
@ -355,7 +351,15 @@ public:
std::vector<size_t> offsets;
// Bounding box of this volume, in unscaled coordinates.
const BoundingBoxf3& bounding_box() const { return this->indexed_vertex_array.bounding_box(); }
BoundingBoxf3 bounding_box() const {
BoundingBoxf3 out;
if (! this->indexed_vertex_array.bounding_box().isEmpty()) {
out.min = this->indexed_vertex_array.bounding_box().min().cast<double>();
out.max = this->indexed_vertex_array.bounding_box().max().cast<double>();
out.defined = true;
};
return out;
}
void set_render_color(float r, float g, float b, float a);
void set_render_color(const float* rgba, unsigned int size);
@ -476,7 +480,7 @@ typedef std::vector<GLVolumeWithIdAndZ> GLVolumeWithIdAndZList;
class GLVolumeCollection
{
public:
enum ERenderType : unsigned char
enum class ERenderType : unsigned char
{
Opaque,
Transparent,

View file

@ -72,8 +72,7 @@ CopyrightsDialog::CopyrightsDialog()
m_html->Bind(wxEVT_HTML_LINK_CLICKED, &CopyrightsDialog::onLinkClicked, this);
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE);
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_CLOSE, this)));
wxGetApp().UpdateDlgDarkUI(this, true);
this->SetEscapeId(wxID_CLOSE);
this->Bind(wxEVT_BUTTON, &CopyrightsDialog::onCloseDialog, this, wxID_CLOSE);
sizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
@ -297,20 +296,18 @@ AboutDialog::AboutDialog()
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE);
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_CLOSE, this)));
m_copy_rights_btn_id = NewControlId();
auto copy_rights_btn = new wxButton(this, m_copy_rights_btn_id, _L("Portions copyright")+dots);
buttons->Insert(0, copy_rights_btn, 0, wxLEFT, 5);
buttons->Insert(0, copy_rights_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
copy_rights_btn->Bind(wxEVT_BUTTON, &AboutDialog::onCopyrightBtn, this);
m_copy_version_btn_id = NewControlId();
auto copy_version_btn = new wxButton(this, m_copy_version_btn_id, _L("Copy Version Info"));
buttons->Insert(1, copy_version_btn, 0, wxLEFT, 5);
buttons->Insert(1, copy_version_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
copy_version_btn->Bind(wxEVT_BUTTON, &AboutDialog::onCopyToClipboard, this);
wxGetApp().UpdateDarkUI(copy_rights_btn);
wxGetApp().UpdateDarkUI(copy_version_btn);
wxGetApp().UpdateDlgDarkUI(this, true);
this->SetEscapeId(wxID_CLOSE);
this->Bind(wxEVT_BUTTON, &AboutDialog::onCloseDialog, this, wxID_CLOSE);

View file

@ -177,7 +177,6 @@ void BedShape::apply_optgroup_values(ConfigOptionsGroupShp optgroup)
void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model)
{
SetFont(wxGetApp().normal_font());
// wxGetApp().UpdateDarkUI(this);
m_panel = new BedShapePanel(this);
m_panel->build_panel(default_pt, custom_texture, custom_model);
@ -186,8 +185,7 @@ void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt, const Co
main_sizer->Add(m_panel, 1, wxEXPAND);
main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_OK, this)), true);
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_CANCEL, this)), true);
wxGetApp().UpdateDlgDarkUI(this, true);
SetSizer(main_sizer);
SetMinSize(GetSize());
@ -249,7 +247,6 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf
line.full_width = 1;
line.widget = [this](wxWindow* parent) {
wxButton* shape_btn = new wxButton(parent, wxID_ANY, _L("Load shape from STL..."));
wxGetApp().UpdateDarkUI(shape_btn, true);
wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL);
shape_sizer->Add(shape_btn, 1, wxEXPAND);
@ -333,7 +330,6 @@ wxPanel* BedShapePanel::init_texture_panel()
line.full_width = 1;
line.widget = [this](wxWindow* parent) {
wxButton* load_btn = new wxButton(parent, wxID_ANY, _(L("Load...")));
wxGetApp().UpdateDarkUI(load_btn, true);
wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL);
load_sizer->Add(load_btn, 1, wxEXPAND);
@ -343,7 +339,6 @@ wxPanel* BedShapePanel::init_texture_panel()
filename_sizer->Add(filename_lbl, 1, wxEXPAND);
wxButton* remove_btn = new wxButton(parent, wxID_ANY, _(L("Remove")));
wxGetApp().UpdateDarkUI(remove_btn, true);
wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL);
remove_sizer->Add(remove_btn, 1, wxEXPAND);
@ -417,7 +412,6 @@ wxPanel* BedShapePanel::init_model_panel()
line.full_width = 1;
line.widget = [this](wxWindow* parent) {
wxButton* load_btn = new wxButton(parent, wxID_ANY, _(L("Load...")));
wxGetApp().UpdateDarkUI(load_btn, true);
wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL);
load_sizer->Add(load_btn, 1, wxEXPAND);
@ -426,7 +420,6 @@ wxPanel* BedShapePanel::init_model_panel()
filename_sizer->Add(filename_lbl, 1, wxEXPAND);
wxButton* remove_btn = new wxButton(parent, wxID_ANY, _(L("Remove")));
wxGetApp().UpdateDarkUI(remove_btn, true);
wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL);
remove_sizer->Add(remove_btn, 1, wxEXPAND);

View file

@ -70,9 +70,9 @@ ButtonsDescription::ButtonsDescription(wxWindow* parent, const std::vector<Entry
auto buttons = CreateStdDialogButtonSizer(wxOK|wxCANCEL);
main_sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
wxGetApp().UpdateDlgDarkUI(this, true);
wxButton* btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
wxGetApp().UpdateDarkUI(btn);
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_CANCEL, this)));
btn->Bind(wxEVT_BUTTON, [sys_colour, mod_colour, this](wxCommandEvent&) {
wxGetApp().set_label_clr_sys(sys_colour->GetColour());
wxGetApp().set_label_clr_modified(mod_colour->GetColour());

View file

@ -25,19 +25,19 @@ std::string Camera::get_type_as_string() const
{
switch (m_type)
{
case Unknown: return "unknown";
case Perspective: return "perspective";
case EType::Unknown: return "unknown";
case EType::Perspective: return "perspective";
default:
case Ortho: return "orthographic";
case EType::Ortho: return "orthographic";
};
}
void Camera::set_type(EType type)
{
if (m_type != type) {
if (m_type != type && (type == EType::Ortho || type == EType::Perspective)) {
m_type = type;
if (m_update_config_on_type_change_enabled) {
wxGetApp().app_config->set("use_perspective_camera", (m_type == Perspective) ? "1" : "0");
wxGetApp().app_config->set("use_perspective_camera", (m_type == EType::Perspective) ? "1" : "0");
wxGetApp().app_config->save();
}
}
@ -46,7 +46,7 @@ void Camera::set_type(EType type)
void Camera::select_next_type()
{
unsigned char next = (unsigned char)m_type + 1;
if (next == (unsigned char)Num_types)
if (next == (unsigned char)EType::Num_types)
next = 1;
set_type((EType)next);
@ -95,10 +95,10 @@ double Camera::get_fov() const
{
switch (m_type)
{
case Perspective:
case EType::Perspective:
return 2.0 * Geometry::rad2deg(std::atan(1.0 / m_projection_matrix.matrix()(1, 1)));
default:
case Ortho:
case EType::Ortho:
return 0.0;
};
}
@ -143,12 +143,12 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa
switch (m_type)
{
default:
case Ortho:
case EType::Ortho:
{
m_gui_scale = 1.0;
break;
}
case Perspective:
case EType::Perspective:
{
// scale near plane to keep w and h constant on the plane at z = m_distance
const double scale = m_frustrum_zs.first / m_distance;
@ -165,12 +165,12 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa
switch (m_type)
{
default:
case Ortho:
case EType::Ortho:
{
glsafe(::glOrtho(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second));
break;
}
case Perspective:
case EType::Perspective:
{
glsafe(::glFrustum(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second));
break;
@ -501,7 +501,7 @@ void Camera::set_default_orientation()
const Vec3d camera_pos = m_target + m_distance * Vec3d(sin_theta * ::sin(phi_rad), sin_theta * ::cos(phi_rad), ::cos(theta_rad));
m_view_rotation = Eigen::AngleAxisd(theta_rad, Vec3d::UnitX()) * Eigen::AngleAxisd(phi_rad, Vec3d::UnitZ());
m_view_rotation.normalize();
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (- camera_pos), m_view_rotation, Vec3d(1., 1., 1.));
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-camera_pos), m_view_rotation, Vec3d::Ones());
}
Vec3d Camera::validate_target(const Vec3d& target) const

View file

@ -18,7 +18,7 @@ struct Camera
static double FrustrumZMargin;
static double MaxFovDeg;
enum EType : unsigned char
enum class EType : unsigned char
{
Unknown,
Ortho,
@ -29,7 +29,7 @@ struct Camera
bool requires_zoom_to_bed{ false };
private:
EType m_type{ Perspective };
EType m_type{ EType::Perspective };
bool m_update_config_on_type_change_enabled{ false };
Vec3d m_target{ Vec3d::Zero() };
float m_zenit{ 45.0f };
@ -54,7 +54,7 @@ public:
std::string get_type_as_string() const;
void set_type(EType type);
// valid values for type: "0" -> ortho, "1" -> perspective
void set_type(const std::string& type) { set_type((type == "1") ? Perspective : Ortho); }
void set_type(const std::string& type) { set_type((type == "1") ? EType::Perspective : EType::Ortho); }
void select_next_type();
void enable_update_config_on_type_change(bool enable) { m_update_config_on_type_change_enabled = enable; }

View file

@ -276,7 +276,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
toggle_field(el, have_default_acceleration);
bool have_skirt = config->opt_int("skirts") > 0;
toggle_field("skirt_height", have_skirt && !config->opt_bool("draft_shield"));
toggle_field("skirt_height", have_skirt && config->opt_enum<DraftShield>("draft_shield") != dsEnabled);
for (auto el : { "skirt_distance", "draft_shield", "min_skirt_length" })
toggle_field(el, have_skirt);

View file

@ -992,22 +992,20 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected
}
materials->filter_presets(printer, type, vendor, [this, &to_list](const Preset* p) {
bool was_checked = false;
//size_t printer_counter = materials->get_printer_counter(p);
int cur_i = list_profile->find(p->alias);
bool emplace_to_to_list = false;
if (cur_i == wxNOT_FOUND) {
cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias);
emplace_to_to_list = true;
} else
was_checked = list_profile->IsChecked(cur_i);
const std::string& section = materials->appconfig_section();
bool checked = wizard_p()->appconfig_new.has(section, p->name);
bool was_checked = false;
const bool checked = wizard_p()->appconfig_new.has(section, p->name);
list_profile->Check(cur_i, checked || was_checked);
if (emplace_to_to_list)
to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked || was_checked);
int cur_i = list_profile->find(p->alias);
if (cur_i == wxNOT_FOUND) {
cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias);
to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked);
}
else {
was_checked = list_profile->IsChecked(cur_i);
to_list[cur_i].checked = checked || was_checked;
}
list_profile->Check(cur_i, checked || was_checked);
/* Update preset selection in config.
* If one preset from aliases bundle is selected,
@ -2335,6 +2333,7 @@ void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool i
bool ConfigWizard::priv::on_bnt_finish()
{
wxBusyCursor wait;
/* When Filaments or Sla Materials pages are activated,
* materials for this pages are automaticaly updated and presets are reloaded.
*

View file

@ -2417,7 +2417,7 @@ void GCodeViewer::render_toolpaths() const
const Camera& camera = wxGetApp().plater()->get_camera();
double zoom = camera.get_zoom();
const std::array<int, 4>& viewport = camera.get_viewport();
float near_plane_height = camera.get_type() == Camera::Perspective ? static_cast<float>(viewport[3]) / (2.0f * static_cast<float>(2.0 * std::tan(0.5 * Geometry::deg2rad(camera.get_fov())))) :
float near_plane_height = camera.get_type() == Camera::EType::Perspective ? static_cast<float>(viewport[3]) / (2.0f * static_cast<float>(2.0 * std::tan(0.5 * Geometry::deg2rad(camera.get_fov())))) :
static_cast<float>(viewport[3]) * 0.0005;
auto set_uniform_color = [](const std::array<float, 3>& color, GLShaderProgram& shader) {
@ -2593,7 +2593,7 @@ void GCodeViewer::render_shells() const
// glsafe(::glDepthMask(GL_FALSE));
shader->start_using();
m_shells.volumes.render(GLVolumeCollection::Transparent, true, wxGetApp().plater()->get_camera().get_view_matrix());
m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, true, wxGetApp().plater()->get_camera().get_view_matrix());
shader->stop_using();
// glsafe(::glDepthMask(GL_TRUE));

View file

@ -21,7 +21,6 @@
#include "slic3r/GUI/GUI_Preview.hpp"
#include "slic3r/GUI/OpenGLManager.hpp"
#include "slic3r/GUI/3DBed.hpp"
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/MainFrame.hpp"
@ -697,7 +696,7 @@ void GLCanvas3D::Labels::render(const std::vector<const ModelInstance*>& sorted_
Vec3d screen_box_center = world_to_screen * owner.world_box.center();
float x = 0.0f;
float y = 0.0f;
if (camera.get_type() == Camera::Perspective) {
if (camera.get_type() == Camera::EType::Perspective) {
x = (0.5f + 0.001f * 0.5f * (float)screen_box_center(0)) * viewport[2];
y = (0.5f - 0.001f * 0.5f * (float)screen_box_center(1)) * viewport[3];
} else {
@ -729,7 +728,7 @@ void GLCanvas3D::Labels::render(const std::vector<const ModelInstance*>& sorted_
}
// force re-render while the windows gets to its final size (it takes several frames)
if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowExpectedSize(ImGui::GetCurrentWindow()).x)
if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x)
m_canvas.request_extra_frame();
imgui.end();
@ -780,7 +779,7 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas
ImGui::TextUnformatted(m_text.c_str());
// force re-render while the windows gets to its final size (it may take several frames) or while hidden
if (alpha < 1.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowExpectedSize(ImGui::GetCurrentWindow()).x)
if (alpha < 1.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x)
canvas.request_extra_frame();
size = ImGui::GetWindowSize();
@ -1476,12 +1475,20 @@ void GLCanvas3D::render()
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
_render_background();
#if ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
_render_objects(GLVolumeCollection::ERenderType::Opaque);
#else
_render_objects();
#endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
if (!m_main_toolbar.is_enabled())
_render_gcode();
_render_sla_slices();
_render_selection();
_render_bed(!camera.is_looking_downward(), true);
#if ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
_render_objects(GLVolumeCollection::ERenderType::Transparent);
#endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
#if ENABLE_SEQUENTIAL_LIMITS
_render_sequential_clearance();
#endif // ENABLE_SEQUENTIAL_LIMITS
@ -1589,13 +1596,18 @@ void GLCanvas3D::render()
#endif // ENABLE_RENDER_STATISTICS
}
void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type)
{
render_thumbnail(thumbnail_data, w, h, thumbnail_params, m_volumes, camera_type);
}
void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type)
{
switch (OpenGLManager::get_framebuffers_type())
{
case OpenGLManager::EFramebufferType::Arb: { _render_thumbnail_framebuffer(thumbnail_data, w, h, printable_only, parts_only, show_bed, transparent_background); break; }
case OpenGLManager::EFramebufferType::Ext: { _render_thumbnail_framebuffer_ext(thumbnail_data, w, h, printable_only, parts_only, show_bed, transparent_background); break; }
default: { _render_thumbnail_legacy(thumbnail_data, w, h, printable_only, parts_only, show_bed, transparent_background); break; }
case OpenGLManager::EFramebufferType::Arb: { _render_thumbnail_framebuffer(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; }
case OpenGLManager::EFramebufferType::Ext: { _render_thumbnail_framebuffer_ext(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; }
default: { _render_thumbnail_legacy(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; }
}
}
@ -2910,9 +2922,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str());
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
m_dirty = true;
m_dirty = true;
// do not return if dragging or tooltip not empty to allow for tooltip update
if (!m_mouse.dragging && m_tooltip.is_empty())
// also, do not return if the mouse is moving and also is inside MM gizmo to allow update seed fill selection
if (!m_mouse.dragging && m_tooltip.is_empty() && (m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation || !evt.Moving()))
return;
}
@ -4085,7 +4098,7 @@ static void debug_output_thumbnail(const ThumbnailData& thumbnail_data)
}
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type)
{
auto is_visible = [](const GLVolume& v) {
bool ret = v.printable;
@ -4098,9 +4111,9 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
GLVolumePtrs visible_volumes;
for (GLVolume* vol : m_volumes.volumes) {
if (!vol->is_modifier && !vol->is_wipe_tower && (!parts_only || (vol->composite_id.volume_id >= 0))) {
if (!printable_only || is_visible(*vol))
for (GLVolume* vol : volumes.volumes) {
if (!vol->is_modifier && !vol->is_wipe_tower && (!thumbnail_params.parts_only || vol->composite_id.volume_id >= 0)) {
if (!thumbnail_params.printable_only || is_visible(*vol))
visible_volumes.emplace_back(vol);
}
}
@ -4108,37 +4121,37 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
if (visible_volumes.empty())
return;
BoundingBoxf3 box;
BoundingBoxf3 volumes_box;
for (const GLVolume* vol : visible_volumes) {
box.merge(vol->transformed_bounding_box());
volumes_box.merge(vol->transformed_bounding_box());
}
Camera camera;
camera.set_type(Camera::Ortho);
camera.set_type(camera_type);
camera.set_scene_box(scene_bounding_box());
camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height);
camera.zoom_to_volumes(visible_volumes);
camera.zoom_to_box(volumes_box);
camera.apply_view_matrix();
double near_z = -1.0;
double far_z = -1.0;
if (show_bed) {
if (thumbnail_params.show_bed) {
// extends the near and far z of the frustrum to avoid the bed being clipped
// box in eye space
BoundingBoxf3 t_bed_box = wxGetApp().plater()->get_bed().get_bounding_box(true).transformed(camera.get_view_matrix());
near_z = -t_bed_box.max(2);
far_z = -t_bed_box.min(2);
near_z = -t_bed_box.max.z();
far_z = -t_bed_box.min.z();
}
camera.apply_projection(box, near_z, far_z);
camera.apply_projection(volumes_box, near_z, far_z);
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
if (shader == nullptr)
return;
if (transparent_background)
if (thumbnail_params.transparent_background)
glsafe(::glClearColor(0.0f, 0.0f, 0.0f, 0.0f));
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
@ -4160,15 +4173,15 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
glsafe(::glDisable(GL_DEPTH_TEST));
if (show_bed)
if (thumbnail_params.show_bed)
_render_bed(!camera.is_looking_downward(), false);
// restore background color
if (transparent_background)
if (thumbnail_params.transparent_background)
glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
}
void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type)
{
thumbnail_data.set(w, h);
if (!thumbnail_data.is_valid())
@ -4218,7 +4231,7 @@ void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, un
glsafe(::glDrawBuffers(1, drawBufs));
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
_render_thumbnail_internal(thumbnail_data, printable_only, parts_only, show_bed, transparent_background);
_render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type);
if (multisample) {
GLuint resolve_fbo;
@ -4267,7 +4280,7 @@ void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, un
glsafe(::glDisable(GL_MULTISAMPLE));
}
void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type)
{
thumbnail_data.set(w, h);
if (!thumbnail_data.is_valid())
@ -4317,7 +4330,7 @@ void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data
glsafe(::glDrawBuffers(1, drawBufs));
if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) {
_render_thumbnail_internal(thumbnail_data, printable_only, parts_only, show_bed, transparent_background);
_render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type);
if (multisample) {
GLuint resolve_fbo;
@ -4366,7 +4379,7 @@ void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data
glsafe(::glDisable(GL_MULTISAMPLE));
}
void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type)
{
// check that thumbnail size does not exceed the default framebuffer size
const Size& cnv_size = get_canvas_size();
@ -4382,7 +4395,7 @@ void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigne
if (!thumbnail_data.is_valid())
return;
_render_thumbnail_internal(thumbnail_data, printable_only, parts_only, show_bed, transparent_background);
_render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type);
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
@ -5031,7 +5044,11 @@ void GLCanvas3D::_render_bed(bool bottom, bool show_axes)
wxGetApp().plater()->get_bed().render(*this, bottom, scale_factor, show_axes, show_texture);
}
#if ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type)
#else
void GLCanvas3D::_render_objects()
#endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
{
if (m_volumes.empty())
return;
@ -5062,37 +5079,63 @@ void GLCanvas3D::_render_objects()
if (shader != nullptr) {
shader->start_using();
if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) {
int object_id = m_layers_editing.last_object_id;
m_volumes.render(GLVolumeCollection::Opaque, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) {
// Which volume to paint without the layer height profile shader?
return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id);
});
// Let LayersEditing handle rendering of the active object using the layer height profile shader.
m_layers_editing.render_volumes(*this, m_volumes);
} else {
// do not cull backfaces to show broken geometry, if any
m_volumes.render(GLVolumeCollection::Opaque, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this](const GLVolume& volume) {
return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0);
});
}
// In case a painting gizmo is open, it should render the painted triangles
// before transparent objects are rendered. Otherwise they would not be
// visible when inside modifier meshes etc.
#if ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
switch (type)
{
const GLGizmosManager& gm = get_gizmos_manager();
GLGizmosManager::EType type = gm.get_current_type();
if (type == GLGizmosManager::FdmSupports
|| type == GLGizmosManager::Seam
|| type == GLGizmosManager::MmuSegmentation) {
shader->stop_using();
gm.render_painter_gizmo();
shader->start_using();
default:
case GLVolumeCollection::ERenderType::Opaque:
{
#endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) {
int object_id = m_layers_editing.last_object_id;
#if ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
m_volumes.render(type, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) {
#else
m_volumes.render(GLVolumeCollection::ERenderType::Opaque, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) {
#endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
// Which volume to paint without the layer height profile shader?
return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id);
});
// Let LayersEditing handle rendering of the active object using the layer height profile shader.
m_layers_editing.render_volumes(*this, m_volumes);
}
else {
// do not cull backfaces to show broken geometry, if any
#if ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
m_volumes.render(type, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this](const GLVolume& volume) {
#else
m_volumes.render(GLVolumeCollection::ERenderType::Opaque, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this](const GLVolume& volume) {
#endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0);
});
}
}
m_volumes.render(GLVolumeCollection::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix());
// In case a painting gizmo is open, it should render the painted triangles
// before transparent objects are rendered. Otherwise they would not be
// visible when inside modifier meshes etc.
{
const GLGizmosManager& gm = get_gizmos_manager();
GLGizmosManager::EType type = gm.get_current_type();
if (type == GLGizmosManager::FdmSupports
|| type == GLGizmosManager::Seam
|| type == GLGizmosManager::MmuSegmentation) {
shader->stop_using();
gm.render_painter_gizmo();
shader->start_using();
}
}
#if ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
break;
}
case GLVolumeCollection::ERenderType::Transparent:
{
m_volumes.render(type, false, wxGetApp().plater()->get_camera().get_view_matrix());
break;
}
}
#else
m_volumes.render(GLVolumeCollection::ERenderType::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix());
#endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
shader->stop_using();
}
@ -5253,8 +5296,8 @@ void GLCanvas3D::_render_volumes_for_picking() const
const Transform3d& view_matrix = wxGetApp().plater()->get_camera().get_view_matrix();
for (size_t type = 0; type < 2; ++ type) {
GLVolumeWithIdAndZList to_render = volumes_to_render(m_volumes.volumes, (type == 0) ? GLVolumeCollection::Opaque : GLVolumeCollection::Transparent, view_matrix);
for (const GLVolumeWithIdAndZ& volume : to_render)
GLVolumeWithIdAndZList to_render = volumes_to_render(m_volumes.volumes, (type == 0) ? GLVolumeCollection::ERenderType::Opaque : GLVolumeCollection::ERenderType::Transparent, view_matrix);
for (const GLVolumeWithIdAndZ& volume : to_render)
if (!volume.first->disabled && ((volume.first->composite_id.volume_id >= 0) || m_render_sla_auxiliaries)) {
// Object picking mode. Render the object with a color encoding the object index.
unsigned int id = volume.second.first;
@ -5661,7 +5704,7 @@ void GLCanvas3D::_load_print_toolpaths()
if (print == nullptr)
return;
if (!print->is_step_done(psSkirt) || !print->is_step_done(psBrim))
if (! print->is_step_done(psSkirtBrim))
return;
if (!print->has_skirt() && !print->has_brim())

View file

@ -15,6 +15,7 @@
#include "MeshUtils.hpp"
#include "libslic3r/GCode/GCodeProcessor.hpp"
#include "GCodeViewer.hpp"
#include "Camera.hpp"
#include "libslic3r/Slicing.hpp"
@ -37,9 +38,9 @@ class wxGLContext;
namespace Slic3r {
struct Camera;
class BackgroundSlicingProcess;
struct ThumbnailData;
struct ThumbnailsParams;
class ModelObject;
class ModelInstance;
class PrintObject;
@ -622,7 +623,8 @@ public:
void render();
// printable_only == false -> render also non printable volumes as grayed
// parts_only == false -> render also sla support and pad
void render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
void render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type);
void render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type);
void select_all();
void deselect_all();
@ -820,7 +822,11 @@ private:
void _rectangular_selection_picking_pass();
void _render_background() const;
void _render_bed(bool bottom, bool show_axes);
#if ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
void _render_objects(GLVolumeCollection::ERenderType type);
#else
void _render_objects();
#endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
void _render_gcode() const;
void _render_selection() const;
#if ENABLE_SEQUENTIAL_LIMITS
@ -846,13 +852,13 @@ private:
bool _render_undo_redo_stack(const bool is_undo, float pos_x);
bool _render_search_list(float pos_x);
bool _render_arrange_menu(float pos_x);
void _render_thumbnail_internal(ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
void _render_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type);
// render thumbnail using an off-screen framebuffer
void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type);
// render thumbnail using an off-screen framebuffer when GLEW_EXT_framebuffer_object is supported
void _render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
void _render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type);
// render thumbnail using the default framebuffer
void _render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
void _render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type);
void _update_volumes_hover_state();

View file

@ -608,7 +608,7 @@ static void generic_exception_handle()
std::terminate();
throw;
} catch (const std::exception& ex) {
wxLogError("Internal error: %s", ex.what());
wxLogError(format_wxstr(_L("Internal error: %1%"), ex.what()));
BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what();
throw;
}
@ -1115,10 +1115,11 @@ void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool ju
// recursive function for scaling fonts for all controls in Window
#ifdef _WIN32
static void update_dark_children_ui(wxWindow* window)
static void update_dark_children_ui(wxWindow* window, bool just_buttons_update = false)
{
bool highlight_btn = dynamic_cast<wxButton*>(window) != nullptr;
wxGetApp().UpdateDarkUI(window, highlight_btn);
bool is_btn = dynamic_cast<wxButton*>(window) != nullptr;
if (!(just_buttons_update && !is_btn))
wxGetApp().UpdateDarkUI(window, is_btn);
auto children = window->GetChildren();
for (auto child : children) {
@ -1127,16 +1128,17 @@ static void update_dark_children_ui(wxWindow* window)
}
#endif
void GUI_App::UpdateDlgDarkUI(wxDialog* dlg)
// Note: Don't use this function for Dialog contains ScalableButtons
void GUI_App::UpdateDlgDarkUI(wxDialog* dlg, bool just_buttons_update/* = false*/)
{
#ifdef _WIN32
update_dark_children_ui(dlg);
update_dark_children_ui(dlg, just_buttons_update);
#endif
}
void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/)
{
#ifdef _WIN32
UpdateDarkUI(dvc, highlited);
UpdateDarkUI(dvc, highlited ? dark_mode() : false);
wxItemAttr attr(dark_mode() ? m_color_highlight_default : m_color_label_default,
m_color_window_default,
m_normal_font);

View file

@ -182,7 +182,7 @@ public:
// update color mode for window
void UpdateDarkUI(wxWindow *window, bool highlited = false, bool just_font = false);
// update color mode for whole dialog including all children
void UpdateDlgDarkUI(wxDialog* dlg);
void UpdateDlgDarkUI(wxDialog* dlg, bool just_buttons_update = false);
// update color mode for DataViewControl
void UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited = false);
// update color mode for panel including all static texts controls

View file

@ -433,6 +433,12 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty
[type, item](wxCommandEvent&) { obj_list()->load_generic_subobject(item, type); }, "", menu);
}
if (wxGetApp().get_mode() == comExpert) {
sub_menu->AppendSeparator();
append_menu_item(sub_menu, wxID_ANY, _L("Gallery"), "",
[type](wxCommandEvent&) { obj_list()->load_subobject(type, true); }, "", menu);
}
return sub_menu;
}

View file

@ -8,6 +8,7 @@
#include "I18N.hpp"
#include "Plater.hpp"
#include "BitmapComboBox.hpp"
#include "GalleryDialog.hpp"
#if ENABLE_PROJECT_DIRTY_STATE
#include "MainFrame.hpp"
#endif // ENABLE_PROJECT_DIRTY_STATE
@ -594,13 +595,31 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const
take_snapshot(volume_id < 0 ? _(L("Rename Object")) : _(L("Rename Sub-object")));
ModelObject* obj = object(obj_idx);
if (m_objects_model->GetItemType(item) & itObject) {
(*m_objects)[obj_idx]->name = m_objects_model->GetName(item).ToUTF8().data();
obj->name = m_objects_model->GetName(item).ToUTF8().data();
// if object has just one volume, rename this volume too
if (obj->volumes.size() == 1)
obj->volumes[0]->name = obj->name;
return;
}
if (volume_id < 0) return;
(*m_objects)[obj_idx]->volumes[volume_id]->name = m_objects_model->GetName(item).ToUTF8().data();
obj->volumes[volume_id]->name = m_objects_model->GetName(item).ToUTF8().data();
}
void ObjectList::update_name_in_list(int obj_idx, int vol_idx) const
{
if (obj_idx < 0) return;
wxDataViewItem item = GetSelection();
if (!item || !(m_objects_model->GetItemType(item) & (itVolume | itObject)))
return;
wxString new_name = from_u8(object(obj_idx)->volumes[vol_idx]->name);
if (new_name.IsEmpty() || m_objects_model->GetName(item) == new_name)
return;
m_objects_model->SetName(new_name, item);
}
void ObjectList::selection_changed()
@ -1337,8 +1356,13 @@ bool ObjectList::is_instance_or_object_selected()
return selection.is_single_full_instance() || selection.is_single_full_object();
}
void ObjectList::load_subobject(ModelVolumeType type)
void ObjectList::load_subobject(ModelVolumeType type, bool from_galery/* = false*/)
{
if (type == ModelVolumeType::INVALID && from_galery) {
load_shape_object_from_gallery();
return;
}
wxDataViewItem item = GetSelection();
// we can add volumes for Object or Instance
if (!item || !(m_objects_model->GetItemType(item)&(itObject|itInstance)))
@ -1351,10 +1375,17 @@ void ObjectList::load_subobject(ModelVolumeType type)
if (m_objects_model->GetItemType(item)&itInstance)
item = m_objects_model->GetItemById(obj_idx);
take_snapshot(_L("Load Part"));
std::vector<ModelVolume*> volumes;
load_part((*m_objects)[obj_idx], volumes, type);
if (type == ModelVolumeType::MODEL_PART)
load_part(*(*m_objects)[obj_idx], volumes, type, from_galery);
else
load_modifier(*(*m_objects)[obj_idx], volumes, type, from_galery);
if (volumes.empty())
return;
take_snapshot((type == ModelVolumeType::MODEL_PART) ? _L("Load Part") : _L("Load Modifier"));
wxDataViewItemArray items = reorder_volumes_and_get_selection(obj_idx, [volumes](const ModelVolume* volume) {
return std::find(volumes.begin(), volumes.end(), volume) != volumes.end(); });
@ -1371,12 +1402,25 @@ void ObjectList::load_subobject(ModelVolumeType type)
selection_changed();
}
void ObjectList::load_part(ModelObject* model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type)
void ObjectList::load_part(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery/* = false*/)
{
if (type != ModelVolumeType::MODEL_PART)
return;
wxWindow* parent = wxGetApp().tab_panel()->GetPage(0);
wxArrayString input_files;
wxGetApp().import_model(parent, input_files);
if (from_galery) {
GalleryDialog dlg(this);
if (dlg.ShowModal() == wxID_CANCEL)
return;
dlg.get_input_files(input_files);
if (input_files.IsEmpty())
return;
}
else
wxGetApp().import_model(parent, input_files);
wxProgressDialog dlg(_L("Loading") + dots, "", 100, wxGetApp().plater(), wxPD_AUTO_HIDE);
wxBusyCursor busy;
@ -1400,13 +1444,13 @@ void ObjectList::load_part(ModelObject* model_object, std::vector<ModelVolume*>&
for (auto object : model.objects) {
Vec3d delta = Vec3d::Zero();
if (model_object->origin_translation != Vec3d::Zero()) {
if (model_object.origin_translation != Vec3d::Zero()) {
object->center_around_origin();
delta = model_object->origin_translation - object->origin_translation;
delta = model_object.origin_translation - object->origin_translation;
}
for (auto volume : object->volumes) {
volume->translate(delta);
auto new_volume = model_object->add_volume(*volume, type);
auto new_volume = model_object.add_volume(*volume, type);
new_volume->name = boost::filesystem::path(input_file).filename().string();
// set a default extruder value, since user can't add it manually
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
@ -1415,7 +1459,106 @@ void ObjectList::load_part(ModelObject* model_object, std::vector<ModelVolume*>&
}
}
}
}
void ObjectList::load_modifier(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery)
{
if (type == ModelVolumeType::MODEL_PART)
return;
wxWindow* parent = wxGetApp().tab_panel()->GetPage(0);
wxArrayString input_files;
if (from_galery) {
GalleryDialog dlg(this);
if (dlg.ShowModal() == wxID_CANCEL)
return;
dlg.get_input_files(input_files);
if (input_files.IsEmpty())
return;
}
else
wxGetApp().import_model(parent, input_files);
wxProgressDialog dlg(_L("Loading") + dots, "", 100, wxGetApp().plater(), wxPD_AUTO_HIDE);
wxBusyCursor busy;
const int obj_idx = get_selected_obj_idx();
if (obj_idx < 0)
return;
const Selection& selection = scene_selection();
assert(obj_idx == selection.get_object_idx());
/** Any changes of the Object's composition is duplicated for all Object's Instances
* So, It's enough to take a bounding box of a first selected Instance and calculate Part(generic_subobject) position
*/
int instance_idx = *selection.get_instance_idxs().begin();
assert(instance_idx != -1);
if (instance_idx == -1)
return;
// Bounding box of the selected instance in world coordinate system including the translation, without modifiers.
const BoundingBoxf3 instance_bb = model_object.instance_bounding_box(instance_idx);
// First (any) GLVolume of the selected instance. They all share the same instance matrix.
const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
const Geometry::Transformation inst_transform = v->get_instance_transformation();
const Transform3d inv_inst_transform = inst_transform.get_matrix(true).inverse();
const Vec3d instance_offset = v->get_instance_offset();
for (size_t i = 0; i < input_files.size(); ++i) {
const std::string input_file = input_files.Item(i).ToUTF8().data();
dlg.Update(static_cast<int>(100.0f * static_cast<float>(i) / static_cast<float>(input_files.size())),
_L("Loading file") + ": " + from_path(boost::filesystem::path(input_file).filename()));
dlg.Fit();
Model model;
try {
model = Model::read_from_file(input_file);
}
catch (std::exception& e) {
auto msg = _L("Error!") + " " + input_file + " : " + e.what() + ".";
show_error(parent, msg);
exit(1);
}
if (from_galery)
model.center_instances_around_point(Vec2d::Zero());
else {
for (auto object : model.objects) {
if (model_object.origin_translation != Vec3d::Zero()) {
object->center_around_origin();
Vec3d delta = model_object.origin_translation - object->origin_translation;
for (auto volume : object->volumes) {
volume->translate(delta);
}
}
}
}
TriangleMesh mesh = model.mesh();
mesh.repair();
// Mesh will be centered when loading.
ModelVolume* new_volume = model_object.add_volume(std::move(mesh), type);
new_volume->name = boost::filesystem::path(input_file).filename().string();
// set a default extruder value, since user can't add it manually
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
if (from_galery) {
// Transform the new modifier to be aligned with the print bed.
const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box();
new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(inst_transform, mesh_bb));
// Set the modifier position.
// Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed.
const Vec3d offset = Vec3d(instance_bb.max.x(), instance_bb.min.y(), instance_bb.min.z()) + 0.5 * mesh_bb.size() - instance_offset;
new_volume->set_offset(inv_inst_transform * offset);
}
added_volumes.push_back(new_volume);
}
}
static TriangleMesh create_mesh(const std::string& type_name, const BoundingBoxf3& bb)
@ -1465,7 +1608,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
if (instance_idx == -1)
return;
take_snapshot(_(L("Add Generic Subobject")));
take_snapshot(_L("Add Generic Subobject"));
// Selected object
ModelObject &model_object = *(*m_objects)[obj_idx];
@ -1477,26 +1620,24 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
// Mesh will be centered when loading.
ModelVolume *new_volume = model_object.add_volume(std::move(mesh), type);
if (instance_idx != -1)
{
// First (any) GLVolume of the selected instance. They all share the same instance matrix.
const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
// Transform the new modifier to be aligned with the print bed.
const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box();
new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb));
// Set the modifier position.
auto offset = (type_name == "Slab") ?
// Slab: Lift to print bed
Vec3d(0., 0., 0.5 * mesh_bb.size().z() + instance_bb.min.z() - v->get_instance_offset().z()) :
// Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed.
Vec3d(instance_bb.max(0), instance_bb.min(1), instance_bb.min(2)) + 0.5 * mesh_bb.size() - v->get_instance_offset();
new_volume->set_offset(v->get_instance_transformation().get_matrix(true).inverse() * offset);
}
// First (any) GLVolume of the selected instance. They all share the same instance matrix.
const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
// Transform the new modifier to be aligned with the print bed.
const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box();
new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb));
// Set the modifier position.
auto offset = (type_name == "Slab") ?
// Slab: Lift to print bed
Vec3d(0., 0., 0.5 * mesh_bb.size().z() + instance_bb.min.z() - v->get_instance_offset().z()) :
// Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed.
Vec3d(instance_bb.max.x(), instance_bb.min.y(), instance_bb.min.z()) + 0.5 * mesh_bb.size() - v->get_instance_offset();
new_volume->set_offset(v->get_instance_transformation().get_matrix(true).inverse() * offset);
const wxString name = _(L("Generic")) + "-" + _(type_name);
const wxString name = _L("Generic") + "-" + _(type_name);
new_volume->name = into_u8(name);
// set a default extruder value, since user can't add it manually
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
new_volume->source.is_from_builtin_objects = true;
select_item([this, obj_idx, new_volume]() {
wxDataViewItem sel_item;
@ -1536,6 +1677,39 @@ void ObjectList::load_shape_object(const std::string& type_name)
#endif // ENABLE_PROJECT_DIRTY_STATE
}
void ObjectList::load_shape_object_from_gallery()
{
if (wxGetApp().plater()->canvas3D()->get_selection().get_object_idx() != -1)
return;// Add nothing if something is selected on 3DScene
wxArrayString input_files;
GalleryDialog gallery_dlg(this);
if (gallery_dlg.ShowModal() == wxID_CANCEL)
return;
gallery_dlg.get_input_files(input_files);
if (input_files.IsEmpty())
return;
std::vector<boost::filesystem::path> paths;
for (const auto& file : input_files)
paths.push_back(into_path(file));
assert(!paths.empty());
wxString snapshot_label = (paths.size() == 1 ? _L("Add Shape") : _L("Add Shapes")) + ": " +
wxString::FromUTF8(paths.front().filename().string().c_str());
for (size_t i = 1; i < paths.size(); ++i)
snapshot_label += ", " + wxString::FromUTF8(paths[i].filename().string().c_str());
take_snapshot(snapshot_label);
#if ENABLE_PROJECT_DIRTY_STATE
std::vector<size_t> res = wxGetApp().plater()->load_files(paths, true, false);
if (!res.empty())
wxGetApp().mainframe->update_title();
#else
load_files(paths, true, false);
#endif // ENABLE_PROJECT_DIRTY_STATE
}
void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center)
{
// Add mesh to model as a new object
@ -1561,7 +1735,7 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name
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)));
new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -new_object->origin_translation.z()));
} else {
new_object->instances[0]->set_offset(bb.center());
}
@ -3731,22 +3905,8 @@ void ObjectList::rename_item()
return;
}
// The icon can't be edited so get its old value and reuse it.
wxVariant valueOld;
m_objects_model->GetValue(valueOld, item, colName);
DataViewBitmapText bmpText;
bmpText << valueOld;
// But replace the text with the value entered by user.
bmpText.SetText(new_name);
wxVariant value;
value << bmpText;
m_objects_model->SetValue(value, item, colName);
m_objects_model->ItemChanged(item);
update_name_in_model(item);
if (m_objects_model->SetName(new_name, item))
update_name_in_model(item);
}
void ObjectList::fix_through_netfabb()

View file

@ -202,6 +202,7 @@ public:
void update_extruder_in_config(const wxDataViewItem& item);
// update changed name in the object model
void update_name_in_model(const wxDataViewItem& item) const;
void update_name_in_list(int obj_idx, int vol_idx) const;
void update_extruder_values_for_items(const size_t max_extruder);
// Get obj_idx and vol_idx values for the selected (by default) or an adjusted item
@ -238,10 +239,12 @@ public:
void show_settings(const wxDataViewItem settings_item);
bool is_instance_or_object_selected();
void load_subobject(ModelVolumeType type);
void load_part(ModelObject* model_object, std::vector<ModelVolume*> &added_volumes, ModelVolumeType type);
void load_generic_subobject(const std::string& type_name, const ModelVolumeType type);
void load_subobject(ModelVolumeType type, bool from_galery = false);
void load_part(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery = false);
void load_modifier(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery = false);
void load_generic_subobject(const std::string& type_name, const ModelVolumeType type);
void load_shape_object(const std::string &type_name);
void load_shape_object_from_gallery();
void load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center = true);
void del_object(const int obj_idx);
void del_subobject_item(wxDataViewItem& item);

View file

@ -0,0 +1,539 @@
#include "GalleryDialog.hpp"
#include <cstddef>
#include <vector>
#include <string>
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
#include <boost/filesystem.hpp>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>
#include <wx/button.h>
#include <wx/statbox.h>
#include <wx/wupdlock.h>
#include <wx/notebook.h>
#include <wx/listctrl.h>
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "format.hpp"
#include "wxExtensions.hpp"
#include "I18N.hpp"
#include "Notebook.hpp"
#include "3DScene.hpp"
#include "GLCanvas3D.hpp"
#include "Plater.hpp"
#include "3DBed.hpp"
#include "MsgDialog.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/AppConfig.hpp"
#include "libslic3r/Model.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "../Utils/MacDarkMode.hpp"
namespace Slic3r {
namespace GUI {
#define BORDER_W 10
#define IMG_PX_CNT 64
namespace fs = boost::filesystem;
// Gallery::DropTarget
class GalleryDropTarget : public wxFileDropTarget
{
public:
GalleryDropTarget(GalleryDialog* gallery_dlg) : gallery_dlg(gallery_dlg) { this->SetDefaultAction(wxDragCopy); }
bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) override;
private:
GalleryDialog* gallery_dlg {nullptr};
};
bool GalleryDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames)
{
#ifdef WIN32
// hides the system icon
this->MSWUpdateDragImageOnLeave();
#endif // WIN32
return gallery_dlg ? gallery_dlg->load_files(filenames) : false;
}
GalleryDialog::GalleryDialog(wxWindow* parent) :
DPIDialog(parent, wxID_ANY, _L("Shapes Gallery"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
#ifndef _WIN32
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
#endif
SetFont(wxGetApp().normal_font());
wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Select shape from the gallery") + ":");
m_list_ctrl = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(55 * wxGetApp().em_unit(), 35 * wxGetApp().em_unit()),
wxLC_ICON | wxSIMPLE_BORDER);
m_list_ctrl->Bind(wxEVT_LIST_ITEM_SELECTED, &GalleryDialog::select, this);
m_list_ctrl->Bind(wxEVT_LIST_ITEM_DESELECTED, &GalleryDialog::deselect, this);
m_list_ctrl->Bind(wxEVT_LIST_ITEM_ACTIVATED, [this](wxListEvent& event) {
m_selected_items.clear();
select(event);
this->EndModal(wxID_OK);
});
#ifdef _WIN32
this->Bind(wxEVT_SIZE, [this](wxSizeEvent& event) {
event.Skip();
m_list_ctrl->Arrange();
});
#endif
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
wxButton* ok_btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
ok_btn->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_selected_items.empty()); });
auto add_btn = [this, buttons]( size_t pos, int& ID, wxString title, wxString tooltip,
void (GalleryDialog::* method)(wxEvent&),
std::function<bool()> enable_fn = []() {return true; }) {
ID = NewControlId();
wxButton* btn = new wxButton(this, ID, title);
btn->SetToolTip(tooltip);
btn->Bind(wxEVT_UPDATE_UI, [enable_fn](wxUpdateUIEvent& evt) { evt.Enable(enable_fn()); });
buttons->Insert(pos, btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, BORDER_W);
this->Bind(wxEVT_BUTTON, method, this, ID);
};
auto enable_del_fn = [this]() {
if (m_selected_items.empty())
return false;
for (const Item& item : m_selected_items)
if (item.is_system)
return false;
return true;
};
add_btn(0, ID_BTN_ADD_CUSTOM_SHAPE, _L("Add"), _L("Add one or more custom shapes"), &GalleryDialog::add_custom_shapes);
add_btn(1, ID_BTN_DEL_CUSTOM_SHAPE, _L("Delete"), _L("Delete one or more custom shape. You can't delete system shapes"), &GalleryDialog::del_custom_shapes, enable_del_fn);
add_btn(2, ID_BTN_REPLACE_CUSTOM_PNG, _L("Replace PNG"), _L("Replace PNG for custom shape. You can't raplace PNG for system shape"),&GalleryDialog::replace_custom_png, [this]() { return (m_selected_items.size() == 1 && !m_selected_items[0].is_system); });
buttons->InsertStretchSpacer(3, 2* BORDER_W);
load_label_icon_list();
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W);
topSizer->Add(m_list_ctrl, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W);
topSizer->Add(buttons , 0, wxEXPAND | wxALL, BORDER_W);
SetSizer(topSizer);
topSizer->SetSizeHints(this);
wxGetApp().UpdateDlgDarkUI(this);
this->CenterOnScreen();
this->SetDropTarget(new GalleryDropTarget(this));
}
GalleryDialog::~GalleryDialog()
{
}
void GalleryDialog::on_dpi_changed(const wxRect& suggested_rect)
{
const int& em = em_unit();
msw_buttons_rescale(this, em, { ID_BTN_ADD_CUSTOM_SHAPE, ID_BTN_DEL_CUSTOM_SHAPE, ID_BTN_REPLACE_CUSTOM_PNG, wxID_OK, wxID_CANCEL });
wxSize size = wxSize(55 * em, 35 * em);
m_list_ctrl->SetMinSize(size);
m_list_ctrl->SetSize(size);
Fit();
Refresh();
}
static void add_lock(wxImage& image)
{
int lock_sz = 22;
#ifdef __APPLE__
lock_sz /= mac_max_scaling_factor();
#endif
wxBitmap bmp = create_scaled_bitmap("lock", nullptr, lock_sz);
wxImage lock_image = bmp.ConvertToImage();
if (!lock_image.IsOk() || lock_image.GetWidth() == 0 || lock_image.GetHeight() == 0)
return;
auto lock_px_data = (uint8_t*)lock_image.GetData();
auto lock_a_data = (uint8_t*)lock_image.GetAlpha();
int lock_width = lock_image.GetWidth();
int lock_height = lock_image.GetHeight();
auto px_data = (uint8_t*)image.GetData();
auto a_data = (uint8_t*)image.GetAlpha();
int width = image.GetWidth();
int height = image.GetHeight();
size_t beg_x = width - lock_width;
size_t beg_y = height - lock_height;
for (size_t x = 0; x < lock_width; ++x) {
for (size_t y = 0; y < lock_height; ++y) {
const size_t lock_idx = (x + y * lock_width);
if (lock_a_data && lock_a_data[lock_idx] == 0)
continue;
const size_t idx = (beg_x + x + (beg_y + y) * width);
if (a_data)
a_data[idx] = lock_a_data[lock_idx];
const size_t idx_rgb = (beg_x + x + (beg_y + y) * width) * 3;
const size_t lock_idx_rgb = (x + y * lock_width) * 3;
px_data[idx_rgb] = lock_px_data[lock_idx_rgb];
px_data[idx_rgb + 1] = lock_px_data[lock_idx_rgb + 1];
px_data[idx_rgb + 2] = lock_px_data[lock_idx_rgb + 2];
}
}
}
static void add_default_image(wxImageList* img_list, bool is_system)
{
wxBitmap bmp = create_scaled_bitmap("cog", nullptr, IMG_PX_CNT, true);
if (is_system) {
wxImage image = bmp.ConvertToImage();
if (image.IsOk() && image.GetWidth() != 0 && image.GetHeight() != 0) {
add_lock(image);
bmp = wxBitmap(std::move(image));
}
}
img_list->Add(bmp);
};
static fs::path get_dir(bool sys_dir)
{
if (sys_dir)
return fs::absolute(fs::path(sys_shapes_dir())).make_preferred();
return fs::absolute(fs::path(data_dir()) / "shapes").make_preferred();
}
static std::string get_dir_path(bool sys_dir)
{
fs::path dir = get_dir(sys_dir);
#ifdef __WXMSW__
return dir.string() + "\\";
#else
return dir.string() + "/";
#endif
}
static void generate_thumbnail_from_stl(const std::string& filename)
{
if (!boost::algorithm::iends_with(filename, ".stl")) {
BOOST_LOG_TRIVIAL(error) << "Found invalid file type in generate_thumbnail_from_stl() [" << filename << "]";
return;
}
Model model;
try {
model = Model::read_from_file(filename);
}
catch (std::exception&) {
BOOST_LOG_TRIVIAL(error) << "Error loading model from " << filename << " in generate_thumbnail_from_stl()";
return;
}
assert(model.objects.size() == 1);
assert(model.objects[0]->volumes.size() == 1);
assert(model.objects[0]->instances.size() == 1);
model.objects[0]->center_around_origin(false);
model.objects[0]->ensure_on_bed(false);
const Vec3d bed_center_3d = wxGetApp().plater()->get_bed().get_bounding_box(false).center();
const Vec2d bed_center_2d = { bed_center_3d.x(), bed_center_3d.y()};
model.center_instances_around_point(bed_center_2d);
GLVolumeCollection volumes;
volumes.volumes.push_back(new GLVolume());
GLVolume* volume = volumes.volumes[0];
volume->indexed_vertex_array.load_mesh(model.mesh());
volume->indexed_vertex_array.finalize_geometry(true);
volume->set_instance_transformation(model.objects[0]->instances[0]->get_transformation());
volume->set_volume_transformation(model.objects[0]->volumes[0]->get_transformation());
ThumbnailData thumbnail_data;
const ThumbnailsParams thumbnail_params = { {}, false, false, false, true };
wxGetApp().plater()->canvas3D()->render_thumbnail(thumbnail_data, 256, 256, thumbnail_params, volumes, Camera::EType::Perspective);
if (thumbnail_data.width == 0 || thumbnail_data.height == 0)
return;
wxImage image(thumbnail_data.width, thumbnail_data.height);
image.InitAlpha();
for (unsigned int r = 0; r < thumbnail_data.height; ++r) {
unsigned int rr = (thumbnail_data.height - 1 - r) * thumbnail_data.width;
for (unsigned int c = 0; c < thumbnail_data.width; ++c) {
unsigned char* px = (unsigned char*)thumbnail_data.pixels.data() + 4 * (rr + c);
image.SetRGB((int)c, (int)r, px[0], px[1], px[2]);
image.SetAlpha((int)c, (int)r, px[3]);
}
}
fs::path out_path = fs::path(filename);
out_path.replace_extension("png");
image.SaveFile(out_path.string(), wxBITMAP_TYPE_PNG);
}
void GalleryDialog::load_label_icon_list()
{
// load names from files
auto add_files_from_gallery = [](std::vector<Item>& items, bool sys_dir, std::string& dir_path)
{
fs::path dir = get_dir(sys_dir);
if (!fs::exists(dir))
return;
dir_path = get_dir_path(sys_dir);
std::vector<std::string> sorted_names;
for (auto& dir_entry : fs::directory_iterator(dir))
if (TriangleMesh mesh; is_stl_file(dir_entry) && mesh.ReadSTLFile(dir_entry.path().string().c_str()))
sorted_names.push_back(dir_entry.path().stem().string());
// sort the filename case insensitive
std::sort(sorted_names.begin(), sorted_names.end(), [](const std::string& a, const std::string& b)
{ return boost::algorithm::to_lower_copy(a) < boost::algorithm::to_lower_copy(b); });
for (const std::string& name : sorted_names)
items.push_back(Item{ name, sys_dir });
};
wxBusyCursor busy;
std::string m_sys_dir_path, m_cust_dir_path;
std::vector<Item> list_items;
add_files_from_gallery(list_items, true, m_sys_dir_path);
add_files_from_gallery(list_items, false, m_cust_dir_path);
// Make an image list containing large icons
int px_cnt = (int)(em_unit() * IMG_PX_CNT * 0.1f + 0.5f);
m_image_list = new wxImageList(px_cnt, px_cnt);
std::string ext = ".png";
for (const auto& item : list_items) {
std::string img_name = (item.is_system ? m_sys_dir_path : m_cust_dir_path) + item.name + ext;
std::string stl_name = (item.is_system ? m_sys_dir_path : m_cust_dir_path) + item.name + ".stl";
if (!fs::exists(img_name))
generate_thumbnail_from_stl(stl_name);
wxImage image;
if (!image.CanRead(from_u8(img_name)) ||
!image.LoadFile(from_u8(img_name), wxBITMAP_TYPE_PNG) ||
image.GetWidth() == 0 || image.GetHeight() == 0) {
add_default_image(m_image_list, item.is_system);
continue;
}
image.Rescale(px_cnt, px_cnt, wxIMAGE_QUALITY_BILINEAR);
if (item.is_system)
add_lock(image);
wxBitmap bmp = wxBitmap(std::move(image));
m_image_list->Add(bmp);
}
m_list_ctrl->SetImageList(m_image_list, wxIMAGE_LIST_NORMAL);
int img_cnt = m_image_list->GetImageCount();
for (int i = 0; i < img_cnt; i++) {
m_list_ctrl->InsertItem(i, from_u8(list_items[i].name), i);
if (list_items[i].is_system)
m_list_ctrl->SetItemFont(i, wxGetApp().bold_font());
}
}
void GalleryDialog::get_input_files(wxArrayString& input_files)
{
for (const Item& item : m_selected_items)
input_files.Add(from_u8(get_dir_path(item.is_system) + item.name + ".stl"));
}
void GalleryDialog::add_custom_shapes(wxEvent& event)
{
wxArrayString input_files;
wxFileDialog dialog(this, _L("Choose one or more files (STL):"),
from_u8(wxGetApp().app_config->get_last_dir()), "",
file_wildcards(FT_STL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() == wxID_OK)
dialog.GetPaths(input_files);
if (input_files.IsEmpty())
return;
load_files(input_files);
}
void GalleryDialog::del_custom_shapes(wxEvent& event)
{
auto custom_dir = get_dir(false);
auto remove_file = [custom_dir](const std::string& name) {
if (!fs::exists(custom_dir / name))
return;
try {
fs::remove(custom_dir / name);
}
catch (fs::filesystem_error const& e) {
std::cerr << e.what() << '\n';
}
};
for (const Item& item : m_selected_items) {
remove_file(item.name + ".stl");
remove_file(item.name + ".png");
}
update();
}
static void show_warning(const wxString& title, const std::string& error_file_type)
{
const wxString msg_text = format_wxstr(_L("It looks like selected %1%-file has an error or is destructed.\n"
"We can't load this file"), error_file_type);
MessageDialog dialog(nullptr, msg_text, title, wxICON_WARNING | wxOK);
dialog.ShowModal();
}
void GalleryDialog::replace_custom_png(wxEvent& event)
{
if (m_selected_items.size() != 1 || m_selected_items[0].is_system)
return;
wxFileDialog dialog(this, _L("Choose one PNG file:"),
from_u8(wxGetApp().app_config->get_last_dir()), "",
"PNG files (*.png)|*.png;*.PNG", wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() != wxID_OK)
return;
wxArrayString input_files;
dialog.GetPaths(input_files);
if (input_files.IsEmpty())
return;
if (wxImage image; !image.CanRead(input_files.Item(0))) {
show_warning(_L("Replacing of the PNG"), "PNG");
return;
}
try {
fs::path current = fs::path(into_u8(input_files.Item(0)));
fs::copy_file(current, get_dir(false) / (m_selected_items[0].name + ".png"), fs::copy_option::overwrite_if_exists);
}
catch (fs::filesystem_error const& e) {
std::cerr << e.what() << '\n';
return;
}
update();
}
void GalleryDialog::select(wxListEvent& event)
{
int idx = event.GetIndex();
Item item { into_u8(m_list_ctrl->GetItemText(idx)), m_list_ctrl->GetItemFont(idx).GetWeight() == wxFONTWEIGHT_BOLD };
m_selected_items.push_back(item);
}
void GalleryDialog::deselect(wxListEvent& event)
{
if (m_list_ctrl->GetSelectedItemCount() == 0) {
m_selected_items.clear();
return;
}
std::string name = into_u8(m_list_ctrl->GetItemText(event.GetIndex()));
m_selected_items.erase(std::remove_if(m_selected_items.begin(), m_selected_items.end(), [name](Item item) { return item.name == name; }));
}
void GalleryDialog::update()
{
m_selected_items.clear();
m_image_list->RemoveAll();
m_list_ctrl->ClearAll();
load_label_icon_list();
}
bool GalleryDialog::load_files(const wxArrayString& input_files)
{
auto dest_dir = get_dir(false);
try {
if (!fs::exists(dest_dir))
if (!fs::create_directory(dest_dir)) {
std::cerr << "Unable to create destination directory" << dest_dir.string() << '\n' ;
return false;
}
}
catch (fs::filesystem_error const& e) {
std::cerr << e.what() << '\n';
return false;
}
// Iterate through the source directory
for (size_t i = 0; i < input_files.size(); ++i) {
std::string input_file = into_u8(input_files.Item(i));
if (TriangleMesh mesh; !mesh.ReadSTLFile(input_file.c_str())) {
show_warning(format_wxstr(_L("Loading of the \"%1%\""), input_file), "STL");
continue;
}
try {
fs::path current = fs::path(input_file);
if (!fs::exists(dest_dir / current.filename()))
fs::copy_file(current, dest_dir / current.filename());
else {
std::string filename = current.stem().string();
int file_idx = 0;
for (auto& dir_entry : fs::directory_iterator(dest_dir))
if (is_stl_file(dir_entry)) {
std::string name = dir_entry.path().stem().string();
if (filename == name) {
if (file_idx == 0)
file_idx++;
continue;
}
if (name.find(filename) != 0 ||
name[filename.size()] != ' ' || name[filename.size()+1] != '(' || name[name.size()-1] != ')')
continue;
std::string idx_str = name.substr(filename.size() + 2, name.size() - filename.size() - 3);
if (int cur_idx = atoi(idx_str.c_str()); file_idx <= cur_idx)
file_idx = cur_idx+1;
}
if (file_idx > 0) {
filename += " (" + std::to_string(file_idx) + ").stl";
fs::copy_file(current, dest_dir / filename);
}
}
}
catch (fs::filesystem_error const& e) {
std::cerr << e.what() << '\n';
return false;
}
}
update();
return true;
}
}} // namespace Slic3r::GUI

View file

@ -0,0 +1,59 @@
#ifndef slic3r_GalleryDialog_hpp_
#define slic3r_GalleryDialog_hpp_
#include "GUI_Utils.hpp"
class wxListCtrl;
class wxImageList;
class wxListEvent;
namespace Slic3r {
namespace GUI {
//------------------------------------------
// GalleryDialog
//------------------------------------------
class GalleryDialog : public DPIDialog
{
wxListCtrl* m_list_ctrl { nullptr };
wxImageList* m_image_list { nullptr };
struct Item {
std::string name;
bool is_system;
};
std::vector<Item> m_selected_items;
int ID_BTN_ADD_CUSTOM_SHAPE;
int ID_BTN_DEL_CUSTOM_SHAPE;
int ID_BTN_REPLACE_CUSTOM_PNG;
void load_label_icon_list();
void add_custom_shapes(wxEvent& event);
void del_custom_shapes(wxEvent& event);
void replace_custom_png(wxEvent& event);
void select(wxListEvent& event);
void deselect(wxListEvent& event);
void update();
public:
GalleryDialog(wxWindow* parent);
~GalleryDialog();
void get_input_files(wxArrayString& input_files);
bool load_files(const wxArrayString& input_files);
protected:
void on_dpi_changed(const wxRect& suggested_rect) override;
void on_sys_color_changed() override {};
};
} // namespace GUI
} // namespace Slic3r
#endif //slic3r_GalleryDialog_hpp_

View file

@ -27,11 +27,6 @@ const std::array<float, 4> GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0, 1.0 };
GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
, m_cut_z(0.0)
, m_max_z(0.0)
, m_keep_upper(true)
, m_keep_lower(true)
, m_rotate_lower(false)
{}
std::string GLGizmoCut::get_tooltip() const
@ -58,9 +53,8 @@ std::string GLGizmoCut::on_get_name() const
void GLGizmoCut::on_set_state()
{
// Reset m_cut_z on gizmo activation
if (get_state() == On) {
m_cut_z = m_parent.get_selection().get_bounding_box().size()(2) / 2.0;
}
if (get_state() == On)
m_cut_z = bounding_box().center().z();
}
bool GLGizmoCut::on_is_activable() const
@ -74,13 +68,12 @@ void GLGizmoCut::on_start_dragging()
if (m_hover_id == -1)
return;
const Selection& selection = m_parent.get_selection();
const BoundingBoxf3& box = selection.get_bounding_box();
const BoundingBoxf3 box = bounding_box();
m_max_z = box.max.z();
m_start_z = m_cut_z;
update_max_z(selection);
m_drag_pos = m_grabbers[m_hover_id].center;
m_drag_center = box.center();
m_drag_center(2) = m_cut_z;
m_drag_center.z() = m_cut_z;
}
void GLGizmoCut::on_update(const UpdateData& data)
@ -91,13 +84,11 @@ void GLGizmoCut::on_update(const UpdateData& data)
void GLGizmoCut::on_render() const
{
const Selection& selection = m_parent.get_selection();
update_max_z(selection);
const BoundingBoxf3& box = selection.get_bounding_box();
BoundingBoxf3 box = bounding_box();
Vec3d plane_center = box.center();
plane_center.z() = m_cut_z;
m_max_z = box.max.z();
set_cut_z(m_cut_z);
const float min_x = box.min.x() - Margin;
const float max_x = box.max.x() + Margin;
@ -160,10 +151,10 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
const bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
// adjust window position to avoid overlap the view toolbar
float win_h = ImGui::GetWindowHeight();
const float win_h = ImGui::GetWindowHeight();
y = std::min(y, bottom_limit - win_h);
ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always);
if (last_h != win_h || last_y != y) {
@ -198,7 +189,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
ImGui::Separator();
m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || m_cut_z <= 0.0 || m_max_z < m_cut_z);
m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || m_cut_z <= 0.0 || m_max_z <= m_cut_z);
const bool cut_clicked = m_imgui->button(_L("Perform cut"));
m_imgui->disabled_end();
@ -208,12 +199,6 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
perform_cut(m_parent.get_selection());
}
void GLGizmoCut::update_max_z(const Selection& selection) const
{
m_max_z = selection.get_bounding_box().size()(2);
set_cut_z(m_cut_z);
}
void GLGizmoCut::set_cut_z(double cut_z) const
{
// Clamp the plane to the object's bounding box
@ -229,10 +214,10 @@ void GLGizmoCut::perform_cut(const Selection& selection)
// m_cut_z is the distance from the bed. Subtract possible SLA elevation.
const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin());
coordf_t object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z();
const double object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z();
if (object_cut_z > 0.)
wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z,
if (0.0 < object_cut_z && object_cut_z < m_max_z)
wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z,
only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) |
only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) |
only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower));
@ -247,16 +232,15 @@ double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const
const Vec3d starting_vec = m_drag_pos - m_drag_center;
const double len_starting_vec = starting_vec.norm();
if (len_starting_vec != 0.0)
{
Vec3d mouse_dir = mouse_ray.unit_vector();
if (len_starting_vec != 0.0) {
const Vec3d mouse_dir = mouse_ray.unit_vector();
// finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position
// use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
// in our case plane normal and ray direction are the same (orthogonal view)
// when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
const Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
// vector from the starting position to the found intersection
Vec3d inters_vec = inters - m_drag_pos;
const Vec3d inters_vec = inters - m_drag_pos;
// finds projection of the vector along the staring direction
projection = inters_vec.dot(starting_vec.normalized());
@ -264,6 +248,18 @@ double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const
return projection;
}
BoundingBoxf3 GLGizmoCut::bounding_box() const
{
BoundingBoxf3 ret;
const Selection& selection = m_parent.get_selection();
const Selection::IndicesList& idxs = selection.get_volume_idxs();
for (unsigned int i : idxs) {
const GLVolume* volume = selection.get_volume(i);
if (!volume->is_modifier)
ret.merge(volume->transformed_convex_hull_bounding_box());
}
return ret;
}
} // namespace GUI
} // namespace Slic3r

View file

@ -13,14 +13,14 @@ class GLGizmoCut : public GLGizmoBase
static const double Margin;
static const std::array<float, 4> GrabberColor;
mutable double m_cut_z;
double m_start_z;
mutable double m_max_z;
mutable double m_cut_z{ 0.0 };
mutable double m_max_z{ 0.0 };
double m_start_z{ 0.0 };
Vec3d m_drag_pos;
Vec3d m_drag_center;
bool m_keep_upper;
bool m_keep_lower;
bool m_rotate_lower;
bool m_keep_upper{ true };
bool m_keep_lower{ true };
bool m_rotate_lower{ false };
public:
GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
@ -32,7 +32,7 @@ public:
protected:
virtual bool on_init() override;
virtual void on_load(cereal::BinaryInputArchive& ar) override{ ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
virtual void on_load(cereal::BinaryInputArchive& ar) override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
virtual void on_save(cereal::BinaryOutputArchive& ar) const override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
virtual std::string on_get_name() const override;
virtual void on_set_state() override;
@ -44,9 +44,9 @@ protected:
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
private:
void update_max_z(const Selection& selection) const;
void perform_cut(const Selection& selection);
double calc_projection(const Linef3& mouse_ray) const;
BoundingBoxf3 bounding_box() const;
};
} // namespace GUI

View file

@ -170,6 +170,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
if (mv->is_model_part()) {
++idx;
m_triangle_selectors[idx]->reset();
m_triangle_selectors[idx]->request_update_render_data();
}
}
@ -192,6 +193,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
// Manually inserted values aren't clamped by ImGui. Zero cursor size results in a crash.
m_cursor_radius = std::clamp(m_cursor_radius, CursorRadiusMin, CursorRadiusMax);
ImGui::AlignTextToFramePadding();
@ -285,13 +288,12 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block)
// Now calculate dot product of vert_direction and facets' normals.
int idx = -1;
for (const stl_facet& facet : mv->mesh().stl.facet_start) {
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);
if (facet.normal.dot(down) > dot_limit) {
m_triangle_selectors[mesh_id]->set_facet(idx, block ? EnforcerBlockerType::BLOCKER : EnforcerBlockerType::ENFORCER);
m_triangle_selectors.back()->request_update_render_data();
}
}
}
@ -346,6 +348,7 @@ void GLGizmoFdmSupports::update_from_model_object()
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh));
m_triangle_selectors.back()->deserialize(mv->supported_facets.get_data());
m_triangle_selectors.back()->request_update_render_data();
}
}

View file

@ -107,16 +107,24 @@ bool GLGizmoMmuSegmentation::on_init()
m_desc["reset_direction"] = _L("Reset direction");
m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
m_desc["cursor_size"] = _L("Brush size") + ": ";
m_desc["cursor_type"] = _L("Brush shape") + ": ";
m_desc["cursor_type"] = _L("Brush shape");
m_desc["first_color_caption"] = _L("Left mouse button") + ": ";
m_desc["first_color"] = _L("First color");
m_desc["second_color_caption"] = _L("Right mouse button") + ": ";
m_desc["second_color"] = _L("Second color");
m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
m_desc["remove"] = _L("Remove painted color");
m_desc["remove_all"] = _L("Remove all painted colors");
m_desc["remove_all"] = _L("Remove all painted areas");
m_desc["circle"] = _L("Circle");
m_desc["sphere"] = _L("Sphere");
m_desc["pointer"] = _L("Pointer");
m_desc["tool_type"] = _L("Tool type");
m_desc["tool_brush"] = _L("Brush");
m_desc["tool_seed_fill"] = _L("Seed fill");
m_desc["tool_bucket_fill"] = _L("Bucket fill");
m_desc["seed_fill"] = _L("Seed fill");
m_desc["seed_fill_angle"] = _L("Seed fill angle");
init_extruders_data();
@ -147,8 +155,8 @@ void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection)
return;
ModelObject *model_object = m_c->selection_info()->model_object();
int prev_extruders_count = int(m_original_extruders_colors.size());
if (prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors) {
if (int prev_extruders_count = int(m_original_extruders_colors.size());
prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors) {
if (wxGetApp().extruders_edited_cnt() > int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT))
show_notification_extruders_limit_exceeded();
@ -222,7 +230,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
if (!m_c->selection_info()->model_object())
return;
const float approx_height = m_imgui->scaled(23.0f);
const float approx_height = m_imgui->scaled(25.0f);
y = std::min(y, bottom_limit - approx_height);
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
@ -232,10 +240,12 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
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 autoset_slider_left = m_imgui->calc_text_size(m_desc.at("seed_fill_angle")).x + m_imgui->scaled(1.f);
const float cursor_type_radio_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f);
const float cursor_type_radio_width1 = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
const float cursor_type_radio_width2 = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
const float seed_fill_slider_left = m_imgui->calc_text_size(m_desc.at("seed_fill_angle")).x + m_imgui->scaled(1.f);
const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f);
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
const float buttons_width = m_imgui->scaled(0.5f);
const float minimal_slider_width = m_imgui->scaled(4.f);
@ -243,6 +253,10 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
const float combo_label_width = std::max(m_imgui->calc_text_size(m_desc.at("first_color")).x,
m_imgui->calc_text_size(m_desc.at("second_color")).x) + m_imgui->scaled(1.f);
const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f);
const float tool_type_radio_bucket_fill = m_imgui->calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f);
const float tool_type_radio_seed_fill = m_imgui->calc_text_size(m_desc["tool_seed_fill"]).x + m_imgui->scaled(2.5f);
float caption_max = 0.f;
float total_text_max = 0.;
for (const auto &t : std::array<std::string, 3>{"first_color", "second_color", "remove"}) {
@ -252,10 +266,12 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
caption_max += m_imgui->scaled(1.f);
total_text_max += m_imgui->scaled(1.f);
float window_width = minimal_slider_width + std::max(autoset_slider_left, std::max(cursor_slider_left, clipping_slider_left));
float sliders_width = std::max(seed_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left));
float window_width = minimal_slider_width + sliders_width;
window_width = std::max(window_width, total_text_max);
window_width = std::max(window_width, button_width);
window_width = std::max(window_width, cursor_type_radio_left + cursor_type_radio_width1 + cursor_type_radio_width2);
window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer);
window_width = std::max(window_width, tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_seed_fill);
window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f));
auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) {
@ -292,91 +308,166 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
if(ImGui::ColorEdit4("Second color##color_picker", (float*)&second_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel))
m_modified_extruders_colors[m_second_selected_extruder_idx] = {second_color.x, second_color.y, second_color.z, second_color.w};
ImGui::Separator();
if (m_imgui->checkbox(_L("Seed fill"), m_seed_fill_enabled))
if (!m_seed_fill_enabled)
for (auto &triangle_selector : m_triangle_selectors)
triangle_selector->seed_fill_unselect_all_triangles();
m_imgui->text(m_desc["seed_fill_angle"] + ":");
ImGui::AlignTextToFramePadding();
std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in FDM supports gizmo,"
"placed after the number with no whitespace in between.");
ImGui::SameLine(autoset_slider_left);
ImGui::PushItemWidth(window_width - autoset_slider_left);
m_imgui->disabled_begin(!m_seed_fill_enabled);
m_imgui->slider_float("##seed_fill_angle", &m_seed_fill_angle, 0.f, 90.f, format_str.data());
m_imgui->disabled_end();
ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) {
Plater::TakeSnapshot snapshot(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;
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(cursor_slider_left);
ImGui::PushItemWidth(window_width - cursor_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();
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_type"));
ImGui::SameLine(cursor_type_radio_left + m_imgui->scaled(0.f));
ImGui::PushItemWidth(cursor_type_radio_width1);
m_imgui->text(m_desc.at("tool_type"));
bool sphere_sel = m_cursor_type == TriangleSelector::CursorType::SPHERE;
if (m_imgui->radio_button(m_desc["sphere"], sphere_sel))
sphere_sel = true;
float tool_type_offset = (window_width - tool_type_radio_brush - tool_type_radio_bucket_fill - tool_type_radio_seed_fill + m_imgui->scaled(2.f)) / 2.f;
ImGui::NewLine();
ImGui::AlignTextToFramePadding();
ImGui::SameLine(tool_type_offset + m_imgui->scaled(0.f));
ImGui::PushItemWidth(tool_type_radio_brush);
if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BRUSH))
m_tool_type = GLGizmoMmuSegmentation::ToolType::BRUSH;
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data());
ImGui::TextUnformatted(_L("Paints facets according to the chosen painting brush.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SameLine(cursor_type_radio_left + cursor_type_radio_width2 + m_imgui->scaled(0.f));
ImGui::PushItemWidth(cursor_type_radio_width2);
if (m_imgui->radio_button(m_desc["circle"], !sphere_sel))
sphere_sel = false;
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + m_imgui->scaled(0.f));
ImGui::PushItemWidth(tool_type_radio_bucket_fill);
if (m_imgui->radio_button(m_desc["tool_bucket_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BUCKET_FILL)) {
m_tool_type = GLGizmoMmuSegmentation::ToolType::BUCKET_FILL;
for (auto &triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
}
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data());
ImGui::TextUnformatted(_L("Paints neighboring facets that have the same color.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
m_cursor_type = sphere_sel ? TriangleSelector::CursorType::SPHERE : TriangleSelector::CursorType::CIRCLE;
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_bucket_fill + m_imgui->scaled(0.f));
ImGui::PushItemWidth(tool_type_radio_seed_fill);
if (m_imgui->radio_button(m_desc["tool_seed_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::SEED_FILL)) {
m_tool_type = GLGizmoMmuSegmentation::ToolType::SEED_FILL;
for (auto &triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
}
m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints neighboring facets whose relative angle is less or equal to set angle.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
// Manually inserted values aren't clamped by ImGui. Zero cursor size results in a crash.
m_cursor_radius = std::clamp(m_cursor_radius, CursorRadiusMin, CursorRadiusMax);
ImGui::Separator();
if(m_tool_type == ToolType::BRUSH) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_type"));
ImGui::NewLine();
float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(2.f)) / 2.f;
ImGui::AlignTextToFramePadding();
ImGui::SameLine(cursor_type_offset + m_imgui->scaled(0.f));
ImGui::PushItemWidth(cursor_type_radio_sphere);
if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE))
m_cursor_type = TriangleSelector::CursorType::SPHERE;
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SameLine(cursor_type_offset +cursor_type_radio_sphere + m_imgui->scaled(0.f));
ImGui::PushItemWidth(cursor_type_radio_circle);
if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE))
m_cursor_type = TriangleSelector::CursorType::CIRCLE;
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle + m_imgui->scaled(0.f));
ImGui::PushItemWidth(cursor_type_radio_pointer);
if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER))
m_cursor_type = TriangleSelector::CursorType::POINTER;
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Paints only one facet.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE);
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("cursor_size"));
ImGui::SameLine(sliders_width);
ImGui::PushItemWidth(window_width - sliders_width);
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();
}
m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled);
if (ImGui::IsItemHovered()) {
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(max_tooltip_width);
ImGui::TextUnformatted(_L("Split bigger facets into smaller ones while the object is painted.").ToUTF8().data());
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
m_imgui->disabled_end();
ImGui::Separator();
} else if(m_tool_type == ToolType::SEED_FILL) {
m_imgui->text(m_desc["seed_fill_angle"] + ":");
ImGui::AlignTextToFramePadding();
std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo,"
"placed after the number with no whitespace in between.");
ImGui::SameLine(sliders_width);
ImGui::PushItemWidth(window_width - sliders_width);
m_imgui->slider_float("##seed_fill_angle", &m_seed_fill_angle, SeedFillAngleMin, SeedFillAngleMax, format_str.data());
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) {
ImGui::AlignTextToFramePadding();
m_imgui->text(m_desc.at("clipping_of_view"));
@ -386,8 +477,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
}
}
ImGui::SameLine(clipping_slider_left);
ImGui::PushItemWidth(window_width - clipping_slider_left);
ImGui::SameLine(sliders_width);
ImGui::PushItemWidth(window_width - sliders_width);
auto clp_dist = float(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);
@ -398,6 +489,23 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
ImGui::Separator();
if (m_imgui->button(m_desc.at("remove_all"))) {
Plater::TakeSnapshot snapshot(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();
m_triangle_selectors[idx]->request_update_render_data();
}
update_model_object();
m_parent.set_as_dirty();
}
m_imgui->end();
}
@ -437,8 +545,9 @@ void GLGizmoMmuSegmentation::init_model_triangle_selectors()
const TriangleMesh *mesh = &mv->mesh();
int extruder_idx = (mv->extruder_id() > 0) ? mv->extruder_id() - 1 : 0;
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorMmuGui>(*mesh, m_modified_extruders_colors, m_original_extruders_colors[size_t(extruder_idx)]));
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorMmGui>(*mesh, m_modified_extruders_colors, m_original_extruders_colors[size_t(extruder_idx)]));
m_triangle_selectors.back()->deserialize(mv->mmu_segmentation_facets.get_data());
m_triangle_selectors.back()->request_update_render_data();
}
m_original_volumes_extruder_idxs = get_extruder_id_for_volumes(*mo);
}
@ -446,6 +555,13 @@ void GLGizmoMmuSegmentation::init_model_triangle_selectors()
void GLGizmoMmuSegmentation::update_from_model_object()
{
wxBusyCursor wait;
// Extruder colors need to be reloaded before calling init_model_triangle_selectors to render painted triangles
// using colors from loaded 3MF and not from printer profile in Slicer.
if (int prev_extruders_count = int(m_original_extruders_colors.size());
prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors)
this->init_extruders_data();
this->init_model_triangle_selectors();
}
@ -466,56 +582,60 @@ std::array<float, 4> GLGizmoMmuSegmentation::get_cursor_sphere_right_button_colo
return {color[0], color[1], color[2], 0.25f};
}
void TriangleSelectorMmuGui::render(ImGuiWrapper *imgui)
void TriangleSelectorMmGui::render(ImGuiWrapper *imgui)
{
static constexpr std::array<float, 4> seed_fill_color{0.f, 1.f, 0.44f, 1.f};
std::vector<int> color_cnt(m_iva_colors.size());
int seed_fill_cnt = 0;
for (auto &iva_color : m_iva_colors)
iva_color.release_geometry();
m_iva_seed_fill.release_geometry();
auto append_triangle = [this](GLIndexedVertexArray &iva, int &cnt, const Triangle &tr) -> void {
for (int i = 0; i < 3; ++i)
iva.push_geometry(m_vertices[tr.verts_idxs[i]].v, m_mesh->stl.facet_start[tr.source_triangle].normal);
iva.push_triangle(cnt, cnt + 1, cnt + 2);
cnt += 3;
};
for (size_t color_idx = 0; color_idx < m_iva_colors.size(); ++color_idx) {
for (const Triangle &tr : m_triangles) {
if (!tr.valid() || tr.is_split() || tr.is_selected_by_seed_fill() || tr.get_state() != EnforcerBlockerType(color_idx))
continue;
append_triangle(m_iva_colors[color_idx], color_cnt[color_idx], tr);
}
}
for (const Triangle &tr : m_triangles) {
if (!tr.valid() || tr.is_split() || !tr.is_selected_by_seed_fill())
continue;
append_triangle(m_iva_seed_fill, seed_fill_cnt, tr);
}
for (auto &iva_color : m_iva_colors)
iva_color.finalize_geometry(true);
m_iva_seed_fill.finalize_geometry(true);
if (m_update_render_data)
update_render_data();
auto *shader = wxGetApp().get_current_shader();
if (!shader)
return;
assert(shader->get_name() == "gouraud");
ScopeGuard guard([shader]() { if (shader) shader->set_uniform("compute_triangle_normals_in_fs", false);});
shader->set_uniform("compute_triangle_normals_in_fs", true);
auto render = [&shader](const GLIndexedVertexArray &iva, const std::array<float, 4> &color) -> void {
if (iva.has_VBOs()) {
shader->set_uniform("uniform_color", color);
iva.render();
for (size_t color_idx = 0; color_idx < m_gizmo_scene.triangle_indices.size(); ++color_idx)
if (m_gizmo_scene.has_VBOs(color_idx)) {
shader->set_uniform("uniform_color", color_idx == 0 ? m_default_volume_color :
color_idx == (m_gizmo_scene.triangle_indices.size() - 1) ? seed_fill_color :
m_colors[color_idx - 1]);
m_gizmo_scene.render(color_idx);
}
};
for (size_t color_idx = 0; color_idx < m_iva_colors.size(); ++color_idx)
render(m_iva_colors[color_idx], (color_idx == 0) ? m_default_volume_color : m_colors[color_idx - 1]);
render(m_iva_seed_fill, seed_fill_color);
m_update_render_data = false;
}
void TriangleSelectorMmGui::update_render_data()
{
m_gizmo_scene.release_geometry();
m_vertices.reserve(m_vertices.size() * 3);
for (const Vertex &vr : m_vertices) {
m_gizmo_scene.vertices.emplace_back(vr.v.x());
m_gizmo_scene.vertices.emplace_back(vr.v.y());
m_gizmo_scene.vertices.emplace_back(vr.v.z());
}
m_gizmo_scene.finalize_vertices();
for (const Triangle &tr : m_triangles)
if (tr.valid() && !tr.is_split()) {
int color = int(tr.get_state());
std::vector<int> &iva = tr.is_selected_by_seed_fill() ? m_gizmo_scene.triangle_indices.back() :
color < int(m_gizmo_scene.triangle_indices.size() - 1) ? m_gizmo_scene.triangle_indices[color] :
m_gizmo_scene.triangle_indices.front();
if (iva.size() + 3 > iva.capacity())
iva.reserve(next_highest_power_of_2(iva.size() + 3));
iva.emplace_back(tr.verts_idxs[0]);
iva.emplace_back(tr.verts_idxs[1]);
iva.emplace_back(tr.verts_idxs[2]);
}
for (size_t color_idx = 0; color_idx < m_gizmo_scene.triangle_indices.size(); ++color_idx)
m_gizmo_scene.triangle_indices_sizes[color_idx] = m_gizmo_scene.triangle_indices[color_idx].size();
m_gizmo_scene.finalize_triangle_indices();
}
wxString GLGizmoMmuSegmentation::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const
@ -530,4 +650,76 @@ wxString GLGizmoMmuSegmentation::handle_snapshot_action_name(bool shift_down, GL
return action_name;
}
void GLMmSegmentationGizmo3DScene::release_geometry() {
if (this->vertices_VBO_id) {
glsafe(::glDeleteBuffers(1, &this->vertices_VBO_id));
this->vertices_VBO_id = 0;
}
for(auto &triangle_indices_VBO_id : triangle_indices_VBO_ids) {
glsafe(::glDeleteBuffers(1, &triangle_indices_VBO_id));
triangle_indices_VBO_id = 0;
}
this->clear();
}
void GLMmSegmentationGizmo3DScene::render(size_t triangle_indices_idx) const
{
assert(triangle_indices_idx < this->triangle_indices_VBO_ids.size());
assert(this->triangle_indices_sizes.size() == this->triangle_indices_VBO_ids.size());
assert(this->vertices_VBO_id != 0);
assert(this->triangle_indices_VBO_ids[triangle_indices_idx] != 0);
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id));
glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), (const void*)(0 * sizeof(float))));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
// Render using the Vertex Buffer Objects.
if (this->triangle_indices_sizes[triangle_indices_idx] > 0) {
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_ids[triangle_indices_idx]));
glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_sizes[triangle_indices_idx]), GL_UNSIGNED_INT, nullptr));
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
}
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
void GLMmSegmentationGizmo3DScene::finalize_vertices()
{
assert(this->vertices_VBO_id == 0);
if (!this->vertices.empty()) {
glsafe(::glGenBuffers(1, &this->vertices_VBO_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id));
glsafe(::glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * 4, this->vertices.data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
this->vertices.clear();
}
}
void GLMmSegmentationGizmo3DScene::finalize_triangle_indices()
{
assert(std::all_of(triangle_indices_VBO_ids.cbegin(), triangle_indices_VBO_ids.cend(), [](const auto &ti_VBO_id) { return ti_VBO_id == 0; }));
assert(this->triangle_indices.size() == this->triangle_indices_VBO_ids.size());
for (size_t buffer_idx = 0; buffer_idx < this->triangle_indices.size(); ++buffer_idx)
if (!this->triangle_indices[buffer_idx].empty()) {
glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_ids[buffer_idx]));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_ids[buffer_idx]));
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices[buffer_idx].size() * 4, this->triangle_indices[buffer_idx].data(),
GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
this->triangle_indices[buffer_idx].clear();
}
}
void GLMmSegmentationGizmo3DScene::finalize_geometry()
{
assert(this->vertices_VBO_id == 0);
assert(this->triangle_indices.size() == this->triangle_indices_VBO_ids.size());
finalize_vertices();
finalize_triangle_indices();
}
} // namespace Slic3r

View file

@ -5,24 +5,80 @@
namespace Slic3r::GUI {
class TriangleSelectorMmuGui : public TriangleSelectorGUI {
class GLMmSegmentationGizmo3DScene
{
public:
explicit TriangleSelectorMmuGui(const TriangleMesh& mesh, const std::vector<std::array<float, 4>> &colors, const std::array<float, 4> &default_volume_color)
: TriangleSelectorGUI(mesh), m_colors(colors), m_default_volume_color(default_volume_color) {
// Plus 1 is because the first position is allocated for non-painted triangles.
m_iva_colors = std::vector<GLIndexedVertexArray>(colors.size() + 1);
GLMmSegmentationGizmo3DScene() = delete;
explicit GLMmSegmentationGizmo3DScene(size_t triangle_indices_buffers_count)
{
this->triangle_indices = std::vector<std::vector<int>>(triangle_indices_buffers_count);
this->triangle_indices_sizes = std::vector<size_t>(triangle_indices_buffers_count);
this->triangle_indices_VBO_ids = std::vector<unsigned int>(triangle_indices_buffers_count);
}
~TriangleSelectorMmuGui() override = default;
virtual ~GLMmSegmentationGizmo3DScene() { release_geometry(); }
[[nodiscard]] inline bool has_VBOs(size_t triangle_indices_idx) const
{
assert(triangle_indices_idx < this->triangle_indices.size());
return this->triangle_indices_VBO_ids[triangle_indices_idx] != 0;
}
// Finalize the initialization of the geometry and indices, upload the geometry and indices to OpenGL VBO objects
// and possibly releasing it if it has been loaded into the VBOs.
void finalize_geometry();
// Release the geometry data, release OpenGL VBOs.
void release_geometry();
// Finalize the initialization of the geometry, upload the geometry to OpenGL VBO objects
// and possibly releasing it if it has been loaded into the VBOs.
void finalize_vertices();
// Finalize the initialization of the indices, upload the indices to OpenGL VBO objects
// and possibly releasing it if it has been loaded into the VBOs.
void finalize_triangle_indices();
void clear()
{
this->vertices.clear();
for (std::vector<int> &ti : this->triangle_indices)
ti.clear();
for (size_t &triangle_indices_size : this->triangle_indices_sizes)
triangle_indices_size = 0;
}
void render(size_t triangle_indices_idx) const;
std::vector<float> vertices;
std::vector<std::vector<int>> triangle_indices;
// When the triangle indices are loaded into the graphics card as Vertex Buffer Objects,
// the above mentioned std::vectors are cleared and the following variables keep their original length.
std::vector<size_t> triangle_indices_sizes;
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
// Zero if the VBOs are not sent to GPU yet.
unsigned int vertices_VBO_id{0};
std::vector<unsigned int> triangle_indices_VBO_ids;
};
class TriangleSelectorMmGui : public TriangleSelectorGUI {
public:
// Plus 2 in the initialization of m_gizmo_scene is because the first position is allocated for non-painted triangles, and the last position is allocated for seed fill.
explicit TriangleSelectorMmGui(const TriangleMesh &mesh, const std::vector<std::array<float, 4>> &colors, const std::array<float, 4> &default_volume_color)
: TriangleSelectorGUI(mesh), m_colors(colors), m_default_volume_color(default_volume_color), m_gizmo_scene(colors.size() + 2) {}
~TriangleSelectorMmGui() override = default;
// Render current selection. Transformation matrices are supposed
// to be already set.
void render(ImGuiWrapper* imgui) override;
private:
void update_render_data();
const std::vector<std::array<float, 4>> &m_colors;
std::vector<GLIndexedVertexArray> m_iva_colors;
const std::array<float, 4> m_default_volume_color;
GLIndexedVertexArray m_iva_seed_fill;
GLMmSegmentationGizmo3DScene m_gizmo_scene;
};
class GLGizmoMmuSegmentation : public GLGizmoPainterBase

View file

@ -194,10 +194,10 @@ void GLGizmoPainterBase::render_cursor() const
if (m_rr.mesh_id == -1)
return;
if (!m_seed_fill_enabled) {
if (m_tool_type == ToolType::BRUSH) {
if (m_cursor_type == TriangleSelector::SPHERE)
render_cursor_sphere(trafo_matrices[m_rr.mesh_id]);
else
else if (m_cursor_type == TriangleSelector::CIRCLE)
render_cursor_circle();
}
}
@ -307,11 +307,19 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
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 (m_tool_type == ToolType::BRUSH) {
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;
} else if (m_tool_type == ToolType::SEED_FILL) {
m_seed_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_seed_fill_angle - SeedFillAngleStep, SeedFillAngleMin)
: std::min(m_seed_fill_angle + SeedFillAngleStep, SeedFillAngleMax);
m_parent.set_as_dirty();
return true;
}
return false;
}
}
@ -396,19 +404,21 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
if (m_seed_fill_enabled) {
if (m_tool_type == ToolType::SEED_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) {
m_triangle_selectors[m_rr.mesh_id]->seed_fill_apply_on_triangles(new_state);
m_seed_fill_last_mesh_id = -1;
} else
} else if (m_tool_type == ToolType::BRUSH)
m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, int(m_rr.facet), camera_pos, m_cursor_radius, m_cursor_type,
new_state, trafo_matrix, m_triangle_splitting_enabled);
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
m_last_mouse_click = mouse_position;
}
return true;
}
if (action == SLAGizmoEventType::Moving && m_seed_fill_enabled) {
if (action == SLAGizmoEventType::Moving && (m_tool_type == ToolType::SEED_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER))) {
if (m_triangle_selectors.empty())
return false;
@ -428,8 +438,10 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
update_raycast_cache(mouse_position, camera, trafo_matrices);
auto seed_fill_unselect_all = [this]() {
for (auto &triangle_selector : m_triangle_selectors)
for (auto &triangle_selector : m_triangle_selectors) {
triangle_selector->seed_fill_unselect_all_triangles();
triangle_selector->request_update_render_data();
}
};
if (m_rr.mesh_id == -1) {
@ -446,7 +458,13 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
seed_fill_unselect_all();
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_seed_fill_angle);
if (m_tool_type == ToolType::SEED_FILL)
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_seed_fill_angle);
else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), false);
else if (m_tool_type == ToolType::BUCKET_FILL)
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), true);
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
m_seed_fill_last_mesh_id = m_rr.mesh_id;
return true;
}
@ -589,28 +607,11 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
static constexpr std::array<float, 4> enforcers_color{0.47f, 0.47f, 1.f, 1.f};
static constexpr std::array<float, 4> blockers_color{1.f, 0.44f, 0.44f, 1.f};
int enf_cnt = 0;
int blc_cnt = 0;
for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
iva->release_geometry();
for (const Triangle& tr : m_triangles) {
if (!tr.valid() || tr.is_split() || tr.get_state() == EnforcerBlockerType::NONE)
continue;
GLIndexedVertexArray &iva = 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)
iva.push_geometry(m_vertices[tr.verts_idxs[i]].v, m_mesh->stl.facet_start[tr.source_triangle].normal);
iva.push_triangle(cnt, cnt + 1, cnt + 2);
cnt += 3;
if (m_update_render_data) {
update_render_data();
m_update_render_data = false;
}
for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
iva->finalize_geometry(true);
auto* shader = wxGetApp().get_current_shader();
if (! shader)
return;
@ -635,6 +636,33 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
void TriangleSelectorGUI::update_render_data()
{
int enf_cnt = 0;
int blc_cnt = 0;
for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
iva->release_geometry();
for (const Triangle &tr : m_triangles) {
if (!tr.valid() || tr.is_split() || tr.get_state() == EnforcerBlockerType::NONE)
continue;
GLIndexedVertexArray &iva = 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)
iva.push_geometry(m_vertices[tr.verts_idxs[i]].v, m_mesh->stl.facet_start[tr.source_triangle].normal);
iva.push_triangle(cnt, cnt + 1, cnt + 2);
cnt += 3;
}
for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
iva->finalize_geometry(true);
}
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui)
{
@ -685,7 +713,7 @@ void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui)
va = &m_varrays[ORIGINAL];
cnt = &cnts[ORIGINAL];
}
else if (tr.valid) {
else if (tr.valid()) {
va = &m_varrays[SPLIT];
cnt = &cnts[SPLIT];
}

View file

@ -38,13 +38,20 @@ public:
virtual void render(ImGuiWrapper *imgui);
void render() { this->render(nullptr); }
void request_update_render_data() { m_update_render_data = true; };
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
void render_debug(ImGuiWrapper* imgui);
bool m_show_triangles{false};
bool m_show_invalid{false};
#endif
protected:
bool m_update_render_data = false;
private:
void update_render_data();
GLIndexedVertexArray m_iva_enforcers;
GLIndexedVertexArray m_iva_blockers;
std::array<GLIndexedVertexArray, 3> m_varrays;
@ -99,13 +106,23 @@ protected:
TriangleSelector::CursorType m_cursor_type = TriangleSelector::SPHERE;
bool m_triangle_splitting_enabled = true;
bool m_seed_fill_enabled = false;
float m_seed_fill_angle = 0.f;
enum class ToolType {
BRUSH,
BUCKET_FILL,
SEED_FILL
};
bool m_triangle_splitting_enabled = true;
ToolType m_tool_type = ToolType::BRUSH;
float m_seed_fill_angle = 0.f;
static constexpr float SeedFillAngleMin = 0.0f;
static constexpr float SeedFillAngleMax = 90.f;
static constexpr float SeedFillAngleStep = 1.f;
// It stores the value of the previous mesh_id to which the seed fill was applied.
// It is used to detect when the mouse has moved from one volume to another one.
int m_seed_fill_last_mesh_id = -1;
int m_seed_fill_last_mesh_id = -1;
enum class Button {
None,

View file

@ -127,6 +127,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
if (mv->is_model_part()) {
++idx;
m_triangle_selectors[idx]->reset();
m_triangle_selectors[idx]->request_update_render_data();
}
}
@ -147,6 +148,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
// Manually inserted values aren't clamped by ImGui. Zero cursor size results in a crash.
m_cursor_radius = std::clamp(m_cursor_radius, CursorRadiusMin, CursorRadiusMax);
ImGui::AlignTextToFramePadding();
@ -257,6 +260,7 @@ void GLGizmoSeam::update_from_model_object()
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh));
m_triangle_selectors.back()->deserialize(mv->seam_facets.get_data());
m_triangle_selectors.back()->request_update_render_data();
}
}

View file

@ -524,63 +524,87 @@ static bool selectable(const char* label, bool selected, ImGuiSelectableFlags fl
ImGuiContext& g = *GImGui;
const ImGuiStyle& style = g.Style;
if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.CurrentColumns) // FIXME-OPT: Avoid if vertically clipped.
ImGui::PushColumnsBackground();
// Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle.
ImGuiID id = window->GetID(label);
ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);
ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
ImVec2 pos = window->DC.CursorPos;
pos.y += window->DC.CurrLineTextBaseOffset;
ImRect bb_inner(pos, pos + size);
ImGui::ItemSize(size, 0.0f);
// Fill horizontal space.
ImVec2 window_padding = window->WindowPadding;
float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? ImGui::GetWindowContentRegionMax().x : ImGui::GetContentRegionMax().x;
float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - pos.x);
ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y);
ImRect bb(pos, pos + size_draw);
if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth))
bb.Max.x += window_padding.x;
// Fill horizontal space
// We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match other widgets.
const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0;
const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x;
const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x;
if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth))
size.x = ImMax(label_size.x, max_x - min_x);
// Selectables are tightly packed together so we extend the box to cover spacing between selectable.
const float spacing_x = style.ItemSpacing.x;
const float spacing_y = style.ItemSpacing.y;
const float spacing_L = IM_FLOOR(spacing_x * 0.50f);
const float spacing_U = IM_FLOOR(spacing_y * 0.50f);
bb.Min.x -= spacing_L;
bb.Min.y -= spacing_U;
bb.Max.x += (spacing_x - spacing_L);
bb.Max.y += (spacing_y - spacing_U);
// Text stays at the submission position, but bounding box may be extended on both sides
const ImVec2 text_min = pos;
const ImVec2 text_max(min_x + size.x, pos.y + size.y);
// Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable.
ImRect bb(min_x, pos.y, text_max.x, text_max.y);
if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0)
{
const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x;
const float spacing_y = style.ItemSpacing.y;
const float spacing_L = IM_FLOOR(spacing_x * 0.50f);
const float spacing_U = IM_FLOOR(spacing_y * 0.50f);
bb.Min.x -= spacing_L;
bb.Min.y -= spacing_U;
bb.Max.x += (spacing_x - spacing_L);
bb.Max.y += (spacing_y - spacing_U);
}
//if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); }
// Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackground for every Selectable..
const float backup_clip_rect_min_x = window->ClipRect.Min.x;
const float backup_clip_rect_max_x = window->ClipRect.Max.x;
if (span_all_columns)
{
window->ClipRect.Min.x = window->ParentWorkRect.Min.x;
window->ClipRect.Max.x = window->ParentWorkRect.Max.x;
}
bool item_add;
if (flags & ImGuiSelectableFlags_Disabled)
{
ImGuiItemFlags backup_item_flags = window->DC.ItemFlags;
window->DC.ItemFlags |= ImGuiItemFlags_Disabled | ImGuiItemFlags_NoNavDefaultFocus;
ImGuiItemFlags backup_item_flags = g.CurrentItemFlags;
g.CurrentItemFlags |= ImGuiItemFlags_Disabled | ImGuiItemFlags_NoNavDefaultFocus;
item_add = ImGui::ItemAdd(bb, id);
window->DC.ItemFlags = backup_item_flags;
g.CurrentItemFlags = backup_item_flags;
}
else
{
item_add = ImGui::ItemAdd(bb, id);
}
if (!item_add)
if (span_all_columns)
{
if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.CurrentColumns)
ImGui::PopColumnsBackground();
return false;
window->ClipRect.Min.x = backup_clip_rect_min_x;
window->ClipRect.Max.x = backup_clip_rect_max_x;
}
if (!item_add)
return false;
// FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only,
// which would be advantageous since most selectable are not selected.
if (span_all_columns && window->DC.CurrentColumns)
ImGui::PushColumnsBackground();
else if (span_all_columns && g.CurrentTable)
ImGui::TablePushBackgroundChannel();
// We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
ImGuiButtonFlags button_flags = 0;
if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; }
if (flags & ImGuiSelectableFlags_PressedOnClick) { button_flags |= ImGuiButtonFlags_PressedOnClick; }
if (flags & ImGuiSelectableFlags_PressedOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; }
if (flags & ImGuiSelectableFlags_Disabled) { button_flags |= ImGuiButtonFlags_Disabled; }
if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; }
if (flags & ImGuiSelectableFlags_AllowItemOverlap) { button_flags |= ImGuiButtonFlags_AllowItemOverlap; }
if (flags & ImGuiSelectableFlags_SelectOnClick) { button_flags |= ImGuiButtonFlags_PressedOnClick; }
if (flags & ImGuiSelectableFlags_SelectOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; }
if (flags & ImGuiSelectableFlags_Disabled) { button_flags |= ImGuiButtonFlags_Disabled; }
if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; }
if (flags & ImGuiSelectableFlags_AllowItemOverlap) { button_flags |= ImGuiButtonFlags_AllowItemOverlap; }
if (flags & ImGuiSelectableFlags_Disabled)
selected = false;
@ -594,8 +618,8 @@ static bool selectable(const char* label, bool selected, ImGuiSelectableFlags fl
{
if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
{
ImGui::SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent, ImRect(bb.Min - window->Pos, bb.Max - window->Pos));
g.NavDisableHighlight = true;
ImGui::SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent);
}
}
if (pressed)
@ -618,29 +642,27 @@ static bool selectable(const char* label, bool selected, ImGuiSelectableFlags fl
ImGui::RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
}
if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.CurrentColumns)
{
if (span_all_columns && window->DC.CurrentColumns)
ImGui::PopColumnsBackground();
bb.Max.x -= (ImGui::GetContentRegionMax().x - max_x);
}
else if (span_all_columns && g.CurrentTable)
ImGui::TablePopBackgroundChannel();
// mark a label with a ImGui::ColorMarkerHovered, if item is hovered
char* marked_label = new char[512]; //255 symbols is not enough for translated string (e.t. to Russian)
// mark a label with a ColorMarkerHovered, if item is hovered
char marked_label[512]; //255 symbols is not enough for translated string (e.t. to Russian)
if (hovered)
sprintf(marked_label, "%c%s", ImGui::ColorMarkerHovered, label);
else
strcpy(marked_label, label);
if (flags & ImGuiSelectableFlags_Disabled) ImGui::PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);
ImGui::RenderTextClipped(bb_inner.Min, bb_inner.Max, marked_label, NULL, &label_size, style.SelectableTextAlign, &bb);
ImGui::RenderTextClipped(text_min, text_max, marked_label, NULL, &label_size, style.SelectableTextAlign, &bb);
if (flags & ImGuiSelectableFlags_Disabled) ImGui::PopStyleColor();
delete[] marked_label;
// Automatically close popups
if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup)) ImGui::CloseCurrentPopup();
if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(g.CurrentItemFlags & ImGuiItemFlags_SelectableDontClosePopup))
ImGui::CloseCurrentPopup();
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags);
return pressed;
}

View file

@ -1293,6 +1293,30 @@ void ObjectDataViewModel::SetExtruder(const wxString& extruder, wxDataViewItem i
UpdateVolumesExtruderBitmap(item);
}
bool ObjectDataViewModel::SetName(const wxString& new_name, wxDataViewItem item)
{
if (!item.IsOk())
return false;
// The icon can't be edited so get its old value and reuse it.
wxVariant valueOld;
GetValue(valueOld, item, colName);
DataViewBitmapText bmpText;
bmpText << valueOld;
// But replace the text with the value entered by user.
bmpText.SetText(new_name);
wxVariant value;
value << bmpText;
if (SetValue(value, item, colName)) {
ItemChanged(item);
return true;
}
return false;
}
void ObjectDataViewModel::AddAllChildren(const wxDataViewItem& parent)
{
ObjectDataViewModelNode* node = static_cast<ObjectDataViewModelNode*>(parent.GetID());

View file

@ -324,6 +324,7 @@ public:
unsigned int col);
void SetExtruder(const wxString& extruder, wxDataViewItem item);
bool SetName (const wxString& new_name, wxDataViewItem item);
// For parent move child from cur_volume_id place to new_volume_id
// Remaining items will moved up/down accordingly

View file

@ -1789,8 +1789,8 @@ struct Plater::priv
bool can_replace_with_stl() const;
bool can_split(bool to_objects) const;
void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
ThumbnailsList generate_thumbnails(const ThumbnailsParams& params);
void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type);
ThumbnailsList generate_thumbnails(const ThumbnailsParams& params, Camera::EType camera_type);
void bring_instance_forward() const;
@ -1866,7 +1866,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
background_process.set_fff_print(&fff_print);
background_process.set_sla_print(&sla_print);
background_process.set_gcode_result(&gcode_result);
background_process.set_thumbnail_cb([this](const ThumbnailsParams& params) { return this->generate_thumbnails(params); });
background_process.set_thumbnail_cb([this](const ThumbnailsParams& params) { return this->generate_thumbnails(params, Camera::EType::Ortho); });
background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED);
background_process.set_finished_event(EVT_PROCESS_COMPLETED);
background_process.set_export_began_event(EVT_EXPORT_BEGAN);
@ -3285,6 +3285,13 @@ void Plater::priv::replace_with_stl()
old_model_object->ensure_on_bed();
old_model_object->sort_volumes(wxGetApp().app_config->get("order_volumes") == "1");
// if object has just one volume, rename object too
if (old_model_object->volumes.size() == 1)
old_model_object->name = old_model_object->volumes[0]->name;
// update new name in ObjectList
sidebar->obj_list()->update_name_in_list(object_idx, volume_idx);
sla::reproject_points_and_holes(old_model_object);
// update 3D scene
@ -3344,7 +3351,7 @@ void Plater::priv::reload_from_disk()
else
missing_input_paths.push_back(volume->source.input_file);
}
else if (!object->input_file.empty() && volume->is_model_part() && !volume->name.empty())
else if (!object->input_file.empty() && volume->is_model_part() && !volume->name.empty() && !volume->source.is_from_builtin_objects)
missing_input_paths.push_back(volume->name);
}
@ -4041,18 +4048,18 @@ void Plater::priv::on_3dcanvas_mouse_dragging_finished(SimpleEvent&)
}
}
void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type)
{
view3D->get_canvas3d()->render_thumbnail(data, w, h, printable_only, parts_only, show_bed, transparent_background);
view3D->get_canvas3d()->render_thumbnail(data, w, h, thumbnail_params, camera_type);
}
ThumbnailsList Plater::priv::generate_thumbnails(const ThumbnailsParams& params)
ThumbnailsList Plater::priv::generate_thumbnails(const ThumbnailsParams& params, Camera::EType camera_type)
{
ThumbnailsList thumbnails;
for (const Vec2d& size : params.sizes) {
thumbnails.push_back(ThumbnailData());
Point isize(size); // round to ints
generate_thumbnail(thumbnails.back(), isize.x(), isize.y(), params.printable_only, params.parts_only, params.show_bed, params.transparent_background);
generate_thumbnail(thumbnails.back(), isize.x(), isize.y(), params, camera_type);
if (!thumbnails.back().is_valid())
thumbnails.pop_back();
}
@ -4283,14 +4290,12 @@ bool Plater::priv::can_reload_from_disk() const
// collects selected ModelVolumes
const std::set<unsigned int>& selected_volumes_idxs = selection.get_volume_idxs();
for (unsigned int idx : selected_volumes_idxs)
{
for (unsigned int idx : selected_volumes_idxs) {
const GLVolume* v = selection.get_volume(idx);
int v_idx = v->volume_idx();
if (v_idx >= 0)
{
if (v_idx >= 0) {
int o_idx = v->object_idx();
if ((0 <= o_idx) && (o_idx < (int)model.objects.size()))
if (0 <= o_idx && o_idx < (int)model.objects.size())
selected_volumes.push_back({ o_idx, v_idx });
}
}
@ -4299,13 +4304,12 @@ bool Plater::priv::can_reload_from_disk() const
// collects paths of files to load
std::vector<fs::path> paths;
for (const SelectedVolume& v : selected_volumes)
{
for (const SelectedVolume& v : selected_volumes) {
const ModelObject* object = model.objects[v.object_idx];
const ModelVolume* volume = object->volumes[v.volume_idx];
if (!volume->source.input_file.empty())
paths.push_back(volume->source.input_file);
else if (!object->input_file.empty() && !volume->name.empty())
else if (!object->input_file.empty() && !volume->name.empty() && !volume->source.is_from_builtin_objects)
paths.push_back(volume->name);
}
std::sort(paths.begin(), paths.end());
@ -4929,11 +4933,11 @@ enum class LoadType : unsigned char
class ProjectDropDialog : public DPIDialog
{
wxRadioBox* m_action{ nullptr };
int m_action { 0 };
public:
ProjectDropDialog(const std::string& filename);
int get_action() const { return m_action->GetSelection() + 1; }
int get_action() const { return m_action + 1; }
protected:
void on_dpi_changed(const wxRect& suggested_rect) override;
@ -4954,12 +4958,24 @@ ProjectDropDialog::ProjectDropDialog(const std::string& filename)
main_sizer->Add(new wxStaticText(this, wxID_ANY,
_L("Select an action to apply to the file") + ": " + from_u8(filename)), 0, wxEXPAND | wxALL, 10);
m_action = new wxRadioBox(this, wxID_ANY, _L("Action"), wxDefaultPosition, wxDefaultSize,
WXSIZEOF(choices), choices, 0, wxRA_SPECIFY_ROWS);
int action = std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")),
static_cast<int>(LoadType::OpenProject), static_cast<int>(LoadType::LoadConfig)) - 1;
m_action->SetSelection(action);
main_sizer->Add(m_action, 1, wxEXPAND | wxRIGHT | wxLEFT, 10);
wxStaticBox* action_stb = new wxStaticBox(this, wxID_ANY, _L("Action"));
if (!wxOSX) action_stb->SetBackgroundStyle(wxBG_STYLE_PAINT);
action_stb->SetFont(wxGetApp().normal_font());
wxStaticBoxSizer* stb_sizer = new wxStaticBoxSizer(action_stb, wxVERTICAL);
int id = 0;
for (const wxString& label : choices) {
wxRadioButton* btn = new wxRadioButton(this, wxID_ANY, label, wxDefaultPosition, wxDefaultSize, id == 0 ? wxRB_GROUP : 0);
btn->SetValue(id == action);
btn->Bind(wxEVT_RADIOBUTTON, [this, id](wxCommandEvent&) { m_action = id; });
stb_sizer->Add(btn, 0, wxEXPAND | wxTOP, 5);
id++;
}
main_sizer->Add(stb_sizer, 1, wxEXPAND | wxRIGHT | wxLEFT, 10);
wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL);
wxCheckBox* check = new wxCheckBox(this, wxID_ANY, _L("Don't show again"));
@ -4973,6 +4989,9 @@ ProjectDropDialog::ProjectDropDialog(const std::string& filename)
SetSizer(main_sizer);
main_sizer->SetSizeHints(this);
// Update DarkUi just for buttons
wxGetApp().UpdateDlgDarkUI(this, true);
}
void ProjectDropDialog::on_dpi_changed(const wxRect& suggested_rect)
@ -5603,7 +5622,8 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
wxBusyCursor wait;
bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1";
ThumbnailData thumbnail_data;
p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true);
ThumbnailsParams thumbnail_params = { {}, false, true, true, true };
p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, thumbnail_params, Camera::EType::Ortho);
#if ENABLE_PROJECT_DIRTY_STATE
bool ret = Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data);
if (ret) {

View file

@ -393,11 +393,9 @@ void PreferencesDialog::build()
sizer->Add(tabs, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5);
auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
wxButton* btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); });
this->Bind(wxEVT_BUTTON, &PreferencesDialog::accept, this, wxID_OK);
wxGetApp().UpdateDarkUI(btn);
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_CANCEL, this)));
wxGetApp().UpdateDlgDarkUI(this, true);
sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM | wxTOP, 10);
@ -406,7 +404,7 @@ void PreferencesDialog::build()
this->CenterOnParent();
}
void PreferencesDialog::accept()
void PreferencesDialog::accept(wxEvent&)
{
// if (m_values.find("no_defaults") != m_values.end()
// warning_catcher(this, wxString::Format(_L("You need to restart %s to make the changes effective."), SLIC3R_APP_NAME));
@ -434,11 +432,6 @@ void PreferencesDialog::accept()
}
}
if (m_values.empty()) {
EndModal(wxID_CANCEL);
return;
}
auto app_config = get_app_config();
m_seq_top_layer_only_changed = false;

View file

@ -46,7 +46,7 @@ public:
#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
bool recreate_GUI() const { return m_recreate_GUI; }
void build();
void accept();
void accept(wxEvent&);
protected:
void on_dpi_changed(const wxRect &suggested_rect) override;

View file

@ -415,7 +415,7 @@ void Selection::remove_all()
// Not taking the snapshot with non-empty Redo stack will likely be more confusing than losing the Redo stack.
// Let's wait for user feedback.
// if (!wxGetApp().plater()->can_redo())
wxGetApp().plater()->take_snapshot(_(L("Selection-Remove All")));
wxGetApp().plater()->take_snapshot(_L("Selection-Remove All"));
m_mode = Instance;
clear();
@ -432,9 +432,9 @@ void Selection::set_deserialized(EMode mode, const std::vector<std::pair<size_t,
m_list.clear();
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++ i)
if (std::binary_search(volumes_and_instances.begin(), volumes_and_instances.end(), (*m_volumes)[i]->geometry_id))
this->do_add_volume(i);
do_add_volume(i);
update_type();
this->set_bounding_boxes_dirty();
set_bounding_boxes_dirty();
}
void Selection::clear()
@ -447,12 +447,14 @@ void Selection::clear()
for (unsigned int i : m_list) {
(*m_volumes)[i]->selected = false;
// ensure the volume gets the proper color before next call to render (expecially needed for transparent volumes)
(*m_volumes)[i]->set_render_color();
}
m_list.clear();
update_type();
this->set_bounding_boxes_dirty();
set_bounding_boxes_dirty();
// this happens while the application is closing
if (wxGetApp().obj_manipul() == nullptr)

View file

@ -163,15 +163,14 @@ SysInfoDialog::SysInfoDialog()
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK);
m_btn_copy_to_clipboard = new wxButton(this, wxID_ANY, _L("Copy to Clipboard"), wxDefaultPosition, wxDefaultSize);
buttons->Insert(0, m_btn_copy_to_clipboard, 0, wxLEFT, 5);
buttons->Insert(0, m_btn_copy_to_clipboard, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
m_btn_copy_to_clipboard->Bind(wxEVT_BUTTON, &SysInfoDialog::onCopyToClipboard, this);
this->SetEscapeId(wxID_OK);
this->Bind(wxEVT_BUTTON, &SysInfoDialog::onCloseDialog, this, wxID_OK);
main_sizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_OK, this)), true);
wxGetApp().UpdateDarkUI(m_btn_copy_to_clipboard, true);
wxGetApp().UpdateDlgDarkUI(this, true);
// this->Bind(wxEVT_LEFT_DOWN, &SysInfoDialog::onCloseDialog, this);
// logo->Bind(wxEVT_LEFT_DOWN, &SysInfoDialog::onCloseDialog, this);

View file

@ -3005,6 +3005,7 @@ void TabPrinter::update()
m_presets->get_edited_preset().printer_technology() == ptFFF ? update_fff() : update_sla();
m_update_cnt--;
update_description_lines();
Layout();
if (m_update_cnt == 0)
@ -4290,6 +4291,9 @@ void TabSLAMaterial::update()
if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF)
return;
update_description_lines();
Layout();
// #ys_FIXME. Just a template for this function
// m_update_cnt++;
// ! something to update