Merge upstream/main into cad-mainline (530 commits)

Catches the fork up from 449a4cf9fc (2026-06-28) to d6cb667b89 (2026-07-24).
Upstream touched 2326 files; 17 of them overlap the 164 this branch touches.

16 of the 17 auto-merged, including all three CMakeLists.txt, the build_all.yml
CI workflow, and every GUI file. The CAD core never conflicts: CadDocument,
SketchEngine, SketchSolver, McpControl and test_caddocument are files this fork
adds, so upstream does not touch them.

The one conflict, tests/libslic3r/test_3mf.cpp, was purely additive in all three
hunks and is resolved as a union: our test pinning that store_bbs_3mf embeds the
CAD recipe as Metadata/SnapOrca_cad.bin, upstream's multi-nozzle plate-metadata
round-trip tests, and both sets of includes. All three were verified present
after resolution rather than assumed.

NOT BUILD-VERIFIED, for a reason that predates this merge and is not caused by
it: this fork cannot be configured on nativedev at all. Its CMakeLists has
required Eigen3 5.0.1 since before the merge (line 592 pre-merge), while the
only deps image on the machine is snaporca-deps, built for snaporca's
find_package(Eigen3 3.3). CMake fails at configure, so nothing compiles.

That means this fork's Catch2 suite has never run. Every "suite green" figure
recorded for M1-M8 was snaporca's suite; the ports were verified by patch-apply
plus the CAD sources being byte-identical to snaporca's. Building an orca_cad
deps image with Eigen 5.0.1 is what would finally close that gap.

Pre-merge state is preserved at branch cad-mainline-pre-upstream-2026-07-25
(30d54f0074).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tommaso Bianchi
2026-07-25 12:28:33 +02:00
co-authored by Claude Opus 5
2326 changed files with 2562372 additions and 1267981 deletions
+378 -186
View File
@@ -285,7 +285,7 @@ void GLCanvas3D::LayersEditing::render_variable_layer_height_dialog(GLCanvas3D&
ImGui::SetCursorPosX(input_align);
ImGui::BBLDragFloat("##adaptive_input", &m_adaptive_quality, 0.05f, 0.0f, 0.0f, "%.2f");
if (imgui.button(_L("Smooth")))
if (imgui.button(_L_CONTEXT("Smooth", "Mesh action")))
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());
@@ -1037,6 +1037,7 @@ 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_OPEN_SPEED_DIAL, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_INCREASE_INSTANCES, Event<int>);
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent);
@@ -1234,6 +1235,14 @@ GLCanvas3D::~GLCanvas3D()
glsafe(::glDeleteTextures(1, &m_ssao_depth_texture_id));
m_ssao_depth_texture_id = 0;
}
if (m_shadow_map_texture_id != 0) {
glsafe(::glDeleteTextures(1, &m_shadow_map_texture_id));
m_shadow_map_texture_id = 0;
}
if (m_shadow_map_fbo != 0) {
glsafe(::glDeleteFramebuffers(1, &m_shadow_map_fbo));
m_shadow_map_fbo = 0;
}
m_plate_shadow_mask.reset();
}
m_plate_shadow_mask_key.clear();
@@ -2046,6 +2055,9 @@ void GLCanvas3D::render(bool only_init)
// draw scene
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
// Invalidate the shadow map each frame; only the View3D path below rebuilds it. This keeps
// the Preview / Assemble canvases from sampling a stale map with an outdated light matrix.
m_shadow_map_valid = false;
_render_background();
//BBS add partplater rendering logic
@@ -2070,11 +2082,13 @@ void GLCanvas3D::render(bool only_init)
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid);
//BBS: add outline logic
_render_cast_shadows_on_plate(camera.get_view_matrix(), camera.get_projection_matrix());
// Depth pass for object-on-object and self shadows; consumed by the gouraud shader below.
_render_shadows(camera.get_view_matrix(), camera.get_projection_matrix());
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
_render_sla_slices();
_render_selection();
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
_render_wireframe_overlay();
}
/* preview render */
else if (m_canvas_type == ECanvasType::CanvasPreview && m_render_preview) {
@@ -2100,6 +2114,7 @@ void GLCanvas3D::render(bool only_init)
//_render_selection();
// BBS: add outline logic
_render_objects(GLVolumeCollection::ERenderType::Transparent, !m_gizmos.is_running());
_render_wireframe_overlay();
}
_render_sequential_clearance();
@@ -2344,11 +2359,6 @@ void GLCanvas3D::remove_curr_plate_all()
m_dirty = true;
}
void GLCanvas3D::update_plate_thumbnails()
{
_update_imgui_select_plate_toolbar();
}
void GLCanvas3D::select_all()
{
if (!m_gizmos.is_allow_select_all()) {
@@ -3019,6 +3029,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
_set_warning_notification(EWarning::MixtureFilamentIncompatible, !filament_mixture_compatible);
bool model_fits = contained_min_one && !m_model->objects.empty() && !partlyOut && object_results.filaments.empty() && tpu_valid && filament_printable;
// Honor the missing-plugin block so this geometry-only path does not re-enable slicing
// that Plater::validate_current_plate disabled (otherwise moving an object would briefly
// re-enable the Slice button while required plugins are still missing).
if (wxGetApp().plater() && wxGetApp().plater()->plugins_block_slicing())
model_fits = false;
post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, model_fits));
ppl.get_curr_plate()->update_slice_ready_status(model_fits);
}
@@ -3229,7 +3244,6 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
// BBS
//m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_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);
@@ -3578,6 +3592,11 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
break;
}
case '?': { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; }
case ' ': {
if (m_canvas_type == ECanvasType::CanvasView3D)
post_event(SimpleEvent(EVT_GLCANVAS_OPEN_SPEED_DIAL));
break;
}
case 'A':
case 'a':
{
@@ -4931,11 +4950,6 @@ void GLCanvas3D::force_set_focus() {
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;
m_is_touchpad_navigation = wxGetApp().app_config->get_bool("camera_navigation_style");
@@ -6146,11 +6160,11 @@ void GLCanvas3D::_render_3d_navigator()
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_Y], "Z"); // ORCA use uppercase to match text on tranform widgets
strcpy(style.AxisLabels[ImGuizmo::Axis::Axis_Z], "X"); // ORCA use uppercase to match text on tranform widgets
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_FRONT], _utf8("Front").c_str());
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BACK], _utf8("Back").c_str());
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BACK], _u8L_CONTEXT("Back", "Camera View").c_str());
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_TOP], _utf8("Top").c_str());
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_BOTTOM], _utf8("Bottom").c_str());
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_LEFT], _CTX_utf8(L_CONTEXT("Left", "Camera"), "Camera").c_str());
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_RIGHT], _CTX_utf8(L_CONTEXT("Right", "Camera"), "Camera").c_str());
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_LEFT], _u8L_CONTEXT("Left", "Camera View").c_str());
strcpy(style.FaceLabels[ImGuizmo::FACES::FACE_RIGHT], _u8L_CONTEXT("Right", "Camera View").c_str());
float sc = get_scale();
#ifdef WIN32
@@ -6602,21 +6616,21 @@ void GLCanvas3D::render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data,
// glsafe(::glEnable(GL_MULTISAMPLE));
GLint max_samples;
glsafe(::glGetIntegerv(GL_MAX_SAMPLES, &max_samples));
glsafe(::glGetIntegerv(GL_MAX_SAMPLES_EXT, &max_samples));
GLsizei num_samples = max_samples / 2;
GLuint render_fbo;
glsafe(::glGenFramebuffers(1, &render_fbo));
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 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(::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));
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));
@@ -6624,31 +6638,31 @@ void GLCanvas3D::render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data,
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));
glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, render_tex, 0));
}
GLuint render_depth;
glsafe(::glGenRenderbuffers(1, &render_depth));
glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth));
glsafe(::glGenRenderbuffersEXT(1, &render_depth));
glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_depth));
if (multisample)
glsafe(::glRenderbufferStorageMultisample(GL_RENDERBUFFER, num_samples, GL_DEPTH_COMPONENT24, w, h));
glsafe(::glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, num_samples, GL_DEPTH_COMPONENT24, w, h));
else
glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w, h));
glsafe(::glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, w, h));
glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth));
glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render_depth));
GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 };
GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0_EXT };
glsafe(::glDrawBuffers(1, drawBufs));
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
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, camera_view_angle_type,
for_picking,
ban_light);
if (multisample) {
GLuint resolve_fbo;
glsafe(::glGenFramebuffers(1, &resolve_fbo));
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, resolve_fbo));
glsafe(::glGenFramebuffersEXT(1, &resolve_fbo));
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, resolve_fbo));
GLuint resolve_tex;
glsafe(::glGenTextures(1, &resolve_tex));
@@ -6656,21 +6670,21 @@ void GLCanvas3D::render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data,
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(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, 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));
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(::glBindFramebuffer(GL_READ_FRAMEBUFFER, resolve_fbo));
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(::glDeleteFramebuffers(1, &resolve_fbo));
glsafe(::glDeleteFramebuffersEXT(1, &resolve_fbo));
}
else
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
@@ -6680,13 +6694,13 @@ void GLCanvas3D::render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data,
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
}
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0));
glsafe(::glDeleteRenderbuffers(1, &render_depth));
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0));
glsafe(::glDeleteRenderbuffersEXT(1, &render_depth));
if (render_tex_buffer != 0)
glsafe(::glDeleteRenderbuffers(1, &render_tex_buffer));
glsafe(::glDeleteRenderbuffersEXT(1, &render_tex_buffer));
if (render_tex != 0)
glsafe(::glDeleteTextures(1, &render_tex));
glsafe(::glDeleteFramebuffers(1, &render_fbo));
glsafe(::glDeleteFramebuffersEXT(1, &render_fbo));
//if (!multisample)
// glsafe(::glDisable(GL_MULTISAMPLE));
@@ -7018,13 +7032,28 @@ void GLCanvas3D::_update_select_plate_toolbar_stats_item(bool force_selected) {
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;
if (!m_sel_plate_toolbar.is_enabled()) {
return false;
}
const auto& p_plater = wxGetApp().plater();
if (!p_plater) {
return false;
}
if (!p_plater->is_plate_toolbar_image_dirty()) {
return false;
}
if (!p_plater->is_gcode_3mf()) {
p_plater->update_all_plate_thumbnails(true);
}
_update_select_plate_toolbar_stats_item();
m_sel_plate_toolbar.del_all_item();
PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list();
PartPlateList& plate_list = p_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);
@@ -7037,7 +7066,7 @@ bool GLCanvas3D::_update_imgui_select_plate_toolbar()
}
m_sel_plate_toolbar.m_items.push_back(item);
}
p_plater->clear_plate_toolbar_image_dirty();
m_sel_plate_toolbar.is_display_scrollbar = false;
return result;
}
@@ -7465,8 +7494,8 @@ void GLCanvas3D::_rectangular_selection_picking_pass()
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo));
}
else {
glsafe(::glGenFramebuffers(1, &render_fbo));
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, render_fbo));
glsafe(::glGenFramebuffersEXT(1, &render_fbo));
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, render_fbo));
}
glsafe(::glGenTextures(1, &render_tex));
glsafe(::glBindTexture(GL_TEXTURE_2D, render_tex));
@@ -7481,11 +7510,11 @@ void GLCanvas3D::_rectangular_selection_picking_pass()
glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth));
}
else {
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_tex, 0));
glsafe(::glGenRenderbuffers(1, &render_depth));
glsafe(::glBindRenderbuffer(GL_RENDERBUFFER, render_depth));
glsafe(::glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height));
glsafe(::glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_depth));
glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, render_tex, 0));
glsafe(::glGenRenderbuffersEXT(1, &render_depth));
glsafe(::glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, render_depth));
glsafe(::glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, width, height));
glsafe(::glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, render_depth));
}
const GLenum drawBufs[] = { GL_COLOR_ATTACHMENT0 };
glsafe(::glDrawBuffers(1, drawBufs));
@@ -7494,7 +7523,7 @@ void GLCanvas3D::_rectangular_selection_picking_pass()
use_framebuffer = false;
}
else {
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) != GL_FRAMEBUFFER_COMPLETE_EXT)
use_framebuffer = false;
}
}
@@ -7605,11 +7634,11 @@ void GLCanvas3D::_rectangular_selection_picking_pass()
glsafe(::glDeleteFramebuffers(1, &render_fbo));
}
else if (framebuffers_type == OpenGLManager::EFramebufferType::Ext) {
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0));
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0));
if (render_depth != 0)
glsafe(::glDeleteRenderbuffers(1, &render_depth));
glsafe(::glDeleteRenderbuffersEXT(1, &render_depth));
if (render_fbo != 0)
glsafe(::glDeleteFramebuffers(1, &render_fbo));
glsafe(::glDeleteFramebuffersEXT(1, &render_fbo));
}
if (render_tex != 0)
@@ -7982,9 +8011,8 @@ void GLCanvas3D::_render_platelist(const Transform3d& view_matrix, const Transfo
plate_list.set_hide_chrome(prev_hide_chrome);
}
void GLCanvas3D::_render_cast_shadows_on_plate(const Transform3d& view_matrix, const Transform3d& projection_matrix)
void GLCanvas3D::_render_shadows(const Transform3d& view_matrix, const Transform3d& projection_matrix)
{
// Check if shadow rendering is enabled in configuration
if (wxGetApp().app_config == nullptr)
return;
if (!wxGetApp().app_config->get_bool(SETTING_OPENGL_REALISTIC_MODE))
@@ -7998,54 +8026,181 @@ void GLCanvas3D::_render_cast_shadows_on_plate(const Transform3d& view_matrix, c
if (shader == nullptr)
return;
// Fixed light direction (pointing downward at an angle)
// Drive shadow direction from current view angle: define light in eye-space,
// then transform it to world-space with inverse view rotation.
const Vec3d light_dir_eye = Vec3d(-0.4574957, 0.4574957, 0.7624929).normalized();
const Matrix3d view_rot = view_matrix.matrix().block<3, 3>(0, 0);
const Vec3d light_dir_to_light = (view_rot.transpose() * light_dir_eye).normalized();
const Vec3d ray_dir = -light_dir_to_light; // Direction of shadow projection
if (std::abs(ray_dir.z()) < 1e-6)
if (OpenGLManager::get_framebuffers_type() == OpenGLManager::EFramebufferType::Arb) {
// Light direction (same as used in shading and plate shading)
const Vec3d light_dir_eye = Vec3d(-0.4574957, 0.4574957, 0.7624929).normalized();
const Matrix3d view_rot = view_matrix.matrix().block<3, 3>(0, 0);
const Vec3d dir_to_light = (view_rot.transpose() * light_dir_eye).normalized();
// Bounding box of the printable objects (the shadow casters).
BoundingBoxf3 obj_bb;
for (const GLVolume* volume : m_volumes.volumes) {
if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower)
continue;
obj_bb.merge(volume->transformed_bounding_box());
}
if (!obj_bb.defined)
return; // no objects to cast shadows
// Orthographic light-space basis (z points toward the light).
const Vec3d up = (std::abs(dir_to_light.z()) > 0.99) ? Vec3d::UnitY() : Vec3d::UnitZ();
const Vec3d z_axis = dir_to_light;
const Vec3d x_axis = up.cross(z_axis).normalized();
const Vec3d y_axis = z_axis.cross(x_axis).normalized();
// Fit the frustum to the object AABB *and* the object's shadow projected onto the plate
// (clamped to the plate footprint). This keeps the map tight/high-res for short shadows
// while still covering long shadows at grazing light angles, which previously fell outside
// the map and were clipped.
const Vec3d ray_dir = -dir_to_light; // direction the shadow travels
const BoundingBoxf3 plate_bb = m_bed.build_volume().valid() ? m_bed.build_volume().bounding_volume() : obj_bb;
Vec3d lmin(DBL_MAX, DBL_MAX, DBL_MAX);
Vec3d lmax(-DBL_MAX, -DBL_MAX, -DBL_MAX);
auto enclose = [&](const Vec3d& p) {
const Vec3d lp(x_axis.dot(p), y_axis.dot(p), z_axis.dot(p));
lmin = lmin.cwiseMin(lp);
lmax = lmax.cwiseMax(lp);
};
for (int i = 0; i < 8; ++i) {
const Vec3d corner((i & 1) ? obj_bb.max.x() : obj_bb.min.x(),
(i & 2) ? obj_bb.max.y() : obj_bb.min.y(),
(i & 4) ? obj_bb.max.z() : obj_bb.min.z());
enclose(corner);
// Where this corner's shadow lands on z = 0, clamped to the plate so a grazing angle
// (t -> infinity) stays bounded.
if (ray_dir.z() < -1e-6) {
const double t = -corner.z() / ray_dir.z();
Vec3d s = corner + t * ray_dir;
s.x() = std::min(std::max(s.x(), plate_bb.min.x()), plate_bb.max.x());
s.y() = std::min(std::max(s.y(), plate_bb.min.y()), plate_bb.max.y());
s.z() = 0.0;
enclose(s);
}
}
// Light "camera" placed just past the nearest enclosed point, looking toward the scene.
const double range = lmax.z() - lmin.z();
const double margin = std::max(1.0, 0.05 * range);
const double cx = 0.5 * (lmin.x() + lmax.x());
const double cy = 0.5 * (lmin.y() + lmax.y());
const Vec3d eye = x_axis * cx + y_axis * cy + z_axis * (lmax.z() + margin);
Matrix4d light_view = Matrix4d::Identity();
light_view.block<1, 3>(0, 0) = x_axis.transpose();
light_view.block<1, 3>(1, 0) = y_axis.transpose();
light_view.block<1, 3>(2, 0) = z_axis.transpose();
light_view(0, 3) = -x_axis.dot(eye);
light_view(1, 3) = -y_axis.dot(eye);
light_view(2, 3) = -z_axis.dot(eye);
// Ortho fit to the light-space extent (symmetric in X/Y around cx,cy; +2% edge padding).
const double halfx = std::max(0.5 * (lmax.x() - lmin.x()), 1.0) * 1.02;
const double halfy = std::max(0.5 * (lmax.y() - lmin.y()), 1.0) * 1.02;
const double near_z = margin * 0.5;
const double far_z = range + margin * 1.5;
Matrix4d light_proj = Matrix4d::Identity();
light_proj(0, 0) = 1.0 / halfx;
light_proj(1, 1) = 1.0 / halfy;
light_proj(2, 2) = -2.0 / (far_z - near_z);
light_proj(2, 3) = -(far_z + near_z) / (far_z - near_z);
m_shadow_light_vp = Transform3d(light_proj * light_view);
// Create / resize the depth texture and FBO
const unsigned int size = 2048;
if (m_shadow_map_texture_id == 0) {
glsafe(::glGenTextures(1, &m_shadow_map_texture_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_shadow_map_texture_id));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER));
const float border[4] = { 1.0f, 1.0f, 1.0f, 1.0f };
glsafe(::glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border));
m_shadow_map_size = 0;
}
if (m_shadow_map_size != size) {
glsafe(::glBindTexture(GL_TEXTURE_2D, m_shadow_map_texture_id));
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, size, size, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));
m_shadow_map_size = size;
}
if (m_shadow_map_fbo == 0)
glsafe(::glGenFramebuffers(1, &m_shadow_map_fbo));
// Save OpenGL state that we will modify
GLint prev_viewport[4] = { 0, 0, 0, 0 };
glsafe(::glGetIntegerv(GL_VIEWPORT, prev_viewport));
GLint prev_fbo = 0;
glsafe(::glGetIntegerv(GL_FRAMEBUFFER_BINDING, &prev_fbo));
GLboolean prev_color_mask[4] = { GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE };
glsafe(::glGetBooleanv(GL_COLOR_WRITEMASK, prev_color_mask));
const GLboolean prev_cull = ::glIsEnabled(GL_CULL_FACE);
GLint prev_depth_func = GL_LESS;
glsafe(::glGetIntegerv(GL_DEPTH_FUNC, &prev_depth_func));
GLboolean prev_depth_mask = GL_TRUE;
glsafe(::glGetBooleanv(GL_DEPTH_WRITEMASK, &prev_depth_mask));
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, m_shadow_map_fbo));
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadow_map_texture_id, 0));
glsafe(::glDrawBuffer(GL_NONE));
glsafe(::glReadBuffer(GL_NONE));
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(prev_fbo)));
m_shadow_map_valid = false;
} else {
glsafe(::glViewport(0, 0, size, size));
glsafe(::glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE));
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glDepthMask(GL_TRUE));
glsafe(::glDepthFunc(GL_LESS));
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
glsafe(::glPolygonOffset(4.0f, 4.0f));
glsafe(::glDisable(GL_CULL_FACE));
shader->start_using();
shader->set_uniform("projection_matrix", Transform3d(light_proj));
for (GLVolume* volume : m_volumes.volumes) {
if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower)
continue;
const Transform3d view_model = Transform3d(light_view) * volume->world_matrix();
shader->set_uniform("view_model_matrix", view_model);
volume->model.render(shader);
}
shader->stop_using();
// Restore state
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
glsafe(::glColorMask(prev_color_mask[0], prev_color_mask[1], prev_color_mask[2], prev_color_mask[3]));
if (prev_cull)
glsafe(::glEnable(GL_CULL_FACE));
else
glsafe(::glDisable(GL_CULL_FACE));
glsafe(::glDepthFunc(prev_depth_func));
glsafe(::glDepthMask(prev_depth_mask));
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(prev_fbo)));
glsafe(::glViewport(prev_viewport[0], prev_viewport[1], prev_viewport[2], prev_viewport[3]));
m_shadow_map_valid = true;
}
} else {
m_shadow_map_valid = false;
}
// ----------------------------------------------------------------------------------
// Unified plate shadow: draw the build-plate footprint and darken it wherever the same
// depth shadow map (built above) says the light is occluded. This replaces the old planar
// stencil projection so plate, object and self shadows all come from one technique.
// ----------------------------------------------------------------------------------
if (!m_shadow_map_valid)
return;
// Shadow projection matrix - flattens geometry onto Z=0 plane along light direction
Matrix4d shadow_proj = Matrix4d::Identity();
shadow_proj(0, 2) = -ray_dir.x() / ray_dir.z();
shadow_proj(1, 2) = -ray_dir.y() / ray_dir.z();
shadow_proj(2, 0) = 0.0;
shadow_proj(2, 1) = 0.0;
shadow_proj(2, 2) = 0.0;
shadow_proj(2, 3) = 0.01; // Bias to prevent shadow acne
GLShaderProgram* plate_shader = wxGetApp().get_shader("printbed_shadow");
if (plate_shader == nullptr)
return;
// Save OpenGL state
GLint prev_depth_func = GL_LESS;
glsafe(::glGetIntegerv(GL_DEPTH_FUNC, &prev_depth_func));
GLboolean prev_depth_mask = GL_TRUE;
glsafe(::glGetBooleanv(GL_DEPTH_WRITEMASK, &prev_depth_mask));
GLint prev_stencil_mask = 0xFF;
glsafe(::glGetIntegerv(GL_STENCIL_WRITEMASK, &prev_stencil_mask));
GLboolean prev_stencil_test = GL_FALSE;
glsafe(::glGetBooleanv(GL_STENCIL_TEST, &prev_stencil_test));
// ============================================================
// PASS 0: Create stencil mask for the build plate (value = 1)
// ============================================================
glsafe(::glEnable(GL_STENCIL_TEST));
glsafe(::glStencilMask(0xFF));
glsafe(::glClearStencil(0));
glsafe(::glClear(GL_STENCIL_BUFFER_BIT));
glsafe(::glStencilFunc(GL_ALWAYS, 1, 0xFF));
glsafe(::glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE));
glsafe(::glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE));
glsafe(::glDisable(GL_DEPTH_TEST));
shader->start_using();
shader->set_uniform("projection_matrix", projection_matrix);
// Draw the build plate (cached model to avoid per-frame uploads)
if (const BuildVolume& build_volume = m_bed.build_volume(); build_volume.valid()) {
const std::string mask_key = build_volume.type() == BuildVolume_Type::Rectangle
? (boost::format("rect|%1$.5f|%2$.5f|%3$.5f|%4$.5f")
@@ -8101,87 +8256,49 @@ void GLCanvas3D::_render_cast_shadows_on_plate(const Transform3d& view_matrix, c
}
if (m_plate_shadow_mask.is_initialized()) {
shader->set_uniform("view_model_matrix", view_matrix);
m_plate_shadow_mask.render(shader);
// Blend the shadow over the already-drawn plate. Depth test keeps it behind anything
// already in front; depth writes are off, and a small negative polygon offset lifts it
// just above the bed to avoid z-fighting.
GLboolean prev_depth_mask = GL_TRUE;
glsafe(::glGetBooleanv(GL_DEPTH_WRITEMASK, &prev_depth_mask));
GLint prev_depth_func = GL_LESS;
glsafe(::glGetIntegerv(GL_DEPTH_FUNC, &prev_depth_func));
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glDepthMask(GL_FALSE));
glsafe(::glDepthFunc(GL_LEQUAL));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
glsafe(::glPolygonOffset(-1.0f, -1.0f));
glsafe(::glActiveTexture(GL_TEXTURE4));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_shadow_map_texture_id));
glsafe(::glActiveTexture(GL_TEXTURE0));
plate_shader->start_using();
plate_shader->set_uniform("view_model_matrix", view_matrix);
plate_shader->set_uniform("projection_matrix", projection_matrix);
plate_shader->set_uniform("shadow_map", 4);
plate_shader->set_uniform("shadow_light_vp", m_shadow_light_vp);
plate_shader->set_uniform("shadow_intensity", 0.35f);
plate_shader->set_uniform("shadow_map_texel", 1.0f / static_cast<float>(m_shadow_map_size));
m_plate_shadow_mask.render(plate_shader);
plate_shader->stop_using();
glsafe(::glActiveTexture(GL_TEXTURE4));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
glsafe(::glActiveTexture(GL_TEXTURE0));
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
glsafe(::glDisable(GL_BLEND));
glsafe(::glDepthFunc(prev_depth_func));
glsafe(::glDepthMask(prev_depth_mask));
}
}
// ============================================================
// PASS 1: Project object shadows onto plate (increment stencil to 2)
// ============================================================
// Only render where plate exists (stencil == 1), then increment to 2
glsafe(::glStencilFunc(GL_EQUAL, 1, 0xFF));
glsafe(::glStencilOp(GL_KEEP, GL_KEEP, GL_INCR));
glsafe(::glDepthMask(GL_FALSE));
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glDepthFunc(GL_ALWAYS)); // Shadows don't need depth testing
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
glsafe(::glPolygonOffset(-2.0f, -2.0f));
glsafe(::glDisable(GL_CULL_FACE));
// Render projected shadow geometry
for (GLVolume* volume : m_volumes.volumes) {
if (volume == nullptr || !volume->is_active || !volume->printable || volume->is_modifier || volume->is_wipe_tower)
continue;
// CRITICAL FIX: Apply shadow projection in object's local space, then to world, then to view
// This ensures shadows are cast from the object's actual position
Matrix4d world_matrix = volume->world_matrix().matrix();
// Project the shadow - this flattens the geometry onto Z=0 in WORLD space
Matrix4d shadow_world_matrix = shadow_proj * world_matrix;
// Transform to view space for rendering
Matrix4d view_shadow_matrix = view_matrix.matrix() * shadow_world_matrix;
shader->set_uniform("view_model_matrix", view_shadow_matrix);
shader->set_uniform("projection_matrix", projection_matrix);
volume->model.render(shader);
}
// ============================================================
// PASS 2: Draw shadow color where stencil == 2
// ============================================================
glsafe(::glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE));
glsafe(::glStencilFunc(GL_EQUAL, 2, 0xFF));
glsafe(::glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP));
glsafe(::glStencilMask(0x00));
glsafe(::glDepthFunc(GL_ALWAYS));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
// Draw shadow fill
shader->set_uniform("view_model_matrix", Transform3d::Identity());
shader->set_uniform("projection_matrix", Transform3d::Identity());
const ColorRGBA shadow_fill_color(0.0f, 0.0f, 0.0f, 0.4f); // Darker shadow for visibility
const ColorRGBA prev_bg_color = m_background.get_geometry().color;
m_background.set_color(shadow_fill_color);
shader->set_uniform("uniform_color", shadow_fill_color);
m_background.render(shader);
m_background.set_color(prev_bg_color);
shader->set_uniform("uniform_color", prev_bg_color);
shader->stop_using();
// ============================================================
// RESTORE STATE
// ============================================================
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glDepthMask(prev_depth_mask));
glsafe(::glDepthFunc(prev_depth_func));
glsafe(::glEnable(GL_CULL_FACE));
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
glsafe(::glDisable(GL_BLEND));
if (!prev_stencil_test)
glsafe(::glDisable(GL_STENCIL_TEST));
glsafe(::glStencilMask(prev_stencil_mask));
}
void GLCanvas3D::_render_plane() const
{
;//TODO render assemble plane
@@ -8260,12 +8377,31 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
shader = wxGetApp().get_shader("gouraud");
ECanvasType canvas_type = this->m_canvas_type;
bool partly_inside_enable = canvas_type == ECanvasType::CanvasAssembleView ? false : true;
// The edited printer's per-extruder printable heights feed the object shader's
// extruder_printable_heights uniform. Empty for single-extruder printers, so the shader flag stays
// 0.0 and rendering is pixel-identical there (see GLVolumeCollection::render).
auto printable_height_option = wxGetApp().preset_bundle->printers.get_edited_preset().config.option<ConfigOptionFloatsNullable>("extruder_printable_height");
std::vector<double>* printable_heights = printable_height_option ? &printable_height_option->values : nullptr;
if (shader != nullptr) {
shader->start_using();
const bool phong_ssao = wxGetApp().app_config != nullptr && wxGetApp().app_config->get_bool(SETTING_OPENGL_PHONG_SSAO);
shader->set_uniform("enable_ssao", phong_ssao);
// Object-on-object and self shadows: sample the depth map built in _render_shadow_map_pass().
// shadow_intensity == 0 disables the effect entirely (unchanged behavior when off / unsupported).
if (m_shadow_map_valid && m_shadow_map_texture_id != 0) {
glsafe(::glActiveTexture(GL_TEXTURE4));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_shadow_map_texture_id));
glsafe(::glActiveTexture(GL_TEXTURE0));
shader->set_uniform("shadow_map", 4);
shader->set_uniform("shadow_light_vp", m_shadow_light_vp);
shader->set_uniform("shadow_map_texel", 1.0f / static_cast<float>(m_shadow_map_size));
shader->set_uniform("shadow_intensity", 0.35f);
}
else
shader->set_uniform("shadow_intensity", 0.0f);
const Size& cvn_size = get_canvas_size();
{
const Camera& camera = wxGetApp().plater()->get_camera();
@@ -8307,7 +8443,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0);
}
},
partly_inside_enable);
partly_inside_enable, printable_heights);
}
}
else {
@@ -8342,7 +8478,7 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
return true;
}
},
partly_inside_enable);
partly_inside_enable, printable_heights);
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();
@@ -8357,12 +8493,58 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type, bool with
shader->set_uniform("show_wireframe", false);
}*/
if (m_shadow_map_valid && m_shadow_map_texture_id != 0) {
glsafe(::glActiveTexture(GL_TEXTURE4));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
glsafe(::glActiveTexture(GL_TEXTURE0));
}
shader->stop_using();
}
m_camera_clipping_plane = ClippingPlane::ClipsNothing();
}
void GLCanvas3D::_render_wireframe_overlay()
{
if (!wxGetApp().plater()->is_show_wireframe())
return;
if (m_volumes.empty())
return;
#if SLIC3R_OPENGL_ES
GLShaderProgram* shader = wxGetApp().get_shader("wireframe");
#else
GLShaderProgram* shader = wxGetApp().get_shader("mm_contour");
#endif
if (shader == nullptr)
return;
const Camera& camera = wxGetApp().plater()->get_camera();
const Transform3d& view_matrix = camera.get_view_matrix();
const Transform3d& proj_matrix = camera.get_projection_matrix();
const Size sz = get_canvas_size();
shader->start_using();
shader->set_uniform("offset", OpenGLManager::get_gl_info().is_mesa() ? 0.0005 : 0.00001);
shader->set_uniform("view_model_matrix", view_matrix);
shader->set_uniform("projection_matrix", proj_matrix);
glsafe(::glEnable(GL_DEPTH_TEST));
#if !SLIC3R_OPENGL_ES
if (!OpenGLManager::get_gl_info().is_core_profile())
glsafe(::glLineWidth(1.0f));
glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_LINE));
#endif
m_volumes.render(GLVolumeCollection::ERenderType::Opaque, false, view_matrix, proj_matrix, sz);
#if !SLIC3R_OPENGL_ES
glsafe(::glPolygonMode(GL_FRONT_AND_BACK, GL_FILL));
#endif
shader->stop_using();
}
//BBS: GUI refactor: add canvas size as parameters
void GLCanvas3D::_render_gcode(int canvas_width, int canvas_height)
{
@@ -8756,6 +8938,8 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar()
return;
}
_update_imgui_select_plate_toolbar();
IMToolbarItem* all_plates_stats_item = m_sel_plate_toolbar.m_all_plates_stats_item;
PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list();
@@ -9172,7 +9356,6 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar()
m_sel_plate_toolbar.is_display_scrollbar = is_win_hovered;
imgui.end();
m_sel_plate_toolbar.is_render_finish = true;
}
//BBS: GUI refactor: GLToolbar adjust
@@ -9401,8 +9584,14 @@ void GLCanvas3D::_render_canvas_toolbar()
[this]{wxGetApp().toggle_show_outline();}
);
create_menu_item( _utf8(L("Wireframe")),
m_canvas_type != ECanvasType::CanvasPreview, // not work on preview
p->is_show_wireframe(),
[this, p]{p->toggle_show_wireframe(); m_dirty = true;}
);
create_menu_item( _utf8(L("Realistic View")),
true,
m_canvas_type != ECanvasType::CanvasPreview, // not work on preview
cfg->get_bool(SETTING_OPENGL_REALISTIC_MODE),
[this, &cfg]{
cfg->set_bool(SETTING_OPENGL_REALISTIC_MODE, !cfg->get_bool(SETTING_OPENGL_REALISTIC_MODE));
@@ -10250,6 +10439,9 @@ void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning)
void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
{
// Skip on shutdown: Plater's pImpl is already freed, so get_notification_manager() would use-after-free.
if (wxGetApp().is_closing())
return;
using NotificationLevel = NotificationManager::NotificationLevel;
enum ErrorType{
PLATER_WARNING,