Compare commits

..
8 changed files with 114 additions and 95 deletions

No files matched your search

+2 -2
View File
@@ -700,7 +700,7 @@ void GLVolume::render_with_outline(const GUI::Size& cnv_size)
} }
//BBS add render for simple case //BBS add render for simple case
void GLVolume::simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_objects, std::vector<ColorRGBA>& extruder_colors, bool ban_light, bool ban_color_volume) void GLVolume::simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_objects, std::vector<ColorRGBA>& extruder_colors, bool ban_light)
{ {
if (this->is_left_handed()) if (this->is_left_handed())
glFrontFace(GL_CW); glFrontFace(GL_CW);
@@ -734,7 +734,7 @@ void GLVolume::simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_obj
} }
} while (0); } while (0);
if (color_volume && !picking && !ban_color_volume) { if (color_volume && !picking) {
const bool brighten_selected = selected && !disabled && !force_native_color && !force_neutral_color; const bool brighten_selected = selected && !disabled && !force_native_color && !force_neutral_color;
// when force_transparent, we need to keep the alpha // when force_transparent, we need to keep the alpha
+1 -1
View File
@@ -370,7 +370,7 @@ public:
virtual void render_with_outline(const GUI::Size& cnv_size); virtual void render_with_outline(const GUI::Size& cnv_size);
//BBS: add simple render function for thumbnail //BBS: add simple render function for thumbnail
void simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_objects, std::vector<ColorRGBA>& extruder_colors, bool ban_light =false, bool ban_color_volume = false); void simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_objects, std::vector<ColorRGBA>& extruder_colors, bool ban_light =false);
void set_bounding_boxes_as_dirty() { void set_bounding_boxes_as_dirty() {
m_transformed_bounding_box.reset(); m_transformed_bounding_box.reset();
+4 -86
View File
@@ -2804,9 +2804,7 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), show_axes); _render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), show_axes);
} }
m_frame_profiler.mark("bed"); m_frame_profiler.mark("bed");
// Exception: the painting gizmos hide the bed mesh on purpose and draw the plate instead, if (show_bed) //BBS: add outline logic
// so there the plate list is the only bed reference in the scene.
if (show_bed || _is_below_plate_view_active()) //BBS: add outline logic
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid); _render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid);
if (m_design_canvas && show_bed) if (m_design_canvas && show_bed)
// Design tab: replace the plate's corner-origin grid with the origin-centred CAD grid. // Design tab: replace the plate's corner-origin grid with the origin-centred CAD grid.
@@ -2818,7 +2816,6 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
_render_shadows(camera.get_view_matrix(), camera.get_projection_matrix()); _render_shadows(camera.get_view_matrix(), camera.get_projection_matrix());
m_frame_profiler.mark("shadows"); m_frame_profiler.mark("shadows");
_render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running()); _render_objects(GLVolumeCollection::ERenderType::Opaque, !m_gizmos.is_running());
_render_below_plate_volumes();
m_frame_profiler.mark("objects"); m_frame_profiler.mark("objects");
_render_sla_slices(); _render_sla_slices();
_render_selection(); _render_selection();
@@ -8843,84 +8840,6 @@ void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d&
m_bed.render(*this, view_matrix, projection_matrix, bottom, scale_factor, show_axes); m_bed.render(*this, view_matrix, projection_matrix, bottom, scale_factor, show_axes);
} }
bool GLCanvas3D::_is_below_plate_view_active() const
{
return dynamic_cast<const GLGizmoPainterBase*>(m_gizmos.get_current()) != nullptr && m_gizmos.is_hiding_instances();
}
// The painting gizmo clips the selected object at the plate, so the part under the plate is not drawn.
// This pass draws that part of the selected instance again at 50% opacity, depth writes off.
void GLCanvas3D::_render_below_plate_volumes()
{
if (!_is_below_plate_view_active() || m_volumes.empty())
return;
GLShaderProgram* shader = wxGetApp().get_shader("gouraud");
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 int object_idx = m_selection.get_object_idx();
const int instance_idx = m_selection.get_instance_idx();
// Depth test stays on so whatever is already in the depth buffer hides the ghost; depth writes
// are off and back faces are culled, so each pixel blends only the near surface once.
const GLboolean cull_face = ::glIsEnabled(GL_CULL_FACE);
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glEnable(GL_CULL_FACE));
glsafe(::glCullFace(GL_BACK));
glsafe(::glDepthMask(GL_FALSE));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
shader->start_using();
// Keep z <= SINKING_Z_THRESHOLD, the complement of the clip the painting gizmo applies.
shader->set_uniform("z_range", std::array<float, 2>{ -FLT_MAX, float(SINKING_Z_THRESHOLD) });
shader->set_uniform("clipping_plane", m_volumes.get_clipping_plane());
shader->set_uniform("use_color_clip_plane", false);
shader->set_uniform("is_outline", false);
shader->set_uniform("slope.actived", false);
shader->set_uniform("print_volume.type", -1);
shader->set_uniform("extruder_printable_heights", std::array<float, 3>{ 0.0f, 0.0f, 0.0f });
shader->set_uniform("shadow_intensity", 0.0f);
// simple_render() needs these to reach the plain surface; with ban_color_volume it ignores both.
ModelObjectPtrs& model_objects = wxGetApp().model().objects;
std::vector<ColorRGBA> extruder_colors = wxGetApp().plater()->get_extruders_colors();
for (GLVolume* volume : m_volumes.volumes) {
// is_active cannot gate this pass: the color painter marks the whole instance inactive and
// draws it in its own gizmo pass. The instance and print-part checks below are what pick
// this ghost out of the scene.
if (volume->object_idx() != object_idx || volume->instance_idx() != instance_idx ||
volume->is_modifier || volume->is_wipe_tower || volume->volume_idx() < 0)
continue;
volume->set_render_color();
ColorRGBA color = volume->render_color;
color.a(0.5f);
volume->model.set_color(color);
const Transform3d model_matrix = volume->world_matrix();
shader->set_uniform("view_model_matrix", view_matrix * model_matrix);
shader->set_uniform("projection_matrix", proj_matrix);
shader->set_uniform("volume_world_matrix", model_matrix);
const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
shader->set_uniform("view_normal_matrix", view_normal_matrix);
// Painted facets would send render() through the MMU segmentation sub-models, which carry
// their own opaque filament colours; the ghost draws the plain surface so the alpha holds.
volume->simple_render(shader, model_objects, extruder_colors, false, true);
}
shader->stop_using();
glsafe(::glDisable(GL_BLEND));
glsafe(::glDepthMask(GL_TRUE));
if (cull_face == GL_FALSE)
glsafe(::glDisable(GL_CULL_FACE));
}
bool GLCanvas3D::_hides_inactive_plates() const bool GLCanvas3D::_hides_inactive_plates() const
{ {
return m_canvas_type == ECanvasType::CanvasView3D && !m_design_canvas && wxGetApp().focus_mode(); return m_canvas_type == ECanvasType::CanvasView3D && !m_design_canvas && wxGetApp().focus_mode();
@@ -12049,10 +11968,9 @@ bool GLCanvas3D::is_bed_visible() const
if (!m_main_toolbar.is_enabled()) if (!m_main_toolbar.is_enabled())
return true; return true;
// The painting gizmos clip the selected instance at the plate (see InstancesHider), so the bed const auto type = m_gizmos.get_current_type();
// mesh would sit in front of the part below it. They draw the plate and its grid instead (see return type != GLGizmosManager::FdmSupports && type != GLGizmosManager::Seam &&
// _render_scene) and the below-plate part as a translucent ghost. type != GLGizmosManager::MmSegmentation && type != GLGizmosManager::FuzzySkin;
return !_is_below_plate_view_active();
} }
Vec3d GLCanvas3D::get_camera_pan_anchor(Camera& camera, ECameraNavigationType navigation_type, Vec3d GLCanvas3D::get_camera_pan_anchor(Camera& camera, ECameraNavigationType navigation_type,
-2
View File
@@ -1552,8 +1552,6 @@ private:
void _render_imex_ghost_tooltip(); void _render_imex_ghost_tooltip();
void _render_section_view_caps(); void _render_section_view_caps();
void _render_wireframe_overlay(); void _render_wireframe_overlay();
bool _is_below_plate_view_active() const;
void _render_below_plate_volumes();
bool _is_xray_view_active() const; bool _is_xray_view_active() const;
void _render_xray_volumes(); void _render_xray_volumes();
//BBS: GUI refactor: add canvas size as parameters //BBS: GUI refactor: add canvas size as parameters
+5 -4
View File
@@ -7,6 +7,7 @@
#include <cstddef> #include <cstddef>
#include <glad/gl.h>
#include "slic3r/GUI/Selection.hpp" #include "slic3r/GUI/Selection.hpp"
#include <limits> #include <limits>
#include "libslic3r/Model.hpp" #include "libslic3r/Model.hpp"
@@ -173,9 +174,6 @@ void InstancesHider::on_update()
m_clippers.emplace_back(new MeshClipper); m_clippers.emplace_back(new MeshClipper);
m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), z_min)); m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), z_min));
m_clippers.back()->set_mesh(mesh->its); m_clippers.back()->set_mesh(mesh->its);
// Outline only: a filled cap would read as a surface the object does not have, and it
// covers the model below it. 0.4 mm matches the cut gizmo's contour width.
m_clippers.back()->set_behaviour(false, 0.4);
} }
m_old_meshes = meshes; m_old_meshes = meshes;
} }
@@ -216,7 +214,10 @@ void InstancesHider::render_cut() const
else else
clipper->set_limiting_plane(ClippingPlane::ClipsNothing()); clipper->set_limiting_plane(ClippingPlane::ClipsNothing());
clipper->render_contour(mv->is_model_part() ? ColorRGBA(0.8f, 0.3f, 0.0f, 1.0f) : color_from_model_volume(*mv)); glsafe(::glPushAttrib(GL_DEPTH_TEST));
glsafe(::glDisable(GL_DEPTH_TEST));
clipper->render_cut(mv->is_model_part() ? ColorRGBA(0.8f, 0.3f, 0.0f, 1.0f) : color_from_model_volume(*mv));
glsafe(::glPopAttrib());
++clipper_id; ++clipper_id;
} }
+6
View File
@@ -2,6 +2,7 @@
#include "GeneratedConfig.hpp" #include "GeneratedConfig.hpp"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
#include "PluginAuditManager.hpp" #include "PluginAuditManager.hpp"
#include "PythonPluginBridge.hpp"
#include <atomic> #include <atomic>
#include <boost/filesystem/operations.hpp> #include <boost/filesystem/operations.hpp>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
@@ -664,6 +665,11 @@ bool PythonInterpreter::initialize()
// background-thread exceptions) to <data_dir>/log/python_*.log. // background-thread exceptions) to <data_dir>/log/python_*.log.
install_python_stderr_redirect(); install_python_stderr_redirect();
// Attribute plugin-spawned Python threads to the plugin/capability that created them.
// Must run while the GIL is still held here, after the audit hook is in place and
// before the GIL is released below.
install_python_thread_audit_shim();
// Release the GIL so other threads can acquire it via PyGILState_Ensure. // Release the GIL so other threads can acquire it via PyGILState_Ensure.
// Without this, calls from background threads will block trying to acquire the GIL. // Without this, calls from background threads will block trying to acquire the GIL.
m_main_thread_state = PyEval_SaveThread(); m_main_thread_state = PyEval_SaveThread();
+87
View File
@@ -603,6 +603,93 @@ request aborts the plugin load.)pbdoc");
} }
void install_python_thread_audit_shim()
{
// Runs with the GIL held from PythonInterpreter::initialize(). CPython's own thread bootstrap
// calls the target with no knowledge of the audit context that created it: the calling
// thread is the only place the plugin key and capability are still available. Capture them
// here, then re-establish the same ScopedPluginAuditContext inside the spawned thread before
// the plugin body runs. The identity lives only in this C++ closure -- no Python-visible
// setter is added.
try {
py::object thread_module = py::module_::import("_thread");
const int installed = PyObject_HasAttrString(thread_module.ptr(), "_orca_thread_audit_shim_installed");
if (installed == 1) {
BOOST_LOG_TRIVIAL(debug) << "[AUDIT] Python thread attribution shim already installed";
return;
}
if (installed < 0)
PyErr_Clear();
py::object original = py::reinterpret_steal<py::object>(
PyObject_GetAttrString(thread_module.ptr(), "start_new_thread"));
if (!original || !PyCallable_Check(original.ptr())) {
PyErr_Clear();
BOOST_LOG_TRIVIAL(error) << "[AUDIT] _thread.start_new_thread is missing or not callable; "
"plugin threads will run without audit attribution";
return;
}
py::object shim = py::cpp_function(
[original](py::object function, py::object args, py::object kwargs) -> py::object {
const std::string plugin_key = PluginAuditManager::instance().current_plugin();
if (plugin_key.empty()) {
// Not called from a plugin: preserve the stock implementation untouched.
return kwargs.is_none() ? original(function, args) : original(function, args, kwargs);
}
const std::string capability = PluginAuditManager::instance().current_capability();
py::object guarded = py::cpp_function(
[function, plugin_key, capability](py::args call_args, py::kwargs call_kwargs) -> py::object {
ScopedPluginAuditContext audit_scope(plugin_key, capability);
PyObject* result = PyObject_Call(function.ptr(), call_args.ptr(), call_kwargs.ptr());
if (result == nullptr)
throw py::error_already_set();
return py::reinterpret_steal<py::object>(result);
});
return kwargs.is_none() ? original(guarded, args) : original(guarded, args, kwargs);
},
py::arg("function"), py::arg("args"), py::arg("kwargs") = py::none());
if (PyObject_SetAttrString(thread_module.ptr(), "start_new_thread", shim.ptr()) < 0) {
PyErr_Clear();
BOOST_LOG_TRIVIAL(error)
<< "[AUDIT] Failed to install Python thread attribution shim on _thread.start_new_thread";
return;
}
if (PyObject_SetAttrString(thread_module.ptr(), "_orca_thread_audit_shim_installed", Py_True) < 0)
PyErr_Clear();
// threading.py caches the function at import time (as `_start_new_thread` in 3.12, and as
// `start_new_thread` in older versions). Patch the cached reference too when the module is
// already loaded; otherwise its Thread.start() would keep calling the unwrapped original.
py::object sys_modules = py::module_::import("sys").attr("modules");
PyObject* threading_module = PyDict_GetItemString(sys_modules.ptr(), "threading");
if (threading_module && threading_module != Py_None) {
const char* cached_names[] = {"_start_new_thread", "start_new_thread"};
for (const char* cached_name : cached_names) {
const int has_cached_name = PyObject_HasAttrString(threading_module, cached_name);
if (has_cached_name < 0) {
PyErr_Clear();
continue;
}
if (has_cached_name != 1)
continue;
if (PyObject_SetAttrString(threading_module, cached_name, shim.ptr()) < 0) {
PyErr_Clear();
BOOST_LOG_TRIVIAL(warning) << "[AUDIT] Failed to update threading." << cached_name
<< " with the Python thread attribution shim";
}
}
}
BOOST_LOG_TRIVIAL(info) << "[AUDIT] Python thread attribution shim installed";
} catch (const std::exception& ex) {
BOOST_LOG_TRIVIAL(error) << "[AUDIT] Failed to install Python thread attribution shim: " << ex.what();
}
}
} // namespace Slic3r } // namespace Slic3r
#ifdef ORCA_PYTHON_STUBGEN_MODULE #ifdef ORCA_PYTHON_STUBGEN_MODULE
+9
View File
@@ -13,6 +13,15 @@ namespace Slic3r { class PluginCapabilityInterface; }
namespace Slic3r { namespace Slic3r {
// Install the thread-attribution shim: wraps `_thread.start_new_thread` (and the reference
// cached by an already-imported `threading` module) so a thread spawned from inside a plugin
// call runs with that call's plugin key/capability audit scope. The captured identity is held
// in a private C++ wrapper; nothing is exposed to Python that could set it.
//
// Call with the GIL held, after PluginAuditManager::install_hook() and before the interpreter
// releases the GIL. Safe to call more than once per interpreter.
void install_python_thread_audit_shim();
// One materialized capability returned from finalize_plugin_capture: the C++ instance // One materialized capability returned from finalize_plugin_capture: the C++ instance
// and its resolved get_name() (cached while the GIL was held). // and its resolved get_name() (cached while the GIL was held).
struct CapturedCapability struct CapturedCapability