9703 lines
410 KiB
C++
9703 lines
410 KiB
C++
#include "libslic3r/libslic3r.h"
|
|
#include "GLCanvas3D.hpp"
|
|
|
|
#include <igl/unproject.h>
|
|
|
|
#include "libslic3r/BuildVolume.hpp"
|
|
#include "libslic3r/ClipperUtils.hpp"
|
|
#include "libslic3r/PrintConfig.hpp"
|
|
#include "libslic3r/GCode/ThumbnailData.hpp"
|
|
#include "libslic3r/Geometry/ConvexHull.hpp"
|
|
#include "libslic3r/ExtrusionEntity.hpp"
|
|
#include "libslic3r/Layer.hpp"
|
|
#include "libslic3r/Utils.hpp"
|
|
#include "libslic3r/Technologies.hpp"
|
|
#include "libslic3r/Tesselate.hpp"
|
|
#include "libslic3r/PresetBundle.hpp"
|
|
|
|
#include "slic3r/GUI/3DScene.hpp"
|
|
#include "slic3r/GUI/BackgroundSlicingProcess.hpp"
|
|
#include "slic3r/GUI/GLShader.hpp"
|
|
#include "slic3r/GUI/GUI.hpp"
|
|
#include "slic3r/GUI/Tab.hpp"
|
|
#include "slic3r/GUI/GUI_Preview.hpp"
|
|
#include "slic3r/GUI/OpenGLManager.hpp"
|
|
#include "slic3r/GUI/Plater.hpp"
|
|
#include "slic3r/GUI/MainFrame.hpp"
|
|
#include "slic3r/Utils/UndoRedo.hpp"
|
|
#include "slic3r/GUI/Gizmos/GLGizmoPainterBase.hpp"
|
|
#include "slic3r/GUI/BitmapCache.hpp"
|
|
#include "slic3r/Utils/MacDarkMode.hpp"
|
|
|
|
#include "GUI_App.hpp"
|
|
#include "GUI_ObjectList.hpp"
|
|
#include "GUI_Colors.hpp"
|
|
#include "Mouse3DController.hpp"
|
|
#include "I18N.hpp"
|
|
#include "NotificationManager.hpp"
|
|
#include "format.hpp"
|
|
#include "DailyTips.hpp"
|
|
|
|
#if ENABLE_RETINA_GL
|
|
#include "slic3r/Utils/RetinaHelper.hpp"
|
|
#endif
|
|
|
|
#include <GL/glew.h>
|
|
|
|
#include <wx/glcanvas.h>
|
|
#include <wx/bitmap.h>
|
|
#include <wx/dcmemory.h>
|
|
#include <wx/image.h>
|
|
#include <wx/settings.h>
|
|
#include <wx/tooltip.h>
|
|
#include <wx/debug.h>
|
|
#include <wx/fontutil.h>
|
|
// Print now includes tbb, and tbb includes Windows. This breaks compilation of wxWidgets if included before wx.
|
|
#include "libslic3r/Print.hpp"
|
|
#include "libslic3r/SLAPrint.hpp"
|
|
|
|
#include "wxExtensions.hpp"
|
|
|
|
#include <tbb/parallel_for.h>
|
|
#include <tbb/spin_mutex.h>
|
|
|
|
#include <boost/log/trivial.hpp>
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
|
|
#include <iostream>
|
|
#include <float.h>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
#ifndef IMGUI_DEFINE_MATH_OPERATORS
|
|
#define IMGUI_DEFINE_MATH_OPERATORS
|
|
#endif
|
|
#include <imgui/imgui_internal.h>
|
|
|
|
static constexpr const float TRACKBALLSIZE = 0.8f;
|
|
|
|
static const float SLIDER_DEFAULT_RIGHT_MARGIN = 10.0f;
|
|
static const float SLIDER_DEFAULT_BOTTOM_MARGIN = 10.0f;
|
|
static const float SLIDER_RIGHT_MARGIN = 124.0f;
|
|
static const float SLIDER_BOTTOM_MARGIN = 64.0f;
|
|
|
|
float GLCanvas3D::DEFAULT_BG_LIGHT_COLOR[3] = { 0.906f, 0.906f, 0.906f };
|
|
float GLCanvas3D::DEFAULT_BG_LIGHT_COLOR_DARK[3] = { 0.329f, 0.329f, 0.353f };
|
|
float GLCanvas3D::ERROR_BG_LIGHT_COLOR[3] = { 0.753f, 0.192f, 0.039f };
|
|
float GLCanvas3D::ERROR_BG_LIGHT_COLOR_DARK[3] = { 0.753f, 0.192f, 0.039f };
|
|
|
|
void GLCanvas3D::update_render_colors()
|
|
{
|
|
GLCanvas3D::DEFAULT_BG_LIGHT_COLOR[0] = RenderColor::colors[RenderCol_3D_Background].x;
|
|
GLCanvas3D::DEFAULT_BG_LIGHT_COLOR[1] = RenderColor::colors[RenderCol_3D_Background].y;
|
|
GLCanvas3D::DEFAULT_BG_LIGHT_COLOR[2] = RenderColor::colors[RenderCol_3D_Background].z;
|
|
}
|
|
|
|
void GLCanvas3D::load_render_colors()
|
|
{
|
|
RenderColor::colors[RenderCol_3D_Background] = ImVec4(GLCanvas3D::DEFAULT_BG_LIGHT_COLOR[0],
|
|
GLCanvas3D::DEFAULT_BG_LIGHT_COLOR[1],
|
|
GLCanvas3D::DEFAULT_BG_LIGHT_COLOR[2],
|
|
1.0f);
|
|
}
|
|
|
|
//static constexpr const float AXES_COLOR[3][3] = { { 1.0f, 0.0f, 0.0f }, { 0.0f, 1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } };
|
|
|
|
// Number of floats
|
|
static constexpr const size_t MAX_VERTEX_BUFFER_SIZE = 131072 * 6; // 3.15MB
|
|
// Reserve size in number of floats.
|
|
static constexpr const size_t VERTEX_BUFFER_RESERVE_SIZE = 131072 * 2; // 1.05MB
|
|
// Reserve size in number of floats, maximum sum of all preallocated buffers.
|
|
//static constexpr const size_t VERTEX_BUFFER_RESERVE_SIZE_SUM_MAX = 1024 * 1024 * 128 / 4; // 128MB
|
|
|
|
namespace Slic3r {
|
|
namespace GUI {
|
|
|
|
#ifdef __WXGTK3__
|
|
// wxGTK3 seems to simulate OSX behavior in regard to HiDPI scaling support.
|
|
RetinaHelper::RetinaHelper(wxWindow* window) : m_window(window), m_self(nullptr) {}
|
|
RetinaHelper::~RetinaHelper() {}
|
|
float RetinaHelper::get_scale_factor() { return float(m_window->GetContentScaleFactor()); }
|
|
#endif // __WXGTK3__
|
|
|
|
// Fixed the collision between BuildVolume::Type::Convex and macro Convex defined inside /usr/include/X11/X.h that is included by WxWidgets 3.0.
|
|
#if defined(__linux__) && defined(Convex)
|
|
#undef Convex
|
|
#endif
|
|
|
|
GLCanvas3D::LayersEditing::~LayersEditing()
|
|
{
|
|
if (m_z_texture_id != 0) {
|
|
glsafe(::glDeleteTextures(1, &m_z_texture_id));
|
|
m_z_texture_id = 0;
|
|
}
|
|
delete m_slicing_parameters;
|
|
}
|
|
|
|
const float GLCanvas3D::LayersEditing::THICKNESS_BAR_WIDTH = 70.0f;
|
|
|
|
void GLCanvas3D::LayersEditing::init()
|
|
{
|
|
glsafe(::glGenTextures(1, (GLuint*)&m_z_texture_id));
|
|
glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1));
|
|
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
|
}
|
|
|
|
void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config)
|
|
{
|
|
m_config = config;
|
|
delete m_slicing_parameters;
|
|
m_slicing_parameters = nullptr;
|
|
m_layers_texture.valid = false;
|
|
m_layer_height_profile.clear();
|
|
}
|
|
|
|
void GLCanvas3D::LayersEditing::select_object(const Model& model, int object_id)
|
|
{
|
|
const ModelObject* model_object_new = (object_id >= 0) ? model.objects[object_id] : nullptr;
|
|
// Maximum height of an object changes when the object gets rotated or scaled.
|
|
// Changing maximum height of an object will invalidate the layer heigth editing profile.
|
|
// m_model_object->bounding_box() is cached, therefore it is cheap even if this method is called frequently.
|
|
const float new_max_z = (model_object_new == nullptr) ? 0.0f : static_cast<float>(model_object_new->bounding_box().max.z());
|
|
if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z ||
|
|
(model_object_new != nullptr && m_model_object->id() != model_object_new->id())) {
|
|
m_layer_height_profile.clear();
|
|
delete m_slicing_parameters;
|
|
m_slicing_parameters = nullptr;
|
|
m_layers_texture.valid = false;
|
|
this->last_object_id = object_id;
|
|
m_model_object = model_object_new;
|
|
m_object_max_z = new_max_z;
|
|
}
|
|
}
|
|
|
|
bool GLCanvas3D::LayersEditing::is_allowed() const
|
|
{
|
|
return wxGetApp().get_shader("variable_layer_height") != nullptr && m_z_texture_id > 0;
|
|
}
|
|
|
|
bool GLCanvas3D::LayersEditing::is_enabled() const
|
|
{
|
|
return m_enabled;
|
|
}
|
|
|
|
void GLCanvas3D::LayersEditing::set_enabled(bool enabled)
|
|
{
|
|
m_enabled = is_allowed() && enabled;
|
|
}
|
|
|
|
float GLCanvas3D::LayersEditing::s_overlay_window_width;
|
|
|
|
void GLCanvas3D::LayersEditing::show_tooltip_information(const GLCanvas3D& canvas, std::map<wxString, wxString> captions_texts, float x, float y)
|
|
{
|
|
ImTextureID normal_id = canvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP);
|
|
ImTextureID hover_id = canvas.get_gizmos_manager().get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER);
|
|
|
|
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
|
float caption_max = 0.f;
|
|
for (auto caption_text : captions_texts) {
|
|
caption_max = std::max(imgui.calc_text_size(caption_text.first).x, caption_max);
|
|
}
|
|
caption_max += GImGui->Style.WindowPadding.x + imgui.scaled(1);
|
|
|
|
float font_size = ImGui::GetFontSize();
|
|
ImVec2 button_size = ImVec2(font_size * 1.8, font_size * 1.3);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0.0f, GImGui->Style.FramePadding.y});
|
|
ImGui::ImageButton3(normal_id, hover_id, button_size);
|
|
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::BeginTooltip2(ImVec2(x, y));
|
|
auto draw_text_with_caption = [this, &caption_max, &imgui](const wxString& caption, const wxString& text) {
|
|
imgui.text_colored(ImGuiWrapper::COL_ACTIVE, caption);
|
|
ImGui::SameLine(caption_max);
|
|
imgui.text_colored(ImGuiWrapper::COL_WINDOW_BG, text);
|
|
};
|
|
|
|
for (const auto& caption_text : captions_texts) draw_text_with_caption(caption_text.first, caption_text.second);
|
|
|
|
ImGui::EndTooltip();
|
|
}
|
|
ImGui::PopStyleVar(2);
|
|
}
|
|
|
|
void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(const GLCanvas3D& canvas) {
|
|
if (!m_enabled)
|
|
return;
|
|
|
|
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
|
const Size& cnv_size = canvas.get_canvas_size();
|
|
float zoom = (float)wxGetApp().plater()->get_camera().get_zoom();
|
|
float left_pos = canvas.m_main_toolbar.get_item("layersediting")->render_left_pos;
|
|
const float x = 0.5 * cnv_size.get_width() + left_pos * zoom;
|
|
imgui.set_next_window_pos(x, canvas.m_main_toolbar.get_height(), ImGuiCond_Always, 0.0f, 0.0f);
|
|
|
|
imgui.push_toolbar_style(canvas.get_scale());
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f * canvas.get_scale(), 4.0f * canvas.get_scale()));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(6.0f * canvas.get_scale(), 10.0f * canvas.get_scale()));
|
|
imgui.begin(_L("Variable layer height"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
|
|
const float sliders_width = imgui.scaled(7.0f);
|
|
const float input_box_width = 1.5 * imgui.get_slider_icon_size().x;
|
|
|
|
if (imgui.button(_L("Adaptive")))
|
|
wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), Event<float>(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, m_adaptive_quality));
|
|
ImGui::SameLine();
|
|
static float text_align = ImGui::GetCursorPosX();
|
|
ImGui::AlignTextToFramePadding();
|
|
text_align = std::max(text_align, ImGui::GetCursorPosX());
|
|
ImGui::SetCursorPosX(text_align);
|
|
imgui.text(_L("Quality / Speed"));
|
|
if (ImGui::IsItemHovered()) {
|
|
//ImGui::BeginTooltip();
|
|
//ImGui::TextUnformatted(_L("Higher print quality versus higher print speed.").ToUTF8());
|
|
//ImGui::EndTooltip();
|
|
}
|
|
ImGui::SameLine();
|
|
static float slider_align = ImGui::GetCursorPosX();
|
|
ImGui::PushItemWidth(sliders_width);
|
|
m_adaptive_quality = std::clamp(m_adaptive_quality, 0.0f, 1.f);
|
|
slider_align = std::max(slider_align, ImGui::GetCursorPosX());
|
|
ImGui::SetCursorPosX(slider_align);
|
|
imgui.bbl_slider_float_style("##adaptive_slider", &m_adaptive_quality, 0.0f, 1.f, "%.2f");
|
|
ImGui::SameLine();
|
|
static float input_align = ImGui::GetCursorPosX();
|
|
ImGui::PushItemWidth(input_box_width);
|
|
input_align = std::max(input_align, ImGui::GetCursorPosX());
|
|
ImGui::SetCursorPosX(input_align);
|
|
ImGui::BBLDragFloat("##adaptive_input", &m_adaptive_quality, 0.05f, 0.0f, 0.0f, "%.2f");
|
|
|
|
if (imgui.button(_L("Smooth")))
|
|
wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), HeightProfileSmoothEvent(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, m_smooth_params));
|
|
ImGui::SameLine();
|
|
text_align = std::max(text_align, ImGui::GetCursorPosX());
|
|
ImGui::SetCursorPosX(text_align);
|
|
ImGui::AlignTextToFramePadding();
|
|
imgui.text(_L("Radius"));
|
|
ImGui::SameLine();
|
|
slider_align = std::max(slider_align, ImGui::GetCursorPosX());
|
|
ImGui::SetCursorPosX(slider_align);
|
|
ImGui::PushItemWidth(sliders_width);
|
|
int radius = (int)m_smooth_params.radius;
|
|
int v_min = 1, v_max = 10;
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
|
|
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(238 / 255.0f, 238 / 255.0f, 238 / 255.0f, 0.00f));
|
|
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(238 / 255.0f, 238 / 255.0f, 238 / 255.0f, 0.00f));
|
|
ImGui::PushStyleColor(ImGuiCol_SliderGrab, ImVec4(0.81f, 0.81f, 0.81f, 1.0f));
|
|
ImGui::PushStyleColor(ImGuiCol_SliderGrabActive, ImVec4(0.00f, 0.68f, 0.26f, 1.00f));
|
|
if(ImGui::BBLSliderScalar("##radius_slider", ImGuiDataType_S32, &radius, &v_min, &v_max)){
|
|
radius = std::clamp(radius, 1, 10);
|
|
m_smooth_params.radius = (unsigned int)radius;
|
|
}
|
|
ImGui::PopStyleColor(4);
|
|
ImGui::PopStyleVar();
|
|
ImGui::SameLine();
|
|
input_align = std::max(input_align, ImGui::GetCursorPosX());
|
|
ImGui::SetCursorPosX(input_align);
|
|
ImGui::PushItemWidth(input_box_width);
|
|
ImGui::PushStyleColor(ImGuiCol_BorderActive, ImVec4(0.00f, 0.68f, 0.26f, 1.00f));
|
|
ImGui::PushStyleColor(ImGuiCol_FrameBgHovered, ImVec4(0.00f, 0.68f, 0.26f, 0.00f));
|
|
ImGui::PushStyleColor(ImGuiCol_FrameBgActive, ImVec4(0.00f, 0.68f, 0.26f, 0.00f));
|
|
if (ImGui::BBLDragScalar("##radius_input", ImGuiDataType_S32, &radius, 1, &v_min, &v_max)) {
|
|
radius = std::clamp(radius, 1, 10);
|
|
m_smooth_params.radius = (unsigned int)radius;
|
|
}
|
|
ImGui::PopStyleColor(3);
|
|
|
|
imgui.bbl_checkbox("##keep_min", m_smooth_params.keep_min);
|
|
ImGui::SameLine();
|
|
ImGui::AlignTextToFramePadding();
|
|
imgui.text(_L("Keep min"));
|
|
|
|
ImGui::Separator();
|
|
|
|
float get_cur_y = ImGui::GetContentRegionMax().y + ImGui::GetFrameHeight() + canvas.m_main_toolbar.get_height();
|
|
std::map<wxString, wxString> captions_texts = {
|
|
{_L("Left mouse button:") ,_L("Add detail")},
|
|
{_L("Right mouse button:"), _L("Remove detail")},
|
|
{_L("Shift + Left mouse button:"),_L("Reset to base")},
|
|
{_L("Shift + Right mouse button:"), _L("Smoothing")},
|
|
{_L("Mouse wheel:"), _L("Increase/decrease edit area")}
|
|
};
|
|
show_tooltip_information(canvas, captions_texts, x, get_cur_y);
|
|
ImGui::SameLine();
|
|
if (imgui.button(_L("Reset")))
|
|
wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE));
|
|
|
|
GLCanvas3D::LayersEditing::s_overlay_window_width = ImGui::GetWindowSize().x;
|
|
imgui.end();
|
|
ImGui::PopStyleVar(2);
|
|
imgui.pop_toolbar_style();
|
|
}
|
|
|
|
void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas)
|
|
{
|
|
render_variable_layer_height_dialog(canvas);
|
|
const Rect& bar_rect = get_bar_rect_viewport(canvas);
|
|
render_background_texture(canvas, bar_rect);
|
|
render_curve(bar_rect);
|
|
}
|
|
|
|
float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas)
|
|
{
|
|
const Vec2d mouse_pos = canvas.get_local_mouse_position();
|
|
const Rect& rect = get_bar_rect_screen(canvas);
|
|
float x = (float)mouse_pos.x();
|
|
float y = (float)mouse_pos.y();
|
|
float t = rect.get_top();
|
|
float b = rect.get_bottom();
|
|
|
|
return (rect.get_left() <= x && x <= rect.get_right() && t <= y && y <= b) ?
|
|
// Inside the bar.
|
|
(b - y - 1.0f) / (b - t - 1.0f) :
|
|
// Outside the bar.
|
|
-1000.0f;
|
|
}
|
|
|
|
bool GLCanvas3D::LayersEditing::bar_rect_contains(const GLCanvas3D& canvas, float x, float y)
|
|
{
|
|
const Rect& rect = get_bar_rect_screen(canvas);
|
|
return rect.get_left() <= x && x <= rect.get_right() && rect.get_top() <= y && y <= rect.get_bottom();
|
|
}
|
|
|
|
Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas)
|
|
{
|
|
const Size& cnv_size = canvas.get_canvas_size();
|
|
float w = (float)cnv_size.get_width();
|
|
float h = (float)cnv_size.get_height();
|
|
|
|
return { w - thickness_bar_width(canvas), 0.0f, w, h };
|
|
}
|
|
|
|
Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas)
|
|
{
|
|
const Size& cnv_size = canvas.get_canvas_size();
|
|
float half_w = 0.5f * (float)cnv_size.get_width();
|
|
float half_h = 0.5f * (float)cnv_size.get_height();
|
|
float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
|
|
return { (half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom };
|
|
}
|
|
|
|
bool GLCanvas3D::LayersEditing::is_initialized() const
|
|
{
|
|
return wxGetApp().get_shader("variable_layer_height") != nullptr;
|
|
}
|
|
|
|
std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) const
|
|
{
|
|
std::string ret;
|
|
if (m_enabled && m_layer_height_profile.size() >= 4) {
|
|
float z = get_cursor_z_relative(canvas);
|
|
if (z != -1000.0f) {
|
|
z *= m_object_max_z;
|
|
|
|
float h = 0.0f;
|
|
for (size_t i = m_layer_height_profile.size() - 2; i >= 2; i -= 2) {
|
|
const float zi = static_cast<float>(m_layer_height_profile[i]);
|
|
const float zi_1 = static_cast<float>(m_layer_height_profile[i - 2]);
|
|
if (zi_1 <= z && z <= zi) {
|
|
float dz = zi - zi_1;
|
|
h = (dz != 0.0f) ? static_cast<float>(lerp(m_layer_height_profile[i - 1], m_layer_height_profile[i + 1], (z - zi_1) / dz)) :
|
|
static_cast<float>(m_layer_height_profile[i + 1]);
|
|
break;
|
|
}
|
|
}
|
|
if (h > 0.0f)
|
|
ret = std::to_string(h);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
void GLCanvas3D::LayersEditing::render_background_texture(const GLCanvas3D& canvas, const Rect& bar_rect)
|
|
{
|
|
if (!m_enabled)
|
|
return;
|
|
|
|
GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height");
|
|
if (shader == nullptr)
|
|
return;
|
|
|
|
shader->start_using();
|
|
|
|
shader->set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * m_object_max_z));
|
|
shader->set_uniform("z_texture_row_to_normalized", 1.0f / (float)m_layers_texture.height);
|
|
shader->set_uniform("z_cursor", m_object_max_z * this->get_cursor_z_relative(canvas));
|
|
shader->set_uniform("z_cursor_band_width", band_width);
|
|
shader->set_uniform("object_max_z", m_object_max_z);
|
|
|
|
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
|
|
glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id));
|
|
|
|
// Render the color bar
|
|
const float l = bar_rect.get_left();
|
|
const float r = bar_rect.get_right();
|
|
const float t = bar_rect.get_top();
|
|
const float b = bar_rect.get_bottom();
|
|
|
|
::glBegin(GL_QUADS);
|
|
::glNormal3f(0.0f, 0.0f, 1.0f);
|
|
::glTexCoord2f(0.0f, 0.0f); ::glVertex2f(l, b);
|
|
::glTexCoord2f(1.0f, 0.0f); ::glVertex2f(r, b);
|
|
::glTexCoord2f(1.0f, 1.0f); ::glVertex2f(r, t);
|
|
::glTexCoord2f(0.0f, 1.0f); ::glVertex2f(l, t);
|
|
glsafe(::glEnd());
|
|
|
|
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
|
|
|
shader->stop_using();
|
|
}
|
|
|
|
void GLCanvas3D::LayersEditing::render_curve(const Rect & bar_rect)
|
|
{
|
|
if (!m_enabled)
|
|
return;
|
|
|
|
//FIXME show some kind of legend.
|
|
if (!m_slicing_parameters)
|
|
return;
|
|
|
|
// Make the vertical bar a bit wider so the layer height curve does not touch the edge of the bar region.
|
|
const float scale_x = bar_rect.get_width() / float(1.12 * m_slicing_parameters->max_layer_height);
|
|
const float scale_y = bar_rect.get_height() / m_object_max_z;
|
|
const float x = bar_rect.get_left() + float(m_slicing_parameters->layer_height) * scale_x;
|
|
|
|
// Baseline
|
|
glsafe(::glColor3f(0.0f, 0.0f, 0.0f));
|
|
::glBegin(GL_LINE_STRIP);
|
|
::glVertex2f(x, bar_rect.get_bottom());
|
|
::glVertex2f(x, bar_rect.get_top());
|
|
glsafe(::glEnd());
|
|
|
|
// Curve
|
|
glsafe(::glColor3f(0.0f, 0.0f, 1.0f));
|
|
::glBegin(GL_LINE_STRIP);
|
|
for (unsigned int i = 0; i < m_layer_height_profile.size(); i += 2)
|
|
::glVertex2f(bar_rect.get_left() + (float)m_layer_height_profile[i + 1] * scale_x, bar_rect.get_bottom() + (float)m_layer_height_profile[i] * scale_y);
|
|
glsafe(::glEnd());
|
|
}
|
|
|
|
void GLCanvas3D::LayersEditing::render_volumes(const GLCanvas3D & canvas, const GLVolumeCollection & volumes)//render volume and layer height texture (has mapping relation with each other)
|
|
{
|
|
assert(this->is_allowed());
|
|
assert(this->last_object_id != -1);
|
|
|
|
GLShaderProgram* current_shader = wxGetApp().get_current_shader();
|
|
ScopeGuard guard([current_shader]() { if (current_shader != nullptr) current_shader->start_using(); });
|
|
if (current_shader != nullptr)
|
|
current_shader->stop_using();
|
|
|
|
GLShaderProgram* shader = wxGetApp().get_shader("variable_layer_height");
|
|
if (shader == nullptr)
|
|
return;
|
|
|
|
shader->start_using();
|
|
|
|
generate_layer_height_texture();
|
|
|
|
// Uniforms were resolved, go ahead using the layer editing shader.
|
|
shader->set_uniform("z_to_texture_row", float(m_layers_texture.cells - 1) / (float(m_layers_texture.width) * float(m_object_max_z)));
|
|
shader->set_uniform("z_texture_row_to_normalized", 1.0f / float(m_layers_texture.height));
|
|
shader->set_uniform("z_cursor", float(m_object_max_z) * float(this->get_cursor_z_relative(canvas)));
|
|
shader->set_uniform("z_cursor_band_width", float(this->band_width));
|
|
|
|
// Initialize the layer height texture mapping.
|
|
const GLsizei w = (GLsizei)m_layers_texture.width;
|
|
const GLsizei h = (GLsizei)m_layers_texture.height;
|
|
const GLsizei half_w = w / 2;
|
|
const GLsizei half_h = h / 2;
|
|
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
|
|
glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id));
|
|
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
|
|
glsafe(::glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, half_w, half_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
|
|
glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data()));
|
|
glsafe(::glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, half_w, half_h, GL_RGBA, GL_UNSIGNED_BYTE, m_layers_texture.data.data() + m_layers_texture.width * m_layers_texture.height * 4));
|
|
for (GLVolume* glvolume : volumes.volumes) {
|
|
// Render the object using the layer editing shader and texture.
|
|
if (!glvolume->is_active || glvolume->composite_id.object_id != this->last_object_id || glvolume->is_modifier)
|
|
continue;
|
|
|
|
shader->set_uniform("volume_world_matrix", glvolume->world_matrix());
|
|
shader->set_uniform("object_max_z", 0.0f);
|
|
|
|
glvolume->render();
|
|
}
|
|
// Revert back to the previous shader.
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
}
|
|
|
|
void GLCanvas3D::LayersEditing::adjust_layer_height_profile()
|
|
{
|
|
this->update_slicing_parameters();
|
|
PrintObject::update_layer_height_profile(*m_model_object, *m_slicing_parameters, m_layer_height_profile);
|
|
Slic3r::adjust_layer_height_profile(*m_slicing_parameters, m_layer_height_profile, this->last_z, this->strength, this->band_width, this->last_action);
|
|
m_layers_texture.valid = false;
|
|
}
|
|
|
|
void GLCanvas3D::LayersEditing::reset_layer_height_profile(GLCanvas3D & canvas)
|
|
{
|
|
const_cast<ModelObject*>(m_model_object)->layer_height_profile.clear();
|
|
m_layer_height_profile.clear();
|
|
m_layers_texture.valid = false;
|
|
canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
|
wxGetApp().obj_list()->update_info_items(last_object_id);
|
|
}
|
|
|
|
void GLCanvas3D::LayersEditing::adaptive_layer_height_profile(GLCanvas3D & canvas, float quality_factor)
|
|
{
|
|
this->update_slicing_parameters();
|
|
m_layer_height_profile = layer_height_profile_adaptive(*m_slicing_parameters, *m_model_object, quality_factor);
|
|
const_cast<ModelObject*>(m_model_object)->layer_height_profile.set(m_layer_height_profile);
|
|
m_layers_texture.valid = false;
|
|
canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
|
wxGetApp().obj_list()->update_info_items(last_object_id);
|
|
}
|
|
|
|
void GLCanvas3D::LayersEditing::smooth_layer_height_profile(GLCanvas3D & canvas, const HeightProfileSmoothingParams & smoothing_params)
|
|
{
|
|
this->update_slicing_parameters();
|
|
m_layer_height_profile = smooth_height_profile(m_layer_height_profile, *m_slicing_parameters, smoothing_params);
|
|
const_cast<ModelObject*>(m_model_object)->layer_height_profile.set(m_layer_height_profile);
|
|
m_layers_texture.valid = false;
|
|
canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
|
wxGetApp().obj_list()->update_info_items(last_object_id);
|
|
}
|
|
|
|
void GLCanvas3D::LayersEditing::generate_layer_height_texture()
|
|
{
|
|
this->update_slicing_parameters();
|
|
// Always try to update the layer height profile.
|
|
bool update = !m_layers_texture.valid;
|
|
if (PrintObject::update_layer_height_profile(*m_model_object, *m_slicing_parameters, m_layer_height_profile)) {
|
|
// Initialized to the default value.
|
|
update = true;
|
|
}
|
|
// Update if the layer height profile was changed, or when the texture is not valid.
|
|
if (!update && !m_layers_texture.data.empty() && m_layers_texture.cells > 0)
|
|
// Texture is valid, don't update.
|
|
return;
|
|
|
|
if (m_layers_texture.data.empty()) {
|
|
m_layers_texture.width = 1024;
|
|
m_layers_texture.height = 1024;
|
|
m_layers_texture.levels = 2;
|
|
m_layers_texture.data.assign(m_layers_texture.width * m_layers_texture.height * 5, 0);
|
|
}
|
|
|
|
bool level_of_detail_2nd_level = true;
|
|
m_layers_texture.cells = Slic3r::generate_layer_height_texture(
|
|
*m_slicing_parameters,
|
|
Slic3r::generate_object_layers(*m_slicing_parameters, m_layer_height_profile, false),
|
|
m_layers_texture.data.data(), m_layers_texture.height, m_layers_texture.width, level_of_detail_2nd_level);
|
|
m_layers_texture.valid = true;
|
|
}
|
|
|
|
void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D & canvas)
|
|
{
|
|
if (last_object_id >= 0) {
|
|
wxGetApp().plater()->take_snapshot("Variable layer height - Manual edit");
|
|
const_cast<ModelObject*>(m_model_object)->layer_height_profile.set(m_layer_height_profile);
|
|
canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
|
wxGetApp().obj_list()->update_info_items(last_object_id);
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::LayersEditing::update_slicing_parameters()
|
|
{
|
|
if (m_slicing_parameters == nullptr) {
|
|
m_slicing_parameters = new SlicingParameters();
|
|
*m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z);
|
|
}
|
|
}
|
|
|
|
float GLCanvas3D::LayersEditing::thickness_bar_width(const GLCanvas3D & canvas)
|
|
{
|
|
return
|
|
#if ENABLE_RETINA_GL
|
|
canvas.get_canvas_size().get_scale_factor()
|
|
#else
|
|
canvas.get_wxglcanvas()->GetContentScaleFactor()
|
|
#endif
|
|
* THICKNESS_BAR_WIDTH;
|
|
}
|
|
|
|
Size::Size()
|
|
: m_width(0)
|
|
, m_height(0)
|
|
{
|
|
}
|
|
|
|
Size::Size(int width, int height, float scale_factor)
|
|
: m_width(width)
|
|
, m_height(height)
|
|
, m_scale_factor(scale_factor)
|
|
{
|
|
}
|
|
|
|
int Size::get_width() const
|
|
{
|
|
return m_width;
|
|
}
|
|
|
|
void Size::set_width(int width)
|
|
{
|
|
m_width = width;
|
|
}
|
|
|
|
int Size::get_height() const
|
|
{
|
|
return m_height;
|
|
}
|
|
|
|
void Size::set_height(int height)
|
|
{
|
|
m_height = height;
|
|
}
|
|
|
|
int Size::get_scale_factor() const
|
|
{
|
|
return m_scale_factor;
|
|
}
|
|
|
|
void Size::set_scale_factor(int scale_factor)
|
|
{
|
|
m_scale_factor = scale_factor;
|
|
}
|
|
|
|
const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX);
|
|
const Vec3d GLCanvas3D::Mouse::Drag::Invalid_3D_Point(DBL_MAX, DBL_MAX, DBL_MAX);
|
|
const int GLCanvas3D::Mouse::Drag::MoveThresholdPx = 5;
|
|
|
|
GLCanvas3D::Mouse::Drag::Drag()
|
|
: start_position_2D(Invalid_2D_Point)
|
|
, start_position_3D(Invalid_3D_Point)
|
|
, move_volume_idx(-1)
|
|
, move_requires_threshold(false)
|
|
, move_start_threshold_position_2D(Invalid_2D_Point)
|
|
{
|
|
}
|
|
|
|
GLCanvas3D::Mouse::Mouse()
|
|
: dragging(false)
|
|
, position(DBL_MAX, DBL_MAX)
|
|
, scene_position(DBL_MAX, DBL_MAX, DBL_MAX)
|
|
, ignore_left_up(false)
|
|
, ignore_right_up(false)
|
|
{
|
|
}
|
|
|
|
void GLCanvas3D::Labels::render(const std::vector<const ModelInstance*>& sorted_instances) const
|
|
{
|
|
if (!m_enabled || !is_shown() || m_canvas.get_gizmos_manager().is_running())
|
|
return;
|
|
|
|
const Camera& camera = wxGetApp().plater()->get_camera();
|
|
const Model* model = m_canvas.get_model();
|
|
if (model == nullptr)
|
|
return;
|
|
|
|
Transform3d world_to_eye = camera.get_view_matrix();
|
|
Transform3d world_to_screen = camera.get_projection_matrix() * world_to_eye;
|
|
const std::array<int, 4>& viewport = camera.get_viewport();
|
|
|
|
struct Owner
|
|
{
|
|
int obj_idx;
|
|
int inst_idx;
|
|
size_t model_instance_id;
|
|
BoundingBoxf3 world_box;
|
|
double eye_center_z;
|
|
std::string title;
|
|
std::string label;
|
|
std::string print_order;
|
|
bool selected;
|
|
};
|
|
|
|
// collect owners world bounding boxes and data from volumes
|
|
std::vector<Owner> owners;
|
|
const GLVolumeCollection& volumes = m_canvas.get_volumes();
|
|
PartPlate* cur_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
|
for (const GLVolume* volume : volumes.volumes) {
|
|
int obj_idx = volume->object_idx();
|
|
if (0 <= obj_idx && obj_idx < (int)model->objects.size()) {
|
|
int inst_idx = volume->instance_idx();
|
|
//only show current plate's label
|
|
if (!cur_plate->contain_instance(obj_idx, inst_idx))
|
|
continue;
|
|
std::vector<Owner>::iterator it = std::find_if(owners.begin(), owners.end(), [obj_idx, inst_idx](const Owner& owner) {
|
|
return (owner.obj_idx == obj_idx) && (owner.inst_idx == inst_idx);
|
|
});
|
|
if (it != owners.end()) {
|
|
it->world_box.merge(volume->transformed_bounding_box());
|
|
it->selected &= volume->selected;
|
|
} else {
|
|
const ModelObject* model_object = model->objects[obj_idx];
|
|
Owner owner;
|
|
owner.obj_idx = obj_idx;
|
|
owner.inst_idx = inst_idx;
|
|
owner.model_instance_id = model_object->instances[inst_idx]->id().id;
|
|
owner.world_box = volume->transformed_bounding_box();
|
|
owner.title = "object" + std::to_string(obj_idx) + "_inst##" + std::to_string(inst_idx);
|
|
owner.label = model_object->name;
|
|
if (model_object->instances.size() > 1)
|
|
owner.label += " (" + std::to_string(inst_idx + 1) + ")";
|
|
owner.selected = volume->selected;
|
|
owners.emplace_back(owner);
|
|
}
|
|
}
|
|
}
|
|
|
|
// updates print order strings
|
|
if (sorted_instances.size() > 0) {
|
|
for (size_t i = 0; i < sorted_instances.size(); ++i) {
|
|
size_t id = sorted_instances[i]->id().id;
|
|
std::vector<Owner>::iterator it = std::find_if(owners.begin(), owners.end(), [id](const Owner& owner) {
|
|
return owner.model_instance_id == id;
|
|
});
|
|
if (it != owners.end())
|
|
//it->print_order = std::string((_(L("Sequence"))).ToUTF8()) + "#: " + std::to_string(i + 1);
|
|
it->print_order = std::string((_(L("Sequence"))).ToUTF8()) + "#: " + std::to_string(sorted_instances[i]->arrange_order);
|
|
}
|
|
}
|
|
|
|
// calculate eye bounding boxes center zs
|
|
for (Owner& owner : owners) {
|
|
owner.eye_center_z = (world_to_eye * owner.world_box.center())(2);
|
|
}
|
|
|
|
// sort owners by center eye zs and selection
|
|
std::sort(owners.begin(), owners.end(), [](const Owner& owner1, const Owner& owner2) {
|
|
if (!owner1.selected && owner2.selected)
|
|
return true;
|
|
else if (owner1.selected && !owner2.selected)
|
|
return false;
|
|
else
|
|
return (owner1.eye_center_z < owner2.eye_center_z);
|
|
});
|
|
|
|
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
|
|
|
// render info windows
|
|
for (const Owner& owner : owners) {
|
|
Vec3d screen_box_center = world_to_screen * owner.world_box.center();
|
|
float x = 0.0f;
|
|
float y = 0.0f;
|
|
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 {
|
|
x = (0.5f + 0.5f * (float)screen_box_center(0)) * viewport[2];
|
|
y = (0.5f - 0.5f * (float)screen_box_center(1)) * viewport[3];
|
|
}
|
|
|
|
if (x < 0.0f || viewport[2] < x || y < 0.0f || viewport[3] < y)
|
|
continue;
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, owner.selected ? 3.0f : 1.5f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
|
ImGui::PushStyleColor(ImGuiCol_Border, owner.selected ? ImVec4(0.757f, 0.404f, 0.216f, 1.0f) : ImVec4(0.75f, 0.75f, 0.75f, 1.0f));
|
|
imgui.set_next_window_pos(x, y, ImGuiCond_Always, 0.5f, 0.5f);
|
|
imgui.begin(owner.title, ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove);
|
|
ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow());
|
|
float win_w = ImGui::GetWindowWidth();
|
|
ImGui::AlignTextToFramePadding();
|
|
imgui.text(owner.label);
|
|
|
|
if (!owner.print_order.empty()) {
|
|
ImGui::Separator();
|
|
float po_len = imgui.calc_text_size(owner.print_order).x;
|
|
ImGui::SetCursorPosX(0.5f * (win_w - po_len));
|
|
ImGui::AlignTextToFramePadding();
|
|
imgui.text(owner.print_order);
|
|
}
|
|
|
|
// 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::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x)
|
|
imgui.set_requires_extra_frame();
|
|
|
|
imgui.end();
|
|
ImGui::PopStyleColor();
|
|
ImGui::PopStyleVar(2);
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::Tooltip::set_text(const std::string& text)
|
|
{
|
|
// If the mouse is inside an ImGUI dialog, then the tooltip is suppressed.
|
|
m_text = m_in_imgui ? std::string() : text;
|
|
}
|
|
|
|
void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas)
|
|
{
|
|
static ImVec2 size(0.0f, 0.0f);
|
|
|
|
auto validate_position = [](const Vec2d& position, const GLCanvas3D& canvas, const ImVec2& wnd_size) {
|
|
const Size cnv_size = canvas.get_canvas_size();
|
|
const float x = std::clamp((float)position.x(), 0.0f, (float)cnv_size.get_width() - wnd_size.x);
|
|
const float y = std::clamp((float)position.y() + 16.0f, 0.0f, (float)cnv_size.get_height() - wnd_size.y);
|
|
return Vec2f(x, y);
|
|
};
|
|
|
|
if (m_text.empty()) {
|
|
m_start_time = std::chrono::steady_clock::now();
|
|
return;
|
|
}
|
|
|
|
// draw the tooltip as hidden until the delay is expired
|
|
// use a value of alpha slightly different from 0.0f because newer imgui does not calculate properly the window size if alpha == 0.0f
|
|
const float alpha = (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - m_start_time).count() < 500) ? 0.01f : 1.0f;
|
|
|
|
const Vec2f position = validate_position(mouse_position, canvas, size);
|
|
|
|
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, alpha);
|
|
imgui.set_next_window_pos(position.x(), position.y(), ImGuiCond_Always, 0.0f, 0.0f);
|
|
|
|
imgui.begin(wxString("canvas_tooltip"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMouseInputs | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing);
|
|
ImGui::BringWindowToDisplayFront(ImGui::GetCurrentWindow());
|
|
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 ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
|
if (alpha < 1.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x)
|
|
imgui.set_requires_extra_frame();
|
|
#else
|
|
if (alpha < 1.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x)
|
|
canvas.request_extra_frame();
|
|
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
|
|
|
size = ImGui::GetWindowSize();
|
|
|
|
imgui.end();
|
|
ImGui::PopStyleVar(2);
|
|
}
|
|
|
|
//BBS: add height limit logic
|
|
void GLCanvas3D::SequentialPrintClearance::set_polygons(const Polygons& polygons, const std::vector<std::pair<Polygon, float>>& height_polygons)
|
|
{
|
|
//BBS: add height limit logic
|
|
m_height_limit.reset();
|
|
m_perimeter.reset();
|
|
m_fill.reset();
|
|
if (!polygons.empty()) {
|
|
size_t triangles_count = 0;
|
|
for (const Polygon &poly : polygons) { triangles_count += poly.points.size() - 2; }
|
|
const size_t vertices_count = 3 * triangles_count;
|
|
|
|
if (m_render_fill) {
|
|
GLModel::InitializationData fill_data;
|
|
GLModel::InitializationData::Entity entity;
|
|
entity.type = GLModel::PrimitiveType::Triangles;
|
|
entity.color = {0.8f, 0.8f, 1.0f, 0.5f};
|
|
entity.positions.reserve(vertices_count);
|
|
entity.normals.reserve(vertices_count);
|
|
entity.indices.reserve(vertices_count);
|
|
|
|
const ExPolygons polygons_union = union_ex(polygons);
|
|
for (const ExPolygon &poly : polygons_union) {
|
|
const std::vector<Vec3d> triangulation = triangulate_expolygon_3d(poly);
|
|
for (const Vec3d &v : triangulation) {
|
|
entity.positions.emplace_back(v.cast<float>() + Vec3f(0.0f, 0.0f, 0.0125f)); // add a small positive z to avoid z-fighting
|
|
entity.normals.emplace_back(Vec3f::UnitZ());
|
|
const size_t positions_count = entity.positions.size();
|
|
if (positions_count % 3 == 0) {
|
|
entity.indices.emplace_back(positions_count - 3);
|
|
entity.indices.emplace_back(positions_count - 2);
|
|
entity.indices.emplace_back(positions_count - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
fill_data.entities.emplace_back(entity);
|
|
m_fill.init_from(fill_data);
|
|
}
|
|
|
|
GLModel::InitializationData perimeter_data;
|
|
for (const Polygon &poly : polygons) {
|
|
GLModel::InitializationData::Entity ent;
|
|
ent.type = GLModel::PrimitiveType::LineLoop;
|
|
ent.positions.reserve(poly.points.size());
|
|
ent.indices.reserve(poly.points.size());
|
|
unsigned int id_count = 0;
|
|
for (const Point &p : poly.points) {
|
|
ent.positions.emplace_back(unscale<float>(p.x()), unscale<float>(p.y()), 0.025f); // add a small positive z to avoid z-fighting
|
|
ent.normals.emplace_back(Vec3f::UnitZ());
|
|
ent.indices.emplace_back(id_count++);
|
|
}
|
|
|
|
perimeter_data.entities.emplace_back(ent);
|
|
}
|
|
|
|
m_perimeter.init_from(perimeter_data);
|
|
}
|
|
|
|
//BBS: add the height limit compute logic
|
|
if (!height_polygons.empty()) {
|
|
size_t height_triangles_count = 0;
|
|
for (const auto &poly : height_polygons) { height_triangles_count += poly.first.points.size() - 2; }
|
|
const size_t height_vertices_count = 3 * height_triangles_count;
|
|
|
|
GLModel::InitializationData height_fill_data;
|
|
GLModel::InitializationData::Entity height_entity;
|
|
height_entity.type = GLModel::PrimitiveType::Triangles;
|
|
height_entity.color = {0.8f, 0.8f, 1.0f, 0.5f};
|
|
height_entity.positions.reserve(height_vertices_count);
|
|
height_entity.normals.reserve(height_vertices_count);
|
|
height_entity.indices.reserve(height_vertices_count);
|
|
|
|
for (const auto &poly : height_polygons) {
|
|
ExPolygon ex_poly(poly.first);
|
|
const std::vector<Vec3d> height_triangulation = triangulate_expolygon_3d(ex_poly);
|
|
for (const Vec3d &v : height_triangulation) {
|
|
Vec3d point{v.x(), v.y(), poly.second};
|
|
height_entity.positions.emplace_back(point.cast<float>());
|
|
height_entity.normals.emplace_back(Vec3f::UnitZ());
|
|
const size_t height_positions_count = height_entity.positions.size();
|
|
if (height_positions_count % 3 == 0) {
|
|
height_entity.indices.emplace_back(height_positions_count - 3);
|
|
height_entity.indices.emplace_back(height_positions_count - 2);
|
|
height_entity.indices.emplace_back(height_positions_count - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
height_fill_data.entities.emplace_back(height_entity);
|
|
m_height_limit.init_from(height_fill_data);
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::SequentialPrintClearance::render()
|
|
{
|
|
std::array<float, 4> FILL_COLOR = { 0.7f, 0.7f, 1.0f, 0.5f };
|
|
std::array<float, 4> NO_FILL_COLOR = { 0.75f, 0.75f, 0.75f, 0.75f };
|
|
|
|
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
|
|
if (shader == nullptr)
|
|
return;
|
|
|
|
shader->start_using();
|
|
|
|
glsafe(::glEnable(GL_DEPTH_TEST));
|
|
glsafe(::glDisable(GL_CULL_FACE));
|
|
glsafe(::glEnable(GL_BLEND));
|
|
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
|
|
|
|
m_perimeter.set_color(-1, m_render_fill ? FILL_COLOR : NO_FILL_COLOR);
|
|
m_perimeter.render();
|
|
m_fill.render();
|
|
//BBS: add height limit
|
|
m_height_limit.set_color(-1, m_render_fill ? FILL_COLOR : NO_FILL_COLOR);
|
|
m_height_limit.render();
|
|
|
|
glsafe(::glDisable(GL_BLEND));
|
|
glsafe(::glEnable(GL_CULL_FACE));
|
|
glsafe(::glDisable(GL_DEPTH_TEST));
|
|
|
|
shader->stop_using();
|
|
}
|
|
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_PLATE_NAME_CHANGE, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_PLATE_SELECT, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_PLATE_RIGHT_CLICK, RBtnPlateEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_REMOVE_OBJECT, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE, SimpleEvent);
|
|
//BBS: add arrange and orient event
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_ARRANGE_PARTPLATE, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_ORIENT, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_ORIENT_PARTPLATE, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_CURR_PLATE_ALL, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_SELECT_ALL, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_QUESTION_MARK, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_FORCE_UPDATE, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event<bool>);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_STARTED, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_SLIDERS, wxKeyEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_JUMP_TO, wxKeyEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_SWITCH_TO_OBJECT, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_SWITCH_TO_GLOBAL, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_COLLAPSE_SIDEBAR, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_RELOAD_FROM_DISK, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_RENDER_TIMER, wxTimerEvent/*RenderTimerEvent*/);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, wxTimerEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, wxTimerEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_CUSTOMEVT_TICKSCHANGED, wxCommandEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event<float>);
|
|
wxDEFINE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent);
|
|
|
|
const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25;
|
|
const double GLCanvas3D::DefaultCameraZoomToBedMarginFactor = 2.00;
|
|
const double GLCanvas3D::DefaultCameraZoomToPlateMarginFactor = 1.25;
|
|
|
|
void GLCanvas3D::load_arrange_settings()
|
|
{
|
|
std::string dist_fff_str =
|
|
wxGetApp().app_config->get("arrange", "min_object_distance");
|
|
|
|
std::string dist_fff_seq_print_str =
|
|
wxGetApp().app_config->get("arrange", "min_object_distance_seq_print");
|
|
|
|
std::string dist_sla_str =
|
|
wxGetApp().app_config->get("arrange", "min_object_distance_sla");
|
|
|
|
std::string en_rot_fff_str =
|
|
wxGetApp().app_config->get("arrange", "enable_rotation");
|
|
|
|
std::string en_rot_fff_seqp_str =
|
|
wxGetApp().app_config->get("arrange", "enable_rotation_seq_print");
|
|
|
|
std::string en_rot_sla_str =
|
|
wxGetApp().app_config->get("arrange", "enable_rotation_sla");
|
|
|
|
if (!dist_fff_str.empty())
|
|
m_arrange_settings_fff.distance = std::stof(dist_fff_str);
|
|
|
|
if (!dist_fff_seq_print_str.empty())
|
|
m_arrange_settings_fff_seq_print.distance = std::stof(dist_fff_seq_print_str);
|
|
|
|
if (!dist_sla_str.empty())
|
|
m_arrange_settings_sla.distance = std::stof(dist_sla_str);
|
|
|
|
if (!en_rot_fff_str.empty())
|
|
m_arrange_settings_fff.enable_rotation = (en_rot_fff_str == "1" || en_rot_fff_str == "yes");
|
|
|
|
if (!en_rot_fff_seqp_str.empty())
|
|
m_arrange_settings_fff_seq_print.enable_rotation = (en_rot_fff_seqp_str == "1" || en_rot_fff_seqp_str == "yes");
|
|
|
|
if (!en_rot_sla_str.empty())
|
|
m_arrange_settings_sla.enable_rotation = (en_rot_sla_str == "1" || en_rot_sla_str == "yes");
|
|
|
|
//BBS: add specific arrange settings
|
|
m_arrange_settings_fff_seq_print.is_seq_print = true;
|
|
}
|
|
|
|
GLCanvas3D::ArrangeSettings& GLCanvas3D::get_arrange_settings()
|
|
{
|
|
PrinterTechnology ptech = current_printer_technology();
|
|
|
|
auto* ptr = &m_arrange_settings_fff;
|
|
|
|
if (ptech == ptSLA) {
|
|
ptr = &m_arrange_settings_sla;
|
|
}
|
|
else if (ptech == ptFFF) {
|
|
if (wxGetApp().global_print_sequence() == PrintSequence::ByObject)
|
|
ptr = &m_arrange_settings_fff_seq_print;
|
|
else
|
|
ptr = &m_arrange_settings_fff;
|
|
}
|
|
|
|
return *ptr;
|
|
}
|
|
|
|
int GLCanvas3D::GetHoverId()
|
|
{
|
|
if (m_hover_plate_idxs.size() == 0) {
|
|
return -1; }
|
|
return m_hover_plate_idxs.front();
|
|
|
|
}
|
|
|
|
PrinterTechnology GLCanvas3D::current_printer_technology() const {
|
|
return m_process->current_printer_technology();
|
|
}
|
|
|
|
GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed)
|
|
: m_canvas(canvas)
|
|
, m_context(nullptr)
|
|
, m_bed(bed)
|
|
#if ENABLE_RETINA_GL
|
|
, m_retina_helper(nullptr)
|
|
#endif
|
|
, m_in_render(false)
|
|
, m_main_toolbar(GLToolbar::Normal, "Main")
|
|
, m_separator_toolbar(GLToolbar::Normal, "Separator")
|
|
, m_assemble_view_toolbar(GLToolbar::Normal, "Assembly_View")
|
|
, m_return_toolbar()
|
|
, m_canvas_type(ECanvasType::CanvasView3D)
|
|
, m_gizmos(*this)
|
|
, m_use_clipping_planes(false)
|
|
, m_sidebar_field("")
|
|
, m_extra_frame_requested(false)
|
|
, m_config(nullptr)
|
|
, m_process(nullptr)
|
|
, m_model(nullptr)
|
|
, m_dirty(true)
|
|
, m_initialized(false)
|
|
, m_apply_zoom_to_volumes_filter(false)
|
|
, m_picking_enabled(false)
|
|
, m_moving_enabled(false)
|
|
, m_dynamic_background_enabled(false)
|
|
, m_multisample_allowed(false)
|
|
, m_moving(false)
|
|
, m_tab_down(false)
|
|
, m_cursor_type(Standard)
|
|
, m_color_by("volume")
|
|
, m_reload_delayed(false)
|
|
#if ENABLE_RENDER_PICKING_PASS
|
|
, m_show_picking_texture(false)
|
|
#endif // ENABLE_RENDER_PICKING_PASS
|
|
, m_render_sla_auxiliaries(true)
|
|
, m_labels(*this)
|
|
, m_slope(m_volumes)
|
|
{
|
|
if (m_canvas != nullptr) {
|
|
m_timer.SetOwner(m_canvas);
|
|
m_render_timer.SetOwner(m_canvas);
|
|
#if ENABLE_RETINA_GL
|
|
m_retina_helper.reset(new RetinaHelper(canvas));
|
|
#endif // ENABLE_RETINA_GL
|
|
}
|
|
m_timer_set_color.Bind(wxEVT_TIMER, &GLCanvas3D::on_set_color_timer, this);
|
|
load_arrange_settings();
|
|
|
|
m_selection.set_volumes(&m_volumes.volumes);
|
|
|
|
m_assembly_view_desc["object_selection_caption"] = _L("Left mouse button");
|
|
m_assembly_view_desc["object_selection"] = _L("object selection");
|
|
m_assembly_view_desc["part_selection_caption"] = "Alt +" + _L("Left mouse button");
|
|
m_assembly_view_desc["part_selection"] = _L("part selection");
|
|
m_assembly_view_desc["number_key_caption"] = "1~16 " + _L("number keys");
|
|
m_assembly_view_desc["number_key"] = _L("number keys can quickly change the color of objects");
|
|
}
|
|
|
|
GLCanvas3D::~GLCanvas3D()
|
|
{
|
|
reset_volumes();
|
|
|
|
m_sel_plate_toolbar.del_all_item();
|
|
m_sel_plate_toolbar.del_stats_item();
|
|
}
|
|
|
|
void GLCanvas3D::post_event(wxEvent &&event)
|
|
{
|
|
event.SetEventObject(m_canvas);
|
|
wxPostEvent(m_canvas, event);
|
|
}
|
|
|
|
bool GLCanvas3D::init()
|
|
{
|
|
if (m_initialized)
|
|
return true;
|
|
|
|
if (m_canvas == nullptr || m_context == nullptr)
|
|
return false;
|
|
|
|
// init dark mode status
|
|
on_change_color_mode(wxGetApp().app_config->get("dark_color_mode") == "1", false);
|
|
|
|
BOOST_LOG_TRIVIAL(info) <<__FUNCTION__<< " enter";
|
|
glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
|
|
glsafe(::glClearDepth(1.0f));
|
|
|
|
glsafe(::glDepthFunc(GL_LESS));
|
|
|
|
glsafe(::glEnable(GL_DEPTH_TEST));
|
|
glsafe(::glEnable(GL_CULL_FACE));
|
|
glsafe(::glEnable(GL_BLEND));
|
|
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
|
|
|
|
// Set antialiasing / multisampling
|
|
glsafe(::glDisable(GL_LINE_SMOOTH));
|
|
glsafe(::glDisable(GL_POLYGON_SMOOTH));
|
|
|
|
// ambient lighting
|
|
GLfloat ambient[4] = { 0.3f, 0.3f, 0.3f, 1.0f };
|
|
glsafe(::glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient));
|
|
|
|
glsafe(::glEnable(GL_LIGHT0));
|
|
glsafe(::glEnable(GL_LIGHT1));
|
|
|
|
// light from camera
|
|
GLfloat specular_cam[4] = { 0.3f, 0.3f, 0.3f, 1.0f };
|
|
glsafe(::glLightfv(GL_LIGHT1, GL_SPECULAR, specular_cam));
|
|
GLfloat diffuse_cam[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
|
|
glsafe(::glLightfv(GL_LIGHT1, GL_DIFFUSE, diffuse_cam));
|
|
|
|
// light from above
|
|
GLfloat specular_top[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
|
|
glsafe(::glLightfv(GL_LIGHT0, GL_SPECULAR, specular_top));
|
|
GLfloat diffuse_top[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
|
|
glsafe(::glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse_top));
|
|
|
|
// Enables Smooth Color Shading; try GL_FLAT for (lack of) fun.
|
|
glsafe(::glShadeModel(GL_SMOOTH));
|
|
|
|
// A handy trick -- have surface material mirror the color.
|
|
glsafe(::glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE));
|
|
glsafe(::glEnable(GL_COLOR_MATERIAL));
|
|
|
|
if (m_multisample_allowed)
|
|
glsafe(::glEnable(GL_MULTISAMPLE));
|
|
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": before m_layers_editing init";
|
|
if (m_main_toolbar.is_enabled())
|
|
m_layers_editing.init();
|
|
|
|
// on linux the gl context is not valid until the canvas is not shown on screen
|
|
// we defer the geometry finalization of volumes until the first call to render()
|
|
m_volumes.finalize_geometry(true);
|
|
|
|
BOOST_LOG_TRIVIAL(info) <<__FUNCTION__<< ": before gizmo init";
|
|
if (m_gizmos.is_enabled() && !m_gizmos.init())
|
|
std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl;
|
|
|
|
BOOST_LOG_TRIVIAL(info) <<__FUNCTION__<< ": before _init_toolbars";
|
|
if (!_init_toolbars())
|
|
return false;
|
|
|
|
BOOST_LOG_TRIVIAL(info) <<__FUNCTION__<< ": finish _init_toolbars";
|
|
if (m_selection.is_enabled() && !m_selection.init())
|
|
return false;
|
|
|
|
BOOST_LOG_TRIVIAL(info) <<__FUNCTION__<< ": finish m_selection";
|
|
|
|
#if ENABLE_IMGUI_STYLE_EDITOR
|
|
//BBS load render color for style editor
|
|
GLVolume::load_render_colors();
|
|
PartPlate::load_render_colors();
|
|
GLGizmoBase::load_render_colors();
|
|
GLCanvas3D::load_render_colors();
|
|
Bed3D::load_render_colors();
|
|
#endif
|
|
//if (!wxGetApp().is_gl_version_greater_or_equal_to(3, 0))
|
|
// wxGetApp().plater()->enable_wireframe(false);
|
|
m_initialized = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
void GLCanvas3D::on_change_color_mode(bool is_dark, bool reinit) {
|
|
m_is_dark = is_dark;
|
|
// Bed color
|
|
m_bed.on_change_color_mode(is_dark);
|
|
// GcodeViewer color
|
|
m_gcode_viewer.on_change_color_mode(is_dark);
|
|
// ImGui Style
|
|
wxGetApp().imgui()->on_change_color_mode(is_dark);
|
|
// Notification
|
|
wxGetApp().plater()->get_notification_manager()->on_change_color_mode(is_dark);
|
|
// DailyTips Window
|
|
wxGetApp().plater()->get_dailytips()->on_change_color_mode(is_dark);
|
|
// Preview Slider
|
|
IMSlider* m_layers_slider = get_gcode_viewer().get_layers_slider();
|
|
IMSlider* m_moves_slider = get_gcode_viewer().get_moves_slider();
|
|
m_layers_slider->on_change_color_mode(is_dark);
|
|
m_moves_slider->on_change_color_mode(is_dark);
|
|
// Partplate
|
|
wxGetApp().plater()->get_partplate_list().on_change_color_mode(is_dark);
|
|
|
|
// Toolbar
|
|
if (m_canvas_type == CanvasView3D) {
|
|
m_gizmos.on_change_color_mode(is_dark);
|
|
if (reinit) {
|
|
// reset svg
|
|
_switch_toolbars_icon_filename();
|
|
m_gizmos.switch_gizmos_icon_filename();
|
|
// set dirty to re-generate icon texture
|
|
m_separator_toolbar.set_icon_dirty();
|
|
m_main_toolbar.set_icon_dirty();
|
|
wxGetApp().plater()->get_collapse_toolbar().set_icon_dirty();
|
|
m_assemble_view_toolbar.set_icon_dirty();
|
|
m_gizmos.set_icon_dirty();
|
|
}
|
|
}
|
|
if (m_canvas_type == CanvasAssembleView) {
|
|
m_gizmos.on_change_color_mode(is_dark);
|
|
if (reinit) {
|
|
// reset svg
|
|
m_gizmos.switch_gizmos_icon_filename();
|
|
// set dirty to re-generate icon texture
|
|
m_gizmos.set_icon_dirty();
|
|
}
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::set_as_dirty()
|
|
{
|
|
m_dirty = true;
|
|
}
|
|
|
|
const float GLCanvas3D::get_scale() const
|
|
{
|
|
#if ENABLE_RETINA_GL
|
|
return m_retina_helper->get_scale_factor();
|
|
#else
|
|
return 1.0f;
|
|
#endif
|
|
}
|
|
|
|
unsigned int GLCanvas3D::get_volumes_count() const
|
|
{
|
|
return (unsigned int)m_volumes.volumes.size();
|
|
}
|
|
|
|
void GLCanvas3D::reset_volumes()
|
|
{
|
|
if (!m_initialized)
|
|
return;
|
|
|
|
if (m_volumes.empty())
|
|
return;
|
|
|
|
_set_current();
|
|
|
|
m_selection.clear();
|
|
m_volumes.clear();
|
|
m_dirty = true;
|
|
|
|
_set_warning_notification(EWarning::ObjectOutside, false);
|
|
}
|
|
|
|
//BBS: get current plater's bounding box
|
|
BoundingBoxf3 GLCanvas3D::_get_current_partplate_print_volume()
|
|
{
|
|
BoundingBoxf3 test_volume;
|
|
if (m_process && m_config)
|
|
{
|
|
BoundingBoxf3 plate_bb = m_process->get_current_plate()->get_bounding_box(false);
|
|
BoundingBoxf3 print_volume({ plate_bb.min(0), plate_bb.min(1), 0.0 }, { plate_bb.max(0), plate_bb.max(1), m_config->opt_float("printable_height") });
|
|
// Allow the objects to protrude below the print bed
|
|
print_volume.min(2) = -1e10;
|
|
print_volume.min(0) -= Slic3r::BuildVolume::BedEpsilon;
|
|
print_volume.min(1) -= Slic3r::BuildVolume::BedEpsilon;
|
|
print_volume.max(0) += Slic3r::BuildVolume::BedEpsilon;
|
|
print_volume.max(1) += Slic3r::BuildVolume::BedEpsilon;
|
|
test_volume = print_volume;
|
|
}
|
|
else
|
|
test_volume = BoundingBoxf3();
|
|
|
|
return test_volume;
|
|
}
|
|
|
|
ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const
|
|
{
|
|
//BBS: if not initialized, return inside directly insteadof assert
|
|
if (!m_initialized) {
|
|
return ModelInstancePVS_Inside;
|
|
}
|
|
//assert(m_initialized);
|
|
|
|
ModelInstanceEPrintVolumeState state;
|
|
m_volumes.check_outside_state(m_bed.build_volume(), &state);
|
|
return state;
|
|
}
|
|
|
|
void GLCanvas3D::toggle_selected_volume_visibility(bool selected_visible)
|
|
{
|
|
m_render_sla_auxiliaries = !selected_visible;
|
|
if (selected_visible) {
|
|
const Selection::IndicesList &idxs = m_selection.get_volume_idxs();
|
|
if (idxs.size() > 0) {
|
|
for (GLVolume *vol : m_volumes.volumes) {
|
|
if (vol->composite_id.object_id >= 1000 && vol->composite_id.object_id < 1000 + wxGetApp().plater()->get_partplate_list().get_plate_count())
|
|
continue; // the wipe tower
|
|
if (vol->composite_id.volume_id >= 0) {
|
|
vol->is_active = false;
|
|
}
|
|
}
|
|
for (unsigned int idx : idxs) {
|
|
GLVolume *v = const_cast<GLVolume *>(m_selection.get_volume(idx));
|
|
v->is_active = true;
|
|
}
|
|
}
|
|
} else { // show all
|
|
for (GLVolume *vol : m_volumes.volumes) {
|
|
if (vol->composite_id.object_id >= 1000 && vol->composite_id.object_id < 1000 + wxGetApp().plater()->get_partplate_list().get_plate_count())
|
|
continue; // the wipe tower
|
|
if (vol->composite_id.volume_id >= 0) {
|
|
vol->is_active = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObject *mo, int instance_idx)
|
|
{
|
|
m_render_sla_auxiliaries = visible;
|
|
|
|
for (GLVolume* vol : m_volumes.volumes) {
|
|
if (vol->composite_id.object_id >= 1000 &&
|
|
vol->composite_id.object_id < 1000 + wxGetApp().plater()->get_partplate_list().get_plate_count())
|
|
continue; // the wipe tower
|
|
if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
|
|
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)
|
|
&& vol->composite_id.volume_id < 0)
|
|
vol->is_active = visible;
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx, const ModelVolume* mv)
|
|
{
|
|
for (GLVolume* vol : m_volumes.volumes) {
|
|
// BBS: add partplate logic
|
|
if (vol->composite_id.object_id >= 1000 &&
|
|
vol->composite_id.object_id < 1000 + wxGetApp().plater()->get_partplate_list().get_plate_count()) { // wipe tower
|
|
vol->is_active = (visible && mo == nullptr);
|
|
}
|
|
else {
|
|
if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
|
|
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)
|
|
&& (mv == nullptr || m_model->objects[vol->composite_id.object_id]->volumes[vol->composite_id.volume_id] == mv)) {
|
|
vol->is_active = visible;
|
|
|
|
if (instance_idx == -1) {
|
|
vol->force_native_color = false;
|
|
vol->force_neutral_color = false;
|
|
} else {
|
|
const GLGizmosManager& gm = get_gizmos_manager();
|
|
auto gizmo_type = gm.get_current_type();
|
|
if ( (gizmo_type == GLGizmosManager::FdmSupports
|
|
|| gizmo_type == GLGizmosManager::Seam)
|
|
&& ! vol->is_modifier)
|
|
vol->force_neutral_color = true;
|
|
else if (gizmo_type == GLGizmosManager::MmuSegmentation)
|
|
vol->is_active = false;
|
|
else if (gizmo_type == GLGizmosManager::Text) {
|
|
vol->force_native_color = false;
|
|
vol->force_neutral_color = false;
|
|
}
|
|
else
|
|
vol->force_native_color = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (visible && !mo)
|
|
toggle_sla_auxiliaries_visibility(true, mo, instance_idx);
|
|
|
|
if (!mo && !visible && !m_model->objects.empty() && (m_model->objects.size() > 1 || m_model->objects.front()->instances.size() > 1))
|
|
_set_warning_notification(EWarning::SomethingNotShown, true);
|
|
|
|
if (!mo && visible)
|
|
_set_warning_notification(EWarning::SomethingNotShown, false);
|
|
}
|
|
|
|
void GLCanvas3D::update_instance_printable_state_for_object(const size_t obj_idx)
|
|
{
|
|
ModelObject* model_object = m_model->objects[obj_idx];
|
|
for (int inst_idx = 0; inst_idx < (int)model_object->instances.size(); ++inst_idx) {
|
|
ModelInstance* instance = model_object->instances[inst_idx];
|
|
|
|
for (GLVolume* volume : m_volumes.volumes) {
|
|
if ((volume->object_idx() == (int)obj_idx) && (volume->instance_idx() == inst_idx))
|
|
volume->printable = instance->printable;
|
|
if (!volume->printable) {
|
|
volume->render_color = GLVolume::UNPRINTABLE_COLOR;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::update_instance_printable_state_for_objects(const std::vector<size_t>& object_idxs)
|
|
{
|
|
for (size_t obj_idx : object_idxs)
|
|
update_instance_printable_state_for_object(obj_idx);
|
|
}
|
|
|
|
void GLCanvas3D::set_config(const DynamicPrintConfig* config)
|
|
{
|
|
m_config = config;
|
|
m_layers_editing.set_config(config);
|
|
}
|
|
|
|
void GLCanvas3D::set_process(BackgroundSlicingProcess *process)
|
|
{
|
|
m_process = process;
|
|
}
|
|
|
|
void GLCanvas3D::set_model(Model* model)
|
|
{
|
|
m_model = model;
|
|
m_selection.set_model(m_model);
|
|
}
|
|
|
|
void GLCanvas3D::bed_shape_changed()
|
|
{
|
|
refresh_camera_scene_box();
|
|
wxGetApp().plater()->get_camera().requires_zoom_to_bed = true;
|
|
m_dirty = true;
|
|
}
|
|
|
|
void GLCanvas3D::plates_count_changed()
|
|
{
|
|
refresh_camera_scene_box();
|
|
m_dirty = true;
|
|
}
|
|
|
|
Camera& GLCanvas3D::get_camera()
|
|
{
|
|
return camera;
|
|
}
|
|
|
|
void GLCanvas3D::set_use_clipping_planes(bool use)
|
|
{
|
|
if (m_gizmos.get_current_type() == GLGizmosManager::EType::Text) {
|
|
m_use_clipping_planes = false;
|
|
}
|
|
else{
|
|
m_use_clipping_planes = use;
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::set_color_by(const std::string &value) {
|
|
m_color_by = value;
|
|
}
|
|
|
|
void GLCanvas3D::refresh_camera_scene_box()
|
|
{
|
|
wxGetApp().plater()->get_camera().set_scene_box(scene_bounding_box());
|
|
}
|
|
|
|
BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const
|
|
{
|
|
BoundingBoxf3 bb;
|
|
for (const GLVolume* volume : m_volumes.volumes) {
|
|
if (!m_apply_zoom_to_volumes_filter || ((volume != nullptr) && volume->zoom_to_volumes))
|
|
bb.merge(volume->transformed_bounding_box());
|
|
}
|
|
return bb;
|
|
}
|
|
|
|
BoundingBoxf3 GLCanvas3D::scene_bounding_box() const
|
|
{
|
|
BoundingBoxf3 bb = volumes_bounding_box();
|
|
bb.merge(m_bed.extended_bounding_box());
|
|
double h = m_bed.build_volume().printable_height();
|
|
//FIXME why -h?
|
|
bb.min.z() = std::min(bb.min.z(), -h);
|
|
bb.max.z() = std::max(bb.max.z(), h);
|
|
|
|
//BBS merge plate scene bounding box
|
|
if (m_canvas_type == ECanvasType::CanvasView3D) {
|
|
PartPlateList& plate = wxGetApp().plater()->get_partplate_list();
|
|
bb.merge(plate.get_bounding_box());
|
|
}
|
|
|
|
return bb;
|
|
}
|
|
|
|
BoundingBoxf3 GLCanvas3D::plate_scene_bounding_box(int plate_idx) const
|
|
{
|
|
PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_plate(plate_idx);
|
|
|
|
BoundingBoxf3 bb = plate->get_bounding_box(true);
|
|
if (m_config != nullptr) {
|
|
double h = m_config->opt_float("printable_height");
|
|
bb.min(2) = std::min(bb.min(2), -h);
|
|
bb.max(2) = std::max(bb.max(2), h);
|
|
}
|
|
|
|
return bb;
|
|
}
|
|
|
|
bool GLCanvas3D::is_layers_editing_enabled() const
|
|
{
|
|
return m_layers_editing.is_enabled();
|
|
}
|
|
|
|
bool GLCanvas3D::is_layers_editing_allowed() const
|
|
{
|
|
return m_layers_editing.is_allowed();
|
|
}
|
|
|
|
void GLCanvas3D::reset_layer_height_profile()
|
|
{
|
|
wxGetApp().plater()->take_snapshot("Variable layer height - Reset");
|
|
m_layers_editing.reset_layer_height_profile(*this);
|
|
m_layers_editing.state = LayersEditing::Completed;
|
|
m_dirty = true;
|
|
}
|
|
|
|
void GLCanvas3D::adaptive_layer_height_profile(float quality_factor)
|
|
{
|
|
wxGetApp().plater()->take_snapshot("Variable layer height - Adaptive");
|
|
m_layers_editing.adaptive_layer_height_profile(*this, quality_factor);
|
|
m_layers_editing.state = LayersEditing::Completed;
|
|
m_dirty = true;
|
|
}
|
|
|
|
void GLCanvas3D::smooth_layer_height_profile(const HeightProfileSmoothingParams& smoothing_params)
|
|
{
|
|
wxGetApp().plater()->take_snapshot("Variable layer height - Smooth all");
|
|
m_layers_editing.smooth_layer_height_profile(*this, smoothing_params);
|
|
m_layers_editing.state = LayersEditing::Completed;
|
|
m_dirty = true;
|
|
}
|
|
|
|
bool GLCanvas3D::is_reload_delayed() const
|
|
{
|
|
return m_reload_delayed;
|
|
}
|
|
|
|
void GLCanvas3D::enable_layers_editing(bool enable)
|
|
{
|
|
m_layers_editing.set_enabled(enable);
|
|
set_as_dirty();
|
|
}
|
|
|
|
void GLCanvas3D::enable_legend_texture(bool enable)
|
|
{
|
|
m_gcode_viewer.enable_legend(enable);
|
|
}
|
|
|
|
void GLCanvas3D::enable_picking(bool enable)
|
|
{
|
|
m_picking_enabled = enable;
|
|
m_selection.set_mode(Selection::Instance);
|
|
}
|
|
|
|
void GLCanvas3D::enable_moving(bool enable)
|
|
{
|
|
m_moving_enabled = enable;
|
|
}
|
|
|
|
void GLCanvas3D::enable_gizmos(bool enable)
|
|
{
|
|
m_gizmos.set_enabled(enable);
|
|
}
|
|
|
|
void GLCanvas3D::enable_selection(bool enable)
|
|
{
|
|
m_selection.set_enabled(enable);
|
|
}
|
|
|
|
void GLCanvas3D::enable_main_toolbar(bool enable)
|
|
{
|
|
m_main_toolbar.set_enabled(enable);
|
|
}
|
|
|
|
void GLCanvas3D::reset_select_plate_toolbar_selection() {
|
|
if (m_sel_plate_toolbar.m_all_plates_stats_item)
|
|
m_sel_plate_toolbar.m_all_plates_stats_item->selected = false;
|
|
if (wxGetApp().mainframe)
|
|
wxGetApp().mainframe->update_slice_print_status(MainFrame::eEventSliceUpdate, true, true);
|
|
}
|
|
|
|
void GLCanvas3D::enable_select_plate_toolbar(bool enable)
|
|
{
|
|
m_sel_plate_toolbar.set_enabled(enable);
|
|
}
|
|
|
|
void GLCanvas3D::clear_select_plate_toolbar_render_flag()
|
|
{
|
|
m_sel_plate_toolbar.is_render_finish = false;
|
|
}
|
|
|
|
void GLCanvas3D::enable_assemble_view_toolbar(bool enable)
|
|
{
|
|
m_assemble_view_toolbar.set_enabled(enable);
|
|
}
|
|
|
|
void GLCanvas3D::enable_return_toolbar(bool enable)
|
|
{
|
|
m_return_toolbar.set_enabled(enable);
|
|
}
|
|
|
|
void GLCanvas3D::enable_separator_toolbar(bool enable)
|
|
{
|
|
m_separator_toolbar.set_enabled(enable);
|
|
}
|
|
|
|
void GLCanvas3D::enable_dynamic_background(bool enable)
|
|
{
|
|
m_dynamic_background_enabled = enable;
|
|
}
|
|
|
|
void GLCanvas3D::allow_multisample(bool allow)
|
|
{
|
|
m_multisample_allowed = allow;
|
|
}
|
|
|
|
void GLCanvas3D::zoom_to_bed()
|
|
{
|
|
BoundingBoxf3 box = m_bed.build_volume().bounding_volume();
|
|
box.min.z() = 0.0;
|
|
box.max.z() = 0.0;
|
|
_zoom_to_box(box, DefaultCameraZoomToBedMarginFactor);
|
|
}
|
|
|
|
void GLCanvas3D::zoom_to_volumes()
|
|
{
|
|
m_apply_zoom_to_volumes_filter = true;
|
|
_zoom_to_box(volumes_bounding_box());
|
|
m_apply_zoom_to_volumes_filter = false;
|
|
}
|
|
|
|
void GLCanvas3D::zoom_to_selection()
|
|
{
|
|
if (!m_selection.is_empty())
|
|
_zoom_to_box(m_selection.get_bounding_box());
|
|
}
|
|
|
|
void GLCanvas3D::zoom_to_gcode()
|
|
{
|
|
_zoom_to_box(m_gcode_viewer.get_paths_bounding_box(), 1.05);
|
|
}
|
|
|
|
void GLCanvas3D::zoom_to_plate(int plate_idx)
|
|
{
|
|
BoundingBoxf3 box;
|
|
if (plate_idx == REQUIRES_ZOOM_TO_ALL_PLATE) {
|
|
box = wxGetApp().plater()->get_partplate_list().get_bounding_box();
|
|
box.min.z() = 0.0;
|
|
box.max.z() = 0.0;
|
|
_zoom_to_box(box, DefaultCameraZoomToPlateMarginFactor);
|
|
} else {
|
|
PartPlate* plate = nullptr;
|
|
if (plate_idx == REQUIRES_ZOOM_TO_CUR_PLATE) {
|
|
plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
|
}else {
|
|
assert(plate_idx >= 0 && plate_idx < wxGetApp().plater()->get_partplate_list().get_plate_count());
|
|
plate = wxGetApp().plater()->get_partplate_list().get_plate(plate_idx);
|
|
}
|
|
box = plate->get_bounding_box(true);
|
|
box.min.z() = 0.0;
|
|
box.max.z() = 0.0;
|
|
_zoom_to_box(box, DefaultCameraZoomToPlateMarginFactor);
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::select_view(const std::string& direction)
|
|
{
|
|
wxGetApp().plater()->get_camera().select_view(direction);
|
|
if (m_canvas != nullptr)
|
|
m_canvas->Refresh();
|
|
}
|
|
|
|
void GLCanvas3D::select_plate()
|
|
{
|
|
wxGetApp().plater()->get_partplate_list().select_plate_view();
|
|
if (m_canvas != nullptr)
|
|
m_canvas->Refresh();
|
|
}
|
|
|
|
void GLCanvas3D::update_volumes_colors_by_extruder()
|
|
{
|
|
if (m_config != nullptr)
|
|
m_volumes.update_colors_by_extruder(m_config);
|
|
}
|
|
|
|
float GLCanvas3D::get_collapse_toolbar_width()
|
|
{
|
|
GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();
|
|
|
|
return collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0;
|
|
}
|
|
|
|
float GLCanvas3D::get_collapse_toolbar_height()
|
|
{
|
|
GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();
|
|
|
|
return collapse_toolbar.is_enabled() ? collapse_toolbar.get_height() : 0;
|
|
}
|
|
|
|
bool GLCanvas3D::make_current_for_postinit() {
|
|
return _set_current();
|
|
}
|
|
|
|
void GLCanvas3D::render(bool only_init)
|
|
{
|
|
if (m_in_render) {
|
|
// if called recursively, return
|
|
m_dirty = true;
|
|
return;
|
|
}
|
|
|
|
m_in_render = true;
|
|
Slic3r::ScopeGuard in_render_guard([this]() { m_in_render = false; });
|
|
(void)in_render_guard;
|
|
|
|
if (m_canvas == nullptr)
|
|
return;
|
|
|
|
//BBS: add enable_render
|
|
if (!m_enable_render)
|
|
return;
|
|
|
|
// ensures this canvas is current and initialized
|
|
if (!_is_shown_on_screen() || !_set_current() || !wxGetApp().init_opengl())
|
|
return;
|
|
|
|
if (!is_initialized() && !init())
|
|
return;
|
|
if (m_canvas_type == ECanvasType::CanvasView3D && m_gizmos.get_current_type() == GLGizmosManager::Undefined) {
|
|
enable_return_toolbar(false);
|
|
}
|
|
if (!m_main_toolbar.is_enabled())
|
|
m_gcode_viewer.init(wxGetApp().get_mode(), wxGetApp().preset_bundle);
|
|
|
|
if (! m_bed.build_volume().valid()) {
|
|
// this happens at startup when no data is still saved under <>\AppData\Roaming\Slic3rPE
|
|
post_event(SimpleEvent(EVT_GLCANVAS_UPDATE_BED_SHAPE));
|
|
return;
|
|
}
|
|
|
|
if (only_init)
|
|
return;
|
|
|
|
#if ENABLE_ENVIRONMENT_MAP
|
|
if (wxGetApp().is_editor())
|
|
wxGetApp().plater()->init_environment_texture();
|
|
#endif // ENABLE_ENVIRONMENT_MAP
|
|
|
|
const Size& cnv_size = get_canvas_size();
|
|
// Probably due to different order of events on Linux/GTK2, when one switched from 3D scene
|
|
// to preview, this was called before canvas had its final size. It reported zero width
|
|
// and the viewport was set incorrectly, leading to tripping glAsserts further down
|
|
// the road (in apply_projection). That's why the minimum size is forced to 10.
|
|
Camera& camera = wxGetApp().plater()->get_camera();
|
|
camera.apply_viewport(0, 0, std::max(10u, (unsigned int)cnv_size.get_width()), std::max(10u, (unsigned int)cnv_size.get_height()));
|
|
|
|
if (camera.requires_zoom_to_bed) {
|
|
zoom_to_bed();
|
|
_resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height());
|
|
camera.requires_zoom_to_bed = false;
|
|
}
|
|
|
|
if (camera.requires_zoom_to_plate > REQUIRES_ZOOM_TO_PLATE_IDLE) {
|
|
zoom_to_plate(camera.requires_zoom_to_plate);
|
|
_resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height());
|
|
camera.requires_zoom_to_plate = REQUIRES_ZOOM_TO_PLATE_IDLE;
|
|
}
|
|
|
|
if (camera.requires_zoom_to_volumes) {
|
|
zoom_to_volumes();
|
|
_resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height());
|
|
camera.requires_zoom_to_volumes = false;
|
|
}
|
|
|
|
camera.apply_view_matrix();
|
|
camera.apply_projection(_max_bounding_box(true, true, true));
|
|
|
|
GLfloat position_cam[4] = { 1.0f, 0.0f, 1.0f, 0.0f };
|
|
glsafe(::glLightfv(GL_LIGHT1, GL_POSITION, position_cam));
|
|
GLfloat position_top[4] = { -0.5f, -0.5f, 1.0f, 0.0f };
|
|
glsafe(::glLightfv(GL_LIGHT0, GL_POSITION, position_top));
|
|
|
|
wxGetApp().imgui()->new_frame();
|
|
|
|
if (m_picking_enabled) {
|
|
if (m_rectangle_selection.is_dragging())
|
|
// picking pass using rectangle selection
|
|
_rectangular_selection_picking_pass();
|
|
//BBS: enable picking when no volumes for partplate logic
|
|
//else if (!m_volumes.empty())
|
|
else
|
|
// regular picking pass
|
|
_picking_pass();
|
|
}
|
|
|
|
#if ENABLE_RENDER_PICKING_PASS
|
|
if (!m_picking_enabled || !m_show_picking_texture) {
|
|
#endif // ENABLE_RENDER_PICKING_PASS
|
|
// draw scene
|
|
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
|
|
_render_background();
|
|
|
|
//BBS add partplater rendering logic
|
|
bool only_current = false, only_body = false, show_axes = true, no_partplate = false;
|
|
GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type();
|
|
if (!m_main_toolbar.is_enabled() || m_gizmos.is_show_only_active_plate()) {
|
|
//only_body = true;
|
|
if (m_gizmos.get_object_located_outside_plate()) {
|
|
no_partplate = true;
|
|
} else {
|
|
only_current = true;
|
|
}
|
|
}
|
|
else if ((gizmo_type == GLGizmosManager::FdmSupports) || (gizmo_type == GLGizmosManager::Seam) || (gizmo_type == GLGizmosManager::MmuSegmentation))
|
|
no_partplate = true;
|
|
|
|
/* view3D render*/
|
|
int hover_id = (m_hover_plate_idxs.size() > 0)?m_hover_plate_idxs.front():-1;
|
|
if (m_canvas_type == ECanvasType::CanvasView3D) {
|
|
//BBS: add outline logic
|
|
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
|
|
_render_sla_slices();
|
|
_render_selection();
|
|
if (!no_partplate)
|
|
_render_bed(!camera.is_looking_downward(), show_axes);
|
|
if (!no_partplate) //BBS: add outline logic
|
|
_render_platelist(!camera.is_looking_downward(), only_current, only_body, hover_id, true);
|
|
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
|
|
}
|
|
/* preview render */
|
|
else if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview) {
|
|
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
|
|
_render_sla_slices();
|
|
_render_selection();
|
|
_render_bed(!camera.is_looking_downward(), show_axes);
|
|
_render_platelist(!camera.is_looking_downward(), only_current, true, hover_id);
|
|
// BBS: GUI refactor: add canvas size as parameters
|
|
_render_gcode(cnv_size.get_width(), cnv_size.get_height());
|
|
}
|
|
/* assemble render*/
|
|
else if (m_canvas_type == ECanvasType::CanvasAssembleView) {
|
|
//BBS: add outline logic
|
|
if (m_show_world_axes) {
|
|
m_axes.render();
|
|
}
|
|
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
|
|
//_render_bed(!camera.is_looking_downward(), show_axes);
|
|
_render_plane();
|
|
//BBS: add outline logic insteadof selection under assemble view
|
|
//_render_selection();
|
|
// BBS: add outline logic
|
|
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
|
|
}
|
|
|
|
_render_sequential_clearance();
|
|
#if ENABLE_RENDER_SELECTION_CENTER
|
|
_render_selection_center();
|
|
#endif // ENABLE_RENDER_SELECTION_CENTER
|
|
|
|
// we need to set the mouse's scene position here because the depth buffer
|
|
// could be invalidated by the following gizmo render methods
|
|
// this position is used later into on_mouse() to drag the objects
|
|
if (m_picking_enabled)
|
|
m_mouse.scene_position = _mouse_to_3d(m_mouse.position.cast<coord_t>());
|
|
|
|
// sidebar hints need to be rendered before the gizmos because the depth buffer
|
|
// could be invalidated by the following gizmo render methods
|
|
_render_selection_sidebar_hints();
|
|
_render_current_gizmo();
|
|
#if ENABLE_RENDER_PICKING_PASS
|
|
}
|
|
#endif // ENABLE_RENDER_PICKING_PASS
|
|
|
|
#if ENABLE_SHOW_CAMERA_TARGET
|
|
_render_camera_target();
|
|
#endif // ENABLE_SHOW_CAMERA_TARGET
|
|
|
|
if (m_picking_enabled && m_rectangle_selection.is_dragging())
|
|
m_rectangle_selection.render(*this);
|
|
|
|
// draw overlays
|
|
_render_overlays();
|
|
|
|
if (wxGetApp().plater()->is_render_statistic_dialog_visible()) {
|
|
ImGui::ShowMetricsWindow();
|
|
|
|
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
|
imgui.begin(std::string("Render statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
|
imgui.text("FPS (SwapBuffers() calls per second):");
|
|
ImGui::SameLine();
|
|
imgui.text(std::to_string(m_render_stats.get_fps_and_reset_if_needed()));
|
|
ImGui::Separator();
|
|
imgui.text("Compressed textures:");
|
|
ImGui::SameLine();
|
|
imgui.text(OpenGLManager::are_compressed_textures_supported() ? "supported" : "not supported");
|
|
imgui.text("Max texture size:");
|
|
ImGui::SameLine();
|
|
imgui.text(std::to_string(OpenGLManager::get_gl_info().get_max_tex_size()));
|
|
imgui.end();
|
|
}
|
|
|
|
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
|
|
if (wxGetApp().is_editor() && wxGetApp().plater()->is_view3D_shown())
|
|
wxGetApp().plater()->render_project_state_debug_window();
|
|
#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
|
|
|
|
#if ENABLE_CAMERA_STATISTICS
|
|
camera.debug_render();
|
|
#endif // ENABLE_CAMERA_STATISTICS
|
|
|
|
#if ENABLE_IMGUI_STYLE_EDITOR
|
|
if (wxGetApp().get_mode() == ConfigOptionMode::comDevelop)
|
|
_render_style_editor();
|
|
#endif
|
|
|
|
|
|
std::string tooltip;
|
|
|
|
// Negative coordinate means out of the window, likely because the window was deactivated.
|
|
// In that case the tooltip should be hidden.
|
|
if (m_mouse.position.x() >= 0. && m_mouse.position.y() >= 0.) {
|
|
if (tooltip.empty())
|
|
tooltip = m_layers_editing.get_tooltip(*this);
|
|
|
|
if (tooltip.empty())
|
|
tooltip = m_gizmos.get_tooltip();
|
|
|
|
if (tooltip.empty())
|
|
tooltip = m_main_toolbar.get_tooltip();
|
|
|
|
//BBS: GUI refactor: GLToolbar
|
|
if (tooltip.empty())
|
|
tooltip = m_assemble_view_toolbar.get_tooltip();
|
|
|
|
if (tooltip.empty())
|
|
tooltip = wxGetApp().plater()->get_collapse_toolbar().get_tooltip();
|
|
|
|
// BBS
|
|
#if 0
|
|
if (tooltip.empty())
|
|
tooltip = wxGetApp().plater()->get_view_toolbar().get_tooltip();
|
|
#endif
|
|
}
|
|
|
|
set_tooltip(tooltip);
|
|
|
|
if (m_tooltip_enabled)
|
|
m_tooltip.render(m_mouse.position, *this);
|
|
|
|
wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this);
|
|
|
|
if (m_canvas_type != ECanvasType::CanvasAssembleView) {
|
|
float right_margin = SLIDER_DEFAULT_RIGHT_MARGIN;
|
|
float bottom_margin = SLIDER_DEFAULT_BOTTOM_MARGIN;
|
|
if (m_canvas_type == ECanvasType::CanvasPreview) {
|
|
right_margin = SLIDER_RIGHT_MARGIN;
|
|
bottom_margin = SLIDER_BOTTOM_MARGIN;
|
|
}
|
|
wxGetApp().plater()->get_notification_manager()->render_notifications(*this, get_overlay_window_width(), bottom_margin, right_margin);
|
|
wxGetApp().plater()->get_dailytips()->render();
|
|
}
|
|
|
|
wxGetApp().imgui()->render();
|
|
|
|
m_canvas->SwapBuffers();
|
|
m_render_stats.increment_fps_counter();
|
|
}
|
|
|
|
void GLCanvas3D::render_thumbnail(ThumbnailData & thumbnail_data,
|
|
unsigned int w,
|
|
unsigned int h,
|
|
const ThumbnailsParams &thumbnail_params,
|
|
Camera::EType camera_type,
|
|
bool use_top_view,
|
|
bool for_picking,
|
|
bool ban_light)
|
|
{
|
|
render_thumbnail(thumbnail_data, w, h, thumbnail_params, m_volumes, camera_type, use_top_view, for_picking, ban_light);
|
|
}
|
|
|
|
void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params,
|
|
const GLVolumeCollection &volumes,
|
|
Camera::EType camera_type,
|
|
bool use_top_view,
|
|
bool for_picking,
|
|
bool ban_light)
|
|
{
|
|
GLShaderProgram* shader = wxGetApp().get_shader("thumbnail");
|
|
ModelObjectPtrs& model_objects = GUI::wxGetApp().model().objects;
|
|
std::vector<std::array<float, 4>> colors = ::get_extruders_colors();
|
|
switch (OpenGLManager::get_framebuffers_type())
|
|
{
|
|
case OpenGLManager::EFramebufferType::Arb:
|
|
{ render_thumbnail_framebuffer(thumbnail_data, w, h, thumbnail_params, wxGetApp().plater()->get_partplate_list(), model_objects, volumes, colors, shader, camera_type,
|
|
use_top_view, for_picking, ban_light);
|
|
break;
|
|
}
|
|
case OpenGLManager::EFramebufferType::Ext:
|
|
{ render_thumbnail_framebuffer_ext(thumbnail_data, w, h, thumbnail_params, wxGetApp().plater()->get_partplate_list(), model_objects, volumes, colors, shader, camera_type,
|
|
use_top_view, for_picking, ban_light);
|
|
break;
|
|
}
|
|
default:{
|
|
render_thumbnail_legacy(thumbnail_data, w, h, thumbnail_params, wxGetApp().plater()->get_partplate_list(), model_objects, volumes, colors, shader, camera_type,
|
|
use_top_view, for_picking, ban_light);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::render_calibration_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params)
|
|
{
|
|
//load current plate gcode
|
|
m_gcode_viewer.render_calibration_thumbnail(thumbnail_data, w, h, thumbnail_params,
|
|
wxGetApp().plater()->get_partplate_list(), wxGetApp().get_opengl_manager());
|
|
}
|
|
|
|
//BBS
|
|
void GLCanvas3D::select_curr_plate_all()
|
|
{
|
|
m_selection.add_curr_plate();
|
|
m_dirty = true;
|
|
}
|
|
|
|
void GLCanvas3D::select_object_from_idx(std::vector<int>& object_idxs) {
|
|
m_selection.add_object_from_idx(object_idxs);
|
|
m_dirty = true;
|
|
}
|
|
|
|
//BBS
|
|
void GLCanvas3D::remove_curr_plate_all()
|
|
{
|
|
m_selection.remove_curr_plate();
|
|
m_dirty = true;
|
|
}
|
|
|
|
void GLCanvas3D::update_plate_thumbnails()
|
|
{
|
|
_update_imgui_select_plate_toolbar();
|
|
}
|
|
|
|
void GLCanvas3D::select_all()
|
|
{
|
|
m_selection.add_all();
|
|
m_dirty = true;
|
|
}
|
|
|
|
void GLCanvas3D::deselect_all()
|
|
{
|
|
m_selection.remove_all();
|
|
// BBS
|
|
//wxGetApp().obj_manipul()->set_dirty();
|
|
m_gizmos.reset_all_states();
|
|
m_gizmos.update_data();
|
|
post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
|
|
}
|
|
|
|
void GLCanvas3D::exit_gizmo() {
|
|
if (m_gizmos.get_current_type() != GLGizmosManager::Undefined) {
|
|
m_gizmos.reset_all_states();
|
|
m_gizmos.update_data();
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::set_selected_visible(bool visible)
|
|
{
|
|
for (unsigned int i : m_selection.get_volume_idxs()) {
|
|
GLVolume* volume = const_cast<GLVolume*>(m_selection.get_volume(i));
|
|
volume->visible = visible;
|
|
volume->color[3] = visible ? 1.f : GLVolume::MODEL_HIDDEN_COL[3];
|
|
volume->render_color[3] = volume->color[3];
|
|
volume->force_native_color = !visible;
|
|
}
|
|
m_dirty = true;
|
|
}
|
|
|
|
void GLCanvas3D::delete_selected()
|
|
{
|
|
m_selection.erase();
|
|
}
|
|
|
|
void GLCanvas3D::ensure_on_bed(unsigned int object_idx, bool allow_negative_z)
|
|
{
|
|
//BBS if asseble view canvas
|
|
if (m_canvas_type == ECanvasType::CanvasAssembleView) {
|
|
return;
|
|
}
|
|
|
|
if (allow_negative_z)
|
|
return;
|
|
|
|
typedef std::map<std::pair<int, int>, double> InstancesToZMap;
|
|
InstancesToZMap instances_min_z;
|
|
|
|
for (GLVolume* volume : m_volumes.volumes) {
|
|
if (volume->object_idx() == (int)object_idx && !volume->is_modifier) {
|
|
double min_z = volume->transformed_convex_hull_bounding_box().min.z();
|
|
std::pair<int, int> instance = std::make_pair(volume->object_idx(), volume->instance_idx());
|
|
InstancesToZMap::iterator it = instances_min_z.find(instance);
|
|
if (it == instances_min_z.end())
|
|
it = instances_min_z.insert(InstancesToZMap::value_type(instance, DBL_MAX)).first;
|
|
|
|
it->second = std::min(it->second, min_z);
|
|
}
|
|
}
|
|
|
|
for (GLVolume* volume : m_volumes.volumes) {
|
|
std::pair<int, int> instance = std::make_pair(volume->object_idx(), volume->instance_idx());
|
|
InstancesToZMap::iterator it = instances_min_z.find(instance);
|
|
if (it != instances_min_z.end())
|
|
volume->set_instance_offset(Z, volume->get_instance_offset(Z) - it->second);
|
|
}
|
|
}
|
|
|
|
|
|
const std::vector<double>& GLCanvas3D::get_gcode_layers_zs() const
|
|
{
|
|
return m_gcode_viewer.get_layers_zs();
|
|
}
|
|
|
|
std::vector<double> GLCanvas3D::get_volumes_print_zs(bool active_only) const
|
|
{
|
|
return m_volumes.get_current_print_zs(active_only);
|
|
}
|
|
|
|
void GLCanvas3D::set_gcode_options_visibility_from_flags(unsigned int flags)
|
|
{
|
|
m_gcode_viewer.set_options_visibility_from_flags(flags);
|
|
}
|
|
|
|
void GLCanvas3D::set_volumes_z_range(const std::array<double, 2>& range)
|
|
{
|
|
m_volumes.set_range(range[0] - 1e-6, range[1] + 1e-6);
|
|
}
|
|
|
|
std::vector<int> GLCanvas3D::load_object(const ModelObject& model_object, int obj_idx, std::vector<int> instance_idxs)
|
|
{
|
|
if (instance_idxs.empty()) {
|
|
for (unsigned int i = 0; i < model_object.instances.size(); ++i) {
|
|
instance_idxs.emplace_back(i);
|
|
}
|
|
}
|
|
return m_volumes.load_object(&model_object, obj_idx, instance_idxs, m_color_by, m_initialized);
|
|
}
|
|
|
|
std::vector<int> GLCanvas3D::load_object(const Model& model, int obj_idx)
|
|
{
|
|
if (0 <= obj_idx && obj_idx < (int)model.objects.size()) {
|
|
const ModelObject* model_object = model.objects[obj_idx];
|
|
if (model_object != nullptr)
|
|
return load_object(*model_object, obj_idx, std::vector<int>());
|
|
}
|
|
|
|
return std::vector<int>();
|
|
}
|
|
|
|
void GLCanvas3D::mirror_selection(Axis axis)
|
|
{
|
|
m_selection.mirror(axis);
|
|
do_mirror(L("Mirror Object"));
|
|
// BBS
|
|
//wxGetApp().obj_manipul()->set_dirty();
|
|
}
|
|
|
|
// Reload the 3D scene of
|
|
// 1) Model / ModelObjects / ModelInstances / ModelVolumes
|
|
// 2) Print bed
|
|
// 3) SLA support meshes for their respective ModelObjects / ModelInstances
|
|
// 4) Wipe tower preview
|
|
// 5) Out of bed collision status & message overlay (texture)
|
|
void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_refresh)
|
|
{
|
|
if (m_canvas == nullptr || m_config == nullptr || m_model == nullptr)
|
|
return;
|
|
|
|
if (!m_initialized)
|
|
return;
|
|
|
|
_set_current();
|
|
|
|
m_hover_volume_idxs.clear();
|
|
|
|
struct ModelVolumeState {
|
|
ModelVolumeState(const GLVolume* volume) :
|
|
model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {}
|
|
ModelVolumeState(const ModelVolume* model_volume, const ObjectID& instance_id, const GLVolume::CompositeID& composite_id) :
|
|
model_volume(model_volume), geometry_id(std::make_pair(model_volume->id().id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {}
|
|
ModelVolumeState(const ObjectID& volume_id, const ObjectID& instance_id) :
|
|
model_volume(nullptr), geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {}
|
|
bool new_geometry() const { return this->volume_idx == size_t(-1); }
|
|
const ModelVolume* model_volume;
|
|
// ObjectID of ModelVolume + ObjectID of ModelInstance
|
|
// or timestamp of an SLAPrintObjectStep + ObjectID of ModelInstance
|
|
std::pair<size_t, size_t> geometry_id;
|
|
GLVolume::CompositeID composite_id;
|
|
// Volume index in the new GLVolume vector.
|
|
size_t volume_idx;
|
|
};
|
|
std::vector<ModelVolumeState> model_volume_state;
|
|
std::vector<ModelVolumeState> aux_volume_state;
|
|
|
|
struct GLVolumeState {
|
|
GLVolumeState() :
|
|
volume_idx(size_t(-1)) {}
|
|
GLVolumeState(const GLVolume* volume, unsigned int volume_idx) :
|
|
composite_id(volume->composite_id), volume_idx(volume_idx) {}
|
|
GLVolumeState(const GLVolume::CompositeID &composite_id) :
|
|
composite_id(composite_id), volume_idx(size_t(-1)) {}
|
|
|
|
GLVolume::CompositeID composite_id;
|
|
// Volume index in the old GLVolume vector.
|
|
size_t volume_idx;
|
|
};
|
|
|
|
// SLA steps to pull the preview meshes for.
|
|
typedef std::array<SLAPrintObjectStep, 3> SLASteps;
|
|
SLASteps sla_steps = { slaposDrillHoles, slaposSupportTree, slaposPad };
|
|
struct SLASupportState {
|
|
std::array<PrintStateBase::StateWithTimeStamp, std::tuple_size<SLASteps>::value> step;
|
|
};
|
|
// State of the sla_steps for all SLAPrintObjects.
|
|
std::vector<SLASupportState> sla_support_state;
|
|
|
|
std::vector<size_t> instance_ids_selected;
|
|
std::vector<size_t> map_glvolume_old_to_new(m_volumes.volumes.size(), size_t(-1));
|
|
std::vector<GLVolumeState> deleted_volumes;
|
|
// BBS
|
|
std::vector<GLVolumeState> deleted_wipe_towers;
|
|
std::vector<GLVolume*> glvolumes_new;
|
|
glvolumes_new.reserve(m_volumes.volumes.size());
|
|
auto model_volume_state_lower = [](const ModelVolumeState& m1, const ModelVolumeState& m2) { return m1.geometry_id < m2.geometry_id; };
|
|
|
|
m_reload_delayed = !m_canvas->IsShown() && !refresh_immediately && !force_full_scene_refresh;
|
|
|
|
PrinterTechnology printer_technology = current_printer_technology();
|
|
|
|
// BBS: support wipe tower for multi-plates
|
|
PartPlateList& ppl = wxGetApp().plater()->get_partplate_list();
|
|
int n_plates = ppl.get_plate_count();
|
|
std::vector<int> volume_idxs_wipe_tower_old(n_plates, -1);
|
|
|
|
// Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed).
|
|
// First initialize model_volumes_new_sorted & model_instances_new_sorted.
|
|
for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++object_idx) {
|
|
const ModelObject* model_object = m_model->objects[object_idx];
|
|
for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++instance_idx) {
|
|
const ModelInstance* model_instance = model_object->instances[instance_idx];
|
|
for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++volume_idx) {
|
|
const ModelVolume* model_volume = model_object->volumes[volume_idx];
|
|
if (m_canvas_type == ECanvasType::CanvasAssembleView) {
|
|
if (model_volume->is_model_part())
|
|
model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx));
|
|
}
|
|
else {
|
|
model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (printer_technology == ptSLA) {
|
|
const SLAPrint* sla_print = this->sla_print();
|
|
#ifndef NDEBUG
|
|
// Verify that the SLAPrint object is synchronized with m_model.
|
|
check_model_ids_equal(*m_model, sla_print->model());
|
|
#endif /* NDEBUG */
|
|
sla_support_state.reserve(sla_print->objects().size());
|
|
for (const SLAPrintObject* print_object : sla_print->objects()) {
|
|
SLASupportState state;
|
|
for (size_t istep = 0; istep < sla_steps.size(); ++istep) {
|
|
state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]);
|
|
if (state.step[istep].state == PrintStateBase::DONE) {
|
|
if (!print_object->has_mesh(sla_steps[istep]))
|
|
// Consider the DONE step without a valid mesh as invalid for the purpose
|
|
// of mesh visualization.
|
|
state.step[istep].state = PrintStateBase::INVALID;
|
|
else if (sla_steps[istep] != slaposDrillHoles)
|
|
for (const ModelInstance* model_instance : print_object->model_object()->instances)
|
|
// Only the instances, which are currently printable, will have the SLA support structures kept.
|
|
// The instances outside the print bed will have the GLVolumes of their support structures released.
|
|
if (model_instance->is_printable())
|
|
aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id());
|
|
}
|
|
}
|
|
sla_support_state.emplace_back(state);
|
|
}
|
|
}
|
|
std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower);
|
|
std::sort(aux_volume_state.begin(), aux_volume_state.end(), model_volume_state_lower);
|
|
|
|
// BBS: normalize painting data with current filament count
|
|
for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++obj_idx) {
|
|
const ModelObject& model_object = *m_model->objects[obj_idx];
|
|
for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++volume_idx) {
|
|
ModelVolume& model_volume = *model_object.volumes[volume_idx];
|
|
if (!model_volume.is_model_part())
|
|
continue;
|
|
|
|
unsigned int filaments_count = (unsigned int)dynamic_cast<const ConfigOptionStrings*>(m_config->option("filament_colour"))->values.size();
|
|
model_volume.update_extruder_count(filaments_count);
|
|
}
|
|
}
|
|
|
|
// Release all ModelVolume based GLVolumes not found in the current Model. Find the GLVolume of a hollowed mesh.
|
|
for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++volume_id) {
|
|
GLVolume* volume = m_volumes.volumes[volume_id];
|
|
ModelVolumeState key(volume);
|
|
ModelVolumeState* mvs = nullptr;
|
|
if (volume->volume_idx() < 0) {
|
|
auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower);
|
|
if (it != aux_volume_state.end() && it->geometry_id == key.geometry_id)
|
|
// This can be an SLA support structure that should not be rendered (in case someone used undo
|
|
// to revert to before it was generated). We only reuse the volume if that's not the case.
|
|
if (m_model->objects[volume->composite_id.object_id]->sla_points_status != sla::PointsStatus::NoPoints)
|
|
mvs = &(*it);
|
|
}
|
|
else {
|
|
auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
|
|
if (it != model_volume_state.end() && it->geometry_id == key.geometry_id)
|
|
mvs = &(*it);
|
|
}
|
|
// Emplace instance ID of the volume. Both the aux volumes and model volumes share the same instance ID.
|
|
// The wipe tower has its own wipe_tower_instance_id().
|
|
if (m_selection.contains_volume(volume_id)) {
|
|
if (m_canvas_type == ECanvasType::CanvasAssembleView) {
|
|
if (!volume->is_modifier)
|
|
instance_ids_selected.emplace_back(volume->geometry_id.second);
|
|
}
|
|
else {
|
|
instance_ids_selected.emplace_back(volume->geometry_id.second);
|
|
}
|
|
}
|
|
if (mvs == nullptr || force_full_scene_refresh) {
|
|
// This GLVolume will be released.
|
|
if (volume->is_wipe_tower) {
|
|
// There is only one wipe tower.
|
|
//assert(volume_idx_wipe_tower_old == -1);
|
|
int plate_id = volume->composite_id.object_id - 1000;
|
|
if (plate_id < n_plates)
|
|
volume_idxs_wipe_tower_old[plate_id] = (int)volume_id;
|
|
}
|
|
if (!m_reload_delayed) {
|
|
deleted_volumes.emplace_back(volume, volume_id);
|
|
// BBS
|
|
if (volume->is_wipe_tower)
|
|
deleted_wipe_towers.emplace_back(volume, volume_id);
|
|
m_volumes.release_volume(volume);
|
|
delete volume;
|
|
}
|
|
}
|
|
else {
|
|
// This GLVolume will be reused.
|
|
volume->set_sla_shift_z(0.0);
|
|
map_glvolume_old_to_new[volume_id] = glvolumes_new.size();
|
|
mvs->volume_idx = glvolumes_new.size();
|
|
glvolumes_new.emplace_back(volume);
|
|
// Update color of the volume based on the current extruder.
|
|
if (mvs->model_volume != nullptr) {
|
|
int extruder_id = mvs->model_volume->extruder_id();
|
|
if (extruder_id != -1)
|
|
volume->extruder_id = extruder_id;
|
|
|
|
volume->is_modifier = !mvs->model_volume->is_model_part();
|
|
volume->set_color(color_from_model_volume(*mvs->model_volume));
|
|
|
|
// updates volumes transformations
|
|
if (m_canvas_type != ECanvasType::CanvasAssembleView) {
|
|
volume->set_instance_transformation(mvs->model_volume->get_object()->instances[mvs->composite_id.instance_id]->get_transformation());
|
|
volume->set_volume_transformation(mvs->model_volume->get_transformation());
|
|
// updates volumes convex hull
|
|
if (mvs->model_volume->is_model_part() && ! volume->convex_hull())
|
|
// Model volume was likely changed from modifier or support blocker / enforcer to a model part.
|
|
// Only model parts require convex hulls.
|
|
volume->set_convex_hull(mvs->model_volume->get_convex_hull_shared_ptr());
|
|
volume->set_offset_to_assembly(Vec3d(0, 0, 0));
|
|
}
|
|
else {
|
|
volume->set_instance_transformation(mvs->model_volume->get_object()->instances[mvs->composite_id.instance_id]->get_assemble_transformation());
|
|
volume->set_volume_transformation(mvs->model_volume->get_transformation());
|
|
// updates volumes convex hull
|
|
if (mvs->model_volume->is_model_part() && ! volume->convex_hull())
|
|
// Model volume was likely changed from modifier or support blocker / enforcer to a model part.
|
|
// Only model parts require convex hulls.
|
|
volume->set_convex_hull(mvs->model_volume->get_convex_hull_shared_ptr());
|
|
volume->set_offset_to_assembly(mvs->model_volume->get_object()->instances[mvs->composite_id.instance_id]->get_offset_to_assembly());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sort_remove_duplicates(instance_ids_selected);
|
|
auto deleted_volumes_lower = [](const GLVolumeState &v1, const GLVolumeState &v2) { return v1.composite_id < v2.composite_id; };
|
|
std::sort(deleted_volumes.begin(), deleted_volumes.end(), deleted_volumes_lower);
|
|
|
|
//BBS clean hover_volume_idxs
|
|
m_hover_volume_idxs.clear();
|
|
|
|
if (m_reload_delayed)
|
|
return;
|
|
|
|
// BBS: do not check wipe tower changes
|
|
bool update_object_list = false;
|
|
if (deleted_volumes.size() != deleted_wipe_towers.size())
|
|
update_object_list = true;
|
|
|
|
if (m_volumes.volumes != glvolumes_new && !update_object_list) {
|
|
int vol_idx = 0;
|
|
for (; vol_idx < std::min(m_volumes.volumes.size(), glvolumes_new.size()); vol_idx++) {
|
|
if (m_volumes.volumes[vol_idx] != glvolumes_new[vol_idx]) {
|
|
update_object_list = true;
|
|
break;
|
|
}
|
|
}
|
|
for (int temp_idx = vol_idx; temp_idx < m_volumes.volumes.size() && !update_object_list; temp_idx++) {
|
|
// Volumes in m_volumes might not exist anymore, so we cannot
|
|
// directly check if they are is_wipe_towers, for which we do
|
|
// not want to update the object list. Instead, we do a kind of
|
|
// slow thing of seeing if they were in the deleted list, and if
|
|
// so, if they were a wipe tower.
|
|
bool was_deleted_wipe_tower = false;
|
|
for (int del_idx = 0; del_idx < deleted_wipe_towers.size(); del_idx++) {
|
|
if (deleted_wipe_towers[del_idx].volume_idx == temp_idx) {
|
|
was_deleted_wipe_tower = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!was_deleted_wipe_tower) {
|
|
update_object_list = true;
|
|
}
|
|
}
|
|
for (int temp_idx = vol_idx; temp_idx < glvolumes_new.size() && !update_object_list; temp_idx++) {
|
|
if (!glvolumes_new[temp_idx]->is_wipe_tower)
|
|
update_object_list = true;
|
|
}
|
|
}
|
|
m_volumes.volumes = std::move(glvolumes_new);
|
|
for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++ obj_idx) {
|
|
const ModelObject &model_object = *m_model->objects[obj_idx];
|
|
for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++ volume_idx) {
|
|
const ModelVolume &model_volume = *model_object.volumes[volume_idx];
|
|
if (m_canvas_type == ECanvasType::CanvasAssembleView && !model_volume.is_model_part())
|
|
continue;
|
|
for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) {
|
|
const ModelInstance &model_instance = *model_object.instances[instance_idx];
|
|
ModelVolumeState key(model_volume.id(), model_instance.id());
|
|
auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
|
|
assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id);
|
|
if (it->new_geometry()) {
|
|
// New volume.
|
|
auto it_old_volume = std::lower_bound(deleted_volumes.begin(), deleted_volumes.end(), GLVolumeState(it->composite_id), deleted_volumes_lower);
|
|
if (it_old_volume != deleted_volumes.end() && it_old_volume->composite_id == it->composite_id)
|
|
// If a volume changed its ObjectID, but it reuses a GLVolume's CompositeID, maintain its selection.
|
|
map_glvolume_old_to_new[it_old_volume->volume_idx] = m_volumes.volumes.size();
|
|
// Note the index of the loaded volume, so that we can reload the main model GLVolume with the hollowed mesh
|
|
// later in this function.
|
|
it->volume_idx = m_volumes.volumes.size();
|
|
m_volumes.load_object_volume(&model_object, obj_idx, volume_idx, instance_idx, m_color_by, m_initialized, m_canvas_type == ECanvasType::CanvasAssembleView);
|
|
m_volumes.volumes.back()->geometry_id = key.geometry_id;
|
|
update_object_list = true;
|
|
} else {
|
|
// Recycling an old GLVolume.
|
|
GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx];
|
|
assert(existing_volume.geometry_id == key.geometry_id);
|
|
// Update the Object/Volume/Instance indices into the current Model.
|
|
if (existing_volume.composite_id != it->composite_id) {
|
|
existing_volume.composite_id = it->composite_id;
|
|
update_object_list = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (printer_technology == ptSLA) {
|
|
size_t idx = 0;
|
|
const SLAPrint *sla_print = this->sla_print();
|
|
std::vector<double> shift_zs(m_model->objects.size(), 0);
|
|
double relative_correction_z = sla_print->relative_correction().z();
|
|
if (relative_correction_z <= EPSILON)
|
|
relative_correction_z = 1.;
|
|
for (const SLAPrintObject *print_object : sla_print->objects()) {
|
|
SLASupportState &state = sla_support_state[idx ++];
|
|
const ModelObject *model_object = print_object->model_object();
|
|
// Find an index of the ModelObject
|
|
int object_idx;
|
|
// There may be new SLA volumes added to the scene for this print_object.
|
|
// Find the object index of this print_object in the Model::objects list.
|
|
auto it = std::find(sla_print->model().objects.begin(), sla_print->model().objects.end(), model_object);
|
|
assert(it != sla_print->model().objects.end());
|
|
object_idx = it - sla_print->model().objects.begin();
|
|
// Cache the Z offset to be applied to all volumes with this object_idx.
|
|
shift_zs[object_idx] = print_object->get_current_elevation() / relative_correction_z;
|
|
// Collect indices of this print_object's instances, for which the SLA support meshes are to be added to the scene.
|
|
// pairs of <instance_idx, print_instance_idx>
|
|
std::vector<std::pair<size_t, size_t>> instances[std::tuple_size<SLASteps>::value];
|
|
for (size_t print_instance_idx = 0; print_instance_idx < print_object->instances().size(); ++ print_instance_idx) {
|
|
const SLAPrintObject::Instance &instance = print_object->instances()[print_instance_idx];
|
|
// Find index of ModelInstance corresponding to this SLAPrintObject::Instance.
|
|
auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(),
|
|
[&instance](const ModelInstance *mi) { return mi->id() == instance.instance_id; });
|
|
assert(it != model_object->instances.end());
|
|
int instance_idx = it - model_object->instances.begin();
|
|
for (size_t istep = 0; istep < sla_steps.size(); ++ istep)
|
|
if (sla_steps[istep] == slaposDrillHoles) {
|
|
// Hollowing is a special case, where the mesh from the backend is being loaded into the 1st volume of an instance,
|
|
// not into its own GLVolume.
|
|
// There shall always be such a GLVolume allocated.
|
|
ModelVolumeState key(model_object->volumes.front()->id(), instance.instance_id);
|
|
auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
|
|
assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id);
|
|
assert(!it->new_geometry());
|
|
GLVolume &volume = *m_volumes.volumes[it->volume_idx];
|
|
if (! volume.offsets.empty() && state.step[istep].timestamp != volume.offsets.front()) {
|
|
// The backend either produced a new hollowed mesh, or it invalidated the one that the front end has seen.
|
|
volume.indexed_vertex_array->release_geometry();
|
|
if (state.step[istep].state == PrintStateBase::DONE) {
|
|
TriangleMesh mesh = print_object->get_mesh(slaposDrillHoles);
|
|
assert(! mesh.empty());
|
|
mesh.transform(sla_print->sla_trafo(*m_model->objects[volume.object_idx()]).inverse());
|
|
#if ENABLE_SMOOTH_NORMALS
|
|
volume.indexed_vertex_array->load_mesh(mesh, true);
|
|
#else
|
|
volume.indexed_vertex_array->load_mesh(mesh);
|
|
#endif // ENABLE_SMOOTH_NORMALS
|
|
} else {
|
|
// Reload the original volume.
|
|
#if ENABLE_SMOOTH_NORMALS
|
|
volume.indexed_vertex_array->load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true);
|
|
#else
|
|
volume.indexed_vertex_array->load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh());
|
|
#endif // ENABLE_SMOOTH_NORMALS
|
|
}
|
|
volume.finalize_geometry(true);
|
|
}
|
|
//FIXME it is an ugly hack to write the timestamp into the "offsets" field to not have to add another member variable
|
|
// to the GLVolume. We should refactor GLVolume significantly, so that the GLVolume will not contain member variables
|
|
// of various concenrs (model vs. 3D print path).
|
|
volume.offsets = { state.step[istep].timestamp };
|
|
} else if (state.step[istep].state == PrintStateBase::DONE) {
|
|
// Check whether there is an existing auxiliary volume to be updated, or a new auxiliary volume to be created.
|
|
ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id);
|
|
auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower);
|
|
assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id);
|
|
if (it->new_geometry()) {
|
|
// This can be an SLA support structure that should not be rendered (in case someone used undo
|
|
// to revert to before it was generated). If that's the case, we should not generate anything.
|
|
if (model_object->sla_points_status != sla::PointsStatus::NoPoints)
|
|
instances[istep].emplace_back(std::pair<size_t, size_t>(instance_idx, print_instance_idx));
|
|
else
|
|
shift_zs[object_idx] = 0.;
|
|
} else {
|
|
// Recycling an old GLVolume. Update the Object/Instance indices into the current Model.
|
|
m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx);
|
|
m_volumes.volumes[it->volume_idx]->set_instance_transformation(model_object->instances[instance_idx]->get_transformation());
|
|
}
|
|
}
|
|
}
|
|
|
|
for (size_t istep = 0; istep < sla_steps.size(); ++istep)
|
|
if (!instances[istep].empty())
|
|
m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_initialized);
|
|
}
|
|
|
|
// Shift-up all volumes of the object so that it has the right elevation with respect to the print bed
|
|
for (GLVolume* volume : m_volumes.volumes)
|
|
if (volume->object_idx() < (int)m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable())
|
|
volume->set_sla_shift_z(shift_zs[volume->object_idx()]);
|
|
}
|
|
|
|
// BBS
|
|
if (printer_technology == ptFFF && m_config->has("filament_colour") && (m_canvas_type != ECanvasType::CanvasAssembleView)) {
|
|
// Should the wipe tower be visualized ?
|
|
unsigned int filaments_count = (unsigned int)dynamic_cast<const ConfigOptionStrings*>(m_config->option("filament_colour"))->values.size();
|
|
|
|
bool wt = dynamic_cast<const ConfigOptionBool*>(m_config->option("enable_prime_tower"))->value;
|
|
auto co = dynamic_cast<const ConfigOptionEnum<PrintSequence>*>(m_config->option<ConfigOptionEnum<PrintSequence>>("print_sequence"));
|
|
|
|
const DynamicPrintConfig &dconfig = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
|
auto timelapse_type = dconfig.option<ConfigOptionEnum<TimelapseType>>("timelapse_type");
|
|
bool timelapse_enabled = timelapse_type ? (timelapse_type->value == TimelapseType::tlSmooth) : false;
|
|
|
|
if (wt && (timelapse_enabled || filaments_count > 1)) {
|
|
for (int plate_id = 0; plate_id < n_plates; plate_id++) {
|
|
// If print ByObject and there is only one object in the plate, the wipe tower is allowed to be generated.
|
|
PartPlate* part_plate = ppl.get_plate(plate_id);
|
|
if (part_plate->get_print_seq() == PrintSequence::ByObject ||
|
|
(part_plate->get_print_seq() == PrintSequence::ByDefault && co != nullptr && co->value == PrintSequence::ByObject)) {
|
|
if (ppl.get_plate(plate_id)->printable_instance_size() != 1)
|
|
continue;
|
|
}
|
|
|
|
DynamicPrintConfig& proj_cfg = wxGetApp().preset_bundle->project_config;
|
|
float x = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_x"))->get_at(plate_id);
|
|
float y = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_y"))->get_at(plate_id);
|
|
float w = dynamic_cast<const ConfigOptionFloat*>(m_config->option("prime_tower_width"))->value;
|
|
float a = dynamic_cast<const ConfigOptionFloat*>(proj_cfg.option("wipe_tower_rotation_angle"))->value;
|
|
// BBS
|
|
float v = dynamic_cast<const ConfigOptionFloat*>(m_config->option("prime_volume"))->value;
|
|
Vec3d plate_origin = ppl.get_plate(plate_id)->get_origin();
|
|
|
|
const Print* print = m_process->fff_print();
|
|
float brim_width = print->wipe_tower_data(filaments_count).brim_width;
|
|
const DynamicPrintConfig &print_cfg = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
|
Vec3d wipe_tower_size = ppl.get_plate(plate_id)->estimate_wipe_tower_size(print_cfg, w, v);
|
|
|
|
const float margin = WIPE_TOWER_MARGIN;
|
|
BoundingBoxf3 plate_bbox = wxGetApp().plater()->get_partplate_list().get_plate(plate_id)->get_bounding_box();
|
|
coordf_t plate_bbox_x_max_local_coord = plate_bbox.max(0) - plate_origin(0);
|
|
coordf_t plate_bbox_y_max_local_coord = plate_bbox.max(1) - plate_origin(1);
|
|
bool need_update = false;
|
|
if (x + margin + wipe_tower_size(0) > plate_bbox_x_max_local_coord) {
|
|
x = plate_bbox_x_max_local_coord - wipe_tower_size(0) - margin;
|
|
need_update = true;
|
|
}
|
|
else if (x < margin) {
|
|
x = margin;
|
|
need_update = true;
|
|
}
|
|
if (need_update) {
|
|
ConfigOptionFloat wt_x_opt(x);
|
|
dynamic_cast<ConfigOptionFloats *>(proj_cfg.option("wipe_tower_x"))->set_at(&wt_x_opt, plate_id, 0);
|
|
need_update = false;
|
|
}
|
|
|
|
if (y + margin + wipe_tower_size(1) > plate_bbox_y_max_local_coord) {
|
|
y = plate_bbox_y_max_local_coord - wipe_tower_size(1) - margin;
|
|
need_update = true;
|
|
}
|
|
else if (y < margin) {
|
|
y = margin;
|
|
need_update = true;
|
|
}
|
|
if (need_update) {
|
|
ConfigOptionFloat wt_y_opt(y);
|
|
dynamic_cast<ConfigOptionFloats *>(proj_cfg.option("wipe_tower_y"))->set_at(&wt_y_opt, plate_id, 0);
|
|
}
|
|
|
|
int volume_idx_wipe_tower_new = m_volumes.load_wipe_tower_preview(
|
|
1000 + plate_id, x + plate_origin(0), y + plate_origin(1),
|
|
(float)wipe_tower_size(0), (float)wipe_tower_size(1), (float)wipe_tower_size(2), a,
|
|
/*!print->is_step_done(psWipeTower)*/ true, brim_width, m_initialized);
|
|
int volume_idx_wipe_tower_old = volume_idxs_wipe_tower_old[plate_id];
|
|
if (volume_idx_wipe_tower_old != -1)
|
|
map_glvolume_old_to_new[volume_idx_wipe_tower_old] = volume_idx_wipe_tower_new;
|
|
}
|
|
}
|
|
}
|
|
|
|
update_volumes_colors_by_extruder();
|
|
// Update selection indices based on the old/new GLVolumeCollection.
|
|
if (m_selection.get_mode() == Selection::Instance)
|
|
m_selection.instances_changed(instance_ids_selected);
|
|
else
|
|
m_selection.volumes_changed(map_glvolume_old_to_new);
|
|
|
|
m_gizmos.update_data();
|
|
m_gizmos.update_assemble_view_data();
|
|
m_gizmos.refresh_on_off_state();
|
|
|
|
// Update the toolbar
|
|
//BBS: notify the PartPlateList to reload all objects
|
|
if (update_object_list)
|
|
{
|
|
post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
|
|
}
|
|
|
|
//BBS:exclude the assmble view
|
|
if (m_canvas_type != ECanvasType::CanvasAssembleView) {
|
|
_set_warning_notification_if_needed(EWarning::GCodeConflict);
|
|
// checks for geometry outside the print volume to render it accordingly
|
|
if (!m_volumes.empty()) {
|
|
ModelInstanceEPrintVolumeState state;
|
|
const bool contained_min_one = m_volumes.check_outside_state(m_bed.build_volume(), &state);
|
|
const bool partlyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Partly_Outside);
|
|
const bool fullyOut = (state == ModelInstanceEPrintVolumeState::ModelInstancePVS_Fully_Outside);
|
|
|
|
_set_warning_notification(EWarning::ObjectClashed, partlyOut);
|
|
//BBS: turn off the warning when fully outside
|
|
//_set_warning_notification(EWarning::ObjectOutside, fullyOut);
|
|
if (printer_technology != ptSLA || !contained_min_one)
|
|
_set_warning_notification(EWarning::SlaSupportsOutside, false);
|
|
|
|
post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS,
|
|
contained_min_one && !m_model->objects.empty() && !partlyOut));
|
|
}
|
|
else {
|
|
_set_warning_notification(EWarning::ObjectOutside, false);
|
|
_set_warning_notification(EWarning::ObjectClashed, false);
|
|
_set_warning_notification(EWarning::SlaSupportsOutside, false);
|
|
post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false));
|
|
}
|
|
}
|
|
|
|
refresh_camera_scene_box();
|
|
|
|
if (m_selection.is_empty()) {
|
|
// If no object is selected, deactivate the active gizmo, if any
|
|
// Otherwise it may be shown after cleaning the scene (if it was active while the objects were deleted)
|
|
m_gizmos.reset_all_states();
|
|
// BBS
|
|
#if 0
|
|
// If no object is selected, reset the objects manipulator on the sidebar
|
|
// to force a reset of its cache
|
|
auto manip = wxGetApp().obj_manipul();
|
|
if (manip != nullptr)
|
|
manip->set_dirty();
|
|
#endif
|
|
}
|
|
|
|
// and force this canvas to be redrawn.
|
|
m_dirty = true;
|
|
}
|
|
|
|
static void reserve_new_volume_finalize_old_volume(GLVolume& vol_new, GLVolume& vol_old, bool gl_initialized, size_t prealloc_size = VERTEX_BUFFER_RESERVE_SIZE)
|
|
{
|
|
// Assign the large pre-allocated buffers to the new GLVolume.
|
|
*(vol_new.indexed_vertex_array) = std::move(*(vol_old.indexed_vertex_array));
|
|
// Copy the content back to the old GLVolume.
|
|
*(vol_old.indexed_vertex_array) = *(vol_new.indexed_vertex_array);
|
|
// Clear the buffers, but keep them pre-allocated.
|
|
vol_new.indexed_vertex_array->clear();
|
|
// Just make sure that clear did not clear the reserved memory.
|
|
// Reserving number of vertices (3x position + 3x color)
|
|
vol_new.indexed_vertex_array->reserve(prealloc_size / 6);
|
|
// Finalize the old geometry, possibly move data to the graphics card.
|
|
vol_old.finalize_geometry(gl_initialized);
|
|
}
|
|
|
|
//BBS: always load shell at preview
|
|
void GLCanvas3D::load_shells(const Print& print, bool force_previewing)
|
|
{
|
|
if (m_initialized)
|
|
{
|
|
m_gcode_viewer.load_shells(print, m_initialized, force_previewing);
|
|
m_gcode_viewer.update_shells_color_by_extruder(m_config);
|
|
}
|
|
}
|
|
|
|
//BBS: add only gcode mode
|
|
void GLCanvas3D::load_gcode_preview(const GCodeProcessorResult& gcode_result, const std::vector<std::string>& str_tool_colors, bool only_gcode)
|
|
{
|
|
PartPlateList& partplate_list = wxGetApp().plater()->get_partplate_list();
|
|
PartPlate* plate = partplate_list.get_curr_plate();
|
|
const std::vector<BoundingBoxf3>& exclude_bounding_box = plate->get_exclude_areas();
|
|
|
|
//BBS: init is called in GLCanvas3D.render()
|
|
//when load gcode directly, it is too late
|
|
m_gcode_viewer.init(wxGetApp().get_mode(), wxGetApp().preset_bundle);
|
|
m_gcode_viewer.load(gcode_result, *this->fff_print(), wxGetApp().plater()->build_volume(), exclude_bounding_box,
|
|
m_initialized, wxGetApp().get_mode(), only_gcode);
|
|
|
|
if (wxGetApp().is_editor()) {
|
|
//BBS: always load shell at preview, do this in load_shells
|
|
//m_gcode_viewer.update_shells_color_by_extruder(m_config);
|
|
_set_warning_notification_if_needed(EWarning::ToolHeightOutside);
|
|
_set_warning_notification_if_needed(EWarning::ToolpathOutside);
|
|
_set_warning_notification_if_needed(EWarning::GCodeConflict);
|
|
}
|
|
|
|
m_gcode_viewer.refresh(gcode_result, str_tool_colors);
|
|
set_as_dirty();
|
|
request_extra_frame();
|
|
}
|
|
|
|
void GLCanvas3D::refresh_gcode_preview_render_paths()
|
|
{
|
|
m_gcode_viewer.refresh_render_paths();
|
|
set_as_dirty();
|
|
request_extra_frame();
|
|
}
|
|
|
|
void GLCanvas3D::load_sla_preview()
|
|
{
|
|
const SLAPrint* print = sla_print();
|
|
if (m_canvas != nullptr && print != nullptr) {
|
|
_set_current();
|
|
// Release OpenGL data before generating new data.
|
|
reset_volumes();
|
|
_load_sla_shells();
|
|
_update_sla_shells_outside_state();
|
|
_set_warning_notification_if_needed(EWarning::SlaSupportsOutside);
|
|
}
|
|
}
|
|
|
|
/*void GLCanvas3D::load_preview(const std::vector<std::string>& str_tool_colors, const std::vector<CustomGCode::Item>& color_print_values)
|
|
{
|
|
const Print *print = this->fff_print();
|
|
if (print == nullptr)
|
|
return;
|
|
|
|
_set_current();
|
|
|
|
// Release OpenGL data before generating new data.
|
|
this->reset_volumes();
|
|
|
|
const BuildVolume &build_volume = m_bed.build_volume();
|
|
_load_print_toolpaths(build_volume);
|
|
_load_wipe_tower_toolpaths(build_volume, str_tool_colors);
|
|
for (const PrintObject* object : print->objects())
|
|
_load_print_object_toolpaths(*object, build_volume, str_tool_colors, color_print_values);
|
|
|
|
_set_warning_notification_if_needed(EWarning::ToolpathOutside);
|
|
}*/
|
|
|
|
void GLCanvas3D::bind_event_handlers()
|
|
{
|
|
if (m_canvas != nullptr) {
|
|
m_canvas->Bind(wxEVT_SIZE, &GLCanvas3D::on_size, this);
|
|
m_canvas->Bind(wxEVT_IDLE, &GLCanvas3D::on_idle, this);
|
|
m_canvas->Bind(wxEVT_CHAR, &GLCanvas3D::on_char, this);
|
|
m_canvas->Bind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key, this);
|
|
m_canvas->Bind(wxEVT_KEY_UP, &GLCanvas3D::on_key, this);
|
|
m_canvas->Bind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this);
|
|
m_canvas->Bind(wxEVT_TIMER, &GLCanvas3D::on_timer, this);
|
|
m_canvas->Bind(EVT_GLCANVAS_RENDER_TIMER, &GLCanvas3D::on_render_timer, this);
|
|
m_toolbar_highlighter.set_timer_owner(m_canvas, 0);
|
|
m_canvas->Bind(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, [this](wxTimerEvent&) { m_toolbar_highlighter.blink(); });
|
|
m_gizmo_highlighter.set_timer_owner(m_canvas, 0);
|
|
m_canvas->Bind(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, [this](wxTimerEvent&) { m_gizmo_highlighter.blink(); });
|
|
m_canvas->Bind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Bind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Bind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Bind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Bind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Bind(wxEVT_RIGHT_UP, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Bind(wxEVT_MOTION, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Bind(wxEVT_ENTER_WINDOW, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Bind(wxEVT_LEAVE_WINDOW, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Bind(wxEVT_LEFT_DCLICK, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Bind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Bind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Bind(wxEVT_PAINT, &GLCanvas3D::on_paint, this);
|
|
m_canvas->Bind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this);
|
|
m_canvas->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& evt) {
|
|
ImGui::SetWindowFocus(nullptr);
|
|
render();
|
|
evt.Skip();
|
|
});
|
|
m_event_handlers_bound = true;
|
|
|
|
m_canvas->Bind(wxEVT_GESTURE_PAN, &GLCanvas3D::on_gesture, this);
|
|
m_canvas->Bind(wxEVT_GESTURE_ZOOM, &GLCanvas3D::on_gesture, this);
|
|
m_canvas->Bind(wxEVT_GESTURE_ROTATE, &GLCanvas3D::on_gesture, this);
|
|
m_canvas->EnableTouchEvents(wxTOUCH_ZOOM_GESTURE | wxTOUCH_ROTATE_GESTURE);
|
|
#if __WXOSX__
|
|
initGestures(m_canvas->GetHandle(), m_canvas); // for UIPanGestureRecognizer allowedScrollTypesMask
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::unbind_event_handlers()
|
|
{
|
|
if (m_canvas != nullptr && m_event_handlers_bound) {
|
|
m_canvas->Unbind(wxEVT_SIZE, &GLCanvas3D::on_size, this);
|
|
m_canvas->Unbind(wxEVT_IDLE, &GLCanvas3D::on_idle, this);
|
|
m_canvas->Unbind(wxEVT_CHAR, &GLCanvas3D::on_char, this);
|
|
m_canvas->Unbind(wxEVT_KEY_DOWN, &GLCanvas3D::on_key, this);
|
|
m_canvas->Unbind(wxEVT_KEY_UP, &GLCanvas3D::on_key, this);
|
|
m_canvas->Unbind(wxEVT_MOUSEWHEEL, &GLCanvas3D::on_mouse_wheel, this);
|
|
m_canvas->Unbind(wxEVT_TIMER, &GLCanvas3D::on_timer, this);
|
|
m_canvas->Unbind(EVT_GLCANVAS_RENDER_TIMER, &GLCanvas3D::on_render_timer, this);
|
|
m_canvas->Unbind(wxEVT_LEFT_DOWN, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Unbind(wxEVT_LEFT_UP, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Unbind(wxEVT_MIDDLE_DOWN, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Unbind(wxEVT_MIDDLE_UP, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Unbind(wxEVT_RIGHT_DOWN, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Unbind(wxEVT_RIGHT_UP, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Unbind(wxEVT_MOTION, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Unbind(wxEVT_ENTER_WINDOW, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Unbind(wxEVT_LEAVE_WINDOW, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Unbind(wxEVT_LEFT_DCLICK, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Unbind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Unbind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this);
|
|
m_canvas->Unbind(wxEVT_PAINT, &GLCanvas3D::on_paint, this);
|
|
m_canvas->Unbind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this);
|
|
m_event_handlers_bound = false;
|
|
|
|
m_canvas->Unbind(wxEVT_GESTURE_PAN, &GLCanvas3D::on_gesture, this);
|
|
m_canvas->Unbind(wxEVT_GESTURE_ZOOM, &GLCanvas3D::on_gesture, this);
|
|
m_canvas->Unbind(wxEVT_GESTURE_ROTATE, &GLCanvas3D::on_gesture, this);
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::on_size(wxSizeEvent& evt)
|
|
{
|
|
m_dirty = true;
|
|
}
|
|
|
|
void GLCanvas3D::on_idle(wxIdleEvent& evt)
|
|
{
|
|
if (!m_initialized)
|
|
return;
|
|
|
|
m_dirty |= m_main_toolbar.update_items_state();
|
|
//BBS: GUI refactor: GLToolbar
|
|
m_dirty |= m_assemble_view_toolbar.update_items_state();
|
|
// BBS
|
|
//m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state();
|
|
m_dirty |= wxGetApp().plater()->sidebar().get_update_3d_state();
|
|
if (wxGetApp().plater()->sidebar().get_update_3d_state()) {
|
|
wxGetApp().plater()->sidebar().cancel_update_3d_state();
|
|
}
|
|
m_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state();
|
|
_update_imgui_select_plate_toolbar();
|
|
bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera());
|
|
m_dirty |= mouse3d_controller_applied;
|
|
m_dirty |= wxGetApp().plater()->get_notification_manager()->update_notifications(*this);
|
|
auto gizmo = wxGetApp().plater()->get_view3D_canvas3D()->get_gizmos_manager().get_current();
|
|
if (gizmo != nullptr) m_dirty |= gizmo->update_items_state();
|
|
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
|
// ImGuiWrapper::m_requires_extra_frame may have been set by a render made outside of the OnIdle mechanism
|
|
bool imgui_requires_extra_frame = wxGetApp().imgui()->requires_extra_frame();
|
|
m_dirty |= imgui_requires_extra_frame;
|
|
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
|
|
|
if (!m_dirty)
|
|
return;
|
|
|
|
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
|
// this needs to be done here.
|
|
// during the render launched by the refresh the value may be set again
|
|
wxGetApp().imgui()->reset_requires_extra_frame();
|
|
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
|
|
|
_refresh_if_shown_on_screen();
|
|
|
|
#if ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
|
if (m_extra_frame_requested || mouse3d_controller_applied || imgui_requires_extra_frame || wxGetApp().imgui()->requires_extra_frame()) {
|
|
#else
|
|
if (m_extra_frame_requested || mouse3d_controller_applied) {
|
|
m_dirty = true;
|
|
#endif // ENABLE_ENHANCED_IMGUI_SLIDER_FLOAT
|
|
m_extra_frame_requested = false;
|
|
evt.RequestMore();
|
|
}
|
|
else
|
|
m_dirty = false;
|
|
}
|
|
|
|
void GLCanvas3D::on_char(wxKeyEvent& evt)
|
|
{
|
|
if (!m_initialized)
|
|
return;
|
|
|
|
// see include/wx/defs.h enum wxKeyCode
|
|
int keyCode = evt.GetKeyCode();
|
|
int ctrlMask = wxMOD_CONTROL;
|
|
int shiftMask = wxMOD_SHIFT;
|
|
|
|
auto imgui = wxGetApp().imgui();
|
|
if (imgui->update_key_data(evt)) {
|
|
render();
|
|
return;
|
|
}
|
|
|
|
bool is_in_painting_mode = false;
|
|
GLGizmoPainterBase *current_gizmo_painter = dynamic_cast<GLGizmoPainterBase *>(get_gizmos_manager().get_current());
|
|
if (current_gizmo_painter != nullptr) {
|
|
is_in_painting_mode = true;
|
|
}
|
|
|
|
//BBS: add orient deactivate logic
|
|
if (keyCode == WXK_ESCAPE
|
|
&& (_deactivate_arrange_menu() || _deactivate_orient_menu() || _deactivate_layersediting_menu()))
|
|
return;
|
|
|
|
if (m_gizmos.on_char(evt))
|
|
return;
|
|
|
|
if ((evt.GetModifiers() & ctrlMask) != 0) {
|
|
// CTRL is pressed
|
|
switch (keyCode) {
|
|
#ifdef __APPLE__
|
|
case 'a':
|
|
case 'A':
|
|
#else /* __APPLE__ */
|
|
case WXK_CONTROL_A:
|
|
#endif /* __APPLE__ */
|
|
if (!is_in_painting_mode && !m_layers_editing.is_enabled())
|
|
post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL));
|
|
break;
|
|
#ifdef __APPLE__
|
|
case 'c':
|
|
case 'C':
|
|
#else /* __APPLE__ */
|
|
case WXK_CONTROL_C:
|
|
#endif /* __APPLE__ */
|
|
if (!is_in_painting_mode)
|
|
post_event(SimpleEvent(EVT_GLTOOLBAR_COPY));
|
|
break;
|
|
#ifdef __APPLE__
|
|
case 'm':
|
|
case 'M':
|
|
#else /* __APPLE__ */
|
|
case WXK_CONTROL_M:
|
|
#endif /* __APPLE__ */
|
|
{
|
|
#ifdef _WIN32
|
|
if (wxGetApp().app_config->get("use_legacy_3DConnexion") == "true") {
|
|
#endif //_WIN32
|
|
#ifdef __APPLE__
|
|
// On OSX use Cmd+Shift+M to "Show/Hide 3Dconnexion devices settings dialog"
|
|
if ((evt.GetModifiers() & shiftMask) != 0) {
|
|
#endif // __APPLE__
|
|
Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller();
|
|
controller.show_settings_dialog(!controller.is_settings_dialog_shown());
|
|
m_dirty = true;
|
|
#ifdef __APPLE__
|
|
}
|
|
else
|
|
// and Cmd+M to minimize application
|
|
wxGetApp().mainframe->Iconize();
|
|
#endif // __APPLE__
|
|
#ifdef _WIN32
|
|
}
|
|
#endif //_WIN32
|
|
break;
|
|
}
|
|
#ifdef __APPLE__
|
|
case 'v':
|
|
case 'V':
|
|
#else /* __APPLE__ */
|
|
case WXK_CONTROL_V:
|
|
#endif /* __APPLE__ */
|
|
if (!is_in_painting_mode)
|
|
post_event(SimpleEvent(EVT_GLTOOLBAR_PASTE));
|
|
break;
|
|
|
|
#ifdef __APPLE__
|
|
case 'x':
|
|
case 'X':
|
|
#else /* __APPLE__ */
|
|
case WXK_CONTROL_X:
|
|
#endif /* __APPLE__ */
|
|
if (!is_in_painting_mode)
|
|
post_event(SimpleEvent(EVT_GLTOOLBAR_CUT));
|
|
break;
|
|
|
|
#ifdef __APPLE__
|
|
case 'f':
|
|
case 'F':
|
|
#else /* __APPLE__ */
|
|
case WXK_CONTROL_F:
|
|
#endif /* __APPLE__ */
|
|
break;
|
|
|
|
|
|
#ifdef __APPLE__
|
|
case 'y':
|
|
case 'Y':
|
|
#else /* __APPLE__ */
|
|
case WXK_CONTROL_Y:
|
|
#endif /* __APPLE__ */
|
|
if (m_canvas_type == CanvasView3D || m_canvas_type == CanvasAssembleView) {
|
|
post_event(SimpleEvent(EVT_GLCANVAS_REDO));
|
|
}
|
|
break;
|
|
#ifdef __APPLE__
|
|
case 'z':
|
|
case 'Z':
|
|
#else /* __APPLE__ */
|
|
case WXK_CONTROL_Z:
|
|
#endif /* __APPLE__ */
|
|
// only support redu/undo in CanvasView3D
|
|
if (m_canvas_type == CanvasView3D || m_canvas_type == CanvasAssembleView) {
|
|
post_event(SimpleEvent(EVT_GLCANVAS_UNDO));
|
|
}
|
|
break;
|
|
|
|
// BBS
|
|
#ifdef __APPLE__
|
|
case 'E':
|
|
case 'e':
|
|
#else /* __APPLE__ */
|
|
case WXK_CONTROL_E:
|
|
#endif /* __APPLE__ */
|
|
{ m_labels.show(!m_labels.is_shown()); m_dirty = true; break; }
|
|
case '0': {
|
|
select_view("plate");
|
|
zoom_to_bed();
|
|
break; }
|
|
case '1': { select_view("top"); break; }
|
|
case '2': { select_view("bottom"); break; }
|
|
case '3': { select_view("front"); break; }
|
|
case '4': { select_view("rear"); break; }
|
|
case '5': { select_view("left"); break; }
|
|
case '6': { select_view("right"); break; }
|
|
case '7': { select_plate(); break; }
|
|
|
|
//case WXK_BACK:
|
|
//case WXK_DELETE:
|
|
#ifdef __APPLE__
|
|
case 'd':
|
|
case 'D':
|
|
#else /* __APPLE__ */
|
|
case WXK_CONTROL_D:
|
|
#endif /* __APPLE__ */
|
|
post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL));
|
|
break;
|
|
#ifdef __APPLE__
|
|
case 'k':
|
|
case 'K':
|
|
#else /* __APPLE__ */
|
|
case WXK_CONTROL_K:
|
|
#endif /* __APPLE__ */
|
|
post_event(SimpleEvent(EVT_GLTOOLBAR_CLONE));
|
|
break;
|
|
default: evt.Skip();
|
|
}
|
|
} else {
|
|
auto obj_list = wxGetApp().obj_list();
|
|
switch (keyCode)
|
|
{
|
|
//case WXK_BACK:
|
|
case WXK_DELETE: { post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; }
|
|
// BBS
|
|
#ifdef __APPLE__
|
|
case WXK_BACK: { post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; }
|
|
#endif
|
|
case WXK_ESCAPE: { deselect_all(); break; }
|
|
//case WXK_F5: {
|
|
// if ((wxGetApp().is_editor() && !wxGetApp().plater()->model().objects.empty()) ||
|
|
// (wxGetApp().is_gcode_viewer() && !wxGetApp().plater()->get_last_loaded_gcode().empty()))
|
|
// post_event(SimpleEvent(EVT_GLCANVAS_RELOAD_FROM_DISK));
|
|
// break;
|
|
//}
|
|
|
|
// BBS: use keypad to change extruder
|
|
case '1': {
|
|
if (!m_timer_set_color.IsRunning()) {
|
|
m_timer_set_color.StartOnce(500);
|
|
break;
|
|
}
|
|
}
|
|
case '0': //Color logic for material 10
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9': {
|
|
if (m_timer_set_color.IsRunning()) {
|
|
if (keyCode < '7') keyCode += 10;
|
|
m_timer_set_color.Stop();
|
|
}
|
|
if (m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation)
|
|
obj_list->set_extruder_for_selected_items(keyCode - '0');
|
|
break;
|
|
}
|
|
|
|
//case '+': {
|
|
// if (dynamic_cast<Preview*>(m_canvas->GetParent()) != nullptr)
|
|
// post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt));
|
|
// else
|
|
// post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, +1));
|
|
// break;
|
|
//}
|
|
//case '-': {
|
|
// if (dynamic_cast<Preview*>(m_canvas->GetParent()) != nullptr)
|
|
// post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt));
|
|
// else
|
|
// post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, -1));
|
|
// break;
|
|
//}
|
|
case '?': { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; }
|
|
case 'A':
|
|
case 'a':
|
|
{
|
|
if ((evt.GetModifiers() & shiftMask) != 0)
|
|
post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE_PARTPLATE));
|
|
else
|
|
post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE));
|
|
break;
|
|
}
|
|
case 'r':
|
|
case 'R':
|
|
{
|
|
if ((evt.GetModifiers() & shiftMask) != 0)
|
|
post_event(SimpleEvent(EVT_GLCANVAS_ORIENT_PARTPLATE));
|
|
else
|
|
post_event(SimpleEvent(EVT_GLCANVAS_ORIENT));
|
|
break;
|
|
}
|
|
//case 'B':
|
|
//case 'b': { zoom_to_bed(); break; }
|
|
#if !BBL_RELEASE_TO_PUBLIC
|
|
case 'C':
|
|
case 'c': { m_gcode_viewer.toggle_gcode_window_visibility(); m_dirty = true; request_extra_frame(); break; }
|
|
#endif
|
|
//case 'G':
|
|
//case 'g': {
|
|
// if ((evt.GetModifiers() & shiftMask) != 0) {
|
|
// if (dynamic_cast<Preview*>(m_canvas->GetParent()) != nullptr)
|
|
// post_event(wxKeyEvent(EVT_GLCANVAS_JUMP_TO, evt));
|
|
// }
|
|
// break;
|
|
//}
|
|
//case 'I':
|
|
//case 'i': { _update_camera_zoom(1.0); break; }
|
|
//case 'K':
|
|
//case 'k': { wxGetApp().plater()->get_camera().select_next_type(); m_dirty = true; break; }
|
|
//case 'L':
|
|
//case 'l': {
|
|
//if (!m_main_toolbar.is_enabled()) {
|
|
// m_gcode_viewer.enable_legend(!m_gcode_viewer.is_legend_enabled());
|
|
// m_dirty = true;
|
|
// wxGetApp().plater()->update_preview_bottom_toolbar();
|
|
//}
|
|
//break;
|
|
//}
|
|
//case 'O':
|
|
//case 'o': { _update_camera_zoom(-1.0); break; }
|
|
#if ENABLE_RENDER_PICKING_PASS
|
|
case 'T':
|
|
case 't': {
|
|
m_show_picking_texture = !m_show_picking_texture;
|
|
m_dirty = true;
|
|
break;
|
|
}
|
|
#endif // ENABLE_RENDER_PICKING_PASS
|
|
//case 'Z':
|
|
//case 'z': {
|
|
// if (!m_selection.is_empty())
|
|
// zoom_to_selection();
|
|
// else {
|
|
// if (!m_volumes.empty())
|
|
// zoom_to_volumes();
|
|
// else
|
|
// _zoom_to_box(m_gcode_viewer.get_paths_bounding_box());
|
|
// }
|
|
// break;
|
|
//}
|
|
default: { evt.Skip(); break; }
|
|
}
|
|
}
|
|
}
|
|
|
|
class TranslationProcessor
|
|
{
|
|
using UpAction = std::function<void(void)>;
|
|
using DownAction = std::function<void(const Vec3d&, bool, bool)>;
|
|
|
|
UpAction m_up_action{ nullptr };
|
|
DownAction m_down_action{ nullptr };
|
|
|
|
bool m_running{ false };
|
|
Vec3d m_direction{ Vec3d::UnitX() };
|
|
|
|
public:
|
|
TranslationProcessor(UpAction up_action, DownAction down_action)
|
|
: m_up_action(up_action), m_down_action(down_action)
|
|
{
|
|
}
|
|
|
|
void process(wxKeyEvent& evt)
|
|
{
|
|
const int keyCode = evt.GetKeyCode();
|
|
wxEventType type = evt.GetEventType();
|
|
if (type == wxEVT_KEY_UP) {
|
|
switch (keyCode)
|
|
{
|
|
case WXK_NUMPAD_LEFT: case WXK_LEFT:
|
|
case WXK_NUMPAD_RIGHT: case WXK_RIGHT:
|
|
case WXK_NUMPAD_UP: case WXK_UP:
|
|
case WXK_NUMPAD_DOWN: case WXK_DOWN:
|
|
{
|
|
m_running = false;
|
|
m_up_action();
|
|
break;
|
|
}
|
|
default: { break; }
|
|
}
|
|
}
|
|
else if (type == wxEVT_KEY_DOWN) {
|
|
bool apply = false;
|
|
|
|
switch (keyCode)
|
|
{
|
|
case WXK_SHIFT:
|
|
{
|
|
if (m_running)
|
|
apply = true;
|
|
|
|
break;
|
|
}
|
|
case WXK_NUMPAD_LEFT:
|
|
case WXK_LEFT:
|
|
{
|
|
m_direction = -Vec3d::UnitX();
|
|
apply = true;
|
|
break;
|
|
}
|
|
case WXK_NUMPAD_RIGHT:
|
|
case WXK_RIGHT:
|
|
{
|
|
m_direction = Vec3d::UnitX();
|
|
apply = true;
|
|
break;
|
|
}
|
|
case WXK_NUMPAD_UP:
|
|
case WXK_UP:
|
|
{
|
|
m_direction = Vec3d::UnitY();
|
|
apply = true;
|
|
break;
|
|
}
|
|
case WXK_NUMPAD_DOWN:
|
|
case WXK_DOWN:
|
|
{
|
|
m_direction = -Vec3d::UnitY();
|
|
apply = true;
|
|
break;
|
|
}
|
|
default: { break; }
|
|
}
|
|
|
|
if (apply) {
|
|
m_running = true;
|
|
m_down_action(m_direction, evt.ShiftDown(), evt.CmdDown());
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
void GLCanvas3D::on_key(wxKeyEvent& evt)
|
|
{
|
|
static GLCanvas3D const * thiz = nullptr;
|
|
static TranslationProcessor translationProcessor(nullptr, nullptr);
|
|
if (thiz != this) {
|
|
thiz = this;
|
|
translationProcessor = TranslationProcessor(
|
|
[this]() {
|
|
do_move(L("Tool Move"));
|
|
m_gizmos.update_data();
|
|
|
|
// BBS
|
|
//wxGetApp().obj_manipul()->set_dirty();
|
|
// Let the plater know that the dragging finished, so a delayed refresh
|
|
// of the scene with the background processing data should be performed.
|
|
post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
|
|
// updates camera target constraints
|
|
refresh_camera_scene_box();
|
|
m_dirty = true;
|
|
},
|
|
[this](const Vec3d& direction, bool slow, bool camera_space) {
|
|
m_selection.start_dragging();
|
|
double multiplier = slow ? 1.0 : 10.0;
|
|
|
|
Vec3d displacement;
|
|
if (camera_space) {
|
|
Eigen::Matrix<double, 3, 3, Eigen::DontAlign> inv_view_3x3 = wxGetApp().plater()->get_camera().get_view_matrix().inverse().matrix().block(0, 0, 3, 3);
|
|
displacement = multiplier * (inv_view_3x3 * direction);
|
|
displacement.z() = 0.0;
|
|
}
|
|
else
|
|
displacement = multiplier * direction;
|
|
|
|
m_selection.translate(displacement);
|
|
m_selection.stop_dragging();
|
|
m_dirty = true;
|
|
}
|
|
);}
|
|
|
|
const int keyCode = evt.GetKeyCode();
|
|
|
|
auto imgui = wxGetApp().imgui();
|
|
if (imgui->update_key_data(evt)) {
|
|
render();
|
|
}
|
|
else
|
|
{
|
|
if (!m_gizmos.on_key(evt)) {
|
|
if (evt.GetEventType() == wxEVT_KEY_UP) {
|
|
if (evt.ShiftDown() && evt.ControlDown() && keyCode == WXK_SPACE) {
|
|
#if !BBL_RELEASE_TO_PUBLIC
|
|
wxGetApp().plater()->toggle_render_statistic_dialog();
|
|
m_dirty = true;
|
|
#endif
|
|
} else if ((evt.ShiftDown() && evt.ControlDown() && keyCode == WXK_RETURN) ||
|
|
evt.ShiftDown() && evt.AltDown() && keyCode == WXK_RETURN) {
|
|
wxGetApp().plater()->toggle_show_wireframe();
|
|
m_dirty = true;
|
|
}
|
|
else if (m_tab_down && keyCode == WXK_TAB && !evt.HasAnyModifiers()) {
|
|
// Enable switching between 3D and Preview with Tab
|
|
// m_canvas->HandleAsNavigationKey(evt); // XXX: Doesn't work in some cases / on Linux
|
|
post_event(SimpleEvent(EVT_GLCANVAS_TAB));
|
|
}
|
|
else if (keyCode == WXK_SHIFT) {
|
|
translationProcessor.process(evt);
|
|
|
|
if (m_picking_enabled && m_rectangle_selection.is_dragging()) {
|
|
_update_selection_from_hover();
|
|
m_rectangle_selection.stop_dragging();
|
|
m_mouse.ignore_left_up = true;
|
|
m_dirty = true;
|
|
}
|
|
// set_cursor(Standard);
|
|
}
|
|
else if (keyCode == WXK_ALT) {
|
|
if (m_picking_enabled && m_rectangle_selection.is_dragging()) {
|
|
_update_selection_from_hover();
|
|
m_rectangle_selection.stop_dragging();
|
|
m_mouse.ignore_left_up = true;
|
|
m_dirty = true;
|
|
}
|
|
// set_cursor(Standard);
|
|
}
|
|
else if (keyCode == WXK_CONTROL)
|
|
m_dirty = true;
|
|
else if (m_gizmos.is_enabled() && !m_selection.is_empty() && m_canvas_type != CanvasAssembleView) {
|
|
translationProcessor.process(evt);
|
|
|
|
switch (keyCode)
|
|
{
|
|
case WXK_NUMPAD_PAGEUP: case WXK_PAGEUP:
|
|
case WXK_NUMPAD_PAGEDOWN: case WXK_PAGEDOWN:
|
|
{
|
|
if (m_gizmos.get_gizmo_active_condition(GLGizmosManager::EType::Rotate)) {
|
|
do_rotate(L("Tool Rotate"));
|
|
m_gizmos.update_data();
|
|
|
|
// BBS
|
|
// wxGetApp().obj_manipul()->set_dirty();
|
|
// Let the plater know that the dragging finished, so a delayed refresh
|
|
// of the scene with the background processing data should be performed.
|
|
post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
|
|
// updates camera target constraints
|
|
refresh_camera_scene_box();
|
|
m_dirty = true;
|
|
}
|
|
break;
|
|
}
|
|
default: { break; }
|
|
}
|
|
}
|
|
|
|
// BBS: add select view logic
|
|
if (evt.ControlDown()) {
|
|
switch (keyCode) {
|
|
case '0':
|
|
case WXK_NUMPAD0: //0 on numpad
|
|
{ select_view("plate");
|
|
zoom_to_bed();
|
|
break;
|
|
}
|
|
case '1':
|
|
case WXK_NUMPAD1: //1 on numpad
|
|
{ select_view("top"); break; }
|
|
case '2':
|
|
case WXK_NUMPAD2: //2 on numpad
|
|
{ select_view("bottom"); break; }
|
|
case '3':
|
|
case WXK_NUMPAD3: //3 on numpad
|
|
{ select_view("front"); break; }
|
|
case '4':
|
|
case WXK_NUMPAD4: //4 on numpad
|
|
{ select_view("rear"); break; }
|
|
case '5':
|
|
case WXK_NUMPAD5: //5 on numpad
|
|
{ select_view("left"); break; }
|
|
case '6':
|
|
case WXK_NUMPAD6: //6 on numpad
|
|
{ select_view("right"); break; }
|
|
case '7':
|
|
case WXK_NUMPAD7: //7 on numpad
|
|
{ select_plate(); break; }
|
|
default: break;
|
|
}
|
|
}
|
|
}
|
|
else if (evt.GetEventType() == wxEVT_KEY_DOWN) {
|
|
m_tab_down = keyCode == WXK_TAB && !evt.HasAnyModifiers();
|
|
if (keyCode == WXK_SHIFT) {
|
|
translationProcessor.process(evt);
|
|
|
|
if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports))
|
|
{
|
|
m_mouse.ignore_left_up = false;
|
|
// set_cursor(Cross);
|
|
}
|
|
}
|
|
else if (keyCode == WXK_ALT) {
|
|
if (m_picking_enabled && (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports))
|
|
{
|
|
m_mouse.ignore_left_up = false;
|
|
// set_cursor(Cross);
|
|
}
|
|
}
|
|
else if (keyCode == WXK_CONTROL)
|
|
m_dirty = true;
|
|
else if (keyCode == WXK_TAB && evt.ShiftDown() && !evt.ControlDown() && !wxGetApp().is_gcode_viewer()) {
|
|
// Collapse side-panel with Shift+Tab
|
|
post_event(SimpleEvent(EVT_GLCANVAS_COLLAPSE_SIDEBAR));
|
|
} else if (m_gizmos.is_enabled() && !m_selection.is_empty() && m_canvas_type != CanvasAssembleView) {
|
|
auto _do_rotate = [this](double angle_z_rad) {
|
|
if (!m_gizmos.get_gizmo_active_condition(GLGizmosManager::EType::Rotate)) {
|
|
return;
|
|
}
|
|
m_selection.start_dragging();
|
|
m_selection.rotate(Vec3d(0.0, 0.0, angle_z_rad), TransformationType(TransformationType::World_Relative_Joint));
|
|
m_selection.stop_dragging();
|
|
m_dirty = true;
|
|
// wxGetApp().obj_manipul()->set_dirty();
|
|
};
|
|
|
|
translationProcessor.process(evt);
|
|
|
|
switch (keyCode)
|
|
{
|
|
case WXK_NUMPAD_PAGEUP: case WXK_PAGEUP: { _do_rotate(0.25 * M_PI); break; }
|
|
case WXK_NUMPAD_PAGEDOWN: case WXK_PAGEDOWN: { _do_rotate(-0.25 * M_PI); break; }
|
|
default: { break; }
|
|
}
|
|
} else if (!m_gizmos.is_enabled()) {
|
|
// DoubleSlider navigation in Preview
|
|
if (m_canvas_type == CanvasPreview) {
|
|
IMSlider *m_layers_slider = get_gcode_viewer().get_layers_slider();
|
|
IMSlider *m_moves_slider = get_gcode_viewer().get_moves_slider();
|
|
if (evt.CmdDown() || evt.ShiftDown()) {
|
|
if (evt.GetKeyCode() == 'G') {
|
|
m_layers_slider->show_go_to_layer(true);
|
|
}
|
|
IMSlider *m_layers_slider = get_gcode_viewer().get_layers_slider();
|
|
IMSlider *m_moves_slider = get_gcode_viewer().get_moves_slider();
|
|
if (keyCode == WXK_UP || keyCode == WXK_DOWN) {
|
|
int new_pos;
|
|
if (m_layers_slider->GetSelection() == ssHigher) {
|
|
new_pos = keyCode == WXK_UP ? m_layers_slider->GetHigherValue() + 5 : m_layers_slider->GetHigherValue() - 5;
|
|
m_layers_slider->SetHigherValue(new_pos);
|
|
}
|
|
else if (m_layers_slider->GetSelection() == ssLower) {
|
|
new_pos = keyCode == WXK_UP ? m_layers_slider->GetLowerValue() + 5 : m_layers_slider->GetLowerValue() - 5;
|
|
m_layers_slider->SetLowerValue(new_pos);
|
|
}
|
|
if (m_layers_slider->is_one_layer()) m_layers_slider->SetLowerValue(m_layers_slider->GetHigherValue());
|
|
// BBS set as dirty, update in render_gcode()
|
|
m_layers_slider->set_as_dirty();
|
|
} else if (keyCode == WXK_LEFT || keyCode == WXK_RIGHT) {
|
|
const int new_pos = keyCode == WXK_RIGHT ? m_moves_slider->GetHigherValue() + 5 : m_moves_slider->GetHigherValue() - 5;
|
|
m_moves_slider->SetHigherValue(new_pos);
|
|
// BBS set as dirty, update in render_gcode()
|
|
m_moves_slider->set_as_dirty();
|
|
}
|
|
}
|
|
else if (keyCode == WXK_UP || keyCode == WXK_DOWN) {
|
|
int new_pos;
|
|
if (m_layers_slider->GetSelection() == ssHigher) {
|
|
new_pos = keyCode == WXK_UP ? m_layers_slider->GetHigherValue() + 1 : m_layers_slider->GetHigherValue() - 1;
|
|
m_layers_slider->SetHigherValue(new_pos);
|
|
}
|
|
else if (m_layers_slider->GetSelection() == ssLower) {
|
|
new_pos = keyCode == WXK_UP ? m_layers_slider->GetLowerValue() + 1 : m_layers_slider->GetLowerValue() - 1;
|
|
m_layers_slider->SetLowerValue(new_pos);
|
|
}
|
|
if (m_layers_slider->is_one_layer()) m_layers_slider->SetLowerValue(m_layers_slider->GetHigherValue());
|
|
// BBS set as dirty, update in render_gcode()
|
|
m_layers_slider->set_as_dirty();
|
|
} else if (keyCode == WXK_LEFT || keyCode == WXK_RIGHT) {
|
|
const int new_pos = keyCode == WXK_RIGHT ? m_moves_slider->GetHigherValue() + 1 : m_moves_slider->GetHigherValue() - 1;
|
|
m_moves_slider->SetHigherValue(new_pos);
|
|
// BBS set as dirty, update in render_gcode()
|
|
m_moves_slider->set_as_dirty();
|
|
}
|
|
m_dirty = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else return;
|
|
}
|
|
|
|
if (keyCode != WXK_TAB
|
|
&& keyCode != WXK_LEFT
|
|
&& keyCode != WXK_UP
|
|
&& keyCode != WXK_RIGHT
|
|
&& keyCode != WXK_DOWN) {
|
|
evt.Skip(); // Needed to have EVT_CHAR generated as well
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
|
|
{
|
|
#ifdef WIN32
|
|
// Try to filter out spurious mouse wheel events comming from 3D mouse.
|
|
if (wxGetApp().plater()->get_mouse3d_controller().process_mouse_wheel())
|
|
return;
|
|
#endif
|
|
|
|
if (!m_initialized)
|
|
return;
|
|
|
|
// Ignore the wheel events if the middle button is pressed.
|
|
if (evt.MiddleIsDown())
|
|
return;
|
|
|
|
#if ENABLE_RETINA_GL
|
|
const float scale = m_retina_helper->get_scale_factor();
|
|
evt.SetX(evt.GetX() * scale);
|
|
evt.SetY(evt.GetY() * scale);
|
|
#endif
|
|
|
|
if (wxGetApp().imgui()->update_mouse_data(evt)) {
|
|
if (m_canvas_type == CanvasPreview) {
|
|
IMSlider* m_layers_slider = get_gcode_viewer().get_layers_slider();
|
|
IMSlider* m_moves_slider = get_gcode_viewer().get_moves_slider();
|
|
m_layers_slider->on_mouse_wheel(evt);
|
|
m_moves_slider->on_mouse_wheel(evt);
|
|
}
|
|
render();
|
|
m_dirty = true;
|
|
return;
|
|
}
|
|
|
|
#ifdef __WXMSW__
|
|
// For some reason the Idle event is not being generated after the mouse scroll event in case of scrolling with the two fingers on the touch pad,
|
|
// if the event is not allowed to be passed further.
|
|
// evt.Skip() used to trigger the needed screen refresh, but it does no more. wxWakeUpIdle() seem to work now.
|
|
wxWakeUpIdle();
|
|
#endif /* __WXMSW__ */
|
|
|
|
// Performs layers editing updates, if enabled
|
|
if (is_layers_editing_enabled()) {
|
|
int object_idx_selected = m_selection.get_object_idx();
|
|
if (object_idx_selected != -1) {
|
|
// A volume is selected. Test, whether hovering over a layer thickness bar.
|
|
if (m_layers_editing.bar_rect_contains(*this, (float)evt.GetX(), (float)evt.GetY())) {
|
|
// Adjust the width of the selection.
|
|
m_layers_editing.band_width = std::max(std::min(m_layers_editing.band_width * (1.0f + 0.1f * (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta()), 10.0f), 1.5f);
|
|
if (m_canvas != nullptr)
|
|
m_canvas->Refresh();
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Inform gizmos about the event so they have the opportunity to react.
|
|
if (m_gizmos.on_mouse_wheel(evt))
|
|
return;
|
|
|
|
if (m_canvas_type == CanvasAssembleView && (evt.AltDown() || evt.CmdDown())) {
|
|
float rotation = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta();
|
|
if (evt.AltDown()) {
|
|
auto clp_dist = m_gizmos.m_assemble_view_data->model_objects_clipper()->get_position();
|
|
clp_dist = rotation < 0.f
|
|
? std::max(0., clp_dist - 0.01)
|
|
: std::min(1., clp_dist + 0.01);
|
|
m_gizmos.m_assemble_view_data->model_objects_clipper()->set_position(clp_dist, true);
|
|
}
|
|
else if (evt.CmdDown()) {
|
|
m_explosion_ratio = rotation < 0.f
|
|
? std::max(1., m_explosion_ratio - 0.01)
|
|
: std::min(3., m_explosion_ratio + 0.01);
|
|
if (m_explosion_ratio != GLVolume::explosion_ratio) {
|
|
for (GLVolume* volume : m_volumes.volumes) {
|
|
volume->set_bounding_boxes_as_dirty();
|
|
}
|
|
GLVolume::explosion_ratio = m_explosion_ratio;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
// Calculate the zoom delta and apply it to the current zoom factor
|
|
#ifdef SUPPORT_REVERSE_MOUSE_ZOOM
|
|
double direction_factor = (wxGetApp().app_config->get("reverse_mouse_wheel_zoom") == "1") ? -1.0 : 1.0;
|
|
#else
|
|
double direction_factor = 1.0;
|
|
#endif
|
|
auto delta = direction_factor * (double)evt.GetWheelRotation() / (double)evt.GetWheelDelta();
|
|
bool zoom_to_mouse = wxGetApp().app_config->get("zoom_to_mouse") == "true";
|
|
if (!zoom_to_mouse) {// zoom to center
|
|
_update_camera_zoom(delta);
|
|
}
|
|
else {
|
|
auto cnv_size = get_canvas_size();
|
|
auto screen_center_3d_pos = _mouse_to_3d({ cnv_size.get_width() * 0.5, cnv_size.get_height() * 0.5 });
|
|
auto mouse_3d_pos = _mouse_to_3d({evt.GetX(), evt.GetY()});
|
|
Vec3d displacement = mouse_3d_pos - screen_center_3d_pos;
|
|
wxGetApp().plater()->get_camera().translate(displacement);
|
|
auto origin_zoom = wxGetApp().plater()->get_camera().get_zoom();
|
|
_update_camera_zoom(delta);
|
|
auto new_zoom = wxGetApp().plater()->get_camera().get_zoom();
|
|
wxGetApp().plater()->get_camera().translate((-displacement) / (new_zoom / origin_zoom));
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::on_timer(wxTimerEvent& evt)
|
|
{
|
|
if (m_layers_editing.state == LayersEditing::Editing)
|
|
_perform_layer_editing_action();
|
|
}
|
|
|
|
void GLCanvas3D::on_render_timer(wxTimerEvent& evt)
|
|
{
|
|
// no need to wake up idle
|
|
// right after this event, idle event is fired
|
|
// m_dirty = true;
|
|
// wxWakeUpIdle();
|
|
}
|
|
|
|
void GLCanvas3D::on_set_color_timer(wxTimerEvent& evt)
|
|
{
|
|
auto obj_list = wxGetApp().obj_list();
|
|
if (m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation)
|
|
obj_list->set_extruder_for_selected_items(1);
|
|
m_timer_set_color.Stop();
|
|
}
|
|
|
|
|
|
void GLCanvas3D::schedule_extra_frame(int miliseconds)
|
|
{
|
|
// Schedule idle event right now
|
|
if (miliseconds == 0)
|
|
{
|
|
// We want to wakeup idle evnt but most likely this is call inside render cycle so we need to wait
|
|
if (m_in_render)
|
|
miliseconds = 33;
|
|
else {
|
|
m_dirty = true;
|
|
wxWakeUpIdle();
|
|
return;
|
|
}
|
|
}
|
|
int remaining_time = m_render_timer.GetInterval();
|
|
// Timer is not running
|
|
if (!m_render_timer.IsRunning()) {
|
|
m_render_timer.StartOnce(miliseconds);
|
|
// Timer is running - restart only if new period is shorter than remaning period
|
|
} else {
|
|
if (miliseconds + 20 < remaining_time) {
|
|
m_render_timer.Stop();
|
|
m_render_timer.StartOnce(miliseconds);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
// #define SLIC3R_DEBUG_MOUSE_EVENTS
|
|
#endif
|
|
|
|
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
|
|
std::string format_mouse_event_debug_message(const wxMouseEvent &evt)
|
|
{
|
|
static int idx = 0;
|
|
char buf[2048];
|
|
std::string out;
|
|
sprintf(buf, "Mouse Event %d - ", idx ++);
|
|
out = buf;
|
|
|
|
if (evt.Entering())
|
|
out += "Entering ";
|
|
if (evt.Leaving())
|
|
out += "Leaving ";
|
|
if (evt.Dragging())
|
|
out += "Dragging ";
|
|
if (evt.Moving())
|
|
out += "Moving ";
|
|
if (evt.Magnify())
|
|
out += "Magnify ";
|
|
if (evt.LeftDown())
|
|
out += "LeftDown ";
|
|
if (evt.LeftUp())
|
|
out += "LeftUp ";
|
|
if (evt.LeftDClick())
|
|
out += "LeftDClick ";
|
|
if (evt.MiddleDown())
|
|
out += "MiddleDown ";
|
|
if (evt.MiddleUp())
|
|
out += "MiddleUp ";
|
|
if (evt.MiddleDClick())
|
|
out += "MiddleDClick ";
|
|
if (evt.RightDown())
|
|
out += "RightDown ";
|
|
if (evt.RightUp())
|
|
out += "RightUp ";
|
|
if (evt.RightDClick())
|
|
out += "RightDClick ";
|
|
|
|
sprintf(buf, "(%d, %d)", evt.GetX(), evt.GetY());
|
|
out += buf;
|
|
return out;
|
|
}
|
|
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
|
|
|
|
void GLCanvas3D::on_gesture(wxGestureEvent &evt)
|
|
{
|
|
if (!m_initialized || !_set_current())
|
|
return;
|
|
|
|
auto & camera = wxGetApp().plater()->get_camera();
|
|
if (evt.GetEventType() == wxEVT_GESTURE_PAN) {
|
|
auto p = evt.GetPosition();
|
|
auto d = static_cast<wxPanGestureEvent&>(evt).GetDelta();
|
|
float z = 0;
|
|
const Vec3d &p2 = _mouse_to_3d({p.x, p.y}, &z);
|
|
const Vec3d &p1 = _mouse_to_3d({p.x - d.x, p.y - d.y}, &z);
|
|
camera.set_target(camera.get_target() + p1 - p2);
|
|
} else if (evt.GetEventType() == wxEVT_GESTURE_ZOOM) {
|
|
static float zoom_start = 1;
|
|
if (evt.IsGestureStart())
|
|
zoom_start = camera.get_zoom();
|
|
camera.set_zoom(zoom_start * static_cast<wxZoomGestureEvent&>(evt).GetZoomFactor());
|
|
} else if (evt.GetEventType() == wxEVT_GESTURE_ROTATE) {
|
|
PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
|
bool rotate_limit = current_printer_technology() != ptSLA;
|
|
static double last_rotate = 0;
|
|
if (evt.IsGestureStart())
|
|
last_rotate = 0;
|
|
auto rotate = static_cast<wxRotateGestureEvent&>(evt).GetRotationAngle() - last_rotate;
|
|
last_rotate += rotate;
|
|
if (plate)
|
|
camera.rotate_on_sphere_with_target(-rotate, 0, rotate_limit, plate->get_bounding_box().center());
|
|
else
|
|
camera.rotate_on_sphere(-rotate, 0, rotate_limit);
|
|
}
|
|
m_dirty = true;
|
|
}
|
|
|
|
void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|
{
|
|
if (!m_initialized || !_set_current())
|
|
return;
|
|
|
|
// BBS: single snapshot
|
|
Plater::SingleSnapshot single(wxGetApp().plater());
|
|
|
|
#if ENABLE_RETINA_GL
|
|
const float scale = m_retina_helper->get_scale_factor();
|
|
evt.SetX(evt.GetX() * scale);
|
|
evt.SetY(evt.GetY() * scale);
|
|
#endif
|
|
|
|
Point pos(evt.GetX(), evt.GetY());
|
|
|
|
ImGuiWrapper* imgui = wxGetApp().imgui();
|
|
if (m_tooltip.is_in_imgui() && evt.LeftUp())
|
|
// ignore left up events coming from imgui windows and not processed by them
|
|
m_mouse.ignore_left_up = true;
|
|
m_tooltip.set_in_imgui(false);
|
|
if (imgui->update_mouse_data(evt)) {
|
|
if (evt.LeftDown() && m_canvas != nullptr)
|
|
m_canvas->SetFocus();
|
|
m_mouse.position = evt.Leaving() ? Vec2d(-1.0, -1.0) : pos.cast<double>();
|
|
m_tooltip.set_in_imgui(true);
|
|
render();
|
|
#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;
|
|
// do not return if dragging or tooltip not empty to allow for tooltip update
|
|
// 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;
|
|
}
|
|
|
|
#ifdef __WXMSW__
|
|
bool on_enter_workaround = false;
|
|
if (! evt.Entering() && ! evt.Leaving() && m_mouse.position.x() == -1.0) {
|
|
// Workaround for SPE-832: There seems to be a mouse event sent to the window before evt.Entering()
|
|
m_mouse.position = pos.cast<double>();
|
|
render();
|
|
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
|
|
printf((format_mouse_event_debug_message(evt) + " - OnEnter workaround\n").c_str());
|
|
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
|
|
on_enter_workaround = true;
|
|
} else
|
|
#endif /* __WXMSW__ */
|
|
{
|
|
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
|
|
printf((format_mouse_event_debug_message(evt) + " - other\n").c_str());
|
|
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
|
|
}
|
|
|
|
if (m_main_toolbar.on_mouse(evt, *this)) {
|
|
if (m_main_toolbar.is_any_item_pressed())
|
|
m_gizmos.reset_all_states();
|
|
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
|
|
mouse_up_cleanup();
|
|
m_mouse.set_start_position_3D_as_invalid();
|
|
return;
|
|
}
|
|
|
|
//BBS: GUI refactor: GLToolbar
|
|
if (m_assemble_view_toolbar.on_mouse(evt, *this)) {
|
|
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
|
|
mouse_up_cleanup();
|
|
m_mouse.set_start_position_3D_as_invalid();
|
|
return;
|
|
}
|
|
|
|
if (wxGetApp().plater()->get_collapse_toolbar().on_mouse(evt, *this)) {
|
|
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
|
|
mouse_up_cleanup();
|
|
m_mouse.set_start_position_3D_as_invalid();
|
|
return;
|
|
}
|
|
|
|
// BBS
|
|
#if 0
|
|
if (wxGetApp().plater()->get_view_toolbar().on_mouse(evt, *this)) {
|
|
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
|
|
mouse_up_cleanup();
|
|
m_mouse.set_start_position_3D_as_invalid();
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
for (GLVolume* volume : m_volumes.volumes) {
|
|
volume->force_sinking_contours = false;
|
|
}
|
|
|
|
auto show_sinking_contours = [this]() {
|
|
const Selection::IndicesList& idxs = m_selection.get_volume_idxs();
|
|
for (unsigned int idx : idxs) {
|
|
m_volumes.volumes[idx]->force_sinking_contours = true;
|
|
}
|
|
m_dirty = true;
|
|
};
|
|
|
|
if (m_gizmos.on_mouse(evt)) {
|
|
if (m_gizmos.is_running()) {
|
|
_deactivate_arrange_menu();
|
|
_deactivate_orient_menu();
|
|
_deactivate_layersediting_menu();
|
|
}
|
|
if (wxWindow::FindFocus() != m_canvas)
|
|
// Grab keyboard focus for input in gizmo dialogs.
|
|
m_canvas->SetFocus();
|
|
|
|
if (evt.LeftDown()) {
|
|
// Clear hover state in main toolbar
|
|
wxMouseEvent evt2 = evt;
|
|
evt2.SetEventType(wxEVT_MOTION);
|
|
evt2.SetLeftDown(false);
|
|
m_main_toolbar.on_mouse(evt2, *this);
|
|
}
|
|
|
|
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
|
|
mouse_up_cleanup();
|
|
|
|
m_mouse.set_start_position_3D_as_invalid();
|
|
m_mouse.position = pos.cast<double>();
|
|
|
|
if (evt.Dragging() && current_printer_technology() == ptFFF && (fff_print()->config().print_sequence == PrintSequence::ByObject)) {
|
|
switch (m_gizmos.get_current_type())
|
|
{
|
|
case GLGizmosManager::EType::Move:
|
|
case GLGizmosManager::EType::Scale:
|
|
case GLGizmosManager::EType::Rotate:
|
|
{
|
|
update_sequential_clearance();
|
|
break;
|
|
}
|
|
default: { break; }
|
|
}
|
|
}
|
|
else if (evt.Dragging()) {
|
|
switch (m_gizmos.get_current_type())
|
|
{
|
|
case GLGizmosManager::EType::Move:
|
|
case GLGizmosManager::EType::Scale:
|
|
case GLGizmosManager::EType::Rotate:
|
|
{
|
|
show_sinking_contours();
|
|
break;
|
|
}
|
|
default: { break; }
|
|
}
|
|
}
|
|
else if (evt.LeftUp() &&
|
|
m_gizmos.get_current_type() == GLGizmosManager::EType::Scale &&
|
|
m_gizmos.get_current()->get_state() == GLGizmoBase::EState::On) {
|
|
wxGetApp().obj_list()->selection_changed();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
bool any_gizmo_active = m_gizmos.get_current() != nullptr;
|
|
|
|
int selected_object_idx = m_selection.get_object_idx();
|
|
int layer_editing_object_idx = is_layers_editing_enabled() ? selected_object_idx : -1;
|
|
|
|
|
|
if (m_mouse.drag.move_requires_threshold && m_mouse.is_move_start_threshold_position_2D_defined() && m_mouse.is_move_threshold_met(pos)) {
|
|
m_mouse.drag.move_requires_threshold = false;
|
|
m_mouse.set_move_start_threshold_position_2D_as_invalid();
|
|
}
|
|
|
|
if (evt.ButtonDown() && wxWindow::FindFocus() != m_canvas)
|
|
// Grab keyboard focus on any mouse click event.
|
|
m_canvas->SetFocus();
|
|
|
|
if (evt.Entering()) {
|
|
//#if defined(__WXMSW__) || defined(__linux__)
|
|
// // On Windows and Linux needs focus in order to catch key events
|
|
// Set focus in order to remove it from sidebar fields
|
|
if (m_canvas != nullptr) {
|
|
// Only set focus, if the top level window of this canvas is active.
|
|
auto p = dynamic_cast<wxWindow*>(evt.GetEventObject());
|
|
while (p->GetParent())
|
|
p = p->GetParent();
|
|
auto *top_level_wnd = dynamic_cast<wxTopLevelWindow*>(p);
|
|
if (top_level_wnd && top_level_wnd->IsActive() && !wxGetApp().get_side_menu_popup_status())
|
|
;// m_canvas->SetFocus();
|
|
m_mouse.position = pos.cast<double>();
|
|
m_tooltip_enabled = false;
|
|
// 1) forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while
|
|
// the context menu is shown, ensuring it to disappear if the mouse is outside any volume and to
|
|
// change the volume hover state if any is under the mouse
|
|
// 2) when switching between 3d view and preview the size of the canvas changes if the side panels are visible,
|
|
// so forces a resize to avoid multiple renders with different sizes (seen as flickering)
|
|
_refresh_if_shown_on_screen();
|
|
m_tooltip_enabled = true;
|
|
}
|
|
m_mouse.set_start_position_2D_as_invalid();
|
|
//#endif
|
|
}
|
|
else if (evt.Leaving()) {
|
|
// to remove hover on objects when the mouse goes out of this canvas
|
|
m_mouse.position = Vec2d(-1.0, -1.0);
|
|
m_dirty = true;
|
|
}
|
|
else if (evt.LeftDClick()) {
|
|
// switch to object panel if double click on object, otherwise switch to global panel if double click on background
|
|
if (selected_object_idx >= 0)
|
|
post_event(SimpleEvent(EVT_GLCANVAS_SWITCH_TO_OBJECT));
|
|
else
|
|
post_event(SimpleEvent(EVT_GLCANVAS_SWITCH_TO_GLOBAL));
|
|
}
|
|
else if (evt.LeftDown() || evt.RightDown() || evt.MiddleDown()) {
|
|
//BBS: add orient deactivate logic
|
|
if (!m_gizmos.on_mouse(evt)) {
|
|
if (_deactivate_arrange_menu() || _deactivate_orient_menu())
|
|
return;
|
|
}
|
|
|
|
// If user pressed left or right button we first check whether this happened on a volume or not.
|
|
m_layers_editing.state = LayersEditing::Unknown;
|
|
if (layer_editing_object_idx != -1 && m_layers_editing.bar_rect_contains(*this, pos(0), pos(1))) {
|
|
// A volume is selected and the mouse is inside the layer thickness bar.
|
|
// Start editing the layer height.
|
|
m_layers_editing.state = LayersEditing::Editing;
|
|
_perform_layer_editing_action(&evt);
|
|
}
|
|
|
|
else {
|
|
// BBS: define Alt key to enable volume selection mode
|
|
m_selection.set_volume_selection_mode(evt.AltDown() ? Selection::Volume : Selection::Instance);
|
|
if (evt.LeftDown() && evt.ShiftDown() && m_picking_enabled && m_layers_editing.state != LayersEditing::Editing) {
|
|
if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports
|
|
&& m_gizmos.get_current_type() != GLGizmosManager::FdmSupports
|
|
&& m_gizmos.get_current_type() != GLGizmosManager::Seam
|
|
&& m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation) {
|
|
m_rectangle_selection.start_dragging(m_mouse.position, evt.ShiftDown() ? GLSelectionRectangle::Select : GLSelectionRectangle::Deselect);
|
|
m_dirty = true;
|
|
}
|
|
}
|
|
else {
|
|
// Select volume in this 3D canvas.
|
|
// Don't deselect a volume if layer editing is enabled or any gizmo is active. We want the object to stay selected
|
|
// during the scene manipulation.
|
|
|
|
if (m_picking_enabled && (!any_gizmo_active || !evt.CmdDown()) && (!m_hover_volume_idxs.empty())) {
|
|
if (evt.LeftDown() && !m_hover_volume_idxs.empty()) {
|
|
int volume_idx = get_first_hover_volume_idx();
|
|
bool already_selected = m_selection.contains_volume(volume_idx);
|
|
bool ctrl_down = evt.CmdDown();
|
|
|
|
Selection::IndicesList curr_idxs = m_selection.get_volume_idxs();
|
|
|
|
if (already_selected && ctrl_down)
|
|
m_selection.remove(volume_idx);
|
|
else {
|
|
m_selection.add(volume_idx, !ctrl_down, true);
|
|
m_mouse.drag.move_requires_threshold = !already_selected;
|
|
if (already_selected)
|
|
m_mouse.set_move_start_threshold_position_2D_as_invalid();
|
|
else
|
|
m_mouse.drag.move_start_threshold_position_2D = pos;
|
|
}
|
|
|
|
// propagate event through callback
|
|
if (curr_idxs != m_selection.get_volume_idxs()) {
|
|
if (m_selection.is_empty())
|
|
m_gizmos.reset_all_states();
|
|
else
|
|
m_gizmos.refresh_on_off_state();
|
|
|
|
m_gizmos.update_data();
|
|
post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
|
|
m_dirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!m_hover_volume_idxs.empty()) {
|
|
if (evt.LeftDown() && m_moving_enabled && m_mouse.drag.move_volume_idx == -1) {
|
|
// Only accept the initial position, if it is inside the volume bounding box.
|
|
int volume_idx = get_first_hover_volume_idx();
|
|
BoundingBoxf3 volume_bbox = m_volumes.volumes[volume_idx]->transformed_bounding_box();
|
|
volume_bbox.offset(1.0);
|
|
if ((!any_gizmo_active || !evt.CmdDown()) && volume_bbox.contains(m_mouse.scene_position)) {
|
|
m_volumes.volumes[volume_idx]->hover = GLVolume::HS_None;
|
|
// The dragging operation is initiated.
|
|
m_mouse.drag.move_volume_idx = volume_idx;
|
|
m_selection.start_dragging();
|
|
m_mouse.drag.start_position_3D = m_mouse.scene_position;
|
|
m_sequential_print_clearance_first_displacement = true;
|
|
m_moving = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (evt.Dragging() && evt.LeftIsDown() && m_mouse.drag.move_volume_idx != -1 && m_layers_editing.state == LayersEditing::Unknown) {
|
|
if (m_canvas_type != ECanvasType::CanvasAssembleView) {
|
|
if (!m_mouse.drag.move_requires_threshold) {
|
|
m_mouse.dragging = true;
|
|
Vec3d cur_pos = m_mouse.drag.start_position_3D;
|
|
// we do not want to translate objects if the user just clicked on an object while pressing shift to remove it from the selection and then drag
|
|
if (m_selection.contains_volume(get_first_hover_volume_idx())) {
|
|
const Camera& camera = wxGetApp().plater()->get_camera();
|
|
if (std::abs(camera.get_dir_forward()(2)) < EPSILON) {
|
|
// side view -> move selected volumes orthogonally to camera view direction
|
|
Linef3 ray = mouse_ray(pos);
|
|
Vec3d dir = 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 = ray.a + (m_mouse.drag.start_position_3D - ray.a).dot(dir) / dir.squaredNorm() * dir;
|
|
// vector from the starting position to the found intersection
|
|
Vec3d inters_vec = inters - m_mouse.drag.start_position_3D;
|
|
|
|
Vec3d camera_right = camera.get_dir_right();
|
|
Vec3d camera_up = camera.get_dir_up();
|
|
|
|
// finds projection of the vector along the camera axes
|
|
double projection_x = inters_vec.dot(camera_right);
|
|
double projection_z = inters_vec.dot(camera_up);
|
|
|
|
// apply offset
|
|
cur_pos = m_mouse.drag.start_position_3D + projection_x * camera_right + projection_z * camera_up;
|
|
}
|
|
else {
|
|
// Generic view
|
|
// Get new position at the same Z of the initial click point.
|
|
float z0 = 0.0f;
|
|
float z1 = 1.0f;
|
|
cur_pos = Linef3(_mouse_to_3d(pos, &z0), _mouse_to_3d(pos, &z1)).intersect_plane(m_mouse.drag.start_position_3D(2));
|
|
}
|
|
}
|
|
|
|
m_selection.translate(cur_pos - m_mouse.drag.start_position_3D);
|
|
if (current_printer_technology() == ptFFF && (fff_print()->config().print_sequence == PrintSequence::ByObject))
|
|
update_sequential_clearance();
|
|
// BBS
|
|
//wxGetApp().obj_manipul()->set_dirty();
|
|
m_dirty = true;
|
|
}
|
|
}
|
|
}
|
|
else if (evt.Dragging() && evt.LeftIsDown() && m_picking_enabled && m_rectangle_selection.is_dragging()) {
|
|
//BBS not in assemble view
|
|
if (m_canvas_type != ECanvasType::CanvasAssembleView) {
|
|
m_rectangle_selection.dragging(pos.cast<double>());
|
|
m_dirty = true;
|
|
}
|
|
}
|
|
else if (evt.Dragging()) {
|
|
m_mouse.dragging = true;
|
|
|
|
if (m_layers_editing.state != LayersEditing::Unknown && layer_editing_object_idx != -1) {
|
|
if (m_layers_editing.state == LayersEditing::Editing) {
|
|
_perform_layer_editing_action(&evt);
|
|
m_mouse.position = pos.cast<double>();
|
|
}
|
|
}
|
|
// do not process the dragging if the left mouse was set down in another canvas
|
|
else if (evt.LeftIsDown()) {
|
|
// if dragging over blank area with left button, rotate
|
|
if ((any_gizmo_active || m_hover_volume_idxs.empty()) && m_mouse.is_start_position_3D_defined()) {
|
|
const Vec3d rot = (Vec3d(pos.x(), pos.y(), 0.) - m_mouse.drag.start_position_3D) * (PI * TRACKBALLSIZE / 180.);
|
|
if (this->m_canvas_type == ECanvasType::CanvasAssembleView || m_gizmos.get_current_type() == GLGizmosManager::FdmSupports ||
|
|
m_gizmos.get_current_type() == GLGizmosManager::Seam || m_gizmos.get_current_type() == GLGizmosManager::MmuSegmentation) {
|
|
//BBS rotate around target
|
|
Camera& camera = wxGetApp().plater()->get_camera();
|
|
Vec3d rotate_target = Vec3d::Zero();
|
|
if (!m_selection.is_empty())
|
|
rotate_target = m_selection.get_bounding_box().center();
|
|
else
|
|
rotate_target = volumes_bounding_box().center();
|
|
//BBS do not limit rotate in assemble view
|
|
camera.rotate_local_with_target(Vec3d(rot.y(), rot.x(), 0.), rotate_target);
|
|
//camera.rotate_on_sphere_with_target(rot.x(), rot.y(), false, rotate_target);
|
|
}
|
|
else {
|
|
#ifdef SUPPORT_FEEE_CAMERA
|
|
if (wxGetApp().app_config->get("use_free_camera") == "1")
|
|
// Virtual track ball (similar to the 3DConnexion mouse).
|
|
wxGetApp().plater()->get_camera().rotate_local_around_target(Vec3d(rot.y(), rot.x(), 0.));
|
|
else {
|
|
#endif
|
|
// Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation.
|
|
// It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(),
|
|
// which checks an atomics (flushes CPU caches).
|
|
// See GH issue #3816.
|
|
Camera& camera = wxGetApp().plater()->get_camera();
|
|
|
|
bool rotate_limit = current_printer_technology() != ptSLA;
|
|
Vec3d rotate_target = m_selection.get_bounding_box().center();
|
|
|
|
camera.recover_from_free_camera();
|
|
//BBS modify rotation
|
|
//if (m_gizmos.get_current_type() == GLGizmosManager::FdmSupports
|
|
// || m_gizmos.get_current_type() == GLGizmosManager::Seam
|
|
// || m_gizmos.get_current_type() == GLGizmosManager::MmuSegmentation) {
|
|
// //camera.rotate_local_with_target(Vec3d(rot.y(), rot.x(), 0.), rotate_target);
|
|
// //camera.rotate_on_sphere_with_target(rot.x(), rot.y(), rotate_limit, rotate_target);
|
|
//}
|
|
//else
|
|
if (evt.ControlDown() || evt.CmdDown()) {
|
|
if ((m_rotation_center.x() == 0.f) && (m_rotation_center.y() == 0.f) && (m_rotation_center.z() == 0.f)) {
|
|
auto canvas_w = float(get_canvas_size().get_width());
|
|
auto canvas_h = float(get_canvas_size().get_height());
|
|
Point screen_center(canvas_w/2, canvas_h/2);
|
|
//camera.rotate_on_sphere_with_target(rot.x(), rot.y(), rotate_limit, wxGetApp().plater()->get_partplate_list().get_bounding_box().center());
|
|
m_rotation_center = _mouse_to_3d(screen_center);
|
|
m_rotation_center(2) = 0.f;
|
|
}
|
|
camera.rotate_on_sphere_with_target(rot.x(), rot.y(), rotate_limit, m_rotation_center);
|
|
} else {
|
|
//BBS rotate with current plate center
|
|
PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
|
if (plate)
|
|
camera.rotate_on_sphere_with_target(rot.x(), rot.y(), rotate_limit, plate->get_bounding_box().center());
|
|
else
|
|
camera.rotate_on_sphere(rot.x(), rot.y(), rotate_limit);
|
|
}
|
|
#ifdef SUPPORT_FEEE_CAMERA
|
|
}
|
|
#endif
|
|
}
|
|
|
|
m_dirty = true;
|
|
}
|
|
m_mouse.drag.start_position_3D = Vec3d((double)pos(0), (double)pos(1), 0.0);
|
|
}
|
|
else if (evt.MiddleIsDown() || evt.RightIsDown()) {
|
|
// If dragging over blank area with right button, pan.
|
|
if (m_mouse.is_start_position_2D_defined()) {
|
|
// get point in model space at Z = 0
|
|
float z = 0.0f;
|
|
const Vec3d& cur_pos = _mouse_to_3d(pos, &z);
|
|
Vec3d orig = _mouse_to_3d(m_mouse.drag.start_position_2D, &z);
|
|
Camera& camera = wxGetApp().plater()->get_camera();
|
|
#ifdef SUPPORT_FREE_CAMERA
|
|
if (this->m_canvas_type != ECanvasType::CanvasAssembleView) {
|
|
if (wxGetApp().app_config->get("use_free_camera") != "1")
|
|
// Forces camera right vector to be parallel to XY plane in case it has been misaligned using the 3D mouse free rotation.
|
|
// It is cheaper to call this function right away instead of testing wxGetApp().plater()->get_mouse3d_controller().connected(),
|
|
// which checks an atomics (flushes CPU caches).
|
|
// See GH issue #3816.
|
|
camera.recover_from_free_camera();
|
|
}
|
|
#endif
|
|
|
|
camera.set_target(camera.get_target() + orig - cur_pos);
|
|
m_dirty = true;
|
|
m_mouse.ignore_right_up = true;
|
|
}
|
|
|
|
m_mouse.drag.start_position_2D = pos;
|
|
}
|
|
}
|
|
else if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) {
|
|
if (evt.LeftUp()) {
|
|
m_selection.stop_dragging();
|
|
m_rotation_center(0) = m_rotation_center(1) = m_rotation_center(2) = 0.f;
|
|
}
|
|
|
|
if (m_layers_editing.state != LayersEditing::Unknown) {
|
|
m_layers_editing.state = LayersEditing::Unknown;
|
|
_stop_timer();
|
|
m_layers_editing.accept_changes(*this);
|
|
}
|
|
else if (m_mouse.drag.move_volume_idx != -1 && m_mouse.dragging) {
|
|
do_move(L("Move Object"));
|
|
// BBS
|
|
//wxGetApp().obj_manipul()->set_dirty();
|
|
// Let the plater know that the dragging finished, so a delayed refresh
|
|
// of the scene with the background processing data should be performed.
|
|
post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
|
|
} else if (evt.LeftUp() && m_hover_volume_idxs.empty() && m_gizmos.is_gizmo_click_empty_not_exit()) {
|
|
// Click on blank and not exit the gizmo tool
|
|
}
|
|
else if (evt.LeftUp() && m_picking_enabled && m_rectangle_selection.is_dragging() && m_layers_editing.state != LayersEditing::Editing) {
|
|
//BBS: don't use alt as de-select
|
|
//if (evt.ShiftDown() || evt.AltDown())
|
|
if (evt.ShiftDown())
|
|
_update_selection_from_hover();
|
|
|
|
m_rectangle_selection.stop_dragging();
|
|
}
|
|
else if (evt.LeftUp() && !m_mouse.ignore_left_up && !m_mouse.dragging && m_hover_volume_idxs.empty() && m_hover_plate_idxs.empty() && !is_layers_editing_enabled()) {
|
|
// deselect and propagate event through callback
|
|
if (!evt.ShiftDown() && (!any_gizmo_active || !evt.CmdDown()) && m_picking_enabled)
|
|
deselect_all();
|
|
}
|
|
//BBS Select plate in this 3D canvas.
|
|
else if (evt.LeftUp() && !m_mouse.dragging && m_picking_enabled && !m_hover_plate_idxs.empty() && (m_canvas_type == CanvasView3D) && !is_layers_editing_enabled())
|
|
{
|
|
int hover_idx = m_hover_plate_idxs.front();
|
|
wxGetApp().plater()->select_plate_by_hover_id(hover_idx);
|
|
//wxGetApp().plater()->get_partplate_list().select_plate_view();
|
|
//deselect all the objects
|
|
if (m_gizmos.get_current_type() != GLGizmosManager::MeshBoolean && m_hover_volume_idxs.empty())
|
|
deselect_all();
|
|
}
|
|
else if (evt.RightUp() && !is_layers_editing_enabled()) {
|
|
m_mouse.position = pos.cast<double>();
|
|
// forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while
|
|
// the context menu is already shown
|
|
render();
|
|
if (!m_hover_volume_idxs.empty()) {
|
|
// if right clicking on volume, propagate event through callback (shows context menu)
|
|
int volume_idx = get_first_hover_volume_idx();
|
|
if (!m_volumes.volumes[volume_idx]->is_wipe_tower // no context menu for the wipe tower
|
|
&& m_gizmos.get_current_type() != GLGizmosManager::SlaSupports) // disable context menu when the gizmo is open
|
|
{
|
|
// forces the selection of the volume
|
|
/* m_selection.add(volume_idx); // #et_FIXME_if_needed
|
|
* To avoid extra "Add-Selection" snapshots,
|
|
* call add() with check_for_already_contained=true
|
|
* */
|
|
m_selection.add(volume_idx, true, true);
|
|
m_gizmos.refresh_on_off_state();
|
|
post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
|
|
m_gizmos.update_data();
|
|
// BBS
|
|
//wxGetApp().obj_manipul()->set_dirty();
|
|
// forces a frame render to update the view before the context menu is shown
|
|
render();
|
|
}
|
|
}
|
|
|
|
//BBS change plate selection
|
|
if (!m_hover_plate_idxs.empty() && (m_canvas_type == CanvasView3D) && !m_mouse.dragging) {
|
|
int hover_idx = m_hover_plate_idxs.front();
|
|
wxGetApp().plater()->select_plate_by_hover_id(hover_idx, true);
|
|
if (m_hover_volume_idxs.empty())
|
|
deselect_all();
|
|
render();
|
|
}
|
|
|
|
Vec2d logical_pos = pos.cast<double>();
|
|
#if ENABLE_RETINA_GL
|
|
const float factor = m_retina_helper->get_scale_factor();
|
|
logical_pos = logical_pos.cwiseQuotient(Vec2d(factor, factor));
|
|
#endif // ENABLE_RETINA_GL
|
|
|
|
if (!m_mouse.ignore_right_up && m_gizmos.get_current_type() == GLGizmosManager::EType::Undefined) {
|
|
//BBS post right click event
|
|
if (!m_hover_plate_idxs.empty()) {
|
|
post_event(RBtnPlateEvent(EVT_GLCANVAS_PLATE_RIGHT_CLICK, { logical_pos, m_hover_plate_idxs.front() }));
|
|
}
|
|
else {
|
|
// do not post the event if the user is panning the scene
|
|
// or if right click was done over the wipe tower
|
|
bool post_right_click_event = m_hover_volume_idxs.empty() || !m_volumes.volumes[get_first_hover_volume_idx()]->is_wipe_tower;
|
|
if (post_right_click_event)
|
|
post_event(RBtnEvent(EVT_GLCANVAS_RIGHT_CLICK, { logical_pos, m_hover_volume_idxs.empty() }));
|
|
}
|
|
}
|
|
}
|
|
|
|
mouse_up_cleanup();
|
|
}
|
|
else if (evt.Moving()) {
|
|
m_mouse.position = pos.cast<double>();
|
|
|
|
// updates gizmos overlay
|
|
if (m_selection.is_empty())
|
|
m_gizmos.reset_all_states();
|
|
|
|
m_dirty = true;
|
|
}
|
|
else
|
|
evt.Skip();
|
|
|
|
if (m_moving)
|
|
show_sinking_contours();
|
|
|
|
#ifdef __WXMSW__
|
|
if (on_enter_workaround)
|
|
m_mouse.position = Vec2d(-1., -1.);
|
|
#endif /* __WXMSW__ */
|
|
}
|
|
|
|
void GLCanvas3D::on_paint(wxPaintEvent& evt)
|
|
{
|
|
if (m_initialized)
|
|
m_dirty = true;
|
|
else
|
|
// Call render directly, so it gets initialized immediately, not from On Idle handler.
|
|
this->render();
|
|
}
|
|
|
|
void GLCanvas3D::force_set_focus() {
|
|
m_canvas->SetFocus();
|
|
};
|
|
|
|
void GLCanvas3D::on_set_focus(wxFocusEvent& evt)
|
|
{
|
|
m_tooltip_enabled = false;
|
|
if (m_canvas_type == ECanvasType::CanvasPreview) {
|
|
// update thumbnails and update plate toolbar
|
|
wxGetApp().plater()->update_all_plate_thumbnails();
|
|
_update_imgui_select_plate_toolbar();
|
|
}
|
|
_refresh_if_shown_on_screen();
|
|
m_tooltip_enabled = true;
|
|
}
|
|
|
|
Size GLCanvas3D::get_canvas_size() const
|
|
{
|
|
int w = 0;
|
|
int h = 0;
|
|
|
|
if (m_canvas != nullptr)
|
|
m_canvas->GetSize(&w, &h);
|
|
|
|
#if ENABLE_RETINA_GL
|
|
const float factor = m_retina_helper->get_scale_factor();
|
|
w *= factor;
|
|
h *= factor;
|
|
#else
|
|
const float factor = 1.0f;
|
|
#endif
|
|
|
|
return Size(w, h, factor);
|
|
}
|
|
|
|
Vec2d GLCanvas3D::get_local_mouse_position() const
|
|
{
|
|
if (m_canvas == nullptr)
|
|
return Vec2d::Zero();
|
|
|
|
wxPoint mouse_pos = m_canvas->ScreenToClient(wxGetMousePosition());
|
|
const double factor =
|
|
#if ENABLE_RETINA_GL
|
|
m_retina_helper->get_scale_factor();
|
|
#else
|
|
1.0;
|
|
#endif
|
|
return Vec2d(factor * mouse_pos.x, factor * mouse_pos.y);
|
|
}
|
|
|
|
void GLCanvas3D::set_tooltip(const std::string& tooltip)
|
|
{
|
|
if (m_canvas != nullptr)
|
|
m_tooltip.set_text(tooltip);
|
|
}
|
|
|
|
void GLCanvas3D::do_move(const std::string& snapshot_type)
|
|
{
|
|
if (m_model == nullptr)
|
|
return;
|
|
|
|
if (!snapshot_type.empty())
|
|
wxGetApp().plater()->take_snapshot(snapshot_type);
|
|
|
|
std::set<std::pair<int, int>> done; // keeps track of modified instances
|
|
bool object_moved = false;
|
|
|
|
// BBS: support wipe-tower for multi-plates
|
|
int n_plates = wxGetApp().plater()->get_partplate_list().get_plate_count();
|
|
std::vector<Vec3d> wipe_tower_origins(n_plates, Vec3d::Zero());
|
|
|
|
Selection::EMode selection_mode = m_selection.get_mode();
|
|
|
|
for (const GLVolume* v : m_volumes.volumes) {
|
|
int object_idx = v->object_idx();
|
|
int instance_idx = v->instance_idx();
|
|
int volume_idx = v->volume_idx();
|
|
|
|
std::pair<int, int> done_id(object_idx, instance_idx);
|
|
|
|
if (0 <= object_idx && object_idx < (int)m_model->objects.size()) {
|
|
done.insert(done_id);
|
|
|
|
// Move instances/volumes
|
|
ModelObject* model_object = m_model->objects[object_idx];
|
|
if (model_object != nullptr) {
|
|
if (selection_mode == Selection::Instance) {
|
|
if (m_canvas_type == GLCanvas3D::ECanvasType::CanvasAssembleView) {
|
|
if ((model_object->instances[instance_idx]->get_assemble_offset() - v->get_instance_offset()).norm() > 1e-2) {
|
|
model_object->instances[instance_idx]->set_assemble_offset(v->get_instance_offset());
|
|
}
|
|
} else {
|
|
model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
|
|
}
|
|
}
|
|
else if (selection_mode == Selection::Volume) {
|
|
if (model_object->volumes[volume_idx]->get_offset() != v->get_volume_offset()) {
|
|
model_object->volumes[volume_idx]->set_offset(v->get_volume_offset());
|
|
// BBS: backup
|
|
Slic3r::save_object_mesh(*model_object);
|
|
}
|
|
}
|
|
|
|
object_moved = true;
|
|
model_object->invalidate_bounding_box();
|
|
}
|
|
}
|
|
else if (object_idx >= 1000 && object_idx < 1000 + n_plates) {
|
|
// Move a wipe tower proxy.
|
|
wipe_tower_origins[object_idx - 1000] = v->get_volume_offset();
|
|
}
|
|
}
|
|
|
|
//BBS: notify instance updates to part plater list
|
|
m_selection.notify_instance_update(-1, 0);
|
|
|
|
// Fixes flying instances
|
|
for (const std::pair<int, int>& i : done) {
|
|
ModelObject* m = m_model->objects[i.first];
|
|
const double shift_z = m->get_instance_min_z(i.second);
|
|
//BBS: don't call translate if the z is zero
|
|
if ((current_printer_technology() == ptSLA || shift_z > SINKING_Z_THRESHOLD) && (shift_z != 0.0f)) {
|
|
const Vec3d shift(0.0, 0.0, -shift_z);
|
|
m_selection.translate(i.first, i.second, shift);
|
|
m->translate_instance(i.second, shift);
|
|
//BBS: notify instance updates to part plater list
|
|
m_selection.notify_instance_update(i.first, i.second);
|
|
}
|
|
wxGetApp().obj_list()->update_info_items(static_cast<size_t>(i.first));
|
|
}
|
|
//BBS: nofity object list to update
|
|
wxGetApp().plater()->sidebar().obj_list()->update_plate_values_for_items();
|
|
|
|
if (object_moved)
|
|
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_MOVED));
|
|
|
|
// BBS: support wipe-tower for multi-plates
|
|
for (int plate_id = 0; plate_id < wipe_tower_origins.size(); plate_id++) {
|
|
Vec3d& wipe_tower_origin = wipe_tower_origins[plate_id];
|
|
if (wipe_tower_origin == Vec3d::Zero())
|
|
continue;
|
|
|
|
PartPlateList& ppl = wxGetApp().plater()->get_partplate_list();
|
|
DynamicConfig& proj_cfg = wxGetApp().preset_bundle->project_config;
|
|
Vec3d plate_origin = ppl.get_plate(plate_id)->get_origin();
|
|
ConfigOptionFloat wipe_tower_x(wipe_tower_origin(0) - plate_origin(0));
|
|
ConfigOptionFloat wipe_tower_y(wipe_tower_origin(1) - plate_origin(1));
|
|
|
|
ConfigOptionFloats* wipe_tower_x_opt = proj_cfg.option<ConfigOptionFloats>("wipe_tower_x", true);
|
|
ConfigOptionFloats* wipe_tower_y_opt = proj_cfg.option<ConfigOptionFloats>("wipe_tower_y", true);
|
|
wipe_tower_x_opt->set_at(&wipe_tower_x, plate_id, 0);
|
|
wipe_tower_y_opt->set_at(&wipe_tower_y, plate_id, 0);
|
|
}
|
|
|
|
reset_sequential_print_clearance();
|
|
|
|
m_dirty = true;
|
|
}
|
|
|
|
void GLCanvas3D::do_rotate(const std::string& snapshot_type)
|
|
{
|
|
if (m_model == nullptr)
|
|
return;
|
|
|
|
if (!snapshot_type.empty())
|
|
wxGetApp().plater()->take_snapshot(snapshot_type);
|
|
|
|
// stores current min_z of instances
|
|
std::map<std::pair<int, int>, double> min_zs;
|
|
for (int i = 0; i < static_cast<int>(m_model->objects.size()); ++i) {
|
|
const ModelObject* obj = m_model->objects[i];
|
|
for (int j = 0; j < static_cast<int>(obj->instances.size()); ++j) {
|
|
if (snapshot_type.empty() && m_selection.get_object_idx() == i) {
|
|
// This means we are flattening this object. In that case pretend
|
|
// that it is not sinking (even if it is), so it is placed on bed
|
|
// later on (whatever is sinking will be left sinking).
|
|
min_zs[{ i, j }] = SINKING_Z_THRESHOLD;
|
|
} else
|
|
min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z();
|
|
|
|
}
|
|
}
|
|
|
|
std::set<std::pair<int, int>> done; // keeps track of modified instances
|
|
|
|
Selection::EMode selection_mode = m_selection.get_mode();
|
|
|
|
for (const GLVolume* v : m_volumes.volumes) {
|
|
int object_idx = v->object_idx();
|
|
if (object_idx < 0 || (int)m_model->objects.size() <= object_idx)
|
|
continue;
|
|
|
|
int instance_idx = v->instance_idx();
|
|
int volume_idx = v->volume_idx();
|
|
|
|
done.insert(std::pair<int, int>(object_idx, instance_idx));
|
|
|
|
// Rotate instances/volumes.
|
|
ModelObject* model_object = m_model->objects[object_idx];
|
|
if (model_object != nullptr) {
|
|
if (selection_mode == Selection::Instance) {
|
|
if (m_canvas_type == GLCanvas3D::ECanvasType::CanvasAssembleView) {
|
|
model_object->instances[instance_idx]->set_assemble_rotation(v->get_instance_rotation());
|
|
model_object->instances[instance_idx]->set_assemble_offset(v->get_instance_offset());
|
|
} else {
|
|
model_object->instances[instance_idx]->set_rotation(v->get_instance_rotation());
|
|
model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
|
|
}
|
|
}
|
|
else if (selection_mode == Selection::Volume) {
|
|
if (model_object->volumes[volume_idx]->get_rotation() != v->get_volume_rotation()) {
|
|
model_object->volumes[volume_idx]->set_rotation(v->get_volume_rotation());
|
|
model_object->volumes[volume_idx]->set_offset(v->get_volume_offset());
|
|
// BBS: backup
|
|
Slic3r::save_object_mesh(*model_object);
|
|
}
|
|
}
|
|
model_object->invalidate_bounding_box();
|
|
}
|
|
}
|
|
|
|
//BBS: notify instance updates to part plater list
|
|
m_selection.notify_instance_update(-1, -1);
|
|
if (m_canvas_type != CanvasAssembleView) {
|
|
// Fixes sinking/flying instances
|
|
for (const std::pair<int, int> &i : done) {
|
|
ModelObject *m = m_model->objects[i.first];
|
|
|
|
// BBS: don't call translate if the z is zero
|
|
const double shift_z = m->get_instance_min_z(i.second);
|
|
// leave sinking instances as sinking
|
|
if ((min_zs.find({i.first, i.second})->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) && (shift_z != 0.0f)) {
|
|
const Vec3d shift(0.0, 0.0, -shift_z);
|
|
m_selection.translate(i.first, i.second, shift);
|
|
m->translate_instance(i.second, shift);
|
|
// BBS: notify instance updates to part plater list
|
|
m_selection.notify_instance_update(i.first, i.second);
|
|
}
|
|
|
|
wxGetApp().obj_list()->update_info_items(static_cast<size_t>(i.first));
|
|
}
|
|
}
|
|
//BBS: nofity object list to update
|
|
wxGetApp().plater()->sidebar().obj_list()->update_plate_values_for_items();
|
|
|
|
if (!done.empty())
|
|
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED));
|
|
|
|
m_dirty = true;
|
|
}
|
|
|
|
void GLCanvas3D::do_scale(const std::string& snapshot_type)
|
|
{
|
|
if (m_model == nullptr)
|
|
return;
|
|
|
|
if (!snapshot_type.empty())
|
|
wxGetApp().plater()->take_snapshot(snapshot_type);
|
|
|
|
// stores current min_z of instances
|
|
std::map<std::pair<int, int>, double> min_zs;
|
|
if (!snapshot_type.empty()) {
|
|
for (int i = 0; i < static_cast<int>(m_model->objects.size()); ++i) {
|
|
const ModelObject* obj = m_model->objects[i];
|
|
for (int j = 0; j < static_cast<int>(obj->instances.size()); ++j) {
|
|
min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z();
|
|
}
|
|
}
|
|
}
|
|
|
|
std::set<std::pair<int, int>> done; // keeps track of modified instances
|
|
|
|
Selection::EMode selection_mode = m_selection.get_mode();
|
|
|
|
for (const GLVolume* v : m_volumes.volumes) {
|
|
int object_idx = v->object_idx();
|
|
if (object_idx < 0 || (int)m_model->objects.size() <= object_idx)
|
|
continue;
|
|
|
|
int instance_idx = v->instance_idx();
|
|
int volume_idx = v->volume_idx();
|
|
|
|
done.insert(std::pair<int, int>(object_idx, instance_idx));
|
|
|
|
// Rotate instances/volumes
|
|
ModelObject* model_object = m_model->objects[object_idx];
|
|
if (model_object != nullptr) {
|
|
if (selection_mode == Selection::Instance) {
|
|
model_object->instances[instance_idx]->set_scaling_factor(v->get_instance_scaling_factor());
|
|
model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
|
|
}
|
|
else if (selection_mode == Selection::Volume) {
|
|
if (model_object->volumes[volume_idx]->get_scaling_factor() != v->get_volume_scaling_factor()) {
|
|
model_object->instances[instance_idx]->set_offset(v->get_instance_offset());
|
|
model_object->volumes[volume_idx]->set_scaling_factor(v->get_volume_scaling_factor());
|
|
model_object->volumes[volume_idx]->set_offset(v->get_volume_offset());
|
|
// BBS: backup
|
|
Slic3r::save_object_mesh(*model_object);
|
|
}
|
|
}
|
|
model_object->invalidate_bounding_box();
|
|
}
|
|
}
|
|
|
|
//BBS: notify instance updates to part plater list
|
|
m_selection.notify_instance_update(-1, -1);
|
|
|
|
// Fixes sinking/flying instances
|
|
for (const std::pair<int, int>& i : done) {
|
|
ModelObject* m = m_model->objects[i.first];
|
|
|
|
//BBS: don't call translate if the z is zero
|
|
double shift_z = m->get_instance_min_z(i.second);
|
|
// leave sinking instances as sinking
|
|
if ((min_zs.empty() || min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) && (shift_z != 0.0f)) {
|
|
Vec3d shift(0.0, 0.0, -shift_z);
|
|
m_selection.translate(i.first, i.second, shift);
|
|
m->translate_instance(i.second, shift);
|
|
//BBS: notify instance updates to part plater list
|
|
m_selection.notify_instance_update(i.first, i.second);
|
|
}
|
|
wxGetApp().obj_list()->update_info_items(static_cast<size_t>(i.first));
|
|
}
|
|
//BBS: nofity object list to update
|
|
wxGetApp().plater()->sidebar().obj_list()->update_plate_values_for_items();
|
|
//BBS: notify object info update
|
|
wxGetApp().plater()->show_object_info();
|
|
|
|
if (!done.empty())
|
|
post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_SCALED));
|
|
|
|
m_dirty = true;
|
|
}
|
|
|
|
void GLCanvas3D::do_flatten(const Vec3d& normal, const std::string& snapshot_type)
|
|
{
|
|
if (!snapshot_type.empty())
|
|
wxGetApp().plater()->take_snapshot(snapshot_type);
|
|
|
|
m_selection.flattening_rotate(normal);
|
|
do_rotate(""); // avoid taking another snapshot
|
|
}
|
|
|
|
void GLCanvas3D::do_center()
|
|
{
|
|
if (m_model == nullptr)
|
|
return;
|
|
|
|
m_selection.center();
|
|
}
|
|
|
|
void GLCanvas3D::do_center_plate(const int plate_idx) {
|
|
if (m_model == nullptr)
|
|
return;
|
|
|
|
m_selection.center_plate(plate_idx);
|
|
}
|
|
|
|
void GLCanvas3D::do_mirror(const std::string& snapshot_type)
|
|
{
|
|
if (m_model == nullptr)
|
|
return;
|
|
|
|
if (!snapshot_type.empty())
|
|
wxGetApp().plater()->take_snapshot(snapshot_type);
|
|
|
|
// stores current min_z of instances
|
|
std::map<std::pair<int, int>, double> min_zs;
|
|
if (!snapshot_type.empty()) {
|
|
for (int i = 0; i < static_cast<int>(m_model->objects.size()); ++i) {
|
|
const ModelObject* obj = m_model->objects[i];
|
|
for (int j = 0; j < static_cast<int>(obj->instances.size()); ++j) {
|
|
min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z();
|
|
}
|
|
}
|
|
}
|
|
|
|
std::set<std::pair<int, int>> done; // keeps track of modified instances
|
|
|
|
Selection::EMode selection_mode = m_selection.get_mode();
|
|
|
|
for (const GLVolume* v : m_volumes.volumes) {
|
|
int object_idx = v->object_idx();
|
|
if (object_idx < 0 || (int)m_model->objects.size() <= object_idx)
|
|
continue;
|
|
|
|
int instance_idx = v->instance_idx();
|
|
int volume_idx = v->volume_idx();
|
|
|
|
done.insert(std::pair<int, int>(object_idx, instance_idx));
|
|
|
|
// Mirror instances/volumes
|
|
ModelObject* model_object = m_model->objects[object_idx];
|
|
if (model_object != nullptr) {
|
|
if (selection_mode == Selection::Instance)
|
|
model_object->instances[instance_idx]->set_mirror(v->get_instance_mirror());
|
|
else if (selection_mode == Selection::Volume) {
|
|
if (model_object->volumes[volume_idx]->get_mirror() != v->get_volume_mirror()) {
|
|
model_object->volumes[volume_idx]->set_mirror(v->get_volume_mirror());
|
|
// BBS: backup
|
|
Slic3r::save_object_mesh(*model_object);
|
|
}
|
|
}
|
|
|
|
model_object->invalidate_bounding_box();
|
|
}
|
|
}
|
|
|
|
//BBS: notify instance updates to part plater list
|
|
m_selection.notify_instance_update(-1, -1);
|
|
|
|
// Fixes sinking/flying instances
|
|
for (const std::pair<int, int>& i : done) {
|
|
ModelObject* m = m_model->objects[i.first];
|
|
|
|
//BBS: don't call translate if the z is zero
|
|
double shift_z = m->get_instance_min_z(i.second);
|
|
// leave sinking instances as sinking
|
|
if ((min_zs.empty() || min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD)&&(shift_z != 0.0f)) {
|
|
Vec3d shift(0.0, 0.0, -shift_z);
|
|
m_selection.translate(i.first, i.second, shift);
|
|
m->translate_instance(i.second, shift);
|
|
//BBS: notify instance updates to part plater list
|
|
m_selection.notify_instance_update(i.first, i.second);
|
|
}
|
|
wxGetApp().obj_list()->update_info_items(static_cast<size_t>(i.first));
|
|
|
|
//BBS: notify instance updates to part plater list
|
|
PartPlateList &plate_list = wxGetApp().plater()->get_partplate_list();
|
|
plate_list.notify_instance_update(i.first, i.second);
|
|
|
|
//BBS: nofity object list to update
|
|
wxGetApp().plater()->sidebar().obj_list()->update_plate_values_for_items();
|
|
}
|
|
//BBS: nofity object list to update
|
|
wxGetApp().plater()->sidebar().obj_list()->update_plate_values_for_items();
|
|
|
|
post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
|
|
|
m_dirty = true;
|
|
}
|
|
|
|
void GLCanvas3D::update_gizmos_on_off_state()
|
|
{
|
|
set_as_dirty();
|
|
m_gizmos.update_data();
|
|
m_gizmos.refresh_on_off_state();
|
|
}
|
|
|
|
void GLCanvas3D::handle_sidebar_focus_event(const std::string& opt_key, bool focus_on)
|
|
{
|
|
m_sidebar_field = focus_on ? opt_key : "";
|
|
|
|
//BBS: this event was sent from gizmo now, no need to clear gizmo
|
|
//if (!m_sidebar_field.empty())
|
|
// m_gizmos.reset_all_states();
|
|
|
|
m_dirty = true;
|
|
}
|
|
|
|
void GLCanvas3D::handle_layers_data_focus_event(const t_layer_height_range range, const EditorType type)
|
|
{
|
|
std::string field = "layer_" + std::to_string(type) + "_" + std::to_string(range.first) + "_" + std::to_string(range.second);
|
|
m_gizmos.reset_all_states();
|
|
handle_sidebar_focus_event(field, true);
|
|
}
|
|
|
|
void GLCanvas3D::update_ui_from_settings()
|
|
{
|
|
m_dirty = true;
|
|
|
|
#if __APPLE__
|
|
// Update OpenGL scaling on OSX after the user toggled the "use_retina_opengl" settings in Preferences dialog.
|
|
const float orig_scaling = m_retina_helper->get_scale_factor();
|
|
|
|
const bool use_retina = true;
|
|
BOOST_LOG_TRIVIAL(debug) << "GLCanvas3D: Use Retina OpenGL: " << use_retina;
|
|
m_retina_helper->set_use_retina(use_retina);
|
|
const float new_scaling = m_retina_helper->get_scale_factor();
|
|
|
|
if (new_scaling != orig_scaling) {
|
|
BOOST_LOG_TRIVIAL(debug) << "GLCanvas3D: Scaling factor: " << new_scaling;
|
|
|
|
Camera& camera = wxGetApp().plater()->get_camera();
|
|
camera.set_zoom(camera.get_zoom() * new_scaling / orig_scaling);
|
|
_refresh_if_shown_on_screen();
|
|
}
|
|
#endif // ENABLE_RETINA_GL
|
|
|
|
#ifdef SUPPORT_COLLAPSE_BUTTON
|
|
if (wxGetApp().is_editor())
|
|
wxGetApp().plater()->enable_collapse_toolbar(wxGetApp().app_config->get("show_collapse_button") == "1");
|
|
#endif
|
|
}
|
|
|
|
// BBS: add partplate logic
|
|
GLCanvas3D::WipeTowerInfo GLCanvas3D::get_wipe_tower_info(int plate_idx) const
|
|
{
|
|
WipeTowerInfo wti;
|
|
|
|
for (const GLVolume* vol : m_volumes.volumes) {
|
|
if (vol->is_wipe_tower && vol->object_idx() - 1000 == plate_idx) {
|
|
DynamicPrintConfig& proj_cfg = wxGetApp().preset_bundle->project_config;
|
|
wti.m_pos = Vec2d(proj_cfg.opt<ConfigOptionFloats>("wipe_tower_x")->get_at(plate_idx),
|
|
proj_cfg.opt<ConfigOptionFloats>("wipe_tower_y")->get_at(plate_idx));
|
|
// BBS: don't support rotation
|
|
//wti.m_rotation = (M_PI/180.) * proj_cfg->opt_float("wipe_tower_rotation_angle");
|
|
|
|
auto& preset = wxGetApp().preset_bundle->prints.get_edited_preset();
|
|
float wt_brim_width = preset.config.opt_float("prime_tower_brim_width");
|
|
|
|
const BoundingBoxf3& bb = vol->bounding_box();
|
|
wti.m_bb = BoundingBoxf{to_2d(bb.min), to_2d(bb.max)};
|
|
wti.m_bb.offset(wt_brim_width);
|
|
|
|
float brim_width = wxGetApp().preset_bundle->prints.get_edited_preset().config.opt_float("prime_tower_brim_width");
|
|
wti.m_bb.offset((brim_width));
|
|
|
|
// BBS: the wipe tower pos might be outside bed
|
|
PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_plate(plate_idx);
|
|
Vec2d plate_size = plate->get_size();
|
|
wti.m_pos.x() = std::clamp(wti.m_pos.x(), 0.0, plate_size(0) - wti.m_bb.size().x());
|
|
wti.m_pos.y() = std::clamp(wti.m_pos.y(), 0.0, plate_size(1) - wti.m_bb.size().y());
|
|
|
|
// BBS: add partplate logic
|
|
wti.m_plate_idx = plate_idx;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return wti;
|
|
}
|
|
|
|
Linef3 GLCanvas3D::mouse_ray(const Point& mouse_pos)
|
|
{
|
|
float z0 = 0.0f;
|
|
float z1 = 1.0f;
|
|
return Linef3(_mouse_to_3d(mouse_pos, &z0), _mouse_to_3d(mouse_pos, &z1));
|
|
}
|
|
|
|
double GLCanvas3D::get_size_proportional_to_max_bed_size(double factor) const
|
|
{
|
|
const BoundingBoxf& bbox = m_bed.build_volume().bounding_volume2d();
|
|
return factor * std::max(bbox.size()[0], bbox.size()[1]);
|
|
}
|
|
|
|
//BBS
|
|
std::vector<Vec2f> GLCanvas3D::get_empty_cells(const Vec2f start_point, const Vec2f step)
|
|
{
|
|
PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
|
BoundingBoxf3 build_volume = plate->get_build_volume();
|
|
Vec2d vmin(build_volume.min.x(), build_volume.min.y()), vmax(build_volume.max.x(), build_volume.max.y());
|
|
BoundingBoxf bbox(vmin, vmax);
|
|
std::vector<Vec2f> cells;
|
|
auto min_x = start_point.x() - step(0) * int((start_point.x() - bbox.min.x()) / step(0));
|
|
auto min_y = start_point.y() - step(1) * int((start_point.y() - bbox.min.y()) / step(1));
|
|
for (float x = min_x; x < bbox.max.x() - step(0) / 2; x += step(0))
|
|
for (float y = min_y; y < bbox.max.y() - step(1) / 2; y += step(1))
|
|
{
|
|
cells.emplace_back(x, y);
|
|
}
|
|
for (size_t i = 0; i < m_model->objects.size(); ++i) {
|
|
ModelObject* model_object = m_model->objects[i];
|
|
auto id = model_object->id().id;
|
|
ModelInstance* model_instance0 = model_object->instances.front();
|
|
Polygon hull_2d = model_object->convex_hull_2d(Geometry::assemble_transform({ 0.0, 0.0, model_instance0->get_offset().z() }, model_instance0->get_rotation(),
|
|
model_instance0->get_scaling_factor(), model_instance0->get_mirror()));
|
|
if (hull_2d.empty())
|
|
continue;
|
|
|
|
const auto& instances = model_object->instances;
|
|
double rotation_z0 = instances.front()->get_rotation().z();
|
|
for (const auto& instance : instances) {
|
|
Geometry::Transformation transformation;
|
|
const Vec3d& offset = instance->get_offset();
|
|
transformation.set_offset({ scale_(offset.x()), scale_(offset.y()), 0.0 });
|
|
transformation.set_rotation(Z, instance->get_rotation().z() - rotation_z0);
|
|
const Transform3d& trafo = transformation.get_matrix();
|
|
Polygon inst_hull_2d = hull_2d.transform(trafo);
|
|
|
|
for (auto it = cells.begin(); it != cells.end(); )
|
|
{
|
|
if (inst_hull_2d.contains(Point(scale_(it->x()), scale_(it->y()))))
|
|
it = cells.erase(it);
|
|
else
|
|
it++;
|
|
}
|
|
}
|
|
}
|
|
|
|
Vec2f start = start_point;
|
|
if (start_point(0) < 0 && start_point(1) < 0) {
|
|
start(0) = bbox.center()(0);
|
|
start(1) = bbox.center()(1);
|
|
}
|
|
std::sort(cells.begin(), cells.end(), [start](const Vec2f& cell1, const Vec2f& cell2) {return (cell1 - start).norm() < (cell2 - start).norm(); });
|
|
return cells;
|
|
}
|
|
|
|
Vec2f GLCanvas3D::get_nearest_empty_cell(const Vec2f start_point, const Vec2f step)
|
|
{
|
|
std::vector<Vec2f> empty_cells = get_empty_cells(start_point, step);
|
|
if (!empty_cells.empty())
|
|
return empty_cells.front();
|
|
else {
|
|
double offset = get_size_proportional_to_max_bed_size(0.05);
|
|
return { start_point(0) + offset, start_point(1) + offset };
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::set_cursor(ECursorType type)
|
|
{
|
|
if ((m_canvas != nullptr) && (m_cursor_type != type))
|
|
{
|
|
switch (type)
|
|
{
|
|
case Standard: { m_canvas->SetCursor(*wxSTANDARD_CURSOR); break; }
|
|
case Cross: { m_canvas->SetCursor(*wxCROSS_CURSOR); break; }
|
|
}
|
|
|
|
m_cursor_type = type;
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::msw_rescale()
|
|
{
|
|
}
|
|
|
|
bool GLCanvas3D::has_toolpaths_to_export() const
|
|
{
|
|
return m_gcode_viewer.can_export_toolpaths();
|
|
}
|
|
|
|
void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const
|
|
{
|
|
m_gcode_viewer.export_toolpaths_to_obj(filename);
|
|
}
|
|
|
|
void GLCanvas3D::mouse_up_cleanup()
|
|
{
|
|
m_moving = false;
|
|
m_mouse.drag.move_volume_idx = -1;
|
|
m_mouse.set_start_position_3D_as_invalid();
|
|
m_mouse.set_start_position_2D_as_invalid();
|
|
m_mouse.dragging = false;
|
|
m_mouse.ignore_left_up = false;
|
|
m_mouse.ignore_right_up = false;
|
|
m_dirty = true;
|
|
|
|
if (m_canvas->HasCapture())
|
|
m_canvas->ReleaseMouse();
|
|
}
|
|
|
|
void GLCanvas3D::update_sequential_clearance()
|
|
{
|
|
if (current_printer_technology() != ptFFF || (fff_print()->config().print_sequence == PrintSequence::ByLayer))
|
|
return;
|
|
|
|
if (m_gizmos.is_dragging())
|
|
return;
|
|
|
|
// collects instance transformations from volumes
|
|
// first define temporary cache
|
|
unsigned int instances_count = 0;
|
|
std::vector<std::vector<std::optional<Geometry::Transformation>>> instance_transforms;
|
|
for (size_t obj = 0; obj < m_model->objects.size(); ++obj) {
|
|
instance_transforms.emplace_back(std::vector<std::optional<Geometry::Transformation>>());
|
|
const ModelObject* model_object = m_model->objects[obj];
|
|
for (size_t i = 0; i < model_object->instances.size(); ++i) {
|
|
instance_transforms[obj].emplace_back(std::optional<Geometry::Transformation>());
|
|
++instances_count;
|
|
}
|
|
}
|
|
|
|
//if (instances_count == 1)
|
|
// return;
|
|
|
|
// second fill temporary cache with data from volumes
|
|
for (const GLVolume* v : m_volumes.volumes) {
|
|
if (v->is_modifier || v->is_wipe_tower)
|
|
continue;
|
|
|
|
auto& transform = instance_transforms[v->object_idx()][v->instance_idx()];
|
|
if (!transform.has_value())
|
|
transform = v->get_instance_transformation();
|
|
}
|
|
|
|
// calculates objects 2d hulls (see also: Print::sequential_print_horizontal_clearance_valid())
|
|
// this is done only the first time this method is called while moving the mouse,
|
|
// the results are then cached for following displacements
|
|
if (m_sequential_print_clearance_first_displacement) {
|
|
m_sequential_print_clearance.m_hull_2d_cache.clear();
|
|
bool all_objects_are_short = std::all_of(fff_print()->objects().begin(), fff_print()->objects().end(), \
|
|
[&](PrintObject* obj) { return obj->height() < scale_(fff_print()->config().nozzle_height.value - MARGIN_HEIGHT); });
|
|
float shrink_factor;
|
|
if (all_objects_are_short)
|
|
shrink_factor = scale_(0.5 * MAX_OUTER_NOZZLE_RADIUS - 0.1);
|
|
else
|
|
shrink_factor = static_cast<float>(scale_(0.5 * fff_print()->config().extruder_clearance_max_radius.value - EPSILON));
|
|
|
|
double mitter_limit = scale_(0.1);
|
|
m_sequential_print_clearance.m_hull_2d_cache.reserve(m_model->objects.size());
|
|
for (size_t i = 0; i < m_model->objects.size(); ++i) {
|
|
ModelObject* model_object = m_model->objects[i];
|
|
ModelInstance* model_instance0 = model_object->instances.front();
|
|
Polygon hull_no_offset = model_object->convex_hull_2d(Geometry::assemble_transform({ 0.0, 0.0, model_instance0->get_offset().z() }, model_instance0->get_rotation(),
|
|
model_instance0->get_scaling_factor(), model_instance0->get_mirror()));
|
|
auto tmp = offset(hull_no_offset,
|
|
// Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects
|
|
// exactly by satisfying the extruder_clearance_radius, this test will not trigger collision.
|
|
shrink_factor,
|
|
jtRound, mitter_limit);
|
|
Polygon hull_2d = !tmp.empty() ? tmp.front() : hull_no_offset;// tmp may be empty due to clipper's bug, see STUDIO-2452
|
|
|
|
Pointf3s& cache_hull_2d = m_sequential_print_clearance.m_hull_2d_cache.emplace_back(Pointf3s());
|
|
cache_hull_2d.reserve(hull_2d.points.size());
|
|
for (const Point& p : hull_2d.points) {
|
|
cache_hull_2d.emplace_back(unscale<double>(p.x()), unscale<double>(p.y()), 0.0);
|
|
}
|
|
}
|
|
m_sequential_print_clearance_first_displacement = false;
|
|
}
|
|
|
|
// calculates instances 2d hulls (see also: Print::sequential_print_horizontal_clearance_valid())
|
|
//BBS: add the height logic
|
|
PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
|
Polygons polygons;
|
|
std::vector<std::pair<Polygon, float>> height_polygons;
|
|
polygons.reserve(instances_count);
|
|
height_polygons.reserve(instances_count);
|
|
std::vector<struct height_info> convex_and_bounding_boxes;
|
|
struct height_info
|
|
{
|
|
double instance_height;
|
|
BoundingBox bounding_box;
|
|
Polygon hull_polygon;
|
|
};
|
|
for (size_t i = 0; i < instance_transforms.size(); ++i) {
|
|
const auto& instances = instance_transforms[i];
|
|
double rotation_z0 = instances.front()->get_rotation().z();
|
|
int index = 0;
|
|
for (const auto& instance : instances) {
|
|
Geometry::Transformation transformation;
|
|
const Vec3d& offset = instance->get_offset();
|
|
transformation.set_offset({ offset.x(), offset.y(), 0.0 });
|
|
transformation.set_rotation(Z, instance->get_rotation().z() - rotation_z0);
|
|
const Transform3d& trafo = transformation.get_matrix();
|
|
const Pointf3s& hull_2d = m_sequential_print_clearance.m_hull_2d_cache[i];
|
|
Points inst_pts;
|
|
inst_pts.reserve(hull_2d.size());
|
|
for (size_t j = 0; j < hull_2d.size(); ++j) {
|
|
const Vec3d p = trafo * hull_2d[j];
|
|
inst_pts.emplace_back(scaled<double>(p.x()), scaled<double>(p.y()));
|
|
}
|
|
Polygon convex_hull(std::move(inst_pts));
|
|
BoundingBox bouding_box = convex_hull.bounding_box();
|
|
BoundingBox plate_bb = plate->get_bounding_box_crd();
|
|
double instance_height = m_model->objects[i]->get_instance_max_z(index++);
|
|
//skip the object for not current plate
|
|
if (!plate_bb.overlap(bouding_box))
|
|
continue;
|
|
convex_and_bounding_boxes.push_back({instance_height, bouding_box, convex_hull});
|
|
polygons.emplace_back(std::move(convex_hull));
|
|
}
|
|
}
|
|
|
|
//sort the print instance
|
|
std::sort(convex_and_bounding_boxes.begin(), convex_and_bounding_boxes.end(),
|
|
[](auto &l, auto &r) {
|
|
auto ly1 = l.bounding_box.min.y();
|
|
auto ly2 = l.bounding_box.max.y();
|
|
auto ry1 = r.bounding_box.min.y();
|
|
auto ry2 = r.bounding_box.max.y();
|
|
auto inter_min = std::max(ly1, ry1);
|
|
auto inter_max = std::min(ly2, ry2);
|
|
auto lx = l.bounding_box.min.x();
|
|
auto rx = r.bounding_box.min.x();
|
|
if (inter_max - inter_min > 0)
|
|
return (lx < rx) || ((lx == rx)&&(ly1 < ry1));
|
|
else
|
|
return (ly1 < ry1);
|
|
});
|
|
|
|
/*bool has_interlaced_objects = false;
|
|
for (int k = 0; k < bounding_box_count; k++)
|
|
{
|
|
Polygon& convex = convex_and_bounding_boxes[k].hull_polygon;
|
|
BoundingBox& bbox = convex_and_bounding_boxes[k].bounding_box;
|
|
auto iy1 = bbox.min.y();
|
|
auto iy2 = bbox.max.y();
|
|
|
|
for (int i = k+1; i < bounding_box_count; i++)
|
|
{
|
|
Polygon& next_convex = convex_and_bounding_boxes[i].hull_polygon;
|
|
BoundingBox& next_bbox = convex_and_bounding_boxes[i].bounding_box;
|
|
auto py1 = next_bbox.min.y();
|
|
auto py2 = next_bbox.max.y();
|
|
auto inter_min = std::max(iy1, py1); // min y of intersection
|
|
auto inter_max = std::min(iy2, py2); // max y of intersection. length=max_y-min_y>0 means intersection exists
|
|
if (inter_max - inter_min > 0) {
|
|
has_interlaced_objects = true;
|
|
break;
|
|
}
|
|
}
|
|
if (has_interlaced_objects)
|
|
break;
|
|
}*/
|
|
|
|
int bounding_box_count = convex_and_bounding_boxes.size();
|
|
double printable_height = fff_print()->config().printable_height;
|
|
double hc1 = fff_print()->config().extruder_clearance_height_to_lid;
|
|
double hc2 = fff_print()->config().extruder_clearance_height_to_rod;
|
|
for (int k = 0; k < bounding_box_count; k++)
|
|
{
|
|
Polygon& convex = convex_and_bounding_boxes[k].hull_polygon;
|
|
BoundingBox& bbox = convex_and_bounding_boxes[k].bounding_box;
|
|
auto iy1 = bbox.min.y();
|
|
auto iy2 = bbox.max.y();
|
|
double height = (k == (bounding_box_count - 1))?printable_height:hc1;
|
|
|
|
/*if (has_interlaced_objects) {
|
|
if ((k < (bounding_box_count - 1)) && (convex_and_bounding_boxes[k].instance_height > hc2)) {
|
|
height_polygons.emplace_back(std::make_pair(convex, hc2));
|
|
}
|
|
}
|
|
else {
|
|
if ((k < (bounding_box_count - 1)) && (convex_and_bounding_boxes[k].instance_height > hc1)) {
|
|
height_polygons.emplace_back(std::make_pair(convex, hc1));
|
|
}
|
|
}*/
|
|
|
|
for (int i = k+1; i < bounding_box_count; i++)
|
|
{
|
|
Polygon& next_convex = convex_and_bounding_boxes[i].hull_polygon;
|
|
BoundingBox& next_bbox = convex_and_bounding_boxes[i].bounding_box;
|
|
auto py1 = next_bbox.min.y();
|
|
auto py2 = next_bbox.max.y();
|
|
auto inter_min = std::max(iy1, py1); // min y of intersection
|
|
auto inter_max = std::min(iy2, py2); // max y of intersection. length=max_y-min_y>0 means intersection exists
|
|
if (inter_max - inter_min > 0) {
|
|
height = hc2;
|
|
break;
|
|
}
|
|
}
|
|
if (height < convex_and_bounding_boxes[k].instance_height)
|
|
height_polygons.emplace_back(std::make_pair(convex, height));
|
|
}
|
|
|
|
// sends instances 2d hulls to be rendered
|
|
set_sequential_print_clearance_visible(true);
|
|
set_sequential_print_clearance_render_fill(false);
|
|
set_sequential_print_clearance_polygons(polygons, height_polygons);
|
|
}
|
|
|
|
bool GLCanvas3D::is_object_sinking(int object_idx) const
|
|
{
|
|
for (const GLVolume* v : m_volumes.volumes) {
|
|
if (v->object_idx() == object_idx && (v->is_sinking() || (!v->is_modifier && v->is_below_printbed())))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool GLCanvas3D::_is_shown_on_screen() const
|
|
{
|
|
return (m_canvas != nullptr) ? m_canvas->IsShownOnScreen() : false;
|
|
}
|
|
|
|
// Getter for the const char*[]
|
|
static bool string_getter(const bool is_undo, int idx, const char** out_text)
|
|
{
|
|
return wxGetApp().plater()->undo_redo_string_getter(is_undo, idx, out_text);
|
|
}
|
|
|
|
// Getter for the const char*[] for the search list
|
|
static bool search_string_getter(int idx, const char** label, const char** tooltip)
|
|
{
|
|
return wxGetApp().plater()->search_string_getter(idx, label, tooltip);
|
|
}
|
|
|
|
//BBS: GUI refactor: adjust main toolbar position
|
|
bool GLCanvas3D::_render_orient_menu(float left, float right, float bottom, float top)
|
|
{
|
|
ImGuiWrapper* imgui = wxGetApp().imgui();
|
|
|
|
auto canvas_w = float(get_canvas_size().get_width());
|
|
auto canvas_h = float(get_canvas_size().get_height());
|
|
//BBS: GUI refactor: move main toolbar to the right
|
|
//original use center as {0.0}, and top is (canvas_h/2), bottom is (-canvas_h/2), also plus inv_camera
|
|
//now change to left_up as {0,0}, and top is 0, bottom is canvas_h
|
|
#if BBS_TOOLBAR_ON_TOP
|
|
const float x = left * float(wxGetApp().plater()->get_camera().get_zoom()) + 0.5f * canvas_w;
|
|
ImGuiWrapper::push_toolbar_style(get_scale());
|
|
imgui->set_next_window_pos(x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f);
|
|
#else
|
|
const float x = canvas_w - m_main_toolbar.get_width();
|
|
const float y = 0.5f * canvas_h - top * float(wxGetApp().plater()->get_camera().get_zoom());
|
|
imgui->set_next_window_pos(x, y, ImGuiCond_Always, 1.0f, 0.0f);
|
|
#endif
|
|
|
|
imgui->begin(_L("Auto Orientation options"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
|
|
|
|
OrientSettings settings = get_orient_settings();
|
|
OrientSettings& settings_out = get_orient_settings();
|
|
|
|
auto& appcfg = wxGetApp().app_config;
|
|
PrinterTechnology ptech = current_printer_technology();
|
|
|
|
bool settings_changed = false;
|
|
float angle_min = 45.f;
|
|
std::string angle_key = "overhang_angle", rot_key = "enable_rotation";
|
|
std::string key_min_area = "min_area";
|
|
std::string postfix = "_fff";
|
|
|
|
if (ptech == ptSLA) {
|
|
angle_min = 45.f;
|
|
postfix = "_sla";
|
|
}
|
|
|
|
|
|
angle_key += postfix;
|
|
rot_key += postfix;
|
|
|
|
//if (imgui->slider_float(_L("Overhang Angle"), &settings.overhang_angle, angle_min, 90.0f, "%5.2f") || angle_min > settings.overhang_angle) {
|
|
// settings.overhang_angle = std::max(angle_min, settings.overhang_angle);
|
|
// settings_out.overhang_angle = settings.overhang_angle;
|
|
// appcfg->set("orient", angle_key, std::to_string(settings_out.overhang_angle));
|
|
// settings_changed = true;
|
|
//}
|
|
|
|
if (imgui->checkbox(_L("Enable rotation"), settings.enable_rotation)) {
|
|
settings_out.enable_rotation = settings.enable_rotation;
|
|
appcfg->set("orient", rot_key, settings_out.enable_rotation ? "1" : "0");
|
|
settings_changed = true;
|
|
}
|
|
|
|
if (imgui->checkbox(_L("Optimize support interface area"), settings.min_area)) {
|
|
settings_out.min_area = settings.min_area;
|
|
appcfg->set("orient", key_min_area, settings_out.min_area ? "1" : "0");
|
|
settings_changed = true;
|
|
}
|
|
|
|
ImGui::Separator();
|
|
|
|
if (imgui->button(_L("Orient"))) {
|
|
wxGetApp().plater()->set_prepare_state(Job::PREPARE_STATE_DEFAULT);
|
|
wxGetApp().plater()->orient();
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (imgui->button(_L("Reset"))) {
|
|
settings_out = OrientSettings{};
|
|
settings_out.overhang_angle = 60.f;
|
|
appcfg->set("orient", angle_key, std::to_string(settings_out.overhang_angle));
|
|
appcfg->set("orient", rot_key, settings_out.enable_rotation ? "1" : "0");
|
|
appcfg->set("orient", key_min_area, settings_out.min_area? "1" : "0");
|
|
settings_changed = true;
|
|
}
|
|
|
|
imgui->end();
|
|
ImGuiWrapper::pop_toolbar_style();
|
|
return settings_changed;
|
|
}
|
|
|
|
//BBS: GUI refactor: adjust main toolbar position
|
|
bool GLCanvas3D::_render_arrange_menu(float left, float right, float bottom, float top)
|
|
{
|
|
ImGuiWrapper *imgui = wxGetApp().imgui();
|
|
|
|
auto canvas_w = float(get_canvas_size().get_width());
|
|
auto canvas_h = float(get_canvas_size().get_height());
|
|
//BBS: GUI refactor: move main toolbar to the right
|
|
//original use center as {0.0}, and top is (canvas_h/2), bottom is (-canvas_h/2), also plus inv_camera
|
|
//now change to left_up as {0,0}, and top is 0, bottom is canvas_h
|
|
#if BBS_TOOLBAR_ON_TOP
|
|
float zoom = (float)wxGetApp().plater()->get_camera().get_zoom();
|
|
float left_pos = m_main_toolbar.get_item("arrange")->render_left_pos;
|
|
const float x = 0.5 * canvas_w + left_pos * zoom;
|
|
imgui->set_next_window_pos(x, m_main_toolbar.get_height(), ImGuiCond_Always, 0.0f, 0.0f);
|
|
|
|
#else
|
|
const float x = canvas_w - m_main_toolbar.get_width();
|
|
const float y = 0.5f * canvas_h - top * float(wxGetApp().plater()->get_camera().get_zoom());
|
|
imgui->set_next_window_pos(x, y, ImGuiCond_Always, 1.0f, 0.0f);
|
|
#endif
|
|
|
|
//BBS
|
|
ImGuiWrapper::push_toolbar_style(get_scale());
|
|
|
|
imgui->begin(_L("Arrange options"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
|
|
|
|
ArrangeSettings settings = get_arrange_settings();
|
|
ArrangeSettings &settings_out = get_arrange_settings();
|
|
const float slider_icon_width = imgui->get_slider_icon_size().x;
|
|
const float cursor_slider_left = imgui->calc_text_size(_L("Spacing")).x + imgui->scaled(1.5f);
|
|
const float minimal_slider_width = imgui->scaled(4.f);
|
|
float window_width = minimal_slider_width + 2 * slider_icon_width;
|
|
auto &appcfg = wxGetApp().app_config;
|
|
PrinterTechnology ptech = current_printer_technology();
|
|
|
|
bool settings_changed = false;
|
|
float dist_min = 0.f; // 0 means auto
|
|
std::string dist_key = "min_object_distance", rot_key = "enable_rotation";
|
|
std::string bed_shrink_x_key = "bed_shrink_x", bed_shrink_y_key = "bed_shrink_y";
|
|
std::string multi_material_key = "allow_multi_materials_on_same_plate";
|
|
std::string avoid_extrusion_key = "avoid_extrusion_cali_region";
|
|
std::string align_to_y_axis_key = "align_to_y_axis";
|
|
std::string postfix;
|
|
//BBS:
|
|
bool seq_print = false;
|
|
|
|
if (ptech == ptSLA) {
|
|
postfix = "_sla";
|
|
} else if (ptech == ptFFF) {
|
|
seq_print = &settings == &m_arrange_settings_fff_seq_print;
|
|
if (seq_print) {
|
|
postfix = "_fff_seq_print";
|
|
} else {
|
|
postfix = "_fff";
|
|
}
|
|
}
|
|
|
|
dist_key += postfix;
|
|
rot_key += postfix;
|
|
bed_shrink_x_key += postfix;
|
|
bed_shrink_y_key += postfix;
|
|
|
|
ImGui::AlignTextToFramePadding();
|
|
imgui->text(_L("Spacing"));
|
|
ImGui::SameLine(1.2 * cursor_slider_left);
|
|
ImGui::PushItemWidth(window_width - slider_icon_width);
|
|
bool b_Spacing = imgui->bbl_slider_float_style("##Spacing", &settings.distance, dist_min, 100.0f, "%5.2f") || dist_min > settings.distance;
|
|
ImGui::SameLine(window_width - slider_icon_width + 1.3 * cursor_slider_left);
|
|
ImGui::PushItemWidth(1.5 * slider_icon_width);
|
|
bool b_spacing_input = ImGui::BBLDragFloat("##spacing_input", &settings.distance, 0.05f, 0.0f, 0.0f, "%.2f");
|
|
if (b_Spacing || b_spacing_input)
|
|
{
|
|
settings.distance = std::max(dist_min, settings.distance);
|
|
settings_out.distance = settings.distance;
|
|
appcfg->set("arrange", dist_key.c_str(), float_to_string_decimal_point(settings_out.distance));
|
|
settings_changed = true;
|
|
}
|
|
imgui->text(_L("0 means auto spacing."));
|
|
|
|
ImGui::Separator();
|
|
if (imgui->bbl_checkbox(_L("Auto rotate for arrangement"), settings.enable_rotation)) {
|
|
settings_out.enable_rotation = settings.enable_rotation;
|
|
appcfg->set("arrange", rot_key.c_str(), settings_out.enable_rotation? "1" : "0");
|
|
settings_changed = true;
|
|
}
|
|
|
|
if (imgui->bbl_checkbox(_L("Allow multiple materials on same plate"), settings.allow_multi_materials_on_same_plate)) {
|
|
settings_out.allow_multi_materials_on_same_plate = settings.allow_multi_materials_on_same_plate;
|
|
appcfg->set("arrange", multi_material_key.c_str(), settings_out.allow_multi_materials_on_same_plate ? "1" : "0");
|
|
settings_changed = true;
|
|
}
|
|
|
|
// only show this option if the printer has micro Lidar and can do first layer scan
|
|
DynamicPrintConfig ¤t_config = wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
|
auto op = current_config.option("scan_first_layer");
|
|
if (op && op->getBool()) {
|
|
if (imgui->bbl_checkbox(_L("Avoid extrusion calibration region"), settings.avoid_extrusion_cali_region)) {
|
|
settings_out.avoid_extrusion_cali_region = settings.avoid_extrusion_cali_region;
|
|
appcfg->set("arrange", avoid_extrusion_key.c_str(), settings_out.avoid_extrusion_cali_region ? "1" : "0");
|
|
settings_changed = true;
|
|
}
|
|
} else {
|
|
settings_out.avoid_extrusion_cali_region = false;
|
|
}
|
|
|
|
// Align to Y axis. Only enable this option when auto rotation not enabled
|
|
{
|
|
if (settings_out.enable_rotation) { // do not allow align to Y axis if rotation is enabled
|
|
imgui->disabled_begin(true);
|
|
settings_out.align_to_y_axis = false;
|
|
}
|
|
|
|
if (imgui->bbl_checkbox(_L("Align to Y axis"), settings.align_to_y_axis)) {
|
|
settings_out.align_to_y_axis = settings.align_to_y_axis;
|
|
appcfg->set("arrange", align_to_y_axis_key, settings_out.align_to_y_axis ? "1" : "0");
|
|
settings_changed = true;
|
|
}
|
|
|
|
if (settings_out.enable_rotation == true) { imgui->disabled_end(); }
|
|
}
|
|
|
|
ImGui::Separator();
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(15.0f, 10.0f));
|
|
if (imgui->button(_L("Arrange"))) {
|
|
wxGetApp().plater()->set_prepare_state(Job::PREPARE_STATE_DEFAULT);
|
|
wxGetApp().plater()->arrange();
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (imgui->button(_L("Reset"))) {
|
|
settings_out = ArrangeSettings{};
|
|
settings_out.distance = std::max(dist_min, settings_out.distance);
|
|
//BBS: add specific arrange settings
|
|
if (seq_print) settings_out.is_seq_print = true;
|
|
|
|
if (auto printer_structure_opt = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionEnum<PrinterStructure>>("printer_structure")) {
|
|
settings_out.align_to_y_axis = (printer_structure_opt->value == PrinterStructure::psI3);
|
|
}
|
|
else
|
|
settings_out.align_to_y_axis = false;
|
|
|
|
appcfg->set("arrange", dist_key, float_to_string_decimal_point(settings_out.distance));
|
|
appcfg->set("arrange", rot_key, settings_out.enable_rotation ? "1" : "0");
|
|
appcfg->set("arrange", align_to_y_axis_key, settings_out.align_to_y_axis ? "1" : "0");
|
|
settings_changed = true;
|
|
}
|
|
ImGui::PopStyleVar(1);
|
|
imgui->end();
|
|
|
|
//BBS
|
|
ImGuiWrapper::pop_toolbar_style();
|
|
|
|
return settings_changed;
|
|
}
|
|
|
|
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT 0
|
|
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
|
static void debug_output_thumbnail(const ThumbnailData& thumbnail_data)
|
|
{
|
|
// debug export of generated image
|
|
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]);
|
|
}
|
|
}
|
|
|
|
image.SaveFile("C:/bambu/test/test.png", wxBITMAP_TYPE_PNG);
|
|
}
|
|
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
|
|
|
void GLCanvas3D::render_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params,
|
|
PartPlateList& partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector<std::array<float, 4>>& extruder_colors,
|
|
GLShaderProgram * shader,
|
|
Camera::EType camera_type,
|
|
bool use_top_view,
|
|
bool for_picking,
|
|
bool ban_light)
|
|
{
|
|
//BBS modify visible calc function
|
|
int plate_idx = thumbnail_params.plate_id;
|
|
PartPlate* plate = partplate_list.get_plate(plate_idx);
|
|
BoundingBoxf3 plate_build_volume = plate->get_build_volume();
|
|
plate_build_volume.min(0) -= Slic3r::BuildVolume::SceneEpsilon;
|
|
plate_build_volume.min(1) -= Slic3r::BuildVolume::SceneEpsilon;
|
|
plate_build_volume.min(2) -= Slic3r::BuildVolume::SceneEpsilon;
|
|
plate_build_volume.max(0) += Slic3r::BuildVolume::SceneEpsilon;
|
|
plate_build_volume.max(1) += Slic3r::BuildVolume::SceneEpsilon;
|
|
plate_build_volume.max(2) += Slic3r::BuildVolume::SceneEpsilon;
|
|
/*if (m_config != nullptr) {
|
|
double h = m_config->opt_float("printable_height");
|
|
plate_build_volume.min(2) = std::min(plate_build_volume.min(2), -h);
|
|
plate_build_volume.max(2) = std::max(plate_build_volume.max(2), h);
|
|
}*/
|
|
|
|
auto is_visible = [plate_idx, plate_build_volume](const GLVolume& v) {
|
|
bool ret = v.printable;
|
|
if (plate_idx >= 0) {
|
|
bool contained = false;
|
|
BoundingBoxf3 plate_bbox = plate_build_volume;
|
|
plate_bbox.min(2) = -1e10;
|
|
const BoundingBoxf3& volume_bbox = v.transformed_convex_hull_bounding_box();
|
|
if (plate_bbox.contains(volume_bbox) && (volume_bbox.max(2) > 0)) {
|
|
contained = true;
|
|
}
|
|
ret &= contained;
|
|
}
|
|
else {
|
|
ret &= (!v.shader_outside_printer_detection_enabled || !v.is_outside);
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
static std::array<float, 4> curr_color;
|
|
static const std::array<float, 4> orange = { 0.923f, 0.504f, 0.264f, 1.0f };
|
|
static const std::array<float, 4> gray = { 0.64f, 0.64f, 0.64f, 1.0f };
|
|
|
|
GLVolumePtrs visible_volumes;
|
|
|
|
for (GLVolume* vol : volumes.volumes) {
|
|
if (!vol->is_modifier && !vol->is_wipe_tower && (!thumbnail_params.parts_only || vol->composite_id.volume_id >= 0)) {
|
|
if (is_visible(*vol)) {
|
|
visible_volumes.emplace_back(vol);
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("render_thumbnail: plate_idx %1% volumes size %2%, shader %3%, use_top_view=%4%, for_picking=%5%") % plate_idx % visible_volumes.size() %shader %use_top_view %for_picking;
|
|
//BoundingBoxf3 volumes_box = plate_build_volume;
|
|
BoundingBoxf3 volumes_box;
|
|
volumes_box.min.z() = 0;
|
|
volumes_box.max.z() = 0;
|
|
if (!visible_volumes.empty()) {
|
|
for (const GLVolume* vol : visible_volumes) {
|
|
volumes_box.merge(vol->transformed_bounding_box());
|
|
}
|
|
}
|
|
volumes_box.min.z() = -Slic3r::BuildVolume::SceneEpsilon;
|
|
double width = volumes_box.max.x() - volumes_box.min.x();
|
|
double depth = volumes_box.max.y() - volumes_box.min.y();
|
|
double height = volumes_box.max.z() - volumes_box.min.z();
|
|
volumes_box.max.x() = volumes_box.max.x() + width * 0.1f;
|
|
volumes_box.min.x() = volumes_box.min.x() - width * 0.1f;
|
|
volumes_box.max.y() = volumes_box.max.y() + depth * 0.1f;
|
|
volumes_box.min.y() = volumes_box.min.y() - depth * 0.1f;
|
|
volumes_box.max.z() = volumes_box.max.z() + height * 0.1f;
|
|
volumes_box.min.z() = volumes_box.min.z() - height * 0.1f;
|
|
|
|
Camera camera;
|
|
camera.set_type(camera_type);
|
|
//BBS modify scene box to plate scene bounding box
|
|
//plate_build_volume.min(2) = - plate_build_volume.max(2);
|
|
camera.set_scene_box(plate_build_volume);
|
|
camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height);
|
|
|
|
//BoundingBoxf3 plate_box = plate->get_bounding_box(false);
|
|
//plate_box.min.z() = 0.0;
|
|
//plate_box.max.z() = 0.0;
|
|
|
|
if (use_top_view) {
|
|
float center_x = (plate_build_volume.max(0) + plate_build_volume.min(0))/2;
|
|
float center_y = (plate_build_volume.max(1) + plate_build_volume.min(1))/2;
|
|
float distance_z = plate_build_volume.max(2) - plate_build_volume.min(2);
|
|
Vec3d center(center_x, center_y, 0.f);
|
|
double zoom_ratio, scale_x, scale_y;
|
|
|
|
scale_x = ((double)thumbnail_data.width)/(plate_build_volume.max(0) - plate_build_volume.min(0));
|
|
scale_y = ((double)thumbnail_data.height)/(plate_build_volume.max(1) - plate_build_volume.min(1));
|
|
zoom_ratio = (scale_x <= scale_y)?scale_x:scale_y;
|
|
camera.look_at(center + distance_z * Vec3d::UnitZ(), center, Vec3d::UnitY());
|
|
camera.set_zoom(zoom_ratio);
|
|
//camera.select_view("top");
|
|
}
|
|
else {
|
|
//camera.zoom_to_box(volumes_box);
|
|
|
|
//const Vec3d& target = camera.get_target();
|
|
//double distance = camera.get_distance();
|
|
//camera.look_at(target - 0.707 * distance * Vec3d::UnitY() + 0.3 * distance * Vec3d::UnitZ(), target, Vec3d::UnitY() + Vec3d::UnitZ());
|
|
|
|
//BBS: use original iso view for thumbnail
|
|
camera.select_view("iso");
|
|
camera.zoom_to_box(volumes_box);
|
|
}
|
|
|
|
camera.apply_view_matrix();
|
|
|
|
camera.apply_projection(plate_build_volume);
|
|
|
|
//double near_z = -1.0;
|
|
//double far_z = -1.0;
|
|
//camera.apply_projection(volumes_box, near_z, far_z);
|
|
|
|
//GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
|
|
if (!for_picking && (shader == nullptr)) {
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("render_thumbnail with no picking: shader is null, return directly");
|
|
return;
|
|
}
|
|
|
|
//if (thumbnail_params.transparent_background)
|
|
if (for_picking)
|
|
glsafe(::glClearColor(0.f, 0.f, 0.f, 0.f));
|
|
else {
|
|
//glsafe(::glClearColor(0.906f, 0.906f, 0.906f, 1.0f));
|
|
//glsafe(::glClearColor(0.50f, 0.5f, 0.5f, 1.0f));
|
|
//glsafe(::glClearColor(0.121568f, 0.121568f, 0.121568f, 1.0f));
|
|
//glsafe(::glClearColor(0.17647f, 0.17647f, 0.17647f, 1.0f));
|
|
//glsafe(::glClearColor(0.906f, 0.906f, 0.906f, 1.0f));
|
|
//glsafe(::glClearColor(0.37647f, 0.37647f, 0.37647f, 0.5f)); too lite
|
|
glsafe(::glClearColor(0.0f, 0.0f, 0.0f, 0.0f));
|
|
}
|
|
|
|
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
|
|
glsafe(::glEnable(GL_DEPTH_TEST));
|
|
if (ban_light) {
|
|
glsafe(::glDisable(GL_BLEND));
|
|
}
|
|
if (for_picking) {
|
|
//if (OpenGLManager::can_multisample())
|
|
// This flag is often ignored by NVIDIA drivers if rendering into a screen buffer.
|
|
// glsafe(::glDisable(GL_MULTISAMPLE));
|
|
|
|
glsafe(::glDisable(GL_BLEND));
|
|
|
|
static const GLfloat INV_255 = 1.0f / 255.0f;
|
|
|
|
// do not cull backfaces to show broken geometry, if any
|
|
glsafe(::glDisable(GL_CULL_FACE));
|
|
|
|
//glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
|
//glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
|
|
|
|
for (GLVolume* vol : visible_volumes) {
|
|
// Object picking mode. Render the object with a color encoding the object index.
|
|
// we reserve color = (0,0,0) for occluders (as the printbed)
|
|
// so we shift volumes' id by 1 to get the proper color
|
|
//BBS: remove the bed picking logic
|
|
unsigned int id = vol->model_object_ID;
|
|
//unsigned int id = 1 + volume.second.first;
|
|
unsigned int r = (id & (0x000000FF << 0)) >> 0;
|
|
unsigned int g = (id & (0x000000FF << 8)) >> 8;
|
|
unsigned int b = (id & (0x000000FF << 16)) >> 16;
|
|
unsigned int a = 0xFF;
|
|
glsafe(::glColor4f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255, (GLfloat)a * INV_255));
|
|
/*curr_color[0] = (GLfloat)r * INV_255;
|
|
curr_color[1] = (GLfloat)g * INV_255;
|
|
curr_color[2] = (GLfloat)b * INV_255;
|
|
curr_color[3] = (GLfloat)a * INV_255;
|
|
shader->set_uniform("uniform_color", curr_color);*/
|
|
|
|
bool is_active = vol->is_active;
|
|
vol->is_active = true;
|
|
vol->simple_render(nullptr, model_objects, extruder_colors);
|
|
vol->is_active = is_active;
|
|
}
|
|
|
|
//glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
|
|
//glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
|
|
|
|
glsafe(::glEnable(GL_CULL_FACE));
|
|
|
|
//if (OpenGLManager::can_multisample())
|
|
// glsafe(::glEnable(GL_MULTISAMPLE));
|
|
}
|
|
else {
|
|
shader->start_using();
|
|
shader->set_uniform("emission_factor", 0.1f);
|
|
shader->set_uniform("ban_light", ban_light);
|
|
for (GLVolume* vol : visible_volumes) {
|
|
//BBS set render color for thumbnails
|
|
curr_color[0] = vol->color[0];
|
|
curr_color[1] = vol->color[1];
|
|
curr_color[2] = vol->color[2];
|
|
curr_color[3] = vol->color[3];
|
|
|
|
std::array<float, 4> new_color = adjust_color_for_rendering(curr_color);
|
|
if (ban_light) {
|
|
new_color[3] =(255 - (vol->extruder_id -1))/255.0f;
|
|
}
|
|
shader->set_uniform("uniform_color", new_color);
|
|
shader->set_uniform("volume_world_matrix", vol->world_matrix());
|
|
//BBS set all volume to orange
|
|
//shader->set_uniform("uniform_color", orange);
|
|
/*if (plate_idx > 0) {
|
|
shader->set_uniform("uniform_color", orange);
|
|
}
|
|
else {
|
|
shader->set_uniform("uniform_color", (vol->printable && !vol->is_outside) ? orange : gray);
|
|
}*/
|
|
// the volume may have been deactivated by an active gizmo
|
|
bool is_active = vol->is_active;
|
|
vol->is_active = true;
|
|
vol->simple_render(shader, model_objects, extruder_colors, ban_light);
|
|
vol->is_active = is_active;
|
|
}
|
|
shader->stop_using();
|
|
}
|
|
|
|
glsafe(::glDisable(GL_DEPTH_TEST));
|
|
|
|
//don't render plate in thumbnail
|
|
//plate->render( false, true, true);
|
|
|
|
// restore background color
|
|
//if (thumbnail_params.transparent_background)
|
|
// glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("render_thumbnail: finished");
|
|
}
|
|
|
|
void GLCanvas3D::render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params,
|
|
PartPlateList& partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector<std::array<float, 4>>& extruder_colors,
|
|
GLShaderProgram * shader,
|
|
Camera::EType camera_type,
|
|
bool use_top_view,
|
|
bool for_picking,
|
|
bool ban_light)
|
|
{
|
|
thumbnail_data.set(w, h);
|
|
if (!thumbnail_data.is_valid())
|
|
return;
|
|
|
|
bool multisample = OpenGLManager::can_multisample();
|
|
if (for_picking)
|
|
multisample = false;
|
|
//if (!multisample)
|
|
// glsafe(::glEnable(GL_MULTISAMPLE));
|
|
|
|
GLint max_samples;
|
|
glsafe(::glGetIntegerv(GL_MAX_SAMPLES, &max_samples));
|
|
GLsizei num_samples = max_samples / 2;
|
|
|
|
GLuint render_fbo;
|
|
glsafe(::glGenFramebuffers(1, &render_fbo));
|
|
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo));
|
|
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("render_thumbnail prepare: w %1%, h %2%, max_samples %3%, render_fbo %4%") %w %h %max_samples % render_fbo;
|
|
GLuint render_tex = 0;
|
|
GLuint render_tex_buffer = 0;
|
|
if (multisample) {
|
|
// use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2
|
|
glsafe(::glGenRenderbuffers(1, &render_tex_buffer));
|
|
glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_tex_buffer));
|
|
glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_RGBA8, w, h));
|
|
glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, render_tex_buffer));
|
|
}
|
|
else {
|
|
glsafe(::glGenTextures(1, &render_tex));
|
|
glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex));
|
|
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
|
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_tex, 0));
|
|
}
|
|
|
|
GLuint render_depth;
|
|
glsafe(::glGenRenderbuffers(1, &render_depth));
|
|
glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth));
|
|
if (multisample)
|
|
glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_DEPTH_COMPONENT24, w, h));
|
|
else
|
|
glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w, h));
|
|
|
|
glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth));
|
|
|
|
GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 };
|
|
glsafe(::glDrawBuffers(1, drawBufs));
|
|
|
|
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
|
|
render_thumbnail_internal(thumbnail_data, thumbnail_params, partplate_list, model_objects, volumes, extruder_colors, shader,
|
|
camera_type, use_top_view, for_picking,ban_light);
|
|
|
|
if (multisample) {
|
|
GLuint resolve_fbo;
|
|
glsafe(::glGenFramebuffers(1, &resolve_fbo));
|
|
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, resolve_fbo));
|
|
|
|
GLuint resolve_tex;
|
|
glsafe(::glGenTextures(1, &resolve_tex));
|
|
glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex));
|
|
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
|
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolve_tex, 0));
|
|
|
|
glsafe(::glDrawBuffers(1, drawBufs));
|
|
|
|
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
|
|
glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, render_fbo));
|
|
glsafe(::glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolve_fbo));
|
|
glsafe(::glBlitFramebuffer(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR));
|
|
|
|
glsafe(::glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo));
|
|
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
|
|
}
|
|
|
|
glsafe(::glDeleteTextures(1, &resolve_tex));
|
|
glsafe(::glDeleteFramebuffers(1, &resolve_fbo));
|
|
}
|
|
else
|
|
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
|
|
|
|
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
|
debug_output_thumbnail(thumbnail_data);
|
|
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
|
}
|
|
else {
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("render_thumbnail prepare: GL_FRAMEBUFFER not complete");
|
|
}
|
|
|
|
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0));
|
|
glsafe(::glDeleteRenderbuffers(1, &render_depth));
|
|
if (render_tex_buffer != 0)
|
|
glsafe(::glDeleteRenderbuffers(1, &render_tex_buffer));
|
|
if (render_tex != 0)
|
|
glsafe(::glDeleteTextures(1, &render_tex));
|
|
glsafe(::glDeleteFramebuffers(1, &render_fbo));
|
|
|
|
//if (!multisample)
|
|
// glsafe(::glDisable(GL_MULTISAMPLE));
|
|
BOOST_LOG_TRIVIAL(info) << boost::format("render_thumbnail prepare: finished");
|
|
}
|
|
|
|
void GLCanvas3D::render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params,
|
|
PartPlateList& partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector<std::array<float, 4>>& extruder_colors,
|
|
GLShaderProgram * shader,
|
|
Camera::EType camera_type,
|
|
bool use_top_view,
|
|
bool for_picking,
|
|
bool ban_light)
|
|
{
|
|
thumbnail_data.set(w, h);
|
|
if (!thumbnail_data.is_valid())
|
|
return;
|
|
|
|
bool multisample = OpenGLManager::can_multisample();
|
|
if (for_picking)
|
|
multisample = false;
|
|
//if (!multisample)
|
|
// glsafe(::glEnable(GL_MULTISAMPLE));
|
|
|
|
GLint max_samples;
|
|
glsafe(::glGetIntegerv(GL_MAX_SAMPLES_EXT, &max_samples));
|
|
GLsizei num_samples = max_samples / 2;
|
|
|
|
GLuint render_fbo;
|
|
glsafe(::glGenFramebuffersEXT(1, &render_fbo));
|
|
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, render_fbo));
|
|
|
|
GLuint render_tex = 0;
|
|
GLuint render_tex_buffer = 0;
|
|
if (multisample) {
|
|
// use renderbuffer instead of texture to avoid the need to use glTexImage2DMultisample which is available only since OpenGL 3.2
|
|
glsafe(::glGenRenderbuffersEXT(1, &render_tex_buffer));
|
|
glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_tex_buffer));
|
|
glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_RGBA8, w, h));
|
|
glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, render_tex_buffer));
|
|
}
|
|
else {
|
|
glsafe(::glGenTextures(1, &render_tex));
|
|
glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex));
|
|
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
|
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, render_tex, 0));
|
|
}
|
|
|
|
GLuint render_depth;
|
|
glsafe(::glGenRenderbuffersEXT(1, &render_depth));
|
|
glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_depth));
|
|
if (multisample)
|
|
glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_DEPTH_COMPONENT24, w, h));
|
|
else
|
|
glsafe(::glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, w, h));
|
|
|
|
glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render_depth));
|
|
|
|
GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 };
|
|
glsafe(::glDrawBuffers(1, drawBufs));
|
|
|
|
if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) {
|
|
render_thumbnail_internal(thumbnail_data, thumbnail_params, partplate_list, model_objects, volumes, extruder_colors, shader, camera_type, use_top_view, for_picking,
|
|
ban_light);
|
|
|
|
if (multisample) {
|
|
GLuint resolve_fbo;
|
|
glsafe(::glGenFramebuffersEXT(1, &resolve_fbo));
|
|
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, resolve_fbo));
|
|
|
|
GLuint resolve_tex;
|
|
glsafe(::glGenTextures(1, &resolve_tex));
|
|
glsafe(::glBindTexture(GL_TEXTURE_2D, resolve_tex));
|
|
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
|
glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, resolve_tex, 0));
|
|
|
|
glsafe(::glDrawBuffers(1, drawBufs));
|
|
|
|
if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) {
|
|
glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, render_fbo));
|
|
glsafe(::glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, resolve_fbo));
|
|
glsafe(::glBlitFramebufferEXT(0, 0, w, h, 0, 0, w, h, GL_COLOR_BUFFER_BIT, GL_LINEAR));
|
|
|
|
glsafe(::glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, resolve_fbo));
|
|
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
|
|
}
|
|
|
|
glsafe(::glDeleteTextures(1, &resolve_tex));
|
|
glsafe(::glDeleteFramebuffersEXT(1, &resolve_fbo));
|
|
}
|
|
else
|
|
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
|
|
|
|
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
|
debug_output_thumbnail(thumbnail_data);
|
|
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
|
}
|
|
|
|
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0));
|
|
glsafe(::glDeleteRenderbuffersEXT(1, &render_depth));
|
|
if (render_tex_buffer != 0)
|
|
glsafe(::glDeleteRenderbuffersEXT(1, &render_tex_buffer));
|
|
if (render_tex != 0)
|
|
glsafe(::glDeleteTextures(1, &render_tex));
|
|
glsafe(::glDeleteFramebuffersEXT(1, &render_fbo));
|
|
|
|
//if (!multisample)
|
|
// glsafe(::glDisable(GL_MULTISAMPLE));
|
|
}
|
|
|
|
void GLCanvas3D::render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, PartPlateList &partplate_list, ModelObjectPtrs& model_objects, const GLVolumeCollection& volumes, std::vector<std::array<float, 4>>& extruder_colors, GLShaderProgram* shader, Camera::EType camera_type,
|
|
bool use_top_view,
|
|
bool for_picking,
|
|
bool ban_light)
|
|
{
|
|
// check that thumbnail size does not exceed the default framebuffer size
|
|
const Size& cnv_size = get_canvas_size();
|
|
unsigned int cnv_w = (unsigned int)cnv_size.get_width();
|
|
unsigned int cnv_h = (unsigned int)cnv_size.get_height();
|
|
if (w > cnv_w || h > cnv_h) {
|
|
float ratio = std::min((float)cnv_w / (float)w, (float)cnv_h / (float)h);
|
|
w = (unsigned int)(ratio * (float)w);
|
|
h = (unsigned int)(ratio * (float)h);
|
|
}
|
|
|
|
thumbnail_data.set(w, h);
|
|
if (!thumbnail_data.is_valid())
|
|
return;
|
|
|
|
render_thumbnail_internal(thumbnail_data, thumbnail_params, partplate_list, model_objects, volumes, extruder_colors, shader, camera_type, use_top_view, for_picking,
|
|
ban_light);
|
|
|
|
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
|
|
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
|
debug_output_thumbnail(thumbnail_data);
|
|
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
|
|
|
// restore the default framebuffer size to avoid flickering on the 3D scene
|
|
//wxGetApp().plater()->get_camera().apply_viewport(0, 0, cnv_size.get_width(), cnv_size.get_height());
|
|
}
|
|
|
|
//BBS: GUI refractor
|
|
|
|
void GLCanvas3D::_switch_toolbars_icon_filename()
|
|
{
|
|
BackgroundTexture::Metadata background_data;
|
|
background_data.filename = m_is_dark ? "toolbar_background_dark.png" : "toolbar_background.png";
|
|
background_data.left = 16;
|
|
background_data.top = 16;
|
|
background_data.right = 16;
|
|
background_data.bottom = 16;
|
|
m_main_toolbar.init(background_data);
|
|
m_assemble_view_toolbar.init(background_data);
|
|
m_separator_toolbar.init(background_data);
|
|
wxGetApp().plater()->get_collapse_toolbar().init(background_data);
|
|
|
|
// main toolbar
|
|
{
|
|
GLToolbarItem* item;
|
|
item = m_main_toolbar.get_item("add");
|
|
item->set_icon_filename(m_is_dark ? "toolbar_open_dark.svg" : "toolbar_open.svg");
|
|
|
|
item = m_main_toolbar.get_item("addplate");
|
|
item->set_icon_filename(m_is_dark ? "toolbar_add_plate_dark.svg" : "toolbar_add_plate.svg");
|
|
|
|
item = m_main_toolbar.get_item("orient");
|
|
item->set_icon_filename(m_is_dark ? "toolbar_orient_dark.svg" : "toolbar_orient.svg");
|
|
|
|
item = m_main_toolbar.get_item("addplate");
|
|
item->set_icon_filename(m_is_dark ? "toolbar_add_plate_dark.svg" : "toolbar_add_plate.svg");
|
|
|
|
item = m_main_toolbar.get_item("arrange");
|
|
item->set_icon_filename(m_is_dark ? "toolbar_arrange_dark.svg" : "toolbar_arrange.svg");
|
|
|
|
item = m_main_toolbar.get_item("splitobjects");
|
|
item->set_icon_filename(m_is_dark ? "split_objects_dark.svg" : "split_objects.svg");
|
|
|
|
item = m_main_toolbar.get_item("splitvolumes");
|
|
item->set_icon_filename(m_is_dark ? "split_parts_dark.svg" : "split_parts.svg");
|
|
|
|
item = m_main_toolbar.get_item("layersediting");
|
|
item->set_icon_filename(m_is_dark ? "toolbar_variable_layer_height_dark.svg" : "toolbar_variable_layer_height.svg");
|
|
}
|
|
|
|
// assemble view toolbar
|
|
{
|
|
GLToolbarItem* item;
|
|
item = m_assemble_view_toolbar.get_item("assembly_view");
|
|
item->set_icon_filename(m_is_dark ? "toolbar_assemble_dark.svg" : "toolbar_assemble.svg");
|
|
}
|
|
}
|
|
bool GLCanvas3D::_init_toolbars()
|
|
{
|
|
if (!_init_main_toolbar())
|
|
return false;
|
|
|
|
//BBS: GUI refractor
|
|
if (!_init_assemble_view_toolbar())
|
|
return false;
|
|
|
|
if (!_init_return_toolbar())
|
|
return false;
|
|
|
|
if (!_init_separator_toolbar())
|
|
return false;
|
|
|
|
if (!_init_select_plate_toolbar())
|
|
return false;
|
|
|
|
#if 0
|
|
if (!_init_view_toolbar())
|
|
return false;
|
|
#endif
|
|
|
|
if (!_init_collapse_toolbar())
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//BBS: GUI refactor: GLToolbar
|
|
bool GLCanvas3D::_init_main_toolbar()
|
|
{
|
|
if (!m_main_toolbar.is_enabled())
|
|
return true;
|
|
|
|
BackgroundTexture::Metadata background_data;
|
|
background_data.filename = m_is_dark ? "toolbar_background_dark.png" : "toolbar_background.png";
|
|
background_data.left = 16;
|
|
background_data.top = 16;
|
|
background_data.right = 16;
|
|
background_data.bottom = 16;
|
|
|
|
if (!m_main_toolbar.init(background_data))
|
|
{
|
|
// unable to init the toolbar texture, disable it
|
|
m_main_toolbar.set_enabled(false);
|
|
return true;
|
|
}
|
|
// init arrow
|
|
BackgroundTexture::Metadata arrow_data;
|
|
arrow_data.filename = "toolbar_arrow.svg";
|
|
arrow_data.left = 0;
|
|
arrow_data.top = 0;
|
|
arrow_data.right = 0;
|
|
arrow_data.bottom = 0;
|
|
if (!m_main_toolbar.init_arrow(arrow_data))
|
|
{
|
|
BOOST_LOG_TRIVIAL(error) << "Main toolbar failed to load arrow texture.";
|
|
}
|
|
// m_gizmos is created at constructor, thus we can init arrow here.
|
|
if (!m_gizmos.init_arrow(arrow_data))
|
|
{
|
|
BOOST_LOG_TRIVIAL(error) << "Gizmos manager failed to load arrow texture.";
|
|
}
|
|
|
|
m_main_toolbar.set_layout_type(GLToolbar::Layout::Horizontal);
|
|
//BBS: main toolbar is at the top and left, we don't need the rounded-corner effect at the right side and the top side
|
|
m_main_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right);
|
|
m_main_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top);
|
|
m_main_toolbar.set_border(5.0f);
|
|
m_main_toolbar.set_separator_size(5);
|
|
m_main_toolbar.set_gap_size(4);
|
|
|
|
m_main_toolbar.del_all_item();
|
|
|
|
GLToolbarItem::Data item;
|
|
|
|
item.name = "add";
|
|
item.icon_filename = m_is_dark ? "toolbar_open_dark.svg" : "toolbar_open.svg";
|
|
item.tooltip = _utf8(L("Add")) + " [" + GUI::shortkey_ctrl_prefix() + "I]";
|
|
item.sprite_id = 0;
|
|
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); };
|
|
item.enabling_callback = []()->bool {return wxGetApp().plater()->can_add_model(); };
|
|
if (!m_main_toolbar.add_item(item))
|
|
return false;
|
|
|
|
item.name = "addplate";
|
|
item.icon_filename = m_is_dark ? "toolbar_add_plate_dark.svg" : "toolbar_add_plate.svg";
|
|
item.tooltip = _utf8(L("Add plate"));
|
|
item.sprite_id++;
|
|
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD_PLATE)); };
|
|
item.enabling_callback = []()->bool {return wxGetApp().plater()->can_add_plate(); };
|
|
if (!m_main_toolbar.add_item(item))
|
|
return false;
|
|
|
|
item.name = "orient";
|
|
item.icon_filename = m_is_dark ? "toolbar_orient_dark.svg" : "toolbar_orient.svg";
|
|
item.tooltip = _utf8(L("Auto orient"));
|
|
item.sprite_id++;
|
|
item.left.render_callback = nullptr;
|
|
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); };
|
|
item.left.toggable = false; // allow right mouse click
|
|
//BBS: GUI refactor: adjust the main toolbar position
|
|
item.left.action_callback = [this]() {
|
|
if (m_canvas != nullptr)
|
|
{
|
|
wxGetApp().plater()->set_prepare_state(Job::PREPARE_STATE_DEFAULT);
|
|
wxGetApp().plater()->orient();
|
|
//BBS do not show orient menu
|
|
//_render_orient_menu(left, right, bottom, top);
|
|
NetworkAgent* agent = GUI::wxGetApp().getAgent();
|
|
if (agent) agent->track_update_property("auto_orient", std::to_string(++auto_orient_count));
|
|
}
|
|
};
|
|
if (!m_main_toolbar.add_item(item))
|
|
return false;
|
|
|
|
item.name = "arrange";
|
|
item.icon_filename = m_is_dark ? "toolbar_arrange_dark.svg" : "toolbar_arrange.svg";
|
|
item.tooltip = _utf8(L("Arrange all objects")) + " [A]\n" + _utf8(L("Arrange objects on selected plates")) + " [Shift+A]";
|
|
item.sprite_id++;
|
|
item.left.action_callback = [this]() {
|
|
if (m_canvas != nullptr) {
|
|
NetworkAgent* agent = GUI::wxGetApp().getAgent();
|
|
if (agent) agent->track_update_property("auto_arrange", std::to_string(++auto_arrange_count));
|
|
}
|
|
};
|
|
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); };
|
|
item.left.toggable = true;
|
|
//BBS: GUI refactor: adjust the main toolbar position
|
|
item.left.render_callback = [this](float left, float right, float bottom, float top) {
|
|
if (m_canvas != nullptr)
|
|
{
|
|
_render_arrange_menu(left, right, bottom, top);
|
|
//_render_arrange_menu(0.5f * (left + right));
|
|
}
|
|
};
|
|
if (!m_main_toolbar.add_item(item))
|
|
return false;
|
|
|
|
item.right.toggable = false;
|
|
item.right.render_callback = GLToolbarItem::Default_Render_Callback;
|
|
|
|
if (!m_main_toolbar.add_separator())
|
|
return false;
|
|
|
|
item.name = "splitobjects";
|
|
item.icon_filename = m_is_dark ? "split_objects_dark.svg" : "split_objects.svg";
|
|
item.tooltip = _utf8(L("Split to objects"));
|
|
item.additional_tooltip = _u8L("Please select single object.") + "\n"+
|
|
_u8L("And it is valid when there are at least two parts in object or stl has at least two meshes.");
|
|
item.sprite_id++;
|
|
item.left.render_callback = nullptr;
|
|
item.left.action_callback = [this]() {
|
|
if (m_canvas != nullptr) {
|
|
wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS));
|
|
NetworkAgent* agent = GUI::wxGetApp().getAgent();
|
|
if (agent) agent->track_update_property("split_to_objects", std::to_string(++split_to_objects_count));
|
|
}
|
|
};
|
|
item.visibility_callback = GLToolbarItem::Default_Visibility_Callback;
|
|
item.left.toggable = false;
|
|
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); };
|
|
if (!m_main_toolbar.add_item(item))
|
|
return false;
|
|
|
|
item.name = "splitvolumes";
|
|
item.icon_filename = m_is_dark ? "split_parts_dark.svg" : "split_parts.svg";
|
|
item.tooltip = _utf8(L("Split to parts"));
|
|
item.additional_tooltip = _u8L("Please select single object.")+ "\n" +
|
|
_u8L("And it is valid when importing an stl with at least two meshes.");
|
|
item.sprite_id++;
|
|
item.left.action_callback = [this]() {
|
|
if (m_canvas != nullptr) {
|
|
wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES));
|
|
NetworkAgent* agent = GUI::wxGetApp().getAgent();
|
|
if (agent) agent->track_update_property("split_to_part", std::to_string(++split_to_part_count));
|
|
}
|
|
};
|
|
item.visibility_callback = GLToolbarItem::Default_Visibility_Callback;
|
|
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); };
|
|
if (!m_main_toolbar.add_item(item))
|
|
return false;
|
|
|
|
item.name = "layersediting";
|
|
item.icon_filename = m_is_dark ? "toolbar_variable_layer_height_dark.svg" : "toolbar_variable_layer_height.svg";
|
|
item.tooltip = _utf8(L("Variable layer height"));
|
|
item.additional_tooltip = _u8L("Please select single object.");
|
|
item.sprite_id++;
|
|
item.left.action_callback = [this]() {
|
|
if (m_canvas != nullptr) {
|
|
wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING));
|
|
NetworkAgent* agent = GUI::wxGetApp().getAgent();
|
|
if (agent) agent->track_update_property("custom_height", std::to_string(++custom_height_count));
|
|
}
|
|
};
|
|
item.right.action_callback = [this]() {
|
|
if (m_canvas != nullptr) {
|
|
wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING));
|
|
}
|
|
};
|
|
item.visibility_callback = [this]()->bool {
|
|
bool res = current_printer_technology() == ptFFF;
|
|
// turns off if changing printer technology
|
|
if (!res && m_main_toolbar.is_item_visible("layersediting") && m_main_toolbar.is_item_pressed("layersediting"))
|
|
force_main_toolbar_left_action(get_main_toolbar_item_id("layersediting"));
|
|
|
|
return res;
|
|
};
|
|
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); };
|
|
item.left.toggable = true;
|
|
if (!m_main_toolbar.add_item(item))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//BBS: GUI refactor: GLToolbar
|
|
bool GLCanvas3D::_init_select_plate_toolbar()
|
|
{
|
|
std::string path = resources_dir() + "/images/";
|
|
IMToolbarItem* item = new IMToolbarItem();
|
|
bool result = item->image_texture.load_from_svg_file(path + "im_all_plates_stats.svg", false, false, false, 128);
|
|
result = result && item->image_texture_transparent.load_from_svg_file(path + "im_all_plates_stats_transparent.svg", false, false, false, 128);
|
|
m_sel_plate_toolbar.m_all_plates_stats_item = item;
|
|
return result;
|
|
}
|
|
|
|
void GLCanvas3D::_update_select_plate_toolbar_stats_item(bool force_selected) {
|
|
PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list();
|
|
if (plate_list.get_nonempty_plate_list().size() > 1)
|
|
m_sel_plate_toolbar.show_stats_item = true;
|
|
else
|
|
m_sel_plate_toolbar.show_stats_item = false;
|
|
|
|
if (force_selected && m_sel_plate_toolbar.show_stats_item)
|
|
m_sel_plate_toolbar.m_all_plates_stats_item->selected = true;
|
|
}
|
|
|
|
bool GLCanvas3D::_update_imgui_select_plate_toolbar()
|
|
{
|
|
bool result = true;
|
|
if (!m_sel_plate_toolbar.is_enabled() || m_sel_plate_toolbar.is_render_finish) return false;
|
|
|
|
_update_select_plate_toolbar_stats_item();
|
|
|
|
m_sel_plate_toolbar.del_all_item();
|
|
|
|
PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list();
|
|
for (int i = 0; i < plate_list.get_plate_count(); i++) {
|
|
IMToolbarItem* item = new IMToolbarItem();
|
|
PartPlate* plate = plate_list.get_plate(i);
|
|
if (plate && plate->thumbnail_data.is_valid()) {
|
|
PartPlate* plate = plate_list.get_plate(i);
|
|
item->image_data = plate->thumbnail_data.pixels;
|
|
item->image_width = plate->thumbnail_data.width;
|
|
item->image_height = plate->thumbnail_data.height;
|
|
result = item->generate_texture();
|
|
}
|
|
m_sel_plate_toolbar.m_items.push_back(item);
|
|
}
|
|
|
|
m_sel_plate_toolbar.is_display_scrollbar = false;
|
|
return result;
|
|
}
|
|
|
|
//BBS: GUI refactor
|
|
//init the assemble view toolbar on the top
|
|
bool GLCanvas3D::_init_assemble_view_toolbar()
|
|
{
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": enter, m_assemble_view_toolbar.is_enabled=" << m_assemble_view_toolbar.is_enabled() << "\n";
|
|
if (!m_assemble_view_toolbar.is_enabled())
|
|
return true;
|
|
|
|
BackgroundTexture::Metadata background_data;
|
|
background_data.filename = m_is_dark ? "toolbar_background_dark.png" : "toolbar_background.png";
|
|
background_data.left = 16;
|
|
background_data.top = 16;
|
|
background_data.right = 16;
|
|
background_data.bottom = 16;
|
|
|
|
if (!m_assemble_view_toolbar.init(background_data))
|
|
{
|
|
// unable to init the toolbar texture, disable it
|
|
m_assemble_view_toolbar.set_enabled(false);
|
|
return true;
|
|
}
|
|
|
|
m_assemble_view_toolbar.set_layout_type(GLToolbar::Layout::Horizontal);
|
|
//BBS: assemble toolbar is at the top and right, we don't need the rounded-corner effect at the left side and the top side
|
|
m_assemble_view_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Left);
|
|
m_assemble_view_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top);
|
|
m_assemble_view_toolbar.set_border(5.0f);
|
|
m_assemble_view_toolbar.set_separator_size(10);
|
|
m_assemble_view_toolbar.set_gap_size(4);
|
|
|
|
m_assemble_view_toolbar.del_all_item();
|
|
|
|
GLToolbarItem::Data item;
|
|
item.name = "assembly_view";
|
|
item.icon_filename = m_is_dark ? "toolbar_assemble_dark.svg" : "toolbar_assemble.svg";
|
|
item.tooltip = _utf8(L("Assembly View"));
|
|
item.sprite_id = 1;
|
|
item.left.toggable = false;
|
|
item.left.action_callback = [this]() {
|
|
if (m_canvas != nullptr) {
|
|
wxPostEvent(m_canvas, SimpleEvent(EVT_GLVIEWTOOLBAR_ASSEMBLE)); m_gizmos.reset_all_states(); wxGetApp().plater()->get_assmeble_canvas3D()->get_gizmos_manager().reset_all_states();
|
|
NetworkAgent* agent = GUI::wxGetApp().getAgent();
|
|
if (agent) agent->track_update_property("assembly_view", std::to_string(++assembly_view_count));
|
|
}
|
|
};
|
|
item.left.render_callback = GLToolbarItem::Default_Render_Callback;
|
|
item.visible = true;
|
|
item.visibility_callback = [this]()->bool { return true; };
|
|
item.enabling_callback = [this]()->bool {
|
|
return wxGetApp().plater()->has_assmeble_view();
|
|
};
|
|
if (!m_assemble_view_toolbar.add_item(item))
|
|
return false;
|
|
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Finished Successfully\n";
|
|
return true;
|
|
}
|
|
|
|
bool GLCanvas3D::_init_return_toolbar()
|
|
{
|
|
if (!m_return_toolbar.is_enabled())
|
|
return true;
|
|
|
|
return m_return_toolbar.init();
|
|
}
|
|
|
|
bool GLCanvas3D::_init_separator_toolbar()
|
|
{
|
|
if (!m_separator_toolbar.is_enabled())
|
|
return true;
|
|
|
|
BackgroundTexture::Metadata background_data;
|
|
background_data.filename = m_is_dark ? "toolbar_background_dark.png" : "toolbar_background.png";
|
|
background_data.left = 0;
|
|
background_data.top = 0;
|
|
background_data.right = 0;
|
|
background_data.bottom = 0;
|
|
|
|
if (!m_separator_toolbar.init(background_data))
|
|
{
|
|
// unable to init the toolbar texture, disable it
|
|
m_separator_toolbar.set_enabled(false);
|
|
return true;
|
|
}
|
|
|
|
m_separator_toolbar.set_layout_type(GLToolbar::Layout::Horizontal);
|
|
//BBS: assemble toolbar is at the top and right, we don't need the rounded-corner effect at the left side and the top side
|
|
m_separator_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Left);
|
|
m_separator_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top);
|
|
m_separator_toolbar.set_border(5.0f);
|
|
|
|
m_separator_toolbar.del_all_item();
|
|
|
|
GLToolbarItem::Data sperate_item;
|
|
sperate_item.name = "start_seperator";
|
|
sperate_item.icon_filename = "seperator.svg";
|
|
sperate_item.sprite_id = 0;
|
|
sperate_item.left.action_callback = [this]() {};
|
|
sperate_item.visibility_callback = []()->bool { return true; };
|
|
sperate_item.enabling_callback = []()->bool { return false; };
|
|
if (!m_separator_toolbar.add_item(sperate_item))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// BBS
|
|
#if 0
|
|
bool GLCanvas3D::_init_view_toolbar()
|
|
{
|
|
return wxGetApp().plater()->init_view_toolbar();
|
|
}
|
|
#endif
|
|
|
|
bool GLCanvas3D::_init_collapse_toolbar()
|
|
{
|
|
return wxGetApp().plater()->init_collapse_toolbar();
|
|
}
|
|
|
|
bool GLCanvas3D::_set_current()
|
|
{
|
|
return m_context != nullptr && m_canvas->SetCurrent(*m_context);
|
|
}
|
|
|
|
void GLCanvas3D::_resize(unsigned int w, unsigned int h)
|
|
{
|
|
if (m_canvas == nullptr && m_context == nullptr)
|
|
return;
|
|
|
|
const std::array<unsigned int, 2> new_size = { w, h };
|
|
if (m_old_size == new_size)
|
|
return;
|
|
|
|
m_old_size = new_size;
|
|
|
|
auto* imgui = wxGetApp().imgui();
|
|
imgui->set_display_size(static_cast<float>(w), static_cast<float>(h));
|
|
|
|
//BBS reduce render
|
|
if (m_last_w == w && m_last_h == h) {
|
|
return;
|
|
}
|
|
|
|
m_last_w = w;
|
|
m_last_h = h;
|
|
|
|
float font_size = wxGetApp().em_unit();
|
|
|
|
#ifdef _WIN32
|
|
// On Windows, if manually scaled here, rendering issues can occur when the system's Display
|
|
// scaling is greater than 300% as the font's size gets to be to large. So, use imgui font
|
|
// scaling instead (see: ImGuiWrapper::init_font() and issue #3401)
|
|
font_size *= (font_size > 30.0f) ? 1.0f : 1.5f;
|
|
#else
|
|
font_size *= 1.5f;
|
|
#endif
|
|
|
|
#if ENABLE_RETINA_GL
|
|
imgui->set_scaling(font_size, 1.0f, m_retina_helper->get_scale_factor());
|
|
#else
|
|
imgui->set_scaling(font_size, m_canvas->GetContentScaleFactor(), 1.0f);
|
|
#endif
|
|
|
|
this->request_extra_frame();
|
|
|
|
// ensures that this canvas is current
|
|
_set_current();
|
|
}
|
|
|
|
BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_bed_model, bool include_plates) const
|
|
{
|
|
BoundingBoxf3 bb = volumes_bounding_box();
|
|
|
|
// The following is a workaround for gizmos not being taken in account when calculating the tight camera frustrum
|
|
// A better solution would ask the gizmo manager for the bounding box of the current active gizmo, if any
|
|
if (include_gizmos && m_gizmos.is_running())
|
|
{
|
|
BoundingBoxf3 sel_bb = m_selection.get_bounding_box();
|
|
Vec3d sel_bb_center = sel_bb.center();
|
|
Vec3d extend_by = sel_bb.max_size() * Vec3d::Ones();
|
|
bb.merge(BoundingBoxf3(sel_bb_center - extend_by, sel_bb_center + extend_by));
|
|
}
|
|
|
|
bb.merge(include_bed_model ? m_bed.extended_bounding_box() : m_bed.build_volume().bounding_volume());
|
|
if (include_plates) {
|
|
bb.merge(wxGetApp().plater()->get_partplate_list().get_bounding_box());
|
|
}
|
|
|
|
if (!m_main_toolbar.is_enabled()) {
|
|
const BoundingBoxf3& toolpath_bb = m_gcode_viewer.get_max_bounding_box();
|
|
if (toolpath_bb.max_size() > 0.f)
|
|
bb.merge(toolpath_bb);
|
|
else
|
|
bb.merge(m_gcode_viewer.get_shell_bounding_box());
|
|
}
|
|
|
|
if ((m_canvas_type == CanvasView3D) && (fff_print()->config().print_sequence == PrintSequence::ByObject)) {
|
|
float height_to_lid, height_to_rod;
|
|
wxGetApp().plater()->get_partplate_list().get_height_limits(height_to_lid, height_to_rod);
|
|
bb.max.z() = std::max(bb.max.z(), (double)height_to_lid);
|
|
}
|
|
|
|
return bb;
|
|
}
|
|
|
|
void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box, double margin_factor)
|
|
{
|
|
wxGetApp().plater()->get_camera().zoom_to_box(box, margin_factor);
|
|
m_dirty = true;
|
|
}
|
|
|
|
void GLCanvas3D::_update_camera_zoom(double zoom)
|
|
{
|
|
wxGetApp().plater()->get_camera().update_zoom(zoom);
|
|
m_dirty = true;
|
|
}
|
|
|
|
void GLCanvas3D::_refresh_if_shown_on_screen()
|
|
{
|
|
if (_is_shown_on_screen()) {
|
|
const Size& cnv_size = get_canvas_size();
|
|
_resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height());
|
|
|
|
// Because of performance problems on macOS, where PaintEvents are not delivered
|
|
// frequently enough, we call render() here directly when we can.
|
|
render();
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::_picking_pass()
|
|
{
|
|
std::vector<int>* hover_volume_idxs = const_cast<std::vector<int>*>(&m_hover_volume_idxs);
|
|
std::vector<int>* hover_plate_idxs = const_cast<std::vector<int>*>(&m_hover_plate_idxs);
|
|
|
|
if (m_picking_enabled && !m_mouse.dragging && m_mouse.position != Vec2d(DBL_MAX, DBL_MAX)) {
|
|
hover_volume_idxs->clear();
|
|
hover_plate_idxs->clear();
|
|
|
|
// Render the object for picking.
|
|
// FIXME This cannot possibly work in a multi - sampled context as the color gets mangled by the anti - aliasing.
|
|
// Better to use software ray - casting on a bounding - box hierarchy.
|
|
|
|
if (m_multisample_allowed)
|
|
// This flag is often ignored by NVIDIA drivers if rendering into a screen buffer.
|
|
glsafe(::glDisable(GL_MULTISAMPLE));
|
|
|
|
glsafe(::glDisable(GL_BLEND));
|
|
glsafe(::glEnable(GL_DEPTH_TEST));
|
|
|
|
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
|
|
|
|
//BBS: only render plate in view 3D
|
|
bool is_paint_gizmo=(m_gizmos.get_current_type() == GLGizmosManager::EType::FdmSupports ||
|
|
m_gizmos.get_current_type() == GLGizmosManager::EType::MmuSegmentation ||
|
|
m_gizmos.get_current_type() == GLGizmosManager::EType::Seam);
|
|
if (m_canvas_type == ECanvasType::CanvasView3D && !is_paint_gizmo) {
|
|
_render_plates_for_picking();
|
|
}
|
|
|
|
m_camera_clipping_plane = m_gizmos.get_clipping_plane();
|
|
if (m_camera_clipping_plane.is_active()) {
|
|
::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data());
|
|
::glEnable(GL_CLIP_PLANE0);
|
|
}
|
|
_render_volumes_for_picking();
|
|
if (m_camera_clipping_plane.is_active())
|
|
::glDisable(GL_CLIP_PLANE0);
|
|
|
|
//BBS: remove the bed picking logic
|
|
//_render_bed_for_picking(!wxGetApp().plater()->get_camera().is_looking_downward());
|
|
|
|
m_gizmos.render_current_gizmo_for_picking_pass();
|
|
|
|
if (m_multisample_allowed)
|
|
glsafe(::glEnable(GL_MULTISAMPLE));
|
|
|
|
int volume_id = -1;
|
|
int gizmo_id = -1;
|
|
|
|
GLubyte color[4] = { 0, 0, 0, 0 };
|
|
const Size& cnv_size = get_canvas_size();
|
|
bool inside = 0 <= m_mouse.position(0) && m_mouse.position(0) < cnv_size.get_width() && 0 <= m_mouse.position(1) && m_mouse.position(1) < cnv_size.get_height();
|
|
if (inside) {
|
|
glsafe(::glReadPixels(m_mouse.position(0), cnv_size.get_height() - m_mouse.position(1) - 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void*)color));
|
|
if (picking_checksum_alpha_channel(color[0], color[1], color[2]) == color[3]) {
|
|
// Only non-interpolated colors are valid, those have their lowest three bits zeroed.
|
|
// we reserve color = (0,0,0) for occluders (as the printbed)
|
|
// volumes' id are shifted by 1
|
|
// see: _render_volumes_for_picking()
|
|
//BBS: remove the bed picking logic
|
|
//volume_id = color[0] + (color[1] << 8) + (color[2] << 16) - 1;
|
|
volume_id = color[0] + (color[1] << 8) + (color[2] << 16);
|
|
// gizmos' id are instead properly encoded by the color
|
|
gizmo_id = color[0] + (color[1] << 8) + (color[2] << 16);
|
|
}
|
|
}
|
|
else
|
|
m_gizmos.set_hover_id(inside && (unsigned int)gizmo_id <= GLGizmoBase::BASE_ID ? ((int)GLGizmoBase::BASE_ID - gizmo_id) : -1);
|
|
|
|
//BBS: add plate picking logic
|
|
int plate_hover_id = PartPlate::PLATE_BASE_ID - volume_id;
|
|
if (plate_hover_id >= 0 && plate_hover_id < PartPlateList::MAX_PLATES_COUNT * PartPlate::GRABBER_COUNT) {
|
|
wxGetApp().plater()->get_partplate_list().set_hover_id(plate_hover_id);
|
|
hover_plate_idxs->emplace_back(plate_hover_id);
|
|
const_cast<GLGizmosManager*>(&m_gizmos)->set_hover_id(-1);
|
|
}
|
|
else {
|
|
wxGetApp().plater()->get_partplate_list().reset_hover_id();
|
|
if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) {
|
|
// do not add the volume id if any gizmo is active and CTRL is pressed
|
|
if (m_gizmos.get_current_type() == GLGizmosManager::EType::Undefined || !wxGetKeyState(WXK_CONTROL))
|
|
hover_volume_idxs->emplace_back(volume_id);
|
|
const_cast<GLGizmosManager*>(&m_gizmos)->set_hover_id(-1);
|
|
}
|
|
else
|
|
const_cast<GLGizmosManager*>(&m_gizmos)->set_hover_id(inside && (unsigned int)volume_id <= GLGizmoBase::BASE_ID ? ((int)GLGizmoBase::BASE_ID - volume_id) : -1);
|
|
}
|
|
|
|
_update_volumes_hover_state();
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::_rectangular_selection_picking_pass()
|
|
{
|
|
m_gizmos.set_hover_id(-1);
|
|
|
|
std::set<int> idxs;
|
|
|
|
if (m_picking_enabled) {
|
|
if (m_multisample_allowed)
|
|
// This flag is often ignored by NVIDIA drivers if rendering into a screen buffer.
|
|
glsafe(::glDisable(GL_MULTISAMPLE));
|
|
|
|
glsafe(::glDisable(GL_BLEND));
|
|
glsafe(::glEnable(GL_DEPTH_TEST));
|
|
|
|
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
|
|
|
|
_render_volumes_for_picking();
|
|
//BBS: remove the bed picking logic
|
|
//_render_bed_for_picking(!wxGetApp().plater()->get_camera().is_looking_downward());
|
|
|
|
if (m_multisample_allowed)
|
|
glsafe(::glEnable(GL_MULTISAMPLE));
|
|
|
|
int width = std::max((int)m_rectangle_selection.get_width(), 1);
|
|
int height = std::max((int)m_rectangle_selection.get_height(), 1);
|
|
int px_count = width * height;
|
|
|
|
int left = (int)m_rectangle_selection.get_left();
|
|
int top = get_canvas_size().get_height() - (int)m_rectangle_selection.get_top();
|
|
if (left >= 0 && top >= 0) {
|
|
#define USE_PARALLEL 1
|
|
#if USE_PARALLEL
|
|
struct Pixel
|
|
{
|
|
std::array<GLubyte, 4> data;
|
|
// Only non-interpolated colors are valid, those have their lowest three bits zeroed.
|
|
bool valid() const { return picking_checksum_alpha_channel(data[0], data[1], data[2]) == data[3]; }
|
|
// we reserve color = (0,0,0) for occluders (as the printbed)
|
|
// volumes' id are shifted by 1
|
|
// see: _render_volumes_for_picking()
|
|
//BBS: remove the bed picking logic
|
|
int id() const { return data[0] + (data[1] << 8) + (data[2] << 16); }
|
|
//int id() const { return data[0] + (data[1] << 8) + (data[2] << 16) - 1; }
|
|
};
|
|
|
|
std::vector<Pixel> frame(px_count);
|
|
glsafe(::glReadPixels(left, top, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (void*)frame.data()));
|
|
|
|
tbb::spin_mutex mutex;
|
|
tbb::parallel_for(tbb::blocked_range<size_t>(0, frame.size(), (size_t)width),
|
|
[this, &frame, &idxs, &mutex](const tbb::blocked_range<size_t>& range) {
|
|
for (size_t i = range.begin(); i < range.end(); ++i)
|
|
if (frame[i].valid()) {
|
|
int volume_id = frame[i].id();
|
|
if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size()) {
|
|
mutex.lock();
|
|
idxs.insert(volume_id);
|
|
mutex.unlock();
|
|
}
|
|
}
|
|
});
|
|
#else
|
|
std::vector<GLubyte> frame(4 * px_count);
|
|
glsafe(::glReadPixels(left, top, width, height, GL_RGBA, GL_UNSIGNED_BYTE, (void*)frame.data()));
|
|
|
|
for (int i = 0; i < px_count; ++i)
|
|
{
|
|
int px_id = 4 * i;
|
|
int volume_id = frame[px_id] + (frame[px_id + 1] << 8) + (frame[px_id + 2] << 16);
|
|
if (0 <= volume_id && volume_id < (int)m_volumes.volumes.size())
|
|
idxs.insert(volume_id);
|
|
}
|
|
#endif // USE_PARALLEL
|
|
}
|
|
}
|
|
|
|
m_hover_volume_idxs.assign(idxs.begin(), idxs.end());
|
|
_update_volumes_hover_state();
|
|
}
|
|
|
|
void GLCanvas3D::_render_background() const
|
|
{
|
|
bool use_error_color = false;
|
|
if (wxGetApp().is_editor()) {
|
|
use_error_color = m_dynamic_background_enabled &&
|
|
(current_printer_technology() != ptSLA || !m_volumes.empty());
|
|
|
|
if (!m_volumes.empty())
|
|
use_error_color &= _is_any_volume_outside();
|
|
else {
|
|
//BBS: use current plater's bounding box
|
|
//BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3();
|
|
BoundingBoxf3 test_volume = (const_cast<GLCanvas3D*>(this))->_get_current_partplate_print_volume();
|
|
const BoundingBoxf3& path_bounding_box = m_gcode_viewer.get_paths_bounding_box();
|
|
if (empty(path_bounding_box))
|
|
use_error_color = false;
|
|
else
|
|
//BBS: use previous result
|
|
use_error_color = (test_volume.radius() > 0.0) ? m_toolpath_outside : false;
|
|
//use_error_color &= (test_volume.radius() > 0.0) ? !test_volume.contains(path_bounding_box) : false;
|
|
}
|
|
}
|
|
|
|
glsafe(::glPushMatrix());
|
|
glsafe(::glLoadIdentity());
|
|
glsafe(::glMatrixMode(GL_PROJECTION));
|
|
glsafe(::glPushMatrix());
|
|
glsafe(::glLoadIdentity());
|
|
|
|
// Draws a bottom to top gradient over the complete screen.
|
|
glsafe(::glDisable(GL_DEPTH_TEST));
|
|
|
|
::glBegin(GL_QUADS);
|
|
|
|
float* background_color = m_is_dark ? DEFAULT_BG_LIGHT_COLOR_DARK : DEFAULT_BG_LIGHT_COLOR;
|
|
float* error_background_color = m_is_dark ? ERROR_BG_LIGHT_COLOR_DARK : ERROR_BG_LIGHT_COLOR;
|
|
|
|
if (use_error_color)
|
|
::glColor3fv(error_background_color);
|
|
else
|
|
::glColor3fv(background_color);
|
|
|
|
::glVertex2f(-1.0f, -1.0f);
|
|
::glVertex2f(1.0f, -1.0f);
|
|
|
|
if (use_error_color)
|
|
::glColor3fv(error_background_color);
|
|
else
|
|
::glColor3fv(background_color);
|
|
|
|
::glVertex2f(1.0f, 1.0f);
|
|
::glVertex2f(-1.0f, 1.0f);
|
|
glsafe(::glEnd());
|
|
|
|
glsafe(::glEnable(GL_DEPTH_TEST));
|
|
|
|
glsafe(::glPopMatrix());
|
|
glsafe(::glMatrixMode(GL_MODELVIEW));
|
|
glsafe(::glPopMatrix());
|
|
}
|
|
|
|
void GLCanvas3D::_render_bed(bool bottom, bool show_axes)
|
|
{
|
|
float scale_factor = 1.0;
|
|
#if ENABLE_RETINA_GL
|
|
scale_factor = m_retina_helper->get_scale_factor();
|
|
#endif // ENABLE_RETINA_GL
|
|
|
|
/*
|
|
bool show_texture = ! bottom ||
|
|
(m_gizmos.get_current_type() != GLGizmosManager::FdmSupports
|
|
&& m_gizmos.get_current_type() != GLGizmosManager::SlaSupports
|
|
&& m_gizmos.get_current_type() != GLGizmosManager::Hollow
|
|
&& m_gizmos.get_current_type() != GLGizmosManager::Seam
|
|
&& m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation);
|
|
*/
|
|
//bool show_texture = true;
|
|
//BBS set axes mode
|
|
m_bed.set_axes_mode(m_main_toolbar.is_enabled() && !m_gizmos.is_show_only_active_plate());
|
|
m_bed.render(*this, bottom, scale_factor, show_axes);
|
|
}
|
|
|
|
void GLCanvas3D::_render_bed_for_picking(bool bottom)
|
|
{
|
|
float scale_factor = 1.0;
|
|
#if ENABLE_RETINA_GL
|
|
scale_factor = m_retina_helper->get_scale_factor();
|
|
#endif // ENABLE_RETINA_GL
|
|
|
|
//m_bed.render_for_picking(*this, bottom, scale_factor);
|
|
}
|
|
|
|
void GLCanvas3D::_render_platelist(bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali) const
|
|
{
|
|
wxGetApp().plater()->get_partplate_list().render(bottom, only_current, only_body, hover_id, render_cali);
|
|
}
|
|
|
|
void GLCanvas3D::_render_plates_for_picking() const
|
|
{
|
|
wxGetApp().plater()->get_partplate_list().render_for_picking_pass();
|
|
}
|
|
|
|
void GLCanvas3D::_render_plane() const
|
|
{
|
|
;//TODO render assemble plane
|
|
}
|
|
|
|
//BBS: add outline drawing logic
|
|
void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with_outline)
|
|
{
|
|
if (m_volumes.empty())
|
|
return;
|
|
|
|
glsafe(::glEnable(GL_DEPTH_TEST));
|
|
|
|
m_camera_clipping_plane = m_gizmos.get_clipping_plane();
|
|
|
|
if (m_picking_enabled)
|
|
// Update the layer editing selection to the first object selected, update the current object maximum Z.
|
|
m_layers_editing.select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1);
|
|
|
|
if (const BuildVolume &build_volume = m_bed.build_volume(); build_volume.valid()) {
|
|
switch (build_volume.type()) {
|
|
case BuildVolume::Type::Rectangle: {
|
|
const BoundingBox3Base<Vec3d> bed_bb = build_volume.bounding_volume().inflated(BuildVolume::SceneEpsilon);
|
|
m_volumes.set_print_volume({ 0, // Rectangle
|
|
{ float(bed_bb.min.x()), float(bed_bb.min.y()), float(bed_bb.max.x()), float(bed_bb.max.y()) },
|
|
{ 0.0f, float(build_volume.printable_height()) } });
|
|
break;
|
|
}
|
|
case BuildVolume::Type::Circle: {
|
|
m_volumes.set_print_volume({ 1, // Circle
|
|
{ unscaled<float>(build_volume.circle().center.x()), unscaled<float>(build_volume.circle().center.y()), unscaled<float>(build_volume.circle().radius + BuildVolume::SceneEpsilon), 0.0f },
|
|
{ 0.0f, float(build_volume.printable_height() + BuildVolume::SceneEpsilon) } });
|
|
break;
|
|
}
|
|
default:
|
|
case BuildVolume::Type::Convex:
|
|
case BuildVolume::Type::Custom: {
|
|
m_volumes.set_print_volume({ static_cast<int>(type),
|
|
{ -FLT_MAX, -FLT_MAX, FLT_MAX, FLT_MAX },
|
|
{ -FLT_MAX, FLT_MAX } }
|
|
);
|
|
}
|
|
}
|
|
if (m_requires_check_outside_state) {
|
|
m_volumes.check_outside_state(build_volume, nullptr);
|
|
m_requires_check_outside_state = false;
|
|
}
|
|
}
|
|
|
|
if (m_use_clipping_planes)
|
|
m_volumes.set_z_range(-m_clipping_planes[0].get_data()[3], m_clipping_planes[1].get_data()[3]);
|
|
else
|
|
m_volumes.set_z_range(-FLT_MAX, FLT_MAX);
|
|
|
|
GLGizmosManager& gm = get_gizmos_manager();
|
|
GLGizmoBase* current_gizmo = gm.get_current();
|
|
if (m_canvas_type == CanvasAssembleView) {
|
|
m_volumes.set_clipping_plane(m_gizmos.get_assemble_view_clipping_plane().get_data());
|
|
}
|
|
else if (current_gizmo && !current_gizmo->apply_clipping_plane()) {
|
|
m_volumes.set_clipping_plane(ClippingPlane::ClipsNothing().get_data());
|
|
}
|
|
else {
|
|
m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data());
|
|
}
|
|
if (m_canvas_type == CanvasAssembleView)
|
|
m_volumes.set_show_sinking_contours(false);
|
|
else
|
|
m_volumes.set_show_sinking_contours(!m_gizmos.is_hiding_instances());
|
|
|
|
GLShaderProgram* shader = wxGetApp().get_shader("gouraud");
|
|
ECanvasType canvas_type = this->m_canvas_type;
|
|
std::array<float, 4> body_color = canvas_type == ECanvasType::CanvasAssembleView ? std::array<float, 4>({1.0f, 1.0f, 0.0f, 1.0f}) ://yellow
|
|
std::array<float, 4>({1.0f, 1.0f, 1.0f, 1.0f});//white
|
|
bool partly_inside_enable = canvas_type == ECanvasType::CanvasAssembleView ? false : true;
|
|
if (shader != nullptr) {
|
|
shader->start_using();
|
|
|
|
switch (type)
|
|
{
|
|
default:
|
|
case GLVolumeCollection::ERenderType::Opaque:
|
|
{
|
|
const GLGizmosManager& gm = get_gizmos_manager();
|
|
if (dynamic_cast<GLGizmoPainterBase*>(gm.get_current()) == nullptr)
|
|
{
|
|
if (m_picking_enabled && 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(type, 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);
|
|
});
|
|
m_layers_editing.render_volumes(*this, m_volumes);
|
|
}
|
|
else {
|
|
/*if (wxGetApp().plater()->is_wireframe_enabled()) {
|
|
if (wxGetApp().plater()->is_show_wireframe())
|
|
shader->set_uniform("show_wireframe", true);
|
|
else
|
|
shader->set_uniform("show_wireframe", false);
|
|
}*/
|
|
//BBS:add assemble view related logic
|
|
// do not cull backfaces to show broken geometry, if any
|
|
m_volumes.render(type, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this, canvas_type](const GLVolume& volume) {
|
|
if (canvas_type == ECanvasType::CanvasAssembleView) {
|
|
return !volume.is_modifier && !volume.is_wipe_tower;
|
|
}
|
|
else {
|
|
return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0);
|
|
}
|
|
},
|
|
with_outline, body_color, partly_inside_enable);
|
|
}
|
|
}
|
|
else {
|
|
// 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.
|
|
// GLGizmosManager::EType type = gm.get_current_type();
|
|
if (dynamic_cast<GLGizmoPainterBase*>(gm.get_current())) {
|
|
shader->stop_using();
|
|
gm.render_painter_gizmo();
|
|
shader->start_using();
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
case GLVolumeCollection::ERenderType::Transparent:
|
|
{
|
|
/*if (wxGetApp().plater()->is_wireframe_enabled()) {
|
|
if (wxGetApp().plater()->is_show_wireframe())
|
|
shader->set_uniform("show_wireframe", true);
|
|
else
|
|
shader->set_uniform("show_wireframe", false);
|
|
}*/
|
|
//BBS:add assemble view related logic
|
|
m_volumes.render(type, false, wxGetApp().plater()->get_camera().get_view_matrix(), [this, canvas_type](const GLVolume& volume) {
|
|
if (canvas_type == ECanvasType::CanvasAssembleView) {
|
|
return !volume.is_modifier;
|
|
}
|
|
else {
|
|
return true;
|
|
}
|
|
},
|
|
with_outline, body_color, partly_inside_enable);
|
|
if (m_canvas_type == CanvasAssembleView && m_gizmos.m_assemble_view_data->model_objects_clipper()->get_position() > 0) {
|
|
const GLGizmosManager& gm = get_gizmos_manager();
|
|
shader->stop_using();
|
|
gm.render_painter_assemble_view();
|
|
shader->start_using();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*if (wxGetApp().plater()->is_wireframe_enabled()) {
|
|
shader->set_uniform("show_wireframe", false);
|
|
}*/
|
|
|
|
shader->stop_using();
|
|
}
|
|
|
|
m_camera_clipping_plane = ClippingPlane::ClipsNothing();
|
|
}
|
|
|
|
//BBS: GUI refactor: add canvas size as parameters
|
|
void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height)
|
|
{
|
|
m_gcode_viewer.render(canvas_width, canvas_height, SLIDER_RIGHT_MARGIN);
|
|
IMSlider *layers_slider = m_gcode_viewer.get_layers_slider();
|
|
IMSlider *moves_slider = m_gcode_viewer.get_moves_slider();
|
|
|
|
if (layers_slider->is_need_post_tick_event()) {
|
|
auto evt = new wxCommandEvent(EVT_CUSTOMEVT_TICKSCHANGED, m_canvas->GetId());
|
|
evt->SetInt((int)layers_slider->get_post_tick_event_type());
|
|
wxPostEvent(m_canvas, *evt);
|
|
layers_slider->reset_post_tick_event();
|
|
}
|
|
|
|
if (layers_slider->is_dirty()) {
|
|
set_volumes_z_range({layers_slider->GetLowerValueD(), layers_slider->GetHigherValueD()});
|
|
if (m_gcode_viewer.has_data()) {
|
|
m_gcode_viewer.set_layers_z_range({static_cast<unsigned int>(layers_slider->GetLowerValue()), static_cast<unsigned int>(layers_slider->GetHigherValue())});
|
|
}
|
|
layers_slider->set_as_dirty(false);
|
|
post_event(SimpleEvent(EVT_GLCANVAS_UPDATE));
|
|
m_gcode_viewer.update_marker_curr_move();
|
|
}
|
|
|
|
if (moves_slider->is_dirty()) {
|
|
moves_slider->set_as_dirty(false);
|
|
m_gcode_viewer.update_sequential_view_current((moves_slider->GetLowerValueD() - 1.0), static_cast<unsigned int>(moves_slider->GetHigherValueD() - 1.0));
|
|
post_event(SimpleEvent(EVT_GLCANVAS_UPDATE));
|
|
m_gcode_viewer.update_marker_curr_move();
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::_render_selection() const
|
|
{
|
|
float scale_factor = 1.0;
|
|
#if ENABLE_RETINA_GL
|
|
scale_factor = m_retina_helper->get_scale_factor();
|
|
#endif // ENABLE_RETINA_GL
|
|
|
|
if (!m_gizmos.is_running())
|
|
m_selection.render(scale_factor);
|
|
}
|
|
|
|
void GLCanvas3D::_render_sequential_clearance()
|
|
{
|
|
if (m_gizmos.is_dragging())
|
|
return;
|
|
|
|
switch (m_gizmos.get_current_type())
|
|
{
|
|
case GLGizmosManager::EType::Flatten:
|
|
case GLGizmosManager::EType::Cut:
|
|
case GLGizmosManager::EType::Hollow:
|
|
case GLGizmosManager::EType::SlaSupports:
|
|
case GLGizmosManager::EType::FdmSupports:
|
|
case GLGizmosManager::EType::Seam: { return; }
|
|
default: { break; }
|
|
}
|
|
|
|
m_sequential_print_clearance.render();
|
|
}
|
|
|
|
#if ENABLE_RENDER_SELECTION_CENTER
|
|
void GLCanvas3D::_render_selection_center() const
|
|
{
|
|
m_selection.render_center(m_gizmos.is_dragging());
|
|
}
|
|
#endif // ENABLE_RENDER_SELECTION_CENTER
|
|
|
|
void GLCanvas3D::_check_and_update_toolbar_icon_scale()
|
|
{
|
|
// Don't update a toolbar scale, when we are on a Preview
|
|
if (wxGetApp().plater()->is_preview_shown())
|
|
{
|
|
|
|
#if ENABLE_RETINA_GL
|
|
IMSlider* m_layers_slider = get_gcode_viewer().get_layers_slider();
|
|
IMSlider* m_moves_slider = get_gcode_viewer().get_moves_slider();
|
|
const float sc = m_retina_helper->get_scale_factor();
|
|
m_layers_slider->set_scale(sc);
|
|
m_moves_slider->set_scale(sc);
|
|
m_gcode_viewer.set_scale(sc);
|
|
|
|
auto* m_notification = wxGetApp().plater()->get_notification_manager();
|
|
m_notification->set_scale(sc);
|
|
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
float scale = wxGetApp().toolbar_icon_scale();
|
|
Size cnv_size = get_canvas_size();
|
|
|
|
//BBS: GUI refactor: GLToolbar
|
|
float size = GLToolbar::Default_Icons_Size * scale;
|
|
//float main_size = GLGizmosManager::Default_Icons_Size * scale;
|
|
|
|
// Set current size for all top toolbars. It will be used for next calculations
|
|
GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();
|
|
#if ENABLE_RETINA_GL
|
|
const float sc = m_retina_helper->get_scale_factor() * scale;
|
|
//BBS: GUI refactor: GLToolbar
|
|
m_main_toolbar.set_scale(sc);
|
|
m_assemble_view_toolbar.set_scale(sc);
|
|
m_separator_toolbar.set_scale(sc);
|
|
collapse_toolbar.set_scale(sc);
|
|
size *= m_retina_helper->get_scale_factor();
|
|
|
|
auto* m_notification = wxGetApp().plater()->get_notification_manager();
|
|
m_notification->set_scale(sc);
|
|
#else
|
|
//BBS: GUI refactor: GLToolbar
|
|
m_main_toolbar.set_icons_size(GLGizmosManager::Default_Icons_Size * scale);
|
|
m_assemble_view_toolbar.set_icons_size(size);
|
|
m_separator_toolbar.set_icons_size(size);
|
|
collapse_toolbar.set_icons_size(size);
|
|
#endif // ENABLE_RETINA_GL
|
|
|
|
//BBS: GUI refactor: GLToolbar
|
|
#if BBS_TOOLBAR_ON_TOP
|
|
float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : GLToolbar::Default_Icons_Size;
|
|
|
|
float top_tb_width = m_main_toolbar.get_width() + m_gizmos.get_scaled_total_width() + m_assemble_view_toolbar.get_width() + m_separator_toolbar.get_width() + collapse_toolbar_width;
|
|
int items_cnt = m_main_toolbar.get_visible_items_cnt() + m_gizmos.get_selectable_icons_cnt() + m_assemble_view_toolbar.get_visible_items_cnt() + m_separator_toolbar.get_visible_items_cnt() + collapse_toolbar.get_visible_items_cnt();
|
|
float noitems_width = top_tb_width - size * items_cnt; // width of separators and borders in top toolbars
|
|
|
|
// calculate scale needed for items in all top toolbars
|
|
#ifdef __WINDOWS__
|
|
cnv_size.set_width(cnv_size.get_width() + m_separator_toolbar.get_width() + collapse_toolbar_width);
|
|
#endif
|
|
float new_h_scale = (cnv_size.get_width() - noitems_width) / (items_cnt * GLToolbar::Default_Icons_Size);
|
|
|
|
//for protect
|
|
if (new_h_scale <= 0) {
|
|
new_h_scale = 1;
|
|
}
|
|
|
|
//use the same value as horizon
|
|
float new_v_scale = new_h_scale;
|
|
#else
|
|
float top_tb_width = = collapse_toolbar.get_width();
|
|
int items_cnt = collapse_toolbar.get_visible_items_cnt();
|
|
float noitems_width = top_tb_width - size * items_cnt; // width of separators and borders in top toolbars
|
|
|
|
// calculate scale needed for items in all top toolbars
|
|
float new_h_scale = (cnv_size.get_width() - noitems_width) / (items_cnt * GLToolbar::Default_Icons_Size);
|
|
|
|
//items_cnt = m_main_toolbar.get_visible_items_cnt() + m_gizmos.get_selectable_icons_cnt() + 3; // +3 means a place for top and view toolbars and separators in gizmos toolbar
|
|
|
|
// calculate scale needed for items in the gizmos toolbar
|
|
items_cnt = m_main_toolbar.get_visible_items_cnt() + m_gizmos.get_selectable_icons_cnt() + m_assemble_view_toolbar.get_visible_items_cnt();
|
|
float new_v_scale = cnv_size.get_height() / (items_cnt * GLGizmosManager::Default_Icons_Size);
|
|
#endif
|
|
|
|
// set minimum scale as a auto scale for the toolbars
|
|
float new_scale = std::min(new_h_scale, new_v_scale);
|
|
#if ENABLE_RETINA_GL
|
|
new_scale /= m_retina_helper->get_scale_factor();
|
|
#endif
|
|
if (fabs(new_scale - scale) > 0.01) // scale is changed by 1% and more
|
|
wxGetApp().set_auto_toolbar_icon_scale(new_scale);
|
|
}
|
|
|
|
void GLCanvas3D::_render_overlays()
|
|
{
|
|
glsafe(::glDisable(GL_DEPTH_TEST));
|
|
glsafe(::glPushMatrix());
|
|
glsafe(::glLoadIdentity());
|
|
// ensure that the textures are renderered inside the frustrum
|
|
const Camera& camera = wxGetApp().plater()->get_camera();
|
|
glsafe(::glTranslated(0.0, 0.0, -(camera.get_near_z() + 0.10)));
|
|
// ensure that the overlay fits the frustrum near z plane
|
|
double gui_scale = camera.get_gui_scale();
|
|
glsafe(::glScaled(gui_scale, gui_scale, 1.0));
|
|
|
|
_check_and_update_toolbar_icon_scale();
|
|
|
|
_render_assemble_control();
|
|
_render_assemble_info();
|
|
|
|
// main toolbar and undoredo toolbar need to be both updated before rendering because both their sizes are needed
|
|
// to correctly place them
|
|
#if ENABLE_RETINA_GL
|
|
const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(/*true*/);
|
|
//BBS: GUI refactor: GLToolbar
|
|
m_main_toolbar.set_scale(scale);
|
|
m_assemble_view_toolbar.set_scale(scale);
|
|
m_separator_toolbar.set_scale(scale);
|
|
wxGetApp().plater()->get_collapse_toolbar().set_scale(scale);
|
|
m_gizmos.set_overlay_scale(scale);
|
|
#else
|
|
// BBS adjust display scale
|
|
const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(/*true*/));
|
|
const float gizmo_size = int(GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale());
|
|
//const float size = int(GLToolbar::Default_Icons_Size);
|
|
//const float gizmo_size = int(GLGizmosManager::Default_Icons_Size);
|
|
|
|
//BBS: GUI refactor: GLToolbar
|
|
m_main_toolbar.set_icons_size(gizmo_size);
|
|
m_assemble_view_toolbar.set_icons_size(gizmo_size);
|
|
m_separator_toolbar.set_icons_size(gizmo_size);
|
|
wxGetApp().plater()->get_collapse_toolbar().set_icons_size(size);
|
|
m_gizmos.set_overlay_icon_size(gizmo_size);
|
|
#endif // ENABLE_RETINA_GL
|
|
|
|
_render_separator_toolbar_right();
|
|
_render_separator_toolbar_left();
|
|
_render_main_toolbar();
|
|
_render_collapse_toolbar();
|
|
_render_assemble_view_toolbar();
|
|
//BBS: GUI refactor: GLToolbar
|
|
_render_imgui_select_plate_toolbar();
|
|
_render_return_toolbar();
|
|
// BBS
|
|
//_render_view_toolbar();
|
|
_render_paint_toolbar();
|
|
|
|
//BBS: GUI refactor: GLToolbar
|
|
//move gizmos behind of main
|
|
_render_gizmos_overlay();
|
|
|
|
if (m_layers_editing.last_object_id >= 0 && m_layers_editing.object_max_z() > 0.0f)
|
|
m_layers_editing.render_overlay(*this);
|
|
|
|
auto curr_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
|
auto curr_print_seq = curr_plate->get_real_print_seq();
|
|
bool sequential_print = (curr_print_seq == PrintSequence::ByObject);
|
|
std::vector<const ModelInstance*> sorted_instances;
|
|
if (sequential_print) {
|
|
const Print* print = fff_print();
|
|
if (print) {
|
|
for (const PrintObject *print_object : print->objects())
|
|
{
|
|
for (const PrintInstance &instance : print_object->instances())
|
|
{
|
|
sorted_instances.emplace_back(instance.model_instance);
|
|
}
|
|
}
|
|
}
|
|
/*for (ModelObject* model_object : m_model->objects)
|
|
for (ModelInstance* model_instance : model_object->instances) {
|
|
sorted_instances.emplace_back(model_instance);
|
|
}*/
|
|
}
|
|
m_labels.render(sorted_instances);
|
|
|
|
glsafe(::glPopMatrix());
|
|
}
|
|
|
|
void GLCanvas3D::_render_style_editor()
|
|
{
|
|
bool show_style_editor = true;
|
|
ImGui::Begin("ImGui Style Editor", &show_style_editor);
|
|
// You can pass in a reference ImGuiStyle structure to compare to, revert to and save to
|
|
// (without a reference style pointer, we will use one compared locally as a reference)
|
|
|
|
ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.50f);
|
|
ImGui::ShowFontSelector("Fonts##Selector");
|
|
ImGui::Separator();
|
|
|
|
if (ImGui::BeginTabBar("##tabs", ImGuiTabBarFlags_None))
|
|
{
|
|
if (ImGui::BeginTabItem("Colors"))
|
|
{
|
|
static int output_dest = 0;
|
|
static bool output_only_modified = false;
|
|
if (ImGui::Button("Export"))
|
|
{
|
|
if (output_dest == 0)
|
|
ImGui::LogToClipboard();
|
|
else
|
|
ImGui::LogToTTY();
|
|
|
|
ImGui::LogText("RenderColors:" IM_NEWLINE);
|
|
for (int i = 0; i < RenderCol_Count; i++)
|
|
{
|
|
const ImVec4& col = RenderColor::colors[i];
|
|
const char* name = GetRenderColName(i);
|
|
if (!output_only_modified || memcmp(&col, &RenderColor::colors[i], sizeof(ImVec4)) != 0)
|
|
ImGui::LogText("RenderColor::colors[%s]%*s= ImVec4(%.2ff, %.2ff, %.2ff, %.2ff);" IM_NEWLINE,
|
|
name, 23 - (int)strlen(name), "", col.x, col.y, col.z, col.w);
|
|
}
|
|
ImGui::LogFinish();
|
|
}
|
|
ImGui::SameLine(); ImGui::SetNextItemWidth(120); ImGui::Combo("##output_type", &output_dest, "To Clipboard\0To TTY\0");
|
|
ImGui::SameLine(); ImGui::Checkbox("Only Modified Colors", &output_only_modified);
|
|
|
|
static ImGuiTextFilter filter;
|
|
filter.Draw("Filter colors", ImGui::GetFontSize() * 16);
|
|
|
|
static ImGuiColorEditFlags alpha_flags = 0;
|
|
if (ImGui::RadioButton("Opaque", alpha_flags == ImGuiColorEditFlags_None)) { alpha_flags = ImGuiColorEditFlags_None; } ImGui::SameLine();
|
|
if (ImGui::RadioButton("Alpha", alpha_flags == ImGuiColorEditFlags_AlphaPreview)) { alpha_flags = ImGuiColorEditFlags_AlphaPreview; } ImGui::SameLine();
|
|
if (ImGui::RadioButton("Both", alpha_flags == ImGuiColorEditFlags_AlphaPreviewHalf)) { alpha_flags = ImGuiColorEditFlags_AlphaPreviewHalf; } ImGui::SameLine();
|
|
ImGui::TextDisabled("(?)");
|
|
if (ImGui::IsItemHovered())
|
|
{
|
|
ImGui::BeginTooltip();
|
|
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
|
|
ImGui::TextUnformatted("In the color list:\n"
|
|
"Left-click on color square to open color picker,\n"
|
|
"Right-click to open edit options menu.");
|
|
ImGui::PopTextWrapPos();
|
|
ImGui::EndTooltip();
|
|
}
|
|
ImGui::BeginChild("##colors", ImVec2(0, 0), true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar | ImGuiWindowFlags_NavFlattened);
|
|
ImGui::PushItemWidth(-160);
|
|
for (int i = 0; i < RenderCol_Count; i++)
|
|
{
|
|
const char* name = GetRenderColName(i);
|
|
if (!filter.PassFilter(name))
|
|
continue;
|
|
ImGui::PushID(i);
|
|
ImGui::ColorEdit4("##color", (float*)&RenderColor::colors[i], ImGuiColorEditFlags_AlphaBar | alpha_flags);
|
|
// Tips: in a real user application, you may want to merge and use an icon font into the main font,
|
|
// so instead of "Save"/"Revert" you'd use icons!
|
|
// Read the FAQ and docs/FONTS.md about using icon fonts. It's really easy and super convenient!
|
|
ImGui::SameLine(0.0f, 3.0f);
|
|
if (ImGui::Button("Set")) {
|
|
GLVolume::update_render_colors();
|
|
PartPlate::update_render_colors();
|
|
GLGizmoBase::update_render_colors();
|
|
GLCanvas3D::update_render_colors();
|
|
Bed3D::update_render_colors();
|
|
}
|
|
ImGui::SameLine(0.0f, 3.0f);
|
|
ImGui::TextUnformatted(name);
|
|
ImGui::PopID();
|
|
}
|
|
ImGui::PopItemWidth();
|
|
ImGui::EndChild();
|
|
|
|
ImGui::EndTabItem();
|
|
}
|
|
|
|
ImGui::EndTabBar();
|
|
}
|
|
|
|
ImGui::PopItemWidth();
|
|
ImGui::End();
|
|
}
|
|
|
|
void GLCanvas3D::_render_volumes_for_picking() const
|
|
{
|
|
static const GLfloat INV_255 = 1.0f / 255.0f;
|
|
|
|
// do not cull backfaces to show broken geometry, if any
|
|
glsafe(::glDisable(GL_CULL_FACE));
|
|
|
|
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
|
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
|
|
|
|
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::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.
|
|
// we reserve color = (0,0,0) for occluders (as the printbed)
|
|
// so we shift volumes' id by 1 to get the proper color
|
|
//BBS: remove the bed picking logic
|
|
unsigned int id = volume.second.first;
|
|
//unsigned int id = 1 + volume.second.first;
|
|
unsigned int r = (id & (0x000000FF << 0)) << 0;
|
|
unsigned int g = (id & (0x000000FF << 8)) >> 8;
|
|
unsigned int b = (id & (0x000000FF << 16)) >> 16;
|
|
unsigned int a = picking_checksum_alpha_channel(r, g, b);
|
|
glsafe(::glColor4f((GLfloat)r * INV_255, (GLfloat)g * INV_255, (GLfloat)b * INV_255, (GLfloat)a * INV_255));
|
|
volume.first->render();
|
|
}
|
|
}
|
|
|
|
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
|
|
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
|
|
|
|
glsafe(::glEnable(GL_CULL_FACE));
|
|
}
|
|
|
|
void GLCanvas3D::_render_current_gizmo() const
|
|
{
|
|
//BBS update inv_zoom
|
|
GLGizmoBase::INV_ZOOM = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
|
|
m_gizmos.render_current_gizmo();
|
|
}
|
|
|
|
//BBS: GUI refactor: GLToolbar adjust
|
|
//move the size calc to GLCanvas
|
|
void GLCanvas3D::_render_gizmos_overlay()
|
|
{
|
|
/*#if ENABLE_RETINA_GL
|
|
// m_gizmos.set_overlay_scale(m_retina_helper->get_scale_factor());
|
|
const float scale = m_retina_helper->get_scale_factor()*wxGetApp().toolbar_icon_scale();
|
|
m_gizmos.set_overlay_scale(scale); //! #ys_FIXME_experiment
|
|
#else
|
|
// m_gizmos.set_overlay_scale(m_canvas->GetContentScaleFactor());
|
|
// m_gizmos.set_overlay_scale(wxGetApp().em_unit()*0.1f);
|
|
const float size = int(GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale());
|
|
m_gizmos.set_overlay_icon_size(size); //! #ys_FIXME_experiment
|
|
#endif /* __WXMSW__ */
|
|
m_gizmos.render_overlay();
|
|
|
|
if (m_gizmo_highlighter.m_render_arrow)
|
|
{
|
|
m_gizmos.render_arrow(*this, m_gizmo_highlighter.m_gizmo_type);
|
|
}
|
|
}
|
|
|
|
//BBS: GUI refactor: GLToolbar adjust
|
|
//when rendering, {0, 0} is at the center, left-up is -0.5, 0.5, right-up is 0.5, -0.5
|
|
void GLCanvas3D::_render_main_toolbar()
|
|
{
|
|
if (!m_main_toolbar.is_enabled())
|
|
return;
|
|
|
|
Size cnv_size = get_canvas_size();
|
|
float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
|
|
|
|
#if BBS_TOOLBAR_ON_TOP
|
|
GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();
|
|
float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f;
|
|
float gizmo_width = m_gizmos.get_scaled_total_width();
|
|
float assemble_width = m_assemble_view_toolbar.get_width();
|
|
float separator_width = m_separator_toolbar.get_width();
|
|
float top = 0.5f * (float)cnv_size.get_height() * inv_zoom;
|
|
float left = std::max(-0.5f * cnv_size.get_width(), -0.5f * (m_main_toolbar.get_width() + separator_width + gizmo_width + assemble_width - collapse_toolbar_width)) * inv_zoom;
|
|
#else
|
|
float gizmo_height = m_gizmos.get_scaled_total_height();
|
|
float space_height = GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale();
|
|
float main_toolbar_height = (float)m_main_toolbar.get_height();
|
|
float assemble_height = m_assemble_view_toolbar.get_height();
|
|
float top = 0.5f * (main_toolbar_height + gizmo_height + assemble_height) * inv_zoom;
|
|
float left = (0.5f * (float)cnv_size.get_width() - m_main_toolbar.get_width()) * inv_zoom;
|
|
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": top %1%, main_toolbar_height %2%, space_height %3% gizmo_height %4%") % top % main_toolbar_height % space_height % gizmo_height;
|
|
#endif
|
|
m_main_toolbar.set_position(top, left);
|
|
m_main_toolbar.render(*this);
|
|
if (m_toolbar_highlighter.m_render_arrow)
|
|
{
|
|
m_main_toolbar.render_arrow(*this, m_toolbar_highlighter.m_toolbar_item);
|
|
}
|
|
}
|
|
|
|
//BBS: GUI refactor: GLToolbar adjust
|
|
//when rendering, {0, 0} is at the center, {-0.5, 0.5} at the left-up
|
|
void GLCanvas3D::_render_imgui_select_plate_toolbar()
|
|
{
|
|
if (!m_sel_plate_toolbar.is_enabled()) {
|
|
if (!m_render_preview)
|
|
m_render_preview = true;
|
|
return;
|
|
}
|
|
|
|
IMToolbarItem* all_plates_stats_item = m_sel_plate_toolbar.m_all_plates_stats_item;
|
|
|
|
PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list();
|
|
for (int i = 0; i < plate_list.get_plate_count(); i++) {
|
|
if (i < m_sel_plate_toolbar.m_items.size()) {
|
|
if (i == plate_list.get_curr_plate_index() && !all_plates_stats_item->selected)
|
|
m_sel_plate_toolbar.m_items[i]->selected = true;
|
|
else
|
|
m_sel_plate_toolbar.m_items[i]->selected = false;
|
|
|
|
m_sel_plate_toolbar.m_items[i]->percent = plate_list.get_plate(i)->get_slicing_percent();
|
|
|
|
if (plate_list.get_plate(i)->is_slice_result_valid()) {
|
|
if (plate_list.get_plate(i)->is_slice_result_ready_for_print())
|
|
m_sel_plate_toolbar.m_items[i]->slice_state = IMToolbarItem::SliceState::SLICED;
|
|
else
|
|
m_sel_plate_toolbar.m_items[i]->slice_state = IMToolbarItem::SliceState::SLICE_FAILED;
|
|
}
|
|
else {
|
|
if (!plate_list.get_plate(i)->can_slice())
|
|
m_sel_plate_toolbar.m_items[i]->slice_state = IMToolbarItem::SliceState::SLICE_FAILED;
|
|
else {
|
|
if (plate_list.get_plate(i)->get_slicing_percent() < 0.0f)
|
|
m_sel_plate_toolbar.m_items[i]->slice_state = IMToolbarItem::SliceState::UNSLICED;
|
|
else
|
|
m_sel_plate_toolbar.m_items[i]->slice_state = IMToolbarItem::SliceState::SLICING;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (m_sel_plate_toolbar.show_stats_item) {
|
|
all_plates_stats_item->percent = 0.0f;
|
|
|
|
size_t sliced_plates_cnt = 0;
|
|
for (auto plate : plate_list.get_nonempty_plate_list()) {
|
|
if (plate->is_slice_result_valid() && plate->is_slice_result_ready_for_print())
|
|
sliced_plates_cnt++;
|
|
}
|
|
all_plates_stats_item->percent = (float)(sliced_plates_cnt) / (float)(plate_list.get_nonempty_plate_list().size()) * 100.0f;
|
|
|
|
if (all_plates_stats_item->percent == 0.0f)
|
|
all_plates_stats_item->slice_state = IMToolbarItem::SliceState::UNSLICED;
|
|
else if (sliced_plates_cnt == plate_list.get_nonempty_plate_list().size())
|
|
all_plates_stats_item->slice_state = IMToolbarItem::SliceState::SLICED;
|
|
else if (all_plates_stats_item->percent < 100.0f)
|
|
all_plates_stats_item->slice_state = IMToolbarItem::SliceState::SLICING;
|
|
|
|
for (auto toolbar_item : m_sel_plate_toolbar.m_items) {
|
|
if(toolbar_item->slice_state == IMToolbarItem::SliceState::SLICE_FAILED) {
|
|
all_plates_stats_item->slice_state = IMToolbarItem::SliceState::SLICE_FAILED;
|
|
all_plates_stats_item->selected = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Changing parameters does not invalid all plates, need extra logic to validate
|
|
bool gcode_result_valid = true;
|
|
for (auto gcode_result : plate_list.get_nonempty_plates_slice_results()) {
|
|
if (gcode_result->moves.size() == 0) {
|
|
gcode_result_valid = false;
|
|
}
|
|
}
|
|
if (all_plates_stats_item->selected && all_plates_stats_item->slice_state == IMToolbarItem::SliceState::SLICED && gcode_result_valid) {
|
|
m_gcode_viewer.render_all_plates_stats(plate_list.get_nonempty_plates_slice_results());
|
|
m_render_preview = false;
|
|
}
|
|
else{
|
|
m_gcode_viewer.render_all_plates_stats(plate_list.get_nonempty_plates_slice_results(), false);
|
|
m_render_preview = true;
|
|
}
|
|
}else
|
|
m_render_preview = true;
|
|
|
|
// places the toolbar on the top_left corner of the 3d scene
|
|
#if ENABLE_RETINA_GL
|
|
float f_scale = m_retina_helper->get_scale_factor();
|
|
#else
|
|
float f_scale = 1.0;
|
|
#endif
|
|
Size cnv_size = get_canvas_size();
|
|
auto canvas_w = float(cnv_size.get_width());
|
|
auto canvas_h = float(cnv_size.get_height());
|
|
|
|
bool is_hovered = false;
|
|
|
|
m_sel_plate_toolbar.set_icon_size(100.0f * f_scale, 100.0f * f_scale);
|
|
|
|
float button_width = m_sel_plate_toolbar.icon_width;
|
|
float button_height = m_sel_plate_toolbar.icon_height;
|
|
|
|
float frame_padding = 1.0f * f_scale;
|
|
float margin_size = 4.0f * f_scale;
|
|
float button_margin = frame_padding;
|
|
|
|
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
|
int item_count = m_sel_plate_toolbar.m_items.size() + (m_sel_plate_toolbar.show_stats_item ? 1 : 0);
|
|
bool show_scroll = item_count * (button_height + frame_padding * 2.0f + button_margin) - button_margin + 22.0f * f_scale > canvas_h ? true: false;
|
|
show_scroll = m_sel_plate_toolbar.is_display_scrollbar && show_scroll;
|
|
float window_height = std::min(item_count * (button_height + (frame_padding + margin_size) * 2.0f + button_margin) - button_margin + 28.0f * f_scale, canvas_h);
|
|
float window_width = m_sel_plate_toolbar.icon_width + margin_size * 2 + (show_scroll ? 28.0f * f_scale : 20.0f * f_scale);
|
|
|
|
ImVec4 window_bg = ImVec4(0.82f, 0.82f, 0.82f, 0.5f);
|
|
ImVec4 button_active = ImVec4(0.12f, 0.56f, 0.92, 1.0f);
|
|
ImVec4 button_hover = ImVec4(0.67f, 0.67f, 0.67, 1.0f);
|
|
ImVec4 scroll_col = ImVec4(0.77f, 0.77f, 0.77f, 1.0f);
|
|
//ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.f, 0.f, 0.f, 1.0f));
|
|
//use white text as the background switch to black
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, window_bg);
|
|
ImGui::PushStyleColor(ImGuiCol_ScrollbarBg, window_bg);
|
|
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabActive, scroll_col);
|
|
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrabHovered, scroll_col);
|
|
ImGui::PushStyleColor(ImGuiCol_ScrollbarGrab, scroll_col);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, button_active);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, button_hover);
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ScrollbarSize, 10.0f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 4.0f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
|
|
|
imgui.set_next_window_pos(canvas_w * 0, canvas_h * 0, ImGuiCond_Always, 0, 0);
|
|
imgui.set_next_window_size(window_width, window_height, ImGuiCond_Always);
|
|
|
|
if (show_scroll)
|
|
imgui.begin(_L("Select Plate"), ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse);
|
|
else
|
|
imgui.begin(_L("Select Plate"), ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse);
|
|
ImGui::SetWindowFontScale(1.2f);
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1.0f * f_scale);
|
|
|
|
ImVec2 size = ImVec2(button_width, button_height); // Size of the image we want to make visible
|
|
ImVec4 bg_col = ImVec4(128.0f, 128.0f, 128.0f, 0.0f);
|
|
ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // No tint
|
|
ImVec2 margin = ImVec2(margin_size, margin_size);
|
|
|
|
if(m_sel_plate_toolbar.show_stats_item)
|
|
{
|
|
// draw image
|
|
ImVec2 button_start_pos = ImGui::GetCursorScreenPos();
|
|
|
|
if (all_plates_stats_item->selected) {
|
|
ImGui::PushStyleColor(ImGuiCol_Button, button_active);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, button_active);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, button_active);
|
|
}
|
|
else {
|
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(128.0f, 128.0f, 128.0f, 0.0f));
|
|
if (all_plates_stats_item->slice_state == IMToolbarItem::SliceState::SLICE_FAILED) {
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImGui::GetStyleColorVec4(ImGuiCol_Button));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImGui::GetStyleColorVec4(ImGuiCol_Button));
|
|
}
|
|
else {
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, button_hover);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, button_hover);
|
|
}
|
|
}
|
|
|
|
ImVec4 text_clr;
|
|
ImTextureID btn_texture_id;
|
|
if (all_plates_stats_item->slice_state == IMToolbarItem::SliceState::UNSLICED || all_plates_stats_item->slice_state == IMToolbarItem::SliceState::SLICING || all_plates_stats_item->slice_state == IMToolbarItem::SliceState::SLICE_FAILED)
|
|
{
|
|
text_clr = ImVec4(0, 174.0f / 255.0f, 66.0f / 255.0f, 0.2f);
|
|
btn_texture_id = (ImTextureID)(intptr_t)(all_plates_stats_item->image_texture_transparent.get_id());
|
|
}
|
|
else
|
|
{
|
|
text_clr = ImVec4(0, 174.0f / 255.0f, 66.0f / 255.0f, 1);
|
|
btn_texture_id = (ImTextureID)(intptr_t)(all_plates_stats_item->image_texture.get_id());
|
|
}
|
|
|
|
if (ImGui::ImageButton2(btn_texture_id, size, {0,0}, {1,1}, frame_padding, bg_col, tint_col, margin)) {
|
|
if (all_plates_stats_item->slice_state != IMToolbarItem::SliceState::SLICE_FAILED) {
|
|
if (m_process && !m_process->running()) {
|
|
for (int i = 0; i < m_sel_plate_toolbar.m_items.size(); i++) {
|
|
m_sel_plate_toolbar.m_items[i]->selected = false;
|
|
}
|
|
all_plates_stats_item->selected = true;
|
|
wxGetApp().plater()->update(true, true);
|
|
wxCommandEvent evt = wxCommandEvent(EVT_GLTOOLBAR_SLICE_ALL);
|
|
wxPostEvent(wxGetApp().plater(), evt);
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGui::PopStyleColor(3);
|
|
|
|
ImVec2 start_pos = ImVec2(button_start_pos.x + frame_padding + margin.x, button_start_pos.y + frame_padding + margin.y);
|
|
if (all_plates_stats_item->slice_state == IMToolbarItem::SliceState::UNSLICED) {
|
|
ImVec2 size = ImVec2(button_width, button_height);
|
|
ImVec2 end_pos = ImVec2(start_pos.x + size.x, start_pos.y + size.y);
|
|
ImGui::GetForegroundDrawList()->AddRectFilled(start_pos, end_pos, IM_COL32(0, 0, 0, 80));
|
|
}
|
|
else if (all_plates_stats_item->slice_state == IMToolbarItem::SliceState::SLICING) {
|
|
ImVec2 size = ImVec2(button_width, button_height * all_plates_stats_item->percent / 100.0f);
|
|
ImVec2 rect_start_pos = ImVec2(start_pos.x, start_pos.y + size.y);
|
|
ImVec2 rect_end_pos = ImVec2(start_pos.x + button_width, start_pos.y + button_height);
|
|
ImGui::GetForegroundDrawList()->AddRectFilled(start_pos, rect_end_pos, IM_COL32(0, 0, 0, 10));
|
|
ImGui::GetForegroundDrawList()->AddRectFilled(rect_start_pos, rect_end_pos, IM_COL32(0, 0, 0, 80));
|
|
}
|
|
else if (all_plates_stats_item->slice_state == IMToolbarItem::SliceState::SLICE_FAILED) {
|
|
ImVec2 size = ImVec2(button_width, button_height);
|
|
ImVec2 end_pos = ImVec2(start_pos.x + size.x, start_pos.y + size.y);
|
|
ImGui::GetForegroundDrawList()->AddRectFilled(start_pos, end_pos, IM_COL32(40, 1, 1, 64));
|
|
ImGui::GetForegroundDrawList()->AddRect(start_pos, end_pos, IM_COL32(208, 27, 27, 255), 0.0f, 0, 1.0f);
|
|
}
|
|
else if (all_plates_stats_item->slice_state == IMToolbarItem::SliceState::SLICED) {
|
|
ImVec2 size = ImVec2(button_width, button_height);
|
|
ImVec2 end_pos = ImVec2(start_pos.x + size.x, start_pos.y + size.y);
|
|
ImGui::GetForegroundDrawList()->AddRectFilled(start_pos, end_pos, IM_COL32(0, 0, 0, 10));
|
|
}
|
|
|
|
// draw text
|
|
GImGui->FontSize = 15.0f;
|
|
ImGui::PushStyleColor(ImGuiCol_Text, text_clr);
|
|
ImVec2 text_size = ImGui::CalcTextSize(("All Plates"));
|
|
ImVec2 text_start_pos = ImVec2(start_pos.x + (button_width - text_size.x) / 2, start_pos.y + 3.0f * button_height / 5.0f);
|
|
ImGui::RenderText(text_start_pos, ("All Plates"));
|
|
text_size = ImGui::CalcTextSize(("Stats"));
|
|
text_start_pos = ImVec2(start_pos.x + (button_width - text_size.x) / 2, text_start_pos.y + ImGui::GetTextLineHeight());
|
|
ImGui::RenderText(text_start_pos, ("Stats"));
|
|
ImGui::PopStyleColor();
|
|
ImGui::SetWindowFontScale(1.2f);
|
|
}
|
|
|
|
ImVec4 error_text_clr = ImVec4(1, 0, 0, 1);
|
|
for (int i = 0; i < m_sel_plate_toolbar.m_items.size(); i++) {
|
|
IMToolbarItem* item = m_sel_plate_toolbar.m_items[i];
|
|
|
|
// draw image
|
|
ImVec2 button_start_pos = ImGui::GetCursorScreenPos();
|
|
ImGui::PushID(i);
|
|
ImVec2 uv0 = ImVec2(0.0f, 1.0f); // UV coordinates for lower-left
|
|
ImVec2 uv1 = ImVec2(1.0f, 0.0f); // UV coordinates in our texture
|
|
|
|
auto button_pos = ImGui::GetCursorPos();
|
|
ImGui::SetCursorPos(button_pos + margin);
|
|
|
|
ImGui::Image(item->texture_id, size, uv0, uv1, tint_col);
|
|
|
|
ImGui::SetCursorPos(button_pos);
|
|
|
|
// invisible button
|
|
auto button_size = size + margin + margin + ImVec2(2 * frame_padding, 2 * frame_padding);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 2.0f * f_scale);
|
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
|
if (item->selected) {
|
|
ImGui::PushStyleColor(ImGuiCol_Border, button_active);
|
|
}
|
|
else {
|
|
if (ImGui::IsMouseHoveringRect(button_start_pos, button_start_pos + button_size)) {
|
|
ImGui::PushStyleColor(ImGuiCol_Border, button_hover);
|
|
}
|
|
else {
|
|
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(.0f, .0f, .0f, .0f));
|
|
}
|
|
}
|
|
if(ImGui::Button("##invisible_button", button_size)){
|
|
if (m_process && !m_process->running()) {
|
|
all_plates_stats_item->selected = false;
|
|
item->selected = true;
|
|
// begin to slicing plate
|
|
if (item->slice_state != IMToolbarItem::SliceState::SLICED)
|
|
wxGetApp().plater()->update(true, true);
|
|
wxCommandEvent* evt = new wxCommandEvent(EVT_GLTOOLBAR_SELECT_SLICED_PLATE);
|
|
evt->SetInt(i);
|
|
wxQueueEvent(wxGetApp().plater(), evt);
|
|
}
|
|
}
|
|
ImGui::PopStyleColor(4);
|
|
ImGui::PopStyleVar();
|
|
|
|
ImVec2 start_pos = ImVec2(button_start_pos.x + frame_padding + margin.x, button_start_pos.y + frame_padding + margin.y);
|
|
if (item->slice_state == IMToolbarItem::SliceState::UNSLICED) {
|
|
ImVec2 size = ImVec2(button_width, button_height);
|
|
ImVec2 end_pos = ImVec2(start_pos.x + size.x, start_pos.y + size.y);
|
|
ImGui::GetForegroundDrawList()->AddRectFilled(start_pos, end_pos, IM_COL32(0, 0, 0, 80));
|
|
} else if (item->slice_state == IMToolbarItem::SliceState::SLICING) {
|
|
ImVec2 size = ImVec2(button_width, button_height * item->percent / 100.0f);
|
|
ImVec2 rect_start_pos = ImVec2(start_pos.x, start_pos.y + size.y);
|
|
ImVec2 rect_end_pos = ImVec2(start_pos.x + button_width, start_pos.y + button_height);
|
|
ImGui::GetForegroundDrawList()->AddRectFilled(start_pos, rect_end_pos, IM_COL32(0, 0, 0, 10));
|
|
ImGui::GetForegroundDrawList()->AddRectFilled(rect_start_pos, rect_end_pos, IM_COL32(0, 0, 0, 80));
|
|
} else if (item->slice_state == IMToolbarItem::SliceState::SLICE_FAILED) {
|
|
ImVec2 size = ImVec2(button_width, button_height);
|
|
ImVec2 end_pos = ImVec2(start_pos.x + size.x, start_pos.y + size.y);
|
|
ImGui::GetForegroundDrawList()->AddRectFilled(start_pos, end_pos, IM_COL32(250, 0, 0, 64));
|
|
ImGui::GetForegroundDrawList()->AddRect(start_pos, end_pos, IM_COL32(208, 27, 27, 255), 0.0f, 0, 1.0f);
|
|
} else if (item->slice_state == IMToolbarItem::SliceState::SLICED) {
|
|
ImVec2 size = ImVec2(button_width, button_height);
|
|
ImVec2 end_pos = ImVec2(start_pos.x + size.x, start_pos.y + size.y);
|
|
ImGui::GetForegroundDrawList()->AddRectFilled(start_pos, end_pos, IM_COL32(0, 0, 0, 10));
|
|
}
|
|
// draw text
|
|
if (item->slice_state == IMToolbarItem::SliceState::SLICE_FAILED) {
|
|
ImGui::PushStyleColor(ImGuiCol_Text, error_text_clr);
|
|
ImVec2 text_start_pos = ImVec2(start_pos.x + 10.0f, start_pos.y + 8.0f);
|
|
ImGui::RenderText(text_start_pos, std::to_string(i + 1).c_str());
|
|
ImGui::PopStyleColor();
|
|
|
|
} else {
|
|
ImVec2 text_start_pos = ImVec2(start_pos.x + 10.0f, start_pos.y + 8.0f);
|
|
ImGui::RenderText(text_start_pos, std::to_string(i + 1).c_str());
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
ImGui::SetWindowFontScale(1.0f);
|
|
ImGui::PopStyleColor(8);
|
|
ImGui::PopStyleVar(5);
|
|
|
|
if (ImGui::IsWindowHovered() || is_hovered) {
|
|
m_sel_plate_toolbar.is_display_scrollbar = true;
|
|
} else {
|
|
m_sel_plate_toolbar.is_display_scrollbar = false;
|
|
}
|
|
|
|
imgui.end();
|
|
m_sel_plate_toolbar.is_render_finish = true;
|
|
}
|
|
|
|
//BBS: GUI refactor: GLToolbar adjust
|
|
//when rendering, {0, 0} is at the center, {-0.5, 0.5} at the left-up
|
|
void GLCanvas3D::_render_assemble_view_toolbar() const
|
|
{
|
|
if (!m_assemble_view_toolbar.is_enabled())
|
|
return;
|
|
|
|
Size cnv_size = get_canvas_size();
|
|
float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
|
|
|
|
#if BBS_TOOLBAR_ON_TOP
|
|
GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();
|
|
float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f;
|
|
float gizmo_width = m_gizmos.get_scaled_total_width();
|
|
float assemble_width = m_assemble_view_toolbar.get_width();
|
|
float separator_width = m_separator_toolbar.get_width();
|
|
float top = 0.5f * (float)cnv_size.get_height() * inv_zoom;
|
|
float main_toolbar_left = std::max(-0.5f * cnv_size.get_width(), -0.5f * (m_main_toolbar.get_width() + gizmo_width + assemble_width - separator_width - collapse_toolbar_width)) * inv_zoom;
|
|
float left = main_toolbar_left + (m_main_toolbar.get_width() + gizmo_width) * inv_zoom;
|
|
//float left = 0.5f * (m_main_toolbar.get_width() + gizmo_width - m_assemble_view_toolbar.get_width() + collapse_toolbar_width) * inv_zoom;
|
|
#else
|
|
float gizmo_height = m_gizmos.get_scaled_total_height();
|
|
//float space_height = GLGizmosManager::Default_Icons_Size * wxGetApp().toolbar_icon_scale();
|
|
float main_toolbar_height = (float)m_main_toolbar.get_height();
|
|
float assemble_height = (float)m_assemble_view_toolbar.get_height();
|
|
float top = 0.5f * (assemble_height - main_toolbar_height - gizmo_height) * inv_zoom;
|
|
float left = (0.5f * (float)cnv_size.get_width() - m_assemble_view_toolbar.get_width()) * inv_zoom;
|
|
//BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": top %1%, main_toolbar_height %2%, space_height %3% gizmo_height %4%") % top % main_toolbar_height % space_height % gizmo_height;
|
|
#endif
|
|
m_assemble_view_toolbar.set_position(top, left);
|
|
m_assemble_view_toolbar.render(*this);
|
|
}
|
|
|
|
void GLCanvas3D::_render_return_toolbar()
|
|
{
|
|
if (!m_return_toolbar.is_enabled())
|
|
return;
|
|
|
|
float font_size = ImGui::GetFontSize();
|
|
ImVec2 real_size = ImVec2(font_size * 4, font_size * 1.7);
|
|
ImVec2 button_icon_size = ImVec2(font_size * 1.3, font_size * 1.3);
|
|
|
|
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
|
Size cnv_size = get_canvas_size();
|
|
auto canvas_w = float(cnv_size.get_width());
|
|
auto canvas_h = float(cnv_size.get_height());
|
|
float window_width = real_size.x + button_icon_size.x + imgui.scaled(2.0f);
|
|
float window_height = button_icon_size.y + imgui.scaled(2.0f);
|
|
float window_pos_x = 30.0f;
|
|
float window_pos_y = 14.0f;
|
|
|
|
imgui.set_next_window_pos(window_pos_x, window_pos_y, ImGuiCond_Always, 0, 0);
|
|
#ifdef __WINDOWS__
|
|
imgui.set_next_window_size(window_width, window_height, ImGuiCond_Always);
|
|
#endif
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 18.0f);
|
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.149f, 0.180f, 0.188f, 0.3f));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.149f, 0.180f, 0.188f, 0.15f));
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.149f, 0.180f, 0.188f, 0.5f));
|
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
|
|
|
imgui.begin(_L("Assembly Return"), ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoBackground
|
|
| ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse);
|
|
|
|
float button_width = 20;
|
|
float button_height = 20;
|
|
ImVec2 size = ImVec2(button_width, button_height); // Size of the image we want to make visible
|
|
ImVec2 uv0 = ImVec2(0.0f, 0.0f);
|
|
ImVec2 uv1 = ImVec2(1.0f, 1.0f);
|
|
|
|
ImVec4 bg_col = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
|
|
ImVec4 tint_col = ImVec4(1.0f, 1.0f, 1.0f, 1.0f);
|
|
ImVec2 margin = ImVec2(10.0f, 5.0f);
|
|
|
|
if (ImGui::ImageTextButton(real_size,_utf8(L("return")).c_str(), m_return_toolbar.get_return_texture_id(), button_icon_size, uv0, uv1, -1, bg_col, tint_col, margin)) {
|
|
if (m_canvas_type == ECanvasType::CanvasView3D) {
|
|
deselect_all();
|
|
} else if (m_canvas_type == ECanvasType::CanvasAssembleView) {
|
|
if (m_canvas != nullptr)
|
|
wxPostEvent(m_canvas, SimpleEvent(EVT_GLVIEWTOOLBAR_3D));
|
|
const_cast<GLGizmosManager *>(&m_gizmos)->reset_all_states();
|
|
wxGetApp().plater()->get_view3D_canvas3D()->get_gizmos_manager().reset_all_states();
|
|
GLVolume::explosion_ratio = 1.0;//in 3D view GLVolume::explosion_ratio = 1.0
|
|
wxGetApp().plater()->get_view3D_canvas3D()->reload_scene(true);
|
|
{
|
|
GLCanvas3D * view_3d = wxGetApp().plater()->get_view3D_canvas3D();
|
|
GLToolbarItem * assembly_item = view_3d->m_assemble_view_toolbar.get_item("assembly_view");
|
|
std::chrono::system_clock::time_point end = std::chrono::system_clock::now();
|
|
std::chrono::duration<int> duration = std::chrono::duration_cast<std::chrono::duration<int>>(end - assembly_item->get_start_time_point());
|
|
int times = duration.count();
|
|
|
|
NetworkAgent *agent = GUI::wxGetApp().getAgent();
|
|
if (agent) {
|
|
std::string name = assembly_item->get_name() + "_duration";
|
|
std::string value = "";
|
|
int existing_time = 0;
|
|
|
|
agent->track_get_property(name, value);
|
|
try {
|
|
if (value != "") { existing_time = std::stoi(value); }
|
|
} catch (...) {}
|
|
|
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " tool name:" << name << " duration: " << times + existing_time;
|
|
agent->track_update_property(name, std::to_string(times + existing_time));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
ImGui::PopStyleColor(5);
|
|
ImGui::PopStyleVar(1);
|
|
|
|
imgui.end();
|
|
}
|
|
|
|
void GLCanvas3D::_render_separator_toolbar_right() const
|
|
{
|
|
if (!m_separator_toolbar.is_enabled())
|
|
return;
|
|
|
|
Size cnv_size = get_canvas_size();
|
|
float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
|
|
|
|
GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();
|
|
float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f;
|
|
float gizmo_width = m_gizmos.get_scaled_total_width();
|
|
float assemble_width = m_assemble_view_toolbar.get_width();
|
|
float separator_width = m_separator_toolbar.get_width();
|
|
float top = 0.5f * (float)cnv_size.get_height() * inv_zoom;
|
|
float main_toolbar_left = std::max(-0.5f * cnv_size.get_width(), -0.5f * (m_main_toolbar.get_width() + gizmo_width + assemble_width - collapse_toolbar_width)) * inv_zoom;
|
|
float left = main_toolbar_left + (m_main_toolbar.get_width() + gizmo_width) * inv_zoom;
|
|
|
|
m_separator_toolbar.set_position(top, left);
|
|
m_separator_toolbar.render(*this,GLToolbarItem::SeparatorLine);
|
|
}
|
|
|
|
void GLCanvas3D::_render_separator_toolbar_left() const
|
|
{
|
|
if (!m_separator_toolbar.is_enabled())
|
|
return;
|
|
|
|
Size cnv_size = get_canvas_size();
|
|
float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
|
|
|
|
GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();
|
|
float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f;
|
|
float gizmo_width = m_gizmos.get_scaled_total_width();
|
|
float assemble_width = m_assemble_view_toolbar.get_width();
|
|
float separator_width = m_separator_toolbar.get_width();
|
|
float top = 0.5f * (float)cnv_size.get_height() * inv_zoom;
|
|
float main_toolbar_left = std::max(-0.5f * cnv_size.get_width(), -0.5f * (m_main_toolbar.get_width() + gizmo_width + assemble_width + separator_width - collapse_toolbar_width)) * inv_zoom;
|
|
float left = main_toolbar_left + (m_main_toolbar.get_width()) * inv_zoom;
|
|
|
|
m_separator_toolbar.set_position(top, left);
|
|
m_separator_toolbar.render(*this,GLToolbarItem::SeparatorLine);
|
|
}
|
|
|
|
void GLCanvas3D::_render_collapse_toolbar() const
|
|
{
|
|
GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();
|
|
|
|
Size cnv_size = get_canvas_size();
|
|
float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom();
|
|
|
|
float top = 0.5f * (float)cnv_size.get_height() * inv_zoom;
|
|
//float left = (0.5f * (float)cnv_size.get_width() - (float)collapse_toolbar.get_width() - band) * inv_zoom;
|
|
float left = -0.5f * (float)cnv_size.get_width() * inv_zoom;
|
|
|
|
collapse_toolbar.set_position(top, left);
|
|
collapse_toolbar.render(*this);
|
|
}
|
|
|
|
//BBS reander assemble toolbar
|
|
void GLCanvas3D::_render_paint_toolbar() const
|
|
{
|
|
if (m_canvas_type != ECanvasType::CanvasAssembleView)
|
|
return;
|
|
#if ENABLE_RETINA_GL
|
|
float f_scale = m_retina_helper->get_scale_factor();
|
|
#else
|
|
float f_scale = 1.0f;
|
|
#endif
|
|
int em_unit = wxGetApp().em_unit() / 10;
|
|
|
|
std::vector<std::string> colors = wxGetApp().plater()->get_extruder_colors_from_plater_config();
|
|
int extruder_num = colors.size();
|
|
std::vector<std::string> filament_text_first_line;
|
|
std::vector<std::string> filament_text_second_line;
|
|
{
|
|
auto preset_bundle = wxGetApp().preset_bundle;
|
|
for (auto filament_name : preset_bundle->filament_presets) {
|
|
for (auto iter = preset_bundle->filaments.lbegin(); iter != preset_bundle->filaments.end(); iter++) {
|
|
if (filament_name.compare(iter->name) == 0) {
|
|
std::string display_filament_type;
|
|
iter->config.get_filament_type(display_filament_type);
|
|
auto pos = display_filament_type.find(' ');
|
|
if (pos != std::string::npos) {
|
|
filament_text_first_line.push_back(display_filament_type.substr(0, pos));
|
|
filament_text_second_line.push_back(display_filament_type.substr(pos + 1));
|
|
}
|
|
else {
|
|
filament_text_first_line.push_back(display_filament_type);
|
|
filament_text_second_line.push_back("");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
|
const float canvas_w = float(get_canvas_size().get_width());
|
|
const ImVec2 button_size = ImVec2(64.0f, 48.0f) * f_scale * em_unit;
|
|
const float spacing = 4.0f * em_unit * f_scale;
|
|
const float return_button_margin = 130.0f * em_unit * f_scale;
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(spacing, spacing));
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(spacing, 0));
|
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, { 0.f, 0.f, 0.f, 0.4f });
|
|
|
|
imgui.set_next_window_pos(0.5f * canvas_w, 0, ImGuiCond_Always, 0.5f, 0.0f);
|
|
float constraint_window_width = canvas_w - 2 * return_button_margin;
|
|
ImGui::SetNextWindowSizeConstraints({ 0, 0 }, { constraint_window_width, FLT_MAX });
|
|
imgui.begin(_L("Paint Toolbar"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
|
|
|
|
const float cursor_y = ImGui::GetCursorPosY();
|
|
const ImVec2 arrow_button_size = ImVec2(0.375f * button_size.x, ImGui::GetWindowHeight());
|
|
const ImRect left_arrow_button = ImRect(ImGui::GetCurrentWindow()->Pos, ImGui::GetCurrentWindow()->Pos + arrow_button_size);
|
|
const ImRect right_arrow_button = ImRect(ImGui::GetCurrentWindow()->Pos + ImGui::GetWindowSize() - arrow_button_size, ImGui::GetCurrentWindow()->Pos + ImGui::GetWindowSize());
|
|
ImU32 left_arrow_button_color = IM_COL32(0, 0, 0, 0.4f * 255);
|
|
ImU32 right_arrow_button_color = IM_COL32(0, 0, 0, 0.4f * 255);
|
|
ImU32 arrow_color = IM_COL32(255, 255, 255, 255);
|
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
|
ImGuiContext& context = *GImGui;
|
|
bool disabled = !wxGetApp().plater()->can_fillcolor();
|
|
unsigned char rgb[3];
|
|
|
|
for (int i = 0; i < extruder_num; i++) {
|
|
if (i > 0)
|
|
ImGui::SameLine();
|
|
Slic3r::GUI::BitmapCache::parse_color(colors[i], rgb);
|
|
ImGui::PushStyleColor(ImGuiCol_Button, ImColor(rgb[0], rgb[1], rgb[2]).Value);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImColor(rgb[0], rgb[1], rgb[2]).Value);
|
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImColor(rgb[0], rgb[1], rgb[2]).Value);
|
|
if (disabled)
|
|
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
|
|
if (ImGui::Button(("##filament_button" + std::to_string(i)).c_str(), button_size)) {
|
|
if (!ImGui::IsMouseHoveringRect(left_arrow_button.Min, left_arrow_button.Max) && !ImGui::IsMouseHoveringRect(right_arrow_button.Min, right_arrow_button.Max))
|
|
wxPostEvent(m_canvas, IntEvent(EVT_GLTOOLBAR_FILLCOLOR, i + 1));
|
|
}
|
|
if (ImGui::IsItemHovered() && i < 9) {
|
|
if (!ImGui::IsMouseHoveringRect(left_arrow_button.Min, left_arrow_button.Max) && !ImGui::IsMouseHoveringRect(right_arrow_button.Min, right_arrow_button.Max)) {
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, { 20.0f * f_scale, 10.0f * f_scale });
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 3.0f * f_scale);
|
|
imgui.tooltip(_L("Shortcut Key ") + std::to_string(i + 1), ImGui::GetFontSize() * 20.0f);
|
|
ImGui::PopStyleVar(2);
|
|
}
|
|
}
|
|
ImGui::PopStyleColor(3);
|
|
if (disabled)
|
|
ImGui::PopItemFlag();
|
|
}
|
|
|
|
const float text_offset_y = 4.0f * em_unit * f_scale;
|
|
for (int i = 0; i < extruder_num; i++){
|
|
Slic3r::GUI::BitmapCache::parse_color(colors[i], rgb);
|
|
float gray = 0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2];
|
|
ImVec4 text_color = gray < 80 ? ImVec4(1.0f, 1.0f, 1.0f, 1.0f) : ImVec4(0, 0, 0, 1.0f);
|
|
|
|
imgui.push_bold_font();
|
|
ImVec2 number_label_size = ImGui::CalcTextSize(std::to_string(i + 1).c_str());
|
|
ImGui::SetCursorPosY(cursor_y + text_offset_y);
|
|
ImGui::SetCursorPosX(spacing + i * (spacing + button_size.x) + (button_size.x - number_label_size.x) / 2);
|
|
ImGui::TextColored(text_color, std::to_string(i + 1).c_str());
|
|
imgui.pop_bold_font();
|
|
|
|
ImVec2 filament_first_line_label_size = ImGui::CalcTextSize(filament_text_first_line[i].c_str());
|
|
ImGui::SetCursorPosY(cursor_y + text_offset_y + number_label_size.y);
|
|
ImGui::SetCursorPosX(spacing + i * (spacing + button_size.x) + (button_size.x - filament_first_line_label_size.x) / 2);
|
|
ImGui::TextColored(text_color, filament_text_first_line[i].c_str());
|
|
|
|
ImVec2 filament_second_line_label_size = ImGui::CalcTextSize(filament_text_second_line[i].c_str());
|
|
ImGui::SetCursorPosY(cursor_y + text_offset_y + number_label_size.y + filament_first_line_label_size.y);
|
|
ImGui::SetCursorPosX(spacing + i * (spacing + button_size.x) + (button_size.x - filament_second_line_label_size.x) / 2);
|
|
ImGui::TextColored(text_color, filament_text_second_line[i].c_str());
|
|
}
|
|
|
|
if (ImGui::GetWindowWidth() == constraint_window_width) {
|
|
if (ImGui::IsMouseHoveringRect(left_arrow_button.Min, left_arrow_button.Max)) {
|
|
left_arrow_button_color = IM_COL32(0, 0, 0, 0.64f * 255);
|
|
if (context.IO.MouseClicked[ImGuiMouseButton_Left]) {
|
|
ImGui::SetScrollX(ImGui::GetScrollX() - button_size.x);
|
|
imgui.set_requires_extra_frame();
|
|
}
|
|
}
|
|
draw_list->AddRectFilled(left_arrow_button.Min, left_arrow_button.Max, left_arrow_button_color);
|
|
ImGui::BBLRenderArrow(draw_list, left_arrow_button.GetCenter() - ImVec2(draw_list->_Data->FontSize, draw_list->_Data->FontSize) * 0.5f, arrow_color, ImGuiDir_Left, 2.0f);
|
|
|
|
if (ImGui::IsMouseHoveringRect(right_arrow_button.Min, right_arrow_button.Max)) {
|
|
right_arrow_button_color = IM_COL32(0, 0, 0, 0.64f * 255);
|
|
if (context.IO.MouseClicked[ImGuiMouseButton_Left]) {
|
|
ImGui::SetScrollX(ImGui::GetScrollX() + button_size.x);
|
|
imgui.set_requires_extra_frame();
|
|
}
|
|
}
|
|
draw_list->AddRectFilled(right_arrow_button.Min, right_arrow_button.Max, right_arrow_button_color);
|
|
ImGui::BBLRenderArrow(draw_list, right_arrow_button.GetCenter() - ImVec2(draw_list->_Data->FontSize, draw_list->_Data->FontSize) * 0.5f, arrow_color, ImGuiDir_Right, 2.0f);
|
|
}
|
|
|
|
m_paint_toolbar_width = (ImGui::GetWindowWidth() + 50.0f * em_unit * f_scale);
|
|
imgui.end();
|
|
ImGui::PopStyleVar(3);
|
|
ImGui::PopStyleColor();
|
|
}
|
|
|
|
float GLCanvas3D::_show_assembly_tooltip_information(float caption_max, float x, float y) const
|
|
{
|
|
ImGuiWrapper *imgui = wxGetApp().imgui();
|
|
ImTextureID normal_id = m_gizmos.get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP);
|
|
ImTextureID hover_id = m_gizmos.get_icon_texture_id(GLGizmosManager::MENU_ICON_NAME::IC_TOOLBAR_TOOLTIP_HOVER);
|
|
|
|
caption_max += imgui->calc_text_size(": ").x + 35.f;
|
|
|
|
float font_size = ImGui::GetFontSize();
|
|
ImVec2 button_size = ImVec2(font_size * 1.8, font_size * 1.3);
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0.0f);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {0, ImGui::GetStyle().FramePadding.y});
|
|
ImGui::ImageButton3(normal_id, hover_id, button_size);
|
|
|
|
if (ImGui::IsItemHovered()) {
|
|
ImGui::BeginTooltip2(ImVec2(x, y));
|
|
auto draw_text_with_caption = [this, &imgui, & caption_max](const wxString &caption, const wxString &text) {
|
|
imgui->text_colored(ImGuiWrapper::COL_ACTIVE, caption);
|
|
ImGui::SameLine(caption_max);
|
|
imgui->text_colored(ImGuiWrapper::COL_WINDOW_BG, text);
|
|
};
|
|
|
|
for (const auto &t : std::array<std::string, 3>{"object_selection", "part_selection", "number_key"}) {
|
|
draw_text_with_caption(m_assembly_view_desc.at(t + "_caption") + ": ", m_assembly_view_desc.at(t));
|
|
}
|
|
ImGui::EndTooltip();
|
|
}
|
|
ImGui::PopStyleVar(2);
|
|
auto same_line_size = button_size.x * 1.8;//with an space size
|
|
ImGui::SameLine(same_line_size);
|
|
same_line_size = imgui->calc_text_size("|").x + same_line_size + imgui->calc_text_size(" ").x;
|
|
imgui->text_colored(ImGuiWrapper::COL_ACTIVE, "|");
|
|
ImGui::SameLine(same_line_size);
|
|
return same_line_size;
|
|
}
|
|
|
|
//BBS
|
|
void GLCanvas3D::_render_assemble_control() const
|
|
{
|
|
if (m_canvas_type != ECanvasType::CanvasAssembleView) {
|
|
GLVolume::explosion_ratio = m_explosion_ratio = 1.0;
|
|
return;
|
|
}
|
|
if (m_gizmos.get_current_type() == GLGizmosManager::EType::MmuSegmentation) {
|
|
m_gizmos.m_assemble_view_data->model_objects_clipper()->set_position(0.0, true);
|
|
return;
|
|
}
|
|
|
|
ImGuiWrapper* imgui = wxGetApp().imgui();
|
|
|
|
ImGuiWrapper::push_toolbar_style(get_scale());
|
|
|
|
auto canvas_w = float(get_canvas_size().get_width());
|
|
auto canvas_h = float(get_canvas_size().get_height());
|
|
|
|
const float text_padding = 7.0f;
|
|
const float text_size_x = std::max(imgui->calc_text_size(_L("Reset direction")).x + 2 * ImGui::GetStyle().FramePadding.x,
|
|
std::max(imgui->calc_text_size(_L("Explosion Ratio")).x, imgui->calc_text_size(_L("Section View")).x));
|
|
const float slider_width = 75.0f;
|
|
const float value_size = imgui->calc_text_size("3.00").x + text_padding * 2;
|
|
const float item_spacing = imgui->get_item_spacing().x;
|
|
ImVec2 window_padding = ImGui::GetStyle().WindowPadding;
|
|
|
|
imgui->set_next_window_pos(canvas_w / 2, canvas_h - 10.0f * get_scale(), ImGuiCond_Always, 0.5f, 1.0f);
|
|
imgui->begin(_L("Assemble Control"), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar);
|
|
|
|
ImGui::AlignTextToFramePadding();
|
|
float tip_icon_size;
|
|
{
|
|
float caption_max = 0.f;
|
|
for (const auto &t : std::array<std::string, 3>{"object_selection", "part_selection", "number_key"}) {
|
|
caption_max = std::max(caption_max, imgui->calc_text_size(m_assembly_view_desc.at(t + "_caption")).x);
|
|
}
|
|
const ImVec2 pos = ImGui::GetCursorScreenPos();
|
|
const float text_y =imgui->calc_text_size(_L("part selection")).y;
|
|
float get_cur_x = pos.x;
|
|
float get_cur_y = pos.y - ImGui::GetFrameHeight() - 4 * text_y;
|
|
tip_icon_size =_show_assembly_tooltip_information(caption_max, get_cur_x, get_cur_y);
|
|
}
|
|
{
|
|
float clp_dist = m_gizmos.m_assemble_view_data->model_objects_clipper()->get_position();
|
|
if (clp_dist == 0.f) {
|
|
ImGui::AlignTextToFramePadding();
|
|
imgui->text(_L("Section View"));
|
|
}
|
|
else {
|
|
if (imgui->button(_L("Reset direction"))) {
|
|
wxGetApp().CallAfter([this]() {
|
|
m_gizmos.m_assemble_view_data->model_objects_clipper()->set_position(-1., false);
|
|
});
|
|
}
|
|
}
|
|
|
|
ImGui::SameLine(tip_icon_size + window_padding.x + text_size_x + item_spacing);
|
|
ImGui::PushItemWidth(slider_width);
|
|
bool view_slider_changed = imgui->bbl_slider_float_style("##clp_dist", &clp_dist, 0.f, 1.f, "%.2f", 1.0f, true);
|
|
|
|
ImGui::SameLine(tip_icon_size +window_padding.x + text_size_x + slider_width + item_spacing * 2);
|
|
ImGui::PushItemWidth(value_size);
|
|
bool view_input_changed = ImGui::BBLDragFloat("##clp_dist_input", &clp_dist, 0.05f, 0.0f, 0.0f, "%.2f");
|
|
|
|
if (view_slider_changed || view_input_changed)
|
|
m_gizmos.m_assemble_view_data->model_objects_clipper()->set_position(clp_dist, true);
|
|
}
|
|
|
|
{
|
|
ImGui::SameLine(tip_icon_size +window_padding.x + text_size_x + slider_width + item_spacing * 6 + value_size);
|
|
imgui->text(_L("Explosion Ratio"));
|
|
|
|
ImGui::SameLine(tip_icon_size +window_padding.x + 2 * text_size_x + slider_width + item_spacing * 7 + value_size);
|
|
ImGui::PushItemWidth(slider_width);
|
|
bool explosion_slider_changed = imgui->bbl_slider_float_style("##ratio_slider", &m_explosion_ratio, 1.0f, 3.0f, "%1.2f");
|
|
|
|
ImGui::SameLine(tip_icon_size +window_padding.x + 2 * text_size_x + 2 * slider_width + item_spacing * 8 + value_size);
|
|
ImGui::PushItemWidth(value_size);
|
|
bool explosion_input_changed = ImGui::BBLDragFloat("##ratio_input", &m_explosion_ratio, 0.1f, 1.0f, 3.0f, "%1.2f");
|
|
}
|
|
|
|
imgui->end();
|
|
|
|
ImGuiWrapper::pop_toolbar_style();
|
|
|
|
//BBS check ratio changed
|
|
if (m_explosion_ratio != GLVolume::explosion_ratio) {
|
|
for (GLVolume* volume : m_volumes.volumes) {
|
|
volume->set_bounding_boxes_as_dirty();
|
|
}
|
|
GLVolume::explosion_ratio = m_explosion_ratio;
|
|
}
|
|
}
|
|
void GLCanvas3D::_render_assemble_info() const
|
|
{
|
|
if (m_canvas_type != ECanvasType::CanvasAssembleView) {
|
|
return;
|
|
}
|
|
|
|
if (m_selection.is_empty()) {
|
|
return;
|
|
}
|
|
|
|
ImGuiWrapper* imgui = wxGetApp().imgui();
|
|
auto canvas_w = float(get_canvas_size().get_width());
|
|
auto canvas_h = float(get_canvas_size().get_height());
|
|
float space_size = imgui->get_style_scaling() * 8.0f;
|
|
float caption_max = imgui->calc_text_size(_L("Total Volume:")).x + 3 * space_size;
|
|
char buf[3][64];
|
|
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
ImFont* font = io.Fonts->Fonts[0];
|
|
float origScale = font->Scale;
|
|
font->Scale = 1.2;
|
|
ImGui::PushFont(font);
|
|
ImGui::PopFont();
|
|
float margin = 10.0f * get_scale();
|
|
imgui->set_next_window_pos(canvas_w - margin, canvas_h - margin, ImGuiCond_Always, 1.0f, 1.0f);
|
|
ImGuiWrapper::push_toolbar_style(get_scale());
|
|
imgui->begin(_L("Assembly Info"), ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
|
|
font->Scale = origScale;
|
|
ImGui::PushFont(font);
|
|
ImGui::PopFont();
|
|
|
|
double size0 = m_selection.get_bounding_box().size()(0);
|
|
double size1 = m_selection.get_bounding_box().size()(1);
|
|
double size2 = m_selection.get_bounding_box().size()(2);
|
|
if (!m_selection.is_empty()) {
|
|
ImGui::Text(_L("Volume:").ToUTF8()); ImGui::SameLine(caption_max);
|
|
ImGui::Text("%.2f", size0 * size1 * size2);
|
|
ImGui::Text(_L("Size:").ToUTF8()); ImGui::SameLine(caption_max);
|
|
ImGui::Text("%.2f x %.2f x %.2f", size0, size1, size2);
|
|
}
|
|
imgui->end();
|
|
ImGuiWrapper::pop_toolbar_style();
|
|
}
|
|
|
|
#if ENABLE_SHOW_CAMERA_TARGET
|
|
void GLCanvas3D::_render_camera_target() const
|
|
{
|
|
double half_length = 5.0;
|
|
|
|
glsafe(::glDisable(GL_DEPTH_TEST));
|
|
|
|
glsafe(::glLineWidth(2.0f));
|
|
::glBegin(GL_LINES);
|
|
const Vec3d& target = wxGetApp().plater()->get_camera().get_target();
|
|
// draw line for x axis
|
|
::glColor3f(1.0f, 0.0f, 0.0f);
|
|
::glVertex3d(target(0) - half_length, target(1), target(2));
|
|
::glVertex3d(target(0) + half_length, target(1), target(2));
|
|
// draw line for y axis
|
|
::glColor3f(0.0f, 1.0f, 0.0f);
|
|
::glVertex3d(target(0), target(1) - half_length, target(2));
|
|
::glVertex3d(target(0), target(1) + half_length, target(2));
|
|
// draw line for z axis
|
|
::glColor3f(0.0f, 0.0f, 1.0f);
|
|
::glVertex3d(target(0), target(1), target(2) - half_length);
|
|
::glVertex3d(target(0), target(1), target(2) + half_length);
|
|
glsafe(::glEnd());
|
|
}
|
|
#endif // ENABLE_SHOW_CAMERA_TARGET
|
|
|
|
void GLCanvas3D::_render_sla_slices()
|
|
{
|
|
if (!m_use_clipping_planes || current_printer_technology() != ptSLA)
|
|
return;
|
|
|
|
const SLAPrint* print = this->sla_print();
|
|
const PrintObjects& print_objects = print->objects();
|
|
if (print_objects.empty())
|
|
// nothing to render, return
|
|
return;
|
|
|
|
double clip_min_z = -m_clipping_planes[0].get_data()[3];
|
|
double clip_max_z = m_clipping_planes[1].get_data()[3];
|
|
for (unsigned int i = 0; i < (unsigned int)print_objects.size(); ++i) {
|
|
const SLAPrintObject* obj = print_objects[i];
|
|
|
|
if (!obj->is_step_done(slaposSliceSupports))
|
|
continue;
|
|
|
|
SlaCap::ObjectIdToTrianglesMap::iterator it_caps_bottom = m_sla_caps[0].triangles.find(i);
|
|
SlaCap::ObjectIdToTrianglesMap::iterator it_caps_top = m_sla_caps[1].triangles.find(i);
|
|
{
|
|
if (it_caps_bottom == m_sla_caps[0].triangles.end())
|
|
it_caps_bottom = m_sla_caps[0].triangles.emplace(i, SlaCap::Triangles()).first;
|
|
if (!m_sla_caps[0].matches(clip_min_z)) {
|
|
m_sla_caps[0].z = clip_min_z;
|
|
it_caps_bottom->second.object.clear();
|
|
it_caps_bottom->second.supports.clear();
|
|
}
|
|
if (it_caps_top == m_sla_caps[1].triangles.end())
|
|
it_caps_top = m_sla_caps[1].triangles.emplace(i, SlaCap::Triangles()).first;
|
|
if (!m_sla_caps[1].matches(clip_max_z)) {
|
|
m_sla_caps[1].z = clip_max_z;
|
|
it_caps_top->second.object.clear();
|
|
it_caps_top->second.supports.clear();
|
|
}
|
|
}
|
|
Pointf3s &bottom_obj_triangles = it_caps_bottom->second.object;
|
|
Pointf3s &bottom_sup_triangles = it_caps_bottom->second.supports;
|
|
Pointf3s &top_obj_triangles = it_caps_top->second.object;
|
|
Pointf3s &top_sup_triangles = it_caps_top->second.supports;
|
|
|
|
if ((bottom_obj_triangles.empty() || bottom_sup_triangles.empty() || top_obj_triangles.empty() || top_sup_triangles.empty()) &&
|
|
!obj->get_slice_index().empty())
|
|
{
|
|
double layer_height = print->default_object_config().layer_height.value;
|
|
double initial_layer_height = print->material_config().initial_layer_height.value;
|
|
bool left_handed = obj->is_left_handed();
|
|
|
|
coord_t key_zero = obj->get_slice_index().front().print_level();
|
|
// Slice at the center of the slab starting at clip_min_z will be rendered for the lower plane.
|
|
coord_t key_low = coord_t((clip_min_z - initial_layer_height + layer_height) / SCALING_FACTOR) + key_zero;
|
|
// Slice at the center of the slab ending at clip_max_z will be rendered for the upper plane.
|
|
coord_t key_high = coord_t((clip_max_z - initial_layer_height) / SCALING_FACTOR) + key_zero;
|
|
|
|
const SliceRecord& slice_low = obj->closest_slice_to_print_level(key_low, coord_t(SCALED_EPSILON));
|
|
const SliceRecord& slice_high = obj->closest_slice_to_print_level(key_high, coord_t(SCALED_EPSILON));
|
|
|
|
// Offset to avoid OpenGL Z fighting between the object's horizontal surfaces and the triangluated surfaces of the cuts.
|
|
double plane_shift_z = 0.002;
|
|
|
|
if (slice_low.is_valid()) {
|
|
const ExPolygons& obj_bottom = slice_low.get_slice(soModel);
|
|
const ExPolygons& sup_bottom = slice_low.get_slice(soSupport);
|
|
// calculate model bottom cap
|
|
if (bottom_obj_triangles.empty() && !obj_bottom.empty())
|
|
bottom_obj_triangles = triangulate_expolygons_3d(obj_bottom, clip_min_z - plane_shift_z, ! left_handed);
|
|
// calculate support bottom cap
|
|
if (bottom_sup_triangles.empty() && !sup_bottom.empty())
|
|
bottom_sup_triangles = triangulate_expolygons_3d(sup_bottom, clip_min_z - plane_shift_z, ! left_handed);
|
|
}
|
|
|
|
if (slice_high.is_valid()) {
|
|
const ExPolygons& obj_top = slice_high.get_slice(soModel);
|
|
const ExPolygons& sup_top = slice_high.get_slice(soSupport);
|
|
// calculate model top cap
|
|
if (top_obj_triangles.empty() && !obj_top.empty())
|
|
top_obj_triangles = triangulate_expolygons_3d(obj_top, clip_max_z + plane_shift_z, left_handed);
|
|
// calculate support top cap
|
|
if (top_sup_triangles.empty() && !sup_top.empty())
|
|
top_sup_triangles = triangulate_expolygons_3d(sup_top, clip_max_z + plane_shift_z, left_handed);
|
|
}
|
|
}
|
|
|
|
if (!bottom_obj_triangles.empty() || !top_obj_triangles.empty() || !bottom_sup_triangles.empty() || !top_sup_triangles.empty()) {
|
|
for (const SLAPrintObject::Instance& inst : obj->instances()) {
|
|
glsafe(::glPushMatrix());
|
|
glsafe(::glTranslated(unscale<double>(inst.shift.x()), unscale<double>(inst.shift.y()), 0));
|
|
glsafe(::glRotatef(Geometry::rad2deg(inst.rotation), 0.0, 0.0, 1.0));
|
|
if (obj->is_left_handed())
|
|
// The polygons are mirrored by X.
|
|
glsafe(::glScalef(-1.0, 1.0, 1.0));
|
|
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
|
glsafe(::glColor3f(1.0f, 0.37f, 0.0f));
|
|
if (!bottom_obj_triangles.empty()) {
|
|
glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)bottom_obj_triangles.front().data()));
|
|
glsafe(::glDrawArrays(GL_TRIANGLES, 0, bottom_obj_triangles.size()));
|
|
}
|
|
if (! top_obj_triangles.empty()) {
|
|
glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)top_obj_triangles.front().data()));
|
|
glsafe(::glDrawArrays(GL_TRIANGLES, 0, top_obj_triangles.size()));
|
|
}
|
|
glsafe(::glColor3f(1.0f, 0.0f, 0.37f));
|
|
if (! bottom_sup_triangles.empty()) {
|
|
glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)bottom_sup_triangles.front().data()));
|
|
glsafe(::glDrawArrays(GL_TRIANGLES, 0, bottom_sup_triangles.size()));
|
|
}
|
|
if (! top_sup_triangles.empty()) {
|
|
glsafe(::glVertexPointer(3, GL_DOUBLE, 0, (GLdouble*)top_sup_triangles.front().data()));
|
|
glsafe(::glDrawArrays(GL_TRIANGLES, 0, top_sup_triangles.size()));
|
|
}
|
|
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
|
|
glsafe(::glPopMatrix());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::_render_selection_sidebar_hints() const
|
|
{
|
|
m_selection.render_sidebar_hints(m_sidebar_field, m_gizmos.get_uniform_scaling());
|
|
}
|
|
|
|
void GLCanvas3D::_update_volumes_hover_state()
|
|
{
|
|
for (GLVolume* v : m_volumes.volumes) {
|
|
v->hover = GLVolume::HS_None;
|
|
}
|
|
|
|
if (m_hover_volume_idxs.empty())
|
|
return;
|
|
|
|
bool ctrl_pressed = wxGetKeyState(WXK_CONTROL); // additive select/deselect
|
|
bool shift_pressed = wxGetKeyState(WXK_SHIFT); // select by rectangle
|
|
bool alt_pressed = wxGetKeyState(WXK_ALT); // deselect by rectangle
|
|
|
|
if (alt_pressed && (shift_pressed || ctrl_pressed)) {
|
|
// illegal combinations of keys
|
|
m_hover_volume_idxs.clear();
|
|
return;
|
|
}
|
|
|
|
bool selection_modifiers_only = m_selection.is_empty() || m_selection.is_any_modifier();
|
|
|
|
bool hover_modifiers_only = true;
|
|
for (int i : m_hover_volume_idxs) {
|
|
if (!m_volumes.volumes[i]->is_modifier) {
|
|
hover_modifiers_only = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
std::set<std::pair<int, int>> hover_instances;
|
|
for (int i : m_hover_volume_idxs) {
|
|
const GLVolume& v = *m_volumes.volumes[i];
|
|
hover_instances.insert(std::make_pair(v.object_idx(), v.instance_idx()));
|
|
}
|
|
|
|
bool hover_from_single_instance = hover_instances.size() == 1;
|
|
|
|
if (hover_modifiers_only && !hover_from_single_instance) {
|
|
// do not allow to select volumes from different instances
|
|
m_hover_volume_idxs.clear();
|
|
return;
|
|
}
|
|
|
|
for (int i : m_hover_volume_idxs) {
|
|
GLVolume& volume = *m_volumes.volumes[i];
|
|
if (volume.hover != GLVolume::HS_None)
|
|
continue;
|
|
|
|
bool deselect = volume.selected && ((ctrl_pressed && !shift_pressed) || alt_pressed);
|
|
// (volume->is_modifier && !selection_modifiers_only && !is_ctrl_pressed) -> allows hovering on selected modifiers belonging to selection of type Instance
|
|
bool select = (!volume.selected || (volume.is_modifier && !selection_modifiers_only && !ctrl_pressed)) && !alt_pressed;
|
|
|
|
if (select || deselect) {
|
|
bool as_volume =
|
|
volume.is_modifier && hover_from_single_instance && !ctrl_pressed &&
|
|
(
|
|
(!deselect) ||
|
|
(deselect && !m_selection.is_single_full_instance() && (volume.object_idx() == m_selection.get_object_idx()) && (volume.instance_idx() == m_selection.get_instance_idx()))
|
|
);
|
|
|
|
if (as_volume)
|
|
volume.hover = deselect ? GLVolume::HS_Deselect : GLVolume::HS_Select;
|
|
else {
|
|
int object_idx = volume.object_idx();
|
|
int instance_idx = volume.instance_idx();
|
|
|
|
for (GLVolume* v : m_volumes.volumes) {
|
|
if (v->object_idx() == object_idx && v->instance_idx() == instance_idx)
|
|
v->hover = deselect ? GLVolume::HS_Deselect : GLVolume::HS_Select;
|
|
}
|
|
}
|
|
}
|
|
else if (volume.selected)
|
|
volume.hover = GLVolume::HS_Hover;
|
|
}
|
|
}
|
|
|
|
void GLCanvas3D::_perform_layer_editing_action(wxMouseEvent* evt)
|
|
{
|
|
int object_idx_selected = m_layers_editing.last_object_id;
|
|
if (object_idx_selected == -1)
|
|
return;
|
|
|
|
// A volume is selected. Test, whether hovering over a layer thickness bar.
|
|
if (evt != nullptr) {
|
|
const Rect& rect = LayersEditing::get_bar_rect_screen(*this);
|
|
float b = rect.get_bottom();
|
|
m_layers_editing.last_z = m_layers_editing.object_max_z() * (b - evt->GetY() - 1.0f) / (b - rect.get_top());
|
|
m_layers_editing.last_action =
|
|
evt->ShiftDown() ? (evt->RightIsDown() ? LAYER_HEIGHT_EDIT_ACTION_SMOOTH : LAYER_HEIGHT_EDIT_ACTION_REDUCE) :
|
|
(evt->RightIsDown() ? LAYER_HEIGHT_EDIT_ACTION_INCREASE : LAYER_HEIGHT_EDIT_ACTION_DECREASE);
|
|
}
|
|
|
|
m_layers_editing.adjust_layer_height_profile();
|
|
_refresh_if_shown_on_screen();
|
|
|
|
// Automatic action on mouse down with the same coordinate.
|
|
_start_timer();
|
|
}
|
|
|
|
Vec3d GLCanvas3D::_mouse_to_3d(const Point& mouse_pos, float* z)
|
|
{
|
|
if (m_canvas == nullptr)
|
|
return Vec3d(DBL_MAX, DBL_MAX, DBL_MAX);
|
|
|
|
const Camera& camera = wxGetApp().plater()->get_camera();
|
|
Matrix4d modelview = camera.get_view_matrix().matrix();
|
|
Matrix4d projection= camera.get_projection_matrix().matrix();
|
|
Vec4i viewport(camera.get_viewport().data());
|
|
|
|
GLint y = viewport[3] - (GLint)mouse_pos(1);
|
|
GLfloat mouse_z;
|
|
if (z == nullptr)
|
|
glsafe(::glReadPixels((GLint)mouse_pos(0), y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, (void*)&mouse_z));
|
|
else
|
|
mouse_z = *z;
|
|
|
|
Vec3d out;
|
|
igl::unproject(Vec3d(mouse_pos(0), y, mouse_z), modelview, projection, viewport, out);
|
|
return out;
|
|
}
|
|
|
|
Vec3d GLCanvas3D::_mouse_to_bed_3d(const Point& mouse_pos)
|
|
{
|
|
return mouse_ray(mouse_pos).intersect_plane(0.0);
|
|
}
|
|
|
|
void GLCanvas3D::_start_timer()
|
|
{
|
|
m_timer.Start(100, wxTIMER_CONTINUOUS);
|
|
}
|
|
|
|
void GLCanvas3D::_stop_timer()
|
|
{
|
|
m_timer.Stop();
|
|
}
|
|
|
|
void GLCanvas3D::_load_print_toolpaths(const BuildVolume &build_volume)
|
|
{
|
|
const Print *print = this->fff_print();
|
|
if (print == nullptr)
|
|
return;
|
|
|
|
if (! print->is_step_done(psSkirtBrim))
|
|
return;
|
|
|
|
if (!print->has_skirt() && !print->has_brim())
|
|
return;
|
|
|
|
const std::array<float, 4> color = { 0.5f, 1.0f, 0.5f, 1.0f }; // greenish
|
|
|
|
// number of skirt layers
|
|
size_t total_layer_count = 0;
|
|
for (const PrintObject* print_object : print->objects()) {
|
|
total_layer_count = std::max(total_layer_count, print_object->total_layer_count());
|
|
}
|
|
size_t skirt_height = print->has_infinite_skirt() ? total_layer_count : std::min<size_t>(print->config().skirt_height.value, total_layer_count);
|
|
if (skirt_height == 0 && print->has_brim())
|
|
skirt_height = 1;
|
|
|
|
// Get first skirt_height layers.
|
|
//FIXME This code is fishy. It may not work for multiple objects with different layering due to variable layer height feature.
|
|
// This is not critical as this is just an initial preview.
|
|
const PrintObject* highest_object = *std::max_element(print->objects().begin(), print->objects().end(), [](auto l, auto r){ return l->layers().size() < r->layers().size(); });
|
|
std::vector<float> print_zs;
|
|
print_zs.reserve(skirt_height * 2);
|
|
for (size_t i = 0; i < std::min(skirt_height, highest_object->layers().size()); ++ i)
|
|
print_zs.emplace_back(float(highest_object->layers()[i]->print_z));
|
|
// Only add skirt for the raft layers.
|
|
for (size_t i = 0; i < std::min(skirt_height, std::min(highest_object->slicing_parameters().raft_layers(), highest_object->support_layers().size())); ++ i)
|
|
print_zs.emplace_back(float(highest_object->support_layers()[i]->print_z));
|
|
sort_remove_duplicates(print_zs);
|
|
skirt_height = std::min(skirt_height, print_zs.size());
|
|
print_zs.erase(print_zs.begin() + skirt_height, print_zs.end());
|
|
|
|
GLVolume *volume = m_volumes.new_toolpath_volume(color, VERTEX_BUFFER_RESERVE_SIZE);
|
|
for (size_t i = 0; i < skirt_height; ++ i) {
|
|
volume->print_zs.emplace_back(print_zs[i]);
|
|
volume->offsets.emplace_back(volume->indexed_vertex_array->quad_indices.size());
|
|
volume->offsets.emplace_back(volume->indexed_vertex_array->triangle_indices.size());
|
|
//BBS: usage of m_brim are deleted
|
|
_3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), *volume);
|
|
// Ensure that no volume grows over the limits. If the volume is too large, allocate a new one.
|
|
if (volume->indexed_vertex_array->vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) {
|
|
GLVolume &vol = *volume;
|
|
volume = m_volumes.new_toolpath_volume(vol.color);
|
|
reserve_new_volume_finalize_old_volume(*volume, vol, m_initialized);
|
|
}
|
|
}
|
|
volume->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(volume->indexed_vertex_array->vertices_and_normals_interleaved, volume->indexed_vertex_array->bounding_box());
|
|
volume->indexed_vertex_array->finalize_geometry(m_initialized);
|
|
}
|
|
|
|
void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, const BuildVolume& build_volume, const std::vector<std::string>& str_tool_colors, const std::vector<CustomGCode::Item>& color_print_values)
|
|
{
|
|
std::vector<std::array<float, 4>> tool_colors = _parse_colors(str_tool_colors);
|
|
|
|
struct Ctxt
|
|
{
|
|
const PrintInstances *shifted_copies;
|
|
std::vector<const Layer*> layers;
|
|
bool has_perimeters;
|
|
bool has_infill;
|
|
bool has_support;
|
|
const std::vector<std::array<float, 4>>* tool_colors;
|
|
bool is_single_material_print;
|
|
int filaments_cnt;
|
|
const std::vector<CustomGCode::Item>* color_print_values;
|
|
|
|
static const std::array<float, 4>& color_perimeters() { static std::array<float, 4> color = { 1.0f, 1.0f, 0.0f, 1.f }; return color; } // yellow
|
|
static const std::array<float, 4>& color_infill() { static std::array<float, 4> color = { 1.0f, 0.5f, 0.5f, 1.f }; return color; } // redish
|
|
static const std::array<float, 4>& color_support() { static std::array<float, 4> color = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish
|
|
static const std::array<float, 4>& color_pause_or_custom_code() { static std::array<float, 4> color = { 0.5f, 0.5f, 0.5f, 1.f }; return color; } // gray
|
|
|
|
// For cloring by a tool, return a parsed color.
|
|
bool color_by_tool() const { return tool_colors != nullptr; }
|
|
size_t number_tools() const { return color_by_tool() ? tool_colors->size() : 0; }
|
|
const std::array<float, 4>& color_tool(size_t tool) const { return (*tool_colors)[tool]; }
|
|
|
|
// For coloring by a color_print(M600), return a parsed color.
|
|
bool color_by_color_print() const { return color_print_values!=nullptr; }
|
|
const size_t color_print_color_idx_by_layer_idx(const size_t layer_idx) const {
|
|
const CustomGCode::Item value{layers[layer_idx]->print_z + EPSILON, CustomGCode::Custom, 0, ""};
|
|
auto it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value);
|
|
return (it - color_print_values->begin()) % number_tools();
|
|
}
|
|
|
|
const size_t color_print_color_idx_by_layer_idx_and_extruder(const size_t layer_idx, const int extruder) const
|
|
{
|
|
const coordf_t print_z = layers[layer_idx]->print_z;
|
|
|
|
auto it = std::find_if(color_print_values->begin(), color_print_values->end(),
|
|
[print_z](const CustomGCode::Item& code)
|
|
{ return fabs(code.print_z - print_z) < EPSILON; });
|
|
if (it != color_print_values->end()) {
|
|
CustomGCode::Type type = it->type;
|
|
// pause print or custom Gcode
|
|
if (type == CustomGCode::PausePrint ||
|
|
(type != CustomGCode::ColorChange && type != CustomGCode::ToolChange))
|
|
return number_tools()-1; // last color item is a gray color for pause print or custom G-code
|
|
|
|
// change tool (extruder)
|
|
if (type == CustomGCode::ToolChange)
|
|
return get_color_idx_for_tool_change(it, extruder);
|
|
// change color for current extruder
|
|
if (type == CustomGCode::ColorChange) {
|
|
int color_idx = get_color_idx_for_color_change(it, extruder);
|
|
if (color_idx >= 0)
|
|
return color_idx;
|
|
}
|
|
}
|
|
|
|
const CustomGCode::Item value{print_z + EPSILON, CustomGCode::Custom, 0, ""};
|
|
it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value);
|
|
while (it != color_print_values->begin()) {
|
|
--it;
|
|
// change color for current extruder
|
|
if (it->type == CustomGCode::ColorChange) {
|
|
int color_idx = get_color_idx_for_color_change(it, extruder);
|
|
if (color_idx >= 0)
|
|
return color_idx;
|
|
}
|
|
// change tool (extruder)
|
|
if (it->type == CustomGCode::ToolChange)
|
|
return get_color_idx_for_tool_change(it, extruder);
|
|
}
|
|
|
|
return std::min<int>(filaments_cnt - 1, std::max<int>(extruder - 1, 0));;
|
|
}
|
|
|
|
private:
|
|
int get_m600_color_idx(std::vector<CustomGCode::Item>::const_iterator it) const
|
|
{
|
|
int shift = 0;
|
|
while (it != color_print_values->begin()) {
|
|
--it;
|
|
if (it->type == CustomGCode::ColorChange)
|
|
shift++;
|
|
}
|
|
return filaments_cnt + shift;
|
|
}
|
|
|
|
int get_color_idx_for_tool_change(std::vector<CustomGCode::Item>::const_iterator it, const int extruder) const
|
|
{
|
|
const int current_extruder = it->extruder == 0 ? extruder : it->extruder;
|
|
if (number_tools() == size_t(filaments_cnt + 1)) // there is no one "M600"
|
|
return std::min<int>(filaments_cnt - 1, std::max<int>(current_extruder - 1, 0));
|
|
|
|
auto it_n = it;
|
|
while (it_n != color_print_values->begin()) {
|
|
--it_n;
|
|
if (it_n->type == CustomGCode::ColorChange && it_n->extruder == current_extruder)
|
|
return get_m600_color_idx(it_n);
|
|
}
|
|
|
|
return std::min<int>(filaments_cnt - 1, std::max<int>(current_extruder - 1, 0));
|
|
}
|
|
|
|
int get_color_idx_for_color_change(std::vector<CustomGCode::Item>::const_iterator it, const int extruder) const
|
|
{
|
|
if (filaments_cnt == 1)
|
|
return get_m600_color_idx(it);
|
|
|
|
auto it_n = it;
|
|
bool is_tool_change = false;
|
|
while (it_n != color_print_values->begin()) {
|
|
--it_n;
|
|
if (it_n->type == CustomGCode::ToolChange) {
|
|
is_tool_change = true;
|
|
if (it_n->extruder == it->extruder || (it_n->extruder == 0 && it->extruder == extruder))
|
|
return get_m600_color_idx(it);
|
|
break;
|
|
}
|
|
}
|
|
if (!is_tool_change && it->extruder == extruder)
|
|
return get_m600_color_idx(it);
|
|
|
|
return -1;
|
|
}
|
|
|
|
} ctxt;
|
|
|
|
ctxt.has_perimeters = print_object.is_step_done(posPerimeters);
|
|
ctxt.has_infill = print_object.is_step_done(posInfill);
|
|
ctxt.has_support = print_object.is_step_done(posSupportMaterial);
|
|
ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors;
|
|
ctxt.color_print_values = color_print_values.empty() ? nullptr : &color_print_values;
|
|
ctxt.is_single_material_print = this->fff_print()->extruders().size()==1;
|
|
ctxt.filaments_cnt = wxGetApp().filaments_cnt();
|
|
|
|
ctxt.shifted_copies = &print_object.instances();
|
|
|
|
// order layers by print_z
|
|
{
|
|
size_t nlayers = 0;
|
|
if (ctxt.has_perimeters || ctxt.has_infill)
|
|
nlayers = print_object.layers().size();
|
|
if (ctxt.has_support)
|
|
nlayers += print_object.support_layers().size();
|
|
ctxt.layers.reserve(nlayers);
|
|
}
|
|
if (ctxt.has_perimeters || ctxt.has_infill)
|
|
for (const Layer *layer : print_object.layers())
|
|
ctxt.layers.emplace_back(layer);
|
|
if (ctxt.has_support)
|
|
for (const Layer *layer : print_object.support_layers())
|
|
ctxt.layers.emplace_back(layer);
|
|
std::sort(ctxt.layers.begin(), ctxt.layers.end(), [](const Layer *l1, const Layer *l2) { return l1->print_z < l2->print_z; });
|
|
|
|
// Maximum size of an allocation block: 32MB / sizeof(float)
|
|
BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - start" << m_volumes.log_memory_info() << log_memory_info();
|
|
|
|
const bool is_selected_separate_extruder = m_selected_extruder > 0 && ctxt.color_by_color_print();
|
|
|
|
//FIXME Improve the heuristics for a grain size.
|
|
size_t grain_size = std::max(ctxt.layers.size() / 16, size_t(1));
|
|
tbb::spin_mutex new_volume_mutex;
|
|
auto new_volume = [this, &new_volume_mutex](const std::array<float, 4>& color) {
|
|
// Allocate the volume before locking.
|
|
GLVolume *volume = new GLVolume(color);
|
|
volume->is_extrusion_path = true;
|
|
tbb::spin_mutex::scoped_lock lock;
|
|
// Lock by ROII, so if the emplace_back() fails, the lock will be released.
|
|
lock.acquire(new_volume_mutex);
|
|
m_volumes.volumes.emplace_back(volume);
|
|
lock.release();
|
|
return volume;
|
|
};
|
|
const size_t volumes_cnt_initial = m_volumes.volumes.size();
|
|
tbb::parallel_for(
|
|
tbb::blocked_range<size_t>(0, ctxt.layers.size(), grain_size),
|
|
[&ctxt, &new_volume, is_selected_separate_extruder, this](const tbb::blocked_range<size_t>& range) {
|
|
GLVolumePtrs vols;
|
|
auto volume = [&ctxt, &vols](size_t layer_idx, int extruder, int feature) -> GLVolume& {
|
|
return *vols[ctxt.color_by_color_print()?
|
|
ctxt.color_print_color_idx_by_layer_idx_and_extruder(layer_idx, extruder) :
|
|
ctxt.color_by_tool() ?
|
|
std::min<int>(ctxt.number_tools() - 1, std::max<int>(extruder - 1, 0)) :
|
|
feature
|
|
];
|
|
};
|
|
if (ctxt.color_by_color_print() || ctxt.color_by_tool()) {
|
|
for (size_t i = 0; i < ctxt.number_tools(); ++i)
|
|
vols.emplace_back(new_volume(ctxt.color_tool(i)));
|
|
}
|
|
else
|
|
vols = { new_volume(ctxt.color_perimeters()), new_volume(ctxt.color_infill()), new_volume(ctxt.color_support()) };
|
|
for (GLVolume *vol : vols)
|
|
// Reserving number of vertices (3x position + 3x color)
|
|
vol->indexed_vertex_array->reserve(VERTEX_BUFFER_RESERVE_SIZE / 6);
|
|
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) {
|
|
const Layer *layer = ctxt.layers[idx_layer];
|
|
|
|
if (is_selected_separate_extruder)
|
|
{
|
|
bool at_least_one_has_correct_extruder = false;
|
|
for (const LayerRegion* layerm : layer->regions())
|
|
{
|
|
if (layerm->slices.surfaces.empty())
|
|
continue;
|
|
const PrintRegionConfig& cfg = layerm->region().config();
|
|
if (cfg.wall_filament.value == m_selected_extruder ||
|
|
cfg.sparse_infill_filament.value == m_selected_extruder ||
|
|
cfg.solid_infill_filament.value == m_selected_extruder ) {
|
|
at_least_one_has_correct_extruder = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!at_least_one_has_correct_extruder)
|
|
continue;
|
|
}
|
|
|
|
for (GLVolume *vol : vols)
|
|
if (vol->print_zs.empty() || vol->print_zs.back() != layer->print_z) {
|
|
vol->print_zs.emplace_back(layer->print_z);
|
|
vol->offsets.emplace_back(vol->indexed_vertex_array->quad_indices.size());
|
|
vol->offsets.emplace_back(vol->indexed_vertex_array->triangle_indices.size());
|
|
}
|
|
for (const PrintInstance &instance : *ctxt.shifted_copies) {
|
|
const Point © = instance.shift;
|
|
for (const LayerRegion *layerm : layer->regions()) {
|
|
if (is_selected_separate_extruder)
|
|
{
|
|
const PrintRegionConfig& cfg = layerm->region().config();
|
|
if (cfg.wall_filament.value != m_selected_extruder ||
|
|
cfg.sparse_infill_filament.value != m_selected_extruder ||
|
|
cfg.solid_infill_filament.value != m_selected_extruder)
|
|
continue;
|
|
}
|
|
if (ctxt.has_perimeters)
|
|
_3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy,
|
|
volume(idx_layer, layerm->region().config().wall_filament.value, 0));
|
|
if (ctxt.has_infill) {
|
|
for (const ExtrusionEntity *ee : layerm->fills.entities) {
|
|
// fill represents infill extrusions of a single island.
|
|
const auto *fill = dynamic_cast<const ExtrusionEntityCollection*>(ee);
|
|
if (! fill->entities.empty())
|
|
_3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy,
|
|
volume(idx_layer,
|
|
is_solid_infill(fill->entities.front()->role()) ?
|
|
layerm->region().config().solid_infill_filament :
|
|
layerm->region().config().sparse_infill_filament,
|
|
1));
|
|
}
|
|
}
|
|
}
|
|
if (ctxt.has_support) {
|
|
const SupportLayer *support_layer = dynamic_cast<const SupportLayer*>(layer);
|
|
if (support_layer) {
|
|
for (const ExtrusionEntity *extrusion_entity : support_layer->support_fills.entities)
|
|
_3DScene::extrusionentity_to_verts(extrusion_entity, float(layer->print_z), copy,
|
|
volume(idx_layer,
|
|
(extrusion_entity->role() == erSupportMaterial ||
|
|
extrusion_entity->role() == erSupportTransition) ?
|
|
support_layer->object()->config().support_filament :
|
|
support_layer->object()->config().support_interface_filament,
|
|
2));
|
|
}
|
|
}
|
|
}
|
|
// Ensure that no volume grows over the limits. If the volume is too large, allocate a new one.
|
|
for (size_t i = 0; i < vols.size(); ++i) {
|
|
GLVolume &vol = *vols[i];
|
|
if (vol.indexed_vertex_array->vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) {
|
|
vols[i] = new_volume(vol.color);
|
|
reserve_new_volume_finalize_old_volume(*vols[i], vol, false);
|
|
}
|
|
}
|
|
}
|
|
for (GLVolume *vol : vols)
|
|
// Ideally one would call vol->indexed_vertex_array->finalize() here to move the buffers to the OpenGL driver,
|
|
// but this code runs in parallel and the OpenGL driver is not thread safe.
|
|
vol->indexed_vertex_array->shrink_to_fit();
|
|
});
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info();
|
|
// Remove empty volumes from the newly added volumes.
|
|
m_volumes.volumes.erase(
|
|
std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(),
|
|
[](const GLVolume *volume) { return volume->empty(); }),
|
|
m_volumes.volumes.end());
|
|
for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) {
|
|
GLVolume* v = m_volumes.volumes[i];
|
|
v->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(v->indexed_vertex_array->vertices_and_normals_interleaved, v->indexed_vertex_array->bounding_box());
|
|
v->indexed_vertex_array->finalize_geometry(m_initialized);
|
|
}
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info();
|
|
}
|
|
|
|
void GLCanvas3D::_load_wipe_tower_toolpaths(const BuildVolume& build_volume, const std::vector<std::string>& str_tool_colors)
|
|
{
|
|
const Print *print = this->fff_print();
|
|
if (print == nullptr || print->wipe_tower_data().tool_changes.empty())
|
|
return;
|
|
|
|
if (!print->is_step_done(psWipeTower))
|
|
return;
|
|
|
|
std::vector<std::array<float, 4>> tool_colors = _parse_colors(str_tool_colors);
|
|
|
|
struct Ctxt
|
|
{
|
|
const Print *print;
|
|
const std::vector<std::array<float, 4>>* tool_colors;
|
|
Vec2f wipe_tower_pos;
|
|
float wipe_tower_angle;
|
|
|
|
static const std::array<float, 4>& color_support() { static std::array<float, 4> color = { 0.5f, 1.0f, 0.5f, 1.f }; return color; } // greenish
|
|
|
|
// For cloring by a tool, return a parsed color.
|
|
bool color_by_tool() const { return tool_colors != nullptr; }
|
|
size_t number_tools() const { return this->color_by_tool() ? tool_colors->size() : 0; }
|
|
const std::array<float, 4>& color_tool(size_t tool) const { return (*tool_colors)[tool]; }
|
|
int volume_idx(int tool, int feature) const {
|
|
return this->color_by_tool() ? std::min<int>(this->number_tools() - 1, std::max<int>(tool, 0)) : feature;
|
|
}
|
|
|
|
const std::vector<WipeTower::ToolChangeResult>& tool_change(size_t idx) {
|
|
const auto &tool_changes = print->wipe_tower_data().tool_changes;
|
|
return priming.empty() ?
|
|
((idx == tool_changes.size()) ? final : tool_changes[idx]) :
|
|
((idx == 0) ? priming : (idx == tool_changes.size() + 1) ? final : tool_changes[idx - 1]);
|
|
}
|
|
std::vector<WipeTower::ToolChangeResult> priming;
|
|
std::vector<WipeTower::ToolChangeResult> final;
|
|
} ctxt;
|
|
|
|
ctxt.print = print;
|
|
ctxt.tool_colors = tool_colors.empty() ? nullptr : &tool_colors;
|
|
//BBS: has no single_extruder_multi_material_priming
|
|
//if (print->wipe_tower_data().priming && print->config().single_extruder_multi_material_priming)
|
|
if (print->wipe_tower_data().priming)
|
|
for (int i=0; i<(int)print->wipe_tower_data().priming.get()->size(); ++i)
|
|
ctxt.priming.emplace_back(print->wipe_tower_data().priming.get()->at(i));
|
|
if (print->wipe_tower_data().final_purge)
|
|
ctxt.final.emplace_back(*print->wipe_tower_data().final_purge.get());
|
|
|
|
ctxt.wipe_tower_angle = ctxt.print->config().wipe_tower_rotation_angle.value/180.f * PI;
|
|
|
|
// BBS: add partplate logic
|
|
int plate_idx = print->get_plate_index();
|
|
Vec3d plate_origin = print->get_plate_origin();
|
|
double wipe_tower_x = ctxt.print->config().wipe_tower_x.get_at(plate_idx) + plate_origin(0);
|
|
double wipe_tower_y = ctxt.print->config().wipe_tower_y.get_at(plate_idx) + plate_origin(1);
|
|
ctxt.wipe_tower_pos = Vec2f(wipe_tower_x, wipe_tower_y);
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - start" << m_volumes.log_memory_info() << log_memory_info();
|
|
|
|
//FIXME Improve the heuristics for a grain size.
|
|
size_t n_items = print->wipe_tower_data().tool_changes.size() + (ctxt.priming.empty() ? 0 : 1);
|
|
size_t grain_size = std::max(n_items / 128, size_t(1));
|
|
tbb::spin_mutex new_volume_mutex;
|
|
auto new_volume = [this, &new_volume_mutex](const std::array<float, 4>& color) {
|
|
auto *volume = new GLVolume(color);
|
|
volume->is_extrusion_path = true;
|
|
tbb::spin_mutex::scoped_lock lock;
|
|
lock.acquire(new_volume_mutex);
|
|
m_volumes.volumes.emplace_back(volume);
|
|
lock.release();
|
|
return volume;
|
|
};
|
|
const size_t volumes_cnt_initial = m_volumes.volumes.size();
|
|
std::vector<GLVolumeCollection> volumes_per_thread(n_items);
|
|
tbb::parallel_for(
|
|
tbb::blocked_range<size_t>(0, n_items, grain_size),
|
|
[&ctxt, &new_volume](const tbb::blocked_range<size_t>& range) {
|
|
// Bounding box of this slab of a wipe tower.
|
|
GLVolumePtrs vols;
|
|
if (ctxt.color_by_tool()) {
|
|
for (size_t i = 0; i < ctxt.number_tools(); ++i)
|
|
vols.emplace_back(new_volume(ctxt.color_tool(i)));
|
|
}
|
|
else
|
|
vols = { new_volume(ctxt.color_support()) };
|
|
for (GLVolume *volume : vols)
|
|
// Reserving number of vertices (3x position + 3x color)
|
|
volume->indexed_vertex_array->reserve(VERTEX_BUFFER_RESERVE_SIZE / 6);
|
|
for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++idx_layer) {
|
|
const std::vector<WipeTower::ToolChangeResult> &layer = ctxt.tool_change(idx_layer);
|
|
for (size_t i = 0; i < vols.size(); ++i) {
|
|
GLVolume &vol = *vols[i];
|
|
if (vol.print_zs.empty() || vol.print_zs.back() != layer.front().print_z) {
|
|
vol.print_zs.emplace_back(layer.front().print_z);
|
|
vol.offsets.emplace_back(vol.indexed_vertex_array->quad_indices.size());
|
|
vol.offsets.emplace_back(vol.indexed_vertex_array->triangle_indices.size());
|
|
}
|
|
}
|
|
for (const WipeTower::ToolChangeResult &extrusions : layer) {
|
|
for (size_t i = 1; i < extrusions.extrusions.size();) {
|
|
const WipeTower::Extrusion &e = extrusions.extrusions[i];
|
|
if (e.width == 0.) {
|
|
++i;
|
|
continue;
|
|
}
|
|
size_t j = i + 1;
|
|
if (ctxt.color_by_tool())
|
|
for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].tool == e.tool && extrusions.extrusions[j].width > 0.f; ++j);
|
|
else
|
|
for (; j < extrusions.extrusions.size() && extrusions.extrusions[j].width > 0.f; ++j);
|
|
size_t n_lines = j - i;
|
|
Lines lines;
|
|
std::vector<double> widths;
|
|
std::vector<double> heights;
|
|
lines.reserve(n_lines);
|
|
widths.reserve(n_lines);
|
|
heights.assign(n_lines, extrusions.layer_height);
|
|
WipeTower::Extrusion e_prev = extrusions.extrusions[i-1];
|
|
|
|
if (!extrusions.priming) { // wipe tower extrusions describe the wipe tower at the origin with no rotation
|
|
e_prev.pos = Eigen::Rotation2Df(ctxt.wipe_tower_angle) * e_prev.pos;
|
|
e_prev.pos += ctxt.wipe_tower_pos;
|
|
}
|
|
|
|
for (; i < j; ++i) {
|
|
WipeTower::Extrusion e = extrusions.extrusions[i];
|
|
assert(e.width > 0.f);
|
|
if (!extrusions.priming) {
|
|
e.pos = Eigen::Rotation2Df(ctxt.wipe_tower_angle) * e.pos;
|
|
e.pos += ctxt.wipe_tower_pos;
|
|
}
|
|
|
|
lines.emplace_back(Point::new_scale(e_prev.pos.x(), e_prev.pos.y()), Point::new_scale(e.pos.x(), e.pos.y()));
|
|
widths.emplace_back(e.width);
|
|
|
|
e_prev = e;
|
|
}
|
|
_3DScene::thick_lines_to_verts(lines, widths, heights, lines.front().a == lines.back().b, extrusions.print_z,
|
|
*vols[ctxt.volume_idx(e.tool, 0)]);
|
|
}
|
|
}
|
|
}
|
|
for (size_t i = 0; i < vols.size(); ++i) {
|
|
GLVolume &vol = *vols[i];
|
|
if (vol.indexed_vertex_array->vertices_and_normals_interleaved.size() > MAX_VERTEX_BUFFER_SIZE) {
|
|
vols[i] = new_volume(vol.color);
|
|
reserve_new_volume_finalize_old_volume(*vols[i], vol, false);
|
|
}
|
|
}
|
|
for (GLVolume *vol : vols)
|
|
vol->indexed_vertex_array->shrink_to_fit();
|
|
});
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - finalizing results" << m_volumes.log_memory_info() << log_memory_info();
|
|
// Remove empty volumes from the newly added volumes.
|
|
m_volumes.volumes.erase(
|
|
std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(),
|
|
[](const GLVolume *volume) { return volume->empty(); }),
|
|
m_volumes.volumes.end());
|
|
for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) {
|
|
GLVolume* v = m_volumes.volumes[i];
|
|
v->is_outside = ! build_volume.all_paths_inside_vertices_and_normals_interleaved(v->indexed_vertex_array->vertices_and_normals_interleaved, v->indexed_vertex_array->bounding_box());
|
|
v->indexed_vertex_array->finalize_geometry(m_initialized);
|
|
}
|
|
|
|
BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info();
|
|
}
|
|
|
|
// While it looks like we can call
|
|
// this->reload_scene(true, true)
|
|
// the two functions are quite different:
|
|
// 1) This function only loads objects, for which the step slaposSliceSupports already finished. Therefore objects outside of the print bed never load.
|
|
// 2) This function loads object mesh with the relative scaling correction (the "relative_correction" parameter) was applied,
|
|
// therefore the mesh may be slightly larger or smaller than the mesh shown in the 3D scene.
|
|
void GLCanvas3D::_load_sla_shells()
|
|
{
|
|
const SLAPrint* print = this->sla_print();
|
|
if (print->objects().empty())
|
|
// nothing to render, return
|
|
return;
|
|
|
|
auto add_volume = [this](const SLAPrintObject &object, int volume_id, const SLAPrintObject::Instance& instance,
|
|
const TriangleMesh& mesh, const std::array<float, 4>& color, bool outside_printer_detection_enabled) {
|
|
m_volumes.volumes.emplace_back(new GLVolume(color));
|
|
GLVolume& v = *m_volumes.volumes.back();
|
|
#if ENABLE_SMOOTH_NORMALS
|
|
v.indexed_vertex_array->load_mesh(mesh, true);
|
|
#else
|
|
v.indexed_vertex_array->load_mesh(mesh);
|
|
#endif // ENABLE_SMOOTH_NORMALS
|
|
v.indexed_vertex_array->finalize_geometry(m_initialized);
|
|
v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled;
|
|
v.composite_id.volume_id = volume_id;
|
|
v.set_instance_offset(unscale(instance.shift.x(), instance.shift.y(), 0.0));
|
|
v.set_instance_rotation({ 0.0, 0.0, (double)instance.rotation });
|
|
v.set_instance_mirror(X, object.is_left_handed() ? -1. : 1.);
|
|
v.set_convex_hull(mesh.convex_hull_3d());
|
|
};
|
|
|
|
// adds objects' volumes
|
|
for (const SLAPrintObject* obj : print->objects())
|
|
if (obj->is_step_done(slaposSliceSupports)) {
|
|
unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size();
|
|
for (const SLAPrintObject::Instance& instance : obj->instances()) {
|
|
add_volume(*obj, 0, instance, obj->get_mesh_to_print(), GLVolume::MODEL_COLOR[0], true);
|
|
// Set the extruder_id and volume_id to achieve the same color as in the 3D scene when
|
|
// through the update_volumes_colors_by_extruder() call.
|
|
m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id();
|
|
if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree))
|
|
add_volume(*obj, -int(slaposSupportTree), instance, obj->support_mesh(), GLVolume::SLA_SUPPORT_COLOR, true);
|
|
if (obj->is_step_done(slaposPad) && obj->has_mesh(slaposPad))
|
|
add_volume(*obj, -int(slaposPad), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false);
|
|
}
|
|
double shift_z = obj->get_current_elevation();
|
|
for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) {
|
|
// apply shift z
|
|
m_volumes.volumes[i]->set_sla_shift_z(shift_z);
|
|
}
|
|
}
|
|
|
|
update_volumes_colors_by_extruder();
|
|
}
|
|
|
|
void GLCanvas3D::_update_sla_shells_outside_state()
|
|
{
|
|
check_volumes_outside_state();
|
|
}
|
|
|
|
void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning)
|
|
{
|
|
_set_current();
|
|
bool show = false;
|
|
if (!m_volumes.empty()) {
|
|
show = _is_any_volume_outside();
|
|
show &= m_gcode_viewer.has_data() && m_gcode_viewer.is_contained_in_bed() && m_gcode_viewer.m_conflict_result.has_value();
|
|
} else {
|
|
if (wxGetApp().is_editor()) {
|
|
if (current_printer_technology() != ptSLA) {
|
|
unsigned int max_z_layer = m_gcode_viewer.get_layers_z_range().back();
|
|
if (warning == EWarning::ToolHeightOutside) // check if max z_layer height exceed max print height
|
|
show = m_gcode_viewer.has_data() && (m_gcode_viewer.get_layers_zs()[max_z_layer] - m_gcode_viewer.get_max_print_height() >= 1e-6);
|
|
else if (warning == EWarning::ToolpathOutside) { // check if max x,y coords exceed bed area
|
|
show = m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed() &&
|
|
(m_gcode_viewer.get_max_print_height() -m_gcode_viewer.get_layers_zs()[max_z_layer] >= 1e-6);
|
|
}
|
|
else if (warning == EWarning::GCodeConflict)
|
|
show = m_gcode_viewer.has_data() && m_gcode_viewer.is_contained_in_bed() && m_gcode_viewer.m_conflict_result.has_value();
|
|
}
|
|
}
|
|
}
|
|
|
|
_set_warning_notification(warning, show);
|
|
}
|
|
|
|
std::vector<std::array<float, 4>> GLCanvas3D::_parse_colors(const std::vector<std::string>& colors)
|
|
{
|
|
static const float INV_255 = 1.0f / 255.0f;
|
|
|
|
std::vector<std::array<float, 4>> output(colors.size(), { 1.0f, 1.0f, 1.0f, 1.0f });
|
|
for (size_t i = 0; i < colors.size(); ++i) {
|
|
const std::string& color = colors[i];
|
|
const char* c = color.data() + 1;
|
|
if (color.size() == 7 && color.front() == '#') {
|
|
for (size_t j = 0; j < 3; ++j) {
|
|
int digit1 = hex_digit_to_int(*c++);
|
|
int digit2 = hex_digit_to_int(*c++);
|
|
if (digit1 == -1 || digit2 == -1)
|
|
break;
|
|
|
|
output[i][j] = float(digit1 * 16 + digit2) * INV_255;
|
|
}
|
|
}
|
|
}
|
|
return output;
|
|
}
|
|
|
|
void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
|
|
{
|
|
enum ErrorType{
|
|
PLATER_WARNING,
|
|
PLATER_ERROR,
|
|
SLICING_SERIOUS_WARNING,
|
|
SLICING_ERROR
|
|
};
|
|
std::string text;
|
|
ErrorType error = ErrorType::PLATER_WARNING;
|
|
const ModelObject* conflictObj=nullptr;
|
|
switch (warning) {
|
|
case EWarning::GCodeConflict: {
|
|
static std::string prevConflictText;
|
|
text = prevConflictText;
|
|
error = ErrorType::SLICING_SERIOUS_WARNING;
|
|
if (!m_gcode_viewer.m_conflict_result) { break; }
|
|
std::string objName1 = m_gcode_viewer.m_conflict_result.value()._objName1;
|
|
std::string objName2 = m_gcode_viewer.m_conflict_result.value()._objName2;
|
|
double height = m_gcode_viewer.m_conflict_result.value()._height;
|
|
int layer = m_gcode_viewer.m_conflict_result.value().layer;
|
|
text = (boost::format(_u8L("Conflicts of gcode paths have been found at layer %d. Please separate the conflicted objects farther (%s <-> %s).")) % (layer + 1) %
|
|
objName1 % objName2)
|
|
.str();
|
|
prevConflictText = text;
|
|
const PrintObject *obj2 = reinterpret_cast<const PrintObject *>(m_gcode_viewer.m_conflict_result.value()._obj2);
|
|
conflictObj = obj2->model_object();
|
|
break;
|
|
}
|
|
case EWarning::ObjectOutside: text = _u8L("An object is layed over the boundary of plate."); break;
|
|
case EWarning::ToolHeightOutside: text = _u8L("A G-code path goes beyond the max print height."); error = ErrorType::SLICING_ERROR; break;
|
|
case EWarning::ToolpathOutside: text = _u8L("A G-code path goes beyond the boundary of plate."); error = ErrorType::SLICING_ERROR; break;
|
|
// BBS: remove _u8L() for SLA
|
|
case EWarning::SlaSupportsOutside: text = ("SLA supports outside the print area were detected."); error = ErrorType::PLATER_ERROR; break;
|
|
case EWarning::SomethingNotShown: text = _u8L("Only the object being edited is visible."); break;
|
|
case EWarning::ObjectClashed:
|
|
text = _u8L("An object is laid over the boundary of plate or exceeds the height limit.\n"
|
|
"Please solve the problem by moving it totally on or off the plate, and confirming that the height is within the build volume.");
|
|
error = ErrorType::PLATER_ERROR;
|
|
break;
|
|
}
|
|
//BBS: this may happened when exit the app, plater is null
|
|
if (!wxGetApp().plater())
|
|
return;
|
|
auto& notification_manager = *wxGetApp().plater()->get_notification_manager();
|
|
|
|
switch (error)
|
|
{
|
|
case PLATER_WARNING:
|
|
if (state)
|
|
notification_manager.push_plater_warning_notification(text);
|
|
else
|
|
notification_manager.close_plater_warning_notification(text);
|
|
break;
|
|
case PLATER_ERROR:
|
|
if (state)
|
|
notification_manager.push_plater_error_notification(text);
|
|
else
|
|
notification_manager.close_plater_error_notification(text);
|
|
break;
|
|
case SLICING_SERIOUS_WARNING:
|
|
if (state)
|
|
notification_manager.push_slicing_serious_warning_notification(text, conflictObj ? std::vector<ModelObject const*>{conflictObj} : std::vector<ModelObject const*>{});
|
|
else
|
|
notification_manager.close_slicing_serious_warning_notification(text);
|
|
break;
|
|
case SLICING_ERROR:
|
|
if (state)
|
|
notification_manager.push_slicing_error_notification(text, conflictObj ? std::vector<ModelObject const*>{conflictObj} : std::vector<ModelObject const*>{});
|
|
else
|
|
notification_manager.close_slicing_error_notification(text);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool GLCanvas3D::_is_any_volume_outside() const
|
|
{
|
|
for (const GLVolume* volume : m_volumes.volumes) {
|
|
if (volume != nullptr && volume->is_outside)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void GLCanvas3D::_update_selection_from_hover()
|
|
{
|
|
bool ctrl_pressed = wxGetKeyState(WXK_CONTROL);
|
|
|
|
if (m_hover_volume_idxs.empty()) {
|
|
if (!ctrl_pressed && (m_rectangle_selection.get_state() == GLSelectionRectangle::Select))
|
|
m_selection.remove_all();
|
|
|
|
return;
|
|
}
|
|
|
|
GLSelectionRectangle::EState state = m_rectangle_selection.get_state();
|
|
|
|
bool hover_modifiers_only = true;
|
|
for (int i : m_hover_volume_idxs) {
|
|
if (!m_volumes.volumes[i]->is_modifier) {
|
|
hover_modifiers_only = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool selection_changed = false;
|
|
if (state == GLSelectionRectangle::Select) {
|
|
bool contains_all = true;
|
|
for (int i : m_hover_volume_idxs) {
|
|
if (!m_selection.contains_volume((unsigned int)i)) {
|
|
contains_all = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// the selection is going to be modified (Add)
|
|
if (!contains_all) {
|
|
wxGetApp().plater()->take_snapshot(std::string("Select by rectangle"), UndoRedo::SnapshotType::Selection);
|
|
selection_changed = true;
|
|
}
|
|
}
|
|
else {
|
|
bool contains_any = false;
|
|
for (int i : m_hover_volume_idxs) {
|
|
if (m_selection.contains_volume((unsigned int)i)) {
|
|
contains_any = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// the selection is going to be modified (Remove)
|
|
if (contains_any) {
|
|
wxGetApp().plater()->take_snapshot(std::string("Unselect by rectangle"), UndoRedo::SnapshotType::Selection);
|
|
selection_changed = true;
|
|
}
|
|
}
|
|
|
|
if (!selection_changed)
|
|
return;
|
|
|
|
Plater::SuppressSnapshots suppress(wxGetApp().plater());
|
|
|
|
if ((state == GLSelectionRectangle::Select) && !ctrl_pressed)
|
|
m_selection.clear();
|
|
|
|
for (int i : m_hover_volume_idxs) {
|
|
if (state == GLSelectionRectangle::Select) {
|
|
if (hover_modifiers_only) {
|
|
const GLVolume& v = *m_volumes.volumes[i];
|
|
m_selection.add_volume(v.object_idx(), v.volume_idx(), v.instance_idx(), false);
|
|
}
|
|
else
|
|
m_selection.add(i, false);
|
|
}
|
|
else
|
|
m_selection.remove(i);
|
|
}
|
|
|
|
if (m_selection.is_empty())
|
|
m_gizmos.reset_all_states();
|
|
else
|
|
m_gizmos.refresh_on_off_state();
|
|
|
|
m_gizmos.update_data();
|
|
post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
|
|
m_dirty = true;
|
|
}
|
|
|
|
bool GLCanvas3D::_deactivate_arrange_menu()
|
|
{
|
|
if (m_main_toolbar.is_item_pressed("arrange")) {
|
|
m_main_toolbar.force_right_action(m_main_toolbar.get_item_id("arrange"), *this);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//BBS: add deactivate orient menu
|
|
bool GLCanvas3D::_deactivate_orient_menu()
|
|
{
|
|
if (m_main_toolbar.is_item_pressed("orient")) {
|
|
m_main_toolbar.force_right_action(m_main_toolbar.get_item_id("orient"), *this);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//BBS: add deactivate layersediting menu
|
|
bool GLCanvas3D::_deactivate_layersediting_menu()
|
|
{
|
|
if (m_main_toolbar.is_item_pressed("layersediting")) {
|
|
m_main_toolbar.force_right_action(m_main_toolbar.get_item_id("layersediting"), *this);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool GLCanvas3D::_deactivate_collapse_toolbar_items()
|
|
{
|
|
GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar();
|
|
if (collapse_toolbar.is_item_pressed("print")) {
|
|
collapse_toolbar.force_left_action(collapse_toolbar.get_item_id("print"), *this);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void GLCanvas3D::highlight_toolbar_item(const std::string& item_name)
|
|
{
|
|
GLToolbarItem* item = m_main_toolbar.get_item(item_name);
|
|
if (!item || !item->is_visible())
|
|
return;
|
|
m_toolbar_highlighter.init(item, this);
|
|
}
|
|
|
|
void GLCanvas3D::highlight_gizmo(const std::string& gizmo_name)
|
|
{
|
|
GLGizmosManager::EType gizmo = m_gizmos.get_gizmo_from_name(gizmo_name);
|
|
if(gizmo == GLGizmosManager::EType::Undefined)
|
|
return;
|
|
m_gizmo_highlighter.init(&m_gizmos, gizmo, this);
|
|
}
|
|
|
|
const Print* GLCanvas3D::fff_print() const
|
|
{
|
|
return (m_process == nullptr) ? nullptr : m_process->fff_print();
|
|
}
|
|
|
|
const SLAPrint* GLCanvas3D::sla_print() const
|
|
{
|
|
return (m_process == nullptr) ? nullptr : m_process->sla_print();
|
|
}
|
|
|
|
void GLCanvas3D::WipeTowerInfo::apply_wipe_tower() const
|
|
{
|
|
// BBS: add partplate logic
|
|
DynamicConfig& proj_cfg = wxGetApp().preset_bundle->project_config;
|
|
Vec3d plate_origin = wxGetApp().plater()->get_partplate_list().get_plate(m_plate_idx)->get_origin();
|
|
ConfigOptionFloat wipe_tower_x(m_pos(X) - plate_origin(0));
|
|
ConfigOptionFloat wipe_tower_y(m_pos(Y) - plate_origin(1));
|
|
|
|
ConfigOptionFloats* wipe_tower_x_opt = proj_cfg.option<ConfigOptionFloats>("wipe_tower_x", true);
|
|
ConfigOptionFloats* wipe_tower_y_opt = proj_cfg.option<ConfigOptionFloats>("wipe_tower_y", true);
|
|
wipe_tower_x_opt->set_at(&wipe_tower_x, m_plate_idx, 0);
|
|
wipe_tower_y_opt->set_at(&wipe_tower_y, m_plate_idx, 0);
|
|
|
|
//q->update();
|
|
}
|
|
|
|
void GLCanvas3D::RenderTimer::Notify()
|
|
{
|
|
wxPostEvent((wxEvtHandler*)GetOwner(), RenderTimerEvent( EVT_GLCANVAS_RENDER_TIMER, *this));
|
|
}
|
|
|
|
void GLCanvas3D::ToolbarHighlighterTimer::Notify()
|
|
{
|
|
wxPostEvent((wxEvtHandler*)GetOwner(), ToolbarHighlighterTimerEvent(EVT_GLCANVAS_TOOLBAR_HIGHLIGHTER_TIMER, *this));
|
|
}
|
|
|
|
void GLCanvas3D::GizmoHighlighterTimer::Notify()
|
|
{
|
|
wxPostEvent((wxEvtHandler*)GetOwner(), GizmoHighlighterTimerEvent(EVT_GLCANVAS_GIZMO_HIGHLIGHTER_TIMER, *this));
|
|
}
|
|
|
|
void GLCanvas3D::ToolbarHighlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/)
|
|
{
|
|
m_timer.SetOwner(owner, timerid);
|
|
}
|
|
|
|
void GLCanvas3D::ToolbarHighlighter::init(GLToolbarItem* toolbar_item, GLCanvas3D* canvas)
|
|
{
|
|
if (m_timer.IsRunning())
|
|
invalidate();
|
|
if (!toolbar_item || !canvas)
|
|
return;
|
|
|
|
m_timer.Start(300, false);
|
|
|
|
m_toolbar_item = toolbar_item;
|
|
m_canvas = canvas;
|
|
}
|
|
|
|
void GLCanvas3D::ToolbarHighlighter::invalidate()
|
|
{
|
|
m_timer.Stop();
|
|
|
|
if (m_toolbar_item) {
|
|
m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::NotHighlighted);
|
|
}
|
|
m_toolbar_item = nullptr;
|
|
m_blink_counter = 0;
|
|
m_render_arrow = false;
|
|
}
|
|
|
|
void GLCanvas3D::ToolbarHighlighter::blink()
|
|
{
|
|
if (m_toolbar_item) {
|
|
char state = m_toolbar_item->get_highlight();
|
|
if (state != (char)GLToolbarItem::EHighlightState::HighlightedShown)
|
|
m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::HighlightedShown);
|
|
else
|
|
m_toolbar_item->set_highlight(GLToolbarItem::EHighlightState::HighlightedHidden);
|
|
|
|
m_render_arrow = !m_render_arrow;
|
|
m_canvas->set_as_dirty();
|
|
}
|
|
else
|
|
invalidate();
|
|
|
|
if ((++m_blink_counter) >= 11)
|
|
invalidate();
|
|
}
|
|
|
|
void GLCanvas3D::GizmoHighlighter::set_timer_owner(wxEvtHandler* owner, int timerid/* = wxID_ANY*/)
|
|
{
|
|
m_timer.SetOwner(owner, timerid);
|
|
}
|
|
|
|
void GLCanvas3D::GizmoHighlighter::init(GLGizmosManager* manager, GLGizmosManager::EType gizmo, GLCanvas3D* canvas)
|
|
{
|
|
if (m_timer.IsRunning())
|
|
invalidate();
|
|
if (!gizmo || !canvas)
|
|
return;
|
|
|
|
m_timer.Start(300, false);
|
|
|
|
m_gizmo_manager = manager;
|
|
m_gizmo_type = gizmo;
|
|
m_canvas = canvas;
|
|
}
|
|
|
|
void GLCanvas3D::GizmoHighlighter::invalidate()
|
|
{
|
|
m_timer.Stop();
|
|
|
|
if (m_gizmo_manager) {
|
|
m_gizmo_manager->set_highlight(GLGizmosManager::EType::Undefined, false);
|
|
}
|
|
m_gizmo_manager = nullptr;
|
|
m_gizmo_type = GLGizmosManager::EType::Undefined;
|
|
m_blink_counter = 0;
|
|
m_render_arrow = false;
|
|
}
|
|
|
|
void GLCanvas3D::GizmoHighlighter::blink()
|
|
{
|
|
if (m_gizmo_manager) {
|
|
if (m_blink_counter % 2 == 0)
|
|
m_gizmo_manager->set_highlight(m_gizmo_type, true);
|
|
else
|
|
m_gizmo_manager->set_highlight(m_gizmo_type, false);
|
|
|
|
m_render_arrow = !m_render_arrow;
|
|
m_canvas->set_as_dirty();
|
|
}
|
|
else
|
|
invalidate();
|
|
|
|
if ((++m_blink_counter) >= 11)
|
|
invalidate();
|
|
}
|
|
|
|
} // namespace GUI
|
|
} // namespace Slic3r
|