Files
OrcaSlicer/src/slic3r/GUI/3DBed.cpp
T
harrierpigeonandClaude Fable 5.1 dda58b07cd Belt printer: supports reach the belt under a leading overhang
The slicing frame of a belt object started at its lowest vertex, but the
belt under the leading end of an overhang lies below that, by the
overhang's length times the tilt's shear.  Every support generator works
in layers at z >= 0, so none of them could reach it: normal supports
stopped at the object's own lowest layer, and the two tree generators
each carried a stack of hacks to extend themselves below it (a post-hoc
copy of the lowest base area in TreeSupport, "virtual belt raft layers"
in TreeSupport3D/TreeModelVolumes), sized from the pre-rotation bbox
and capped at global z = 0, which is only right for the trailing half
of the belt.

Start the frame at the lowest belt-floor point under the footprint
instead, less a 10 mm margin along the belt for the base of a support
column (BeltSliceStrategy::apply_preslice_transforms and
BeltTransformPipeline::compute_belt_height_and_floor agree on it).  The
layers between it and the first vertex come out empty, which belt
slicing already tolerates, and the generators need no extension at all:

- normal supports: the generator anchors its layer grid at the frame
  origin, so run it in the object frame and shift the global belt Z
  offset onto the result afterwards, as organic supports already did.
  With the offset on the object layers a top contact at negative z
  turned the intermediate-layer count negative and the generator
  allocated layers until the kernel killed it (any overhang in the
  leading half of the belt).  Drop the first-layer flange expansion on a
  belt: the first support layer is the leading tip of the support, not
  a flange, and inflating it put lines in the air ahead of the belt.
- classic tree: a node now keeps dropping until its whole circle is in
  the belt, so the branch tapers to a tip on the belt instead of
  stopping, a radius above it, when its centre crosses.
- organic: the belt is no longer a support blocker.  A blocker is a
  collision, and a branch descending onto one slides off it, down the
  tilted belt and ahead of the part; the belt is where branches end,
  which the per-layer m_belt_floor clipping already does.

The belt brim is generated after the parallel support step instead of
inside it: belt_brim_obstacles() reads every object's layers and support
layers, which another object's support step rebuilds (and, now, shifts)
at the same time.  This is the race behind the Windows arm64 segfault
in "Belt brim of each object precedes its perimeters on its own
filament".

Also: the belt tilt axis moves to Developer mode as its own row (a
shared line is shown by its first option's mode), first_layer_plane
band thickness, belt_support_floor_mode, belt_preslice_global and
gcode_back_transform are retired and presumed on, the gravity arrow is
a plain line along the up direction, and the "Show raw G-code (belt
only)" preview toggle is gone.

Regression test: "Belt supports reach the belt under a leading
overhang" slices a cube with a fin whose underside is parallel to the
layers, 20 mm ahead of the cube and up to 41 mm of slicing Z above the
belt, for normal, organic and classic tree supports, and checks that
the lowest support layer sits on the belt beneath its own lines.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-06 22:14:25 -05:00

877 lines
33 KiB
C++

#include "libslic3r/libslic3r.h"
#include "3DBed.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/BoundingBox.hpp"
#include "libslic3r/Geometry/Circle.hpp"
#include "libslic3r/Tesselate.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "GUI_App.hpp"
#include "GUI_Colors.hpp"
#include "GLCanvas3D.hpp"
#include "Plater.hpp"
#include "Camera.hpp"
#include <boost/version.hpp>
#include <cstddef>
#include <array>
#include <glad/gl.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/log/trivial.hpp>
#include "slic3r/GUI/GLModel.hpp"
#include <vector>
#include "libslic3r/Point.hpp"
#include <utility>
#include "libslic3r/Color.hpp"
#include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/GLShader.hpp"
#include "slic3r/GUI/3DScene.hpp"
#include "libslic3r/Geometry.hpp"
#include <math.h>
#include <tuple>
#include "libslic3r/Preset.hpp"
#include "libslic3r/Config.hpp"
#include <Eigen/Geometry>
#if BOOST_VERSION >= 107800
#include <boost/timer/timer.hpp>
#include <algorithm>
#include <cmath>
#else
#include <boost/timer.hpp>
#endif
static const float GROUND_Z = -0.04f;
namespace Slic3r {
namespace GUI {
bool init_model_from_poly(GLModel &model, const ExPolygon &poly, float z)
{
if (poly.empty())
return false;
const std::vector<Vec2f> triangles = triangulate_expolygon_2f(poly, NORMALS_UP);
if (triangles.empty() || triangles.size() % 3 != 0)
return false;
GLModel::Geometry init_data;
init_data.format = { GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3T2 };
init_data.reserve_vertices(triangles.size());
init_data.reserve_indices(triangles.size() / 3);
Vec2f min = triangles.front();
Vec2f max = min;
for (const Vec2f &v : triangles) {
min = min.cwiseMin(v).eval();
max = max.cwiseMax(v).eval();
}
const Vec2f size = max - min;
if (size.x() <= 0.0f || size.y() <= 0.0f)
return false;
Vec2f inv_size = size.cwiseInverse();
inv_size.y() *= -1.0f;
// vertices + indices
unsigned int vertices_counter = 0;
for (const Vec2f &v : triangles) {
const Vec3f p = {v.x(), v.y(), z};
init_data.add_vertex(p, (Vec2f)(v - min).cwiseProduct(inv_size).eval());
++vertices_counter;
if (vertices_counter % 3 == 0)
init_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1);
}
model.init_from(std::move(init_data));
return true;
}
/*
bool GeometryBuffer::set_from_triangles(const std::vector<Vec2f> &triangles, float z)
{
if (triangles.empty()) {
m_vertices.clear();
return false;
}
m_vertices.clear();
assert(triangles.size() % 3 == 0);
m_vertices = std::vector<Vertex>(triangles.size(), Vertex());
Vec2f min = triangles.front();
Vec2f max = min;
for (size_t v_count = 0; v_count < triangles.size(); ++ v_count) {
const Vec2f &p = triangles[v_count];
Vertex &v = m_vertices[v_count];
v.position = Vec3f(p.x(), p.y(), z);
v.tex_coords = p;
min = min.cwiseMin(p).eval();
max = max.cwiseMax(p).eval();
}
Vec2f size = max - min;
if (size.x() != 0.f && size.y() != 0.f) {
Vec2f inv_size = size.cwiseInverse();
inv_size.y() *= -1;
for (Vertex& v : m_vertices) {
v.tex_coords -= min;
v.tex_coords.x() *= inv_size.x();
v.tex_coords.y() *= inv_size.y();
}
}
return true;
}
bool GeometryBuffer::set_from_lines(const Lines& lines, float z)
{
m_vertices.clear();
unsigned int v_size = 2 * (unsigned int)lines.size();
if (v_size == 0)
return false;
m_vertices = std::vector<Vertex>(v_size, Vertex());
unsigned int v_count = 0;
for (const Line& l : lines) {
Vertex& v1 = m_vertices[v_count];
v1.position[0] = unscale<float>(l.a(0));
v1.position[1] = unscale<float>(l.a(1));
v1.position[2] = z;
++v_count;
Vertex& v2 = m_vertices[v_count];
v2.position[0] = unscale<float>(l.b(0));
v2.position[1] = unscale<float>(l.b(1));
v2.position[2] = z;
++v_count;
}
return true;
}
//BBS: set from 3d lines
bool GeometryBuffer::set_from_3d_Lines(const Lines3& lines)
{
m_vertices.clear();
unsigned int v_size = 2 * (unsigned int)lines.size();
if (v_size == 0)
return false;
m_vertices = std::vector<Vertex>(v_size, Vertex());
unsigned int v_count = 0;
for (const Line3& l : lines) {
Vertex& v1 = m_vertices[v_count];
v1.position[0] = unscale<float>(l.a(0));
v1.position[1] = unscale<float>(l.a(1));
v1.position[2] = unscale<float>(l.a(2));
++v_count;
Vertex& v2 = m_vertices[v_count];
v2.position[0] = unscale<float>(l.b(0));
v2.position[1] = unscale<float>(l.b(1));
v2.position[2] = unscale<float>(l.b(2));
++v_count;
}
return true;
}
const float* GeometryBuffer::get_vertices_data() const
{
return (m_vertices.size() > 0) ? (const float*)m_vertices.data() : nullptr;
}
*/
const float Bed3D::Axes::DefaultStemRadius = 0.5f;
const float Bed3D::Axes::DefaultStemLength = 25.0f;
const float Bed3D::Axes::DefaultTipRadius = 2.5f * Bed3D::Axes::DefaultStemRadius;
const float Bed3D::Axes::DefaultTipLength = 5.0f;
// ORCA make bed colors accessable for 2D bed
ColorRGBA Bed3D::DEFAULT_MODEL_COLOR = { 0.3255f, 0.337f, 0.337f, 1.0f };
ColorRGBA Bed3D::DEFAULT_MODEL_COLOR_DARK = { 0.255f, 0.255f, 0.283f, 1.0f };
ColorRGBA Bed3D::DEFAULT_SOLID_GRID_COLOR = { 0.9f, 0.9f, 0.9f, 1.0f };
ColorRGBA Bed3D::DEFAULT_TRANSPARENT_GRID_COLOR = { 0.9f, 0.9f, 0.9f, 0.6f };
ColorRGBA Bed3D::AXIS_X_COLOR = ColorRGBA::X();
ColorRGBA Bed3D::AXIS_Y_COLOR = ColorRGBA::Y();
ColorRGBA Bed3D::AXIS_Z_COLOR = ColorRGBA::Z();
void Bed3D::update_render_colors()
{
Bed3D::AXIS_X_COLOR = ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Axis_X]);
Bed3D::AXIS_Y_COLOR = ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Axis_Y]);
Bed3D::AXIS_Z_COLOR = ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Axis_Z]);
}
void Bed3D::load_render_colors()
{
RenderColor::colors[RenderCol_Axis_X] = ImGuiWrapper::to_ImVec4(Bed3D::AXIS_X_COLOR);
RenderColor::colors[RenderCol_Axis_Y] = ImGuiWrapper::to_ImVec4(Bed3D::AXIS_Y_COLOR);
RenderColor::colors[RenderCol_Axis_Z] = ImGuiWrapper::to_ImVec4(Bed3D::AXIS_Z_COLOR);
}
void Bed3D::Axes::render()
{
auto render_axis = [this](GLShaderProgram* shader, const Transform3d& transform) {
const Camera& camera = wxGetApp().plater()->get_camera();
const Transform3d& view_matrix = camera.get_view_matrix();
shader->set_uniform("view_model_matrix", view_matrix * transform);
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
//const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * transform.matrix().block(0, 0, 3, 3).inverse().transpose();
//shader->set_uniform("view_normal_matrix", view_normal_matrix);
m_arrow.render();
};
if (!m_arrow.is_initialized())
//m_arrow.init_from(stilized_arrow(16, DefaultTipRadius, DefaultTipLength, DefaultStemRadius, m_stem_length));
m_arrow.init_from(smooth_cylinder(16, /*Radius*/ m_stem_length / 75.f, m_stem_length)); // ORCA use simple cylinder and scale thickness depends on length
GLShaderProgram* shader = wxGetApp().get_shader("flat"); // ORCA dont use shading to get closer color tone
if (shader == nullptr)
return;
glsafe(::glEnable(GL_DEPTH_TEST));
shader->start_using();
//shader->set_uniform("emission_factor", 0.0f);
// x axis
m_arrow.set_color(AXIS_X_COLOR);
render_axis(shader, Geometry::assemble_transform(m_origin, { 0.0, 0.5 * M_PI, 0.0 }));
// y axis
m_arrow.set_color(AXIS_Y_COLOR);
render_axis(shader, Geometry::assemble_transform(m_origin, { -0.5 * M_PI, 0.0, 0.0 }));
// z axis
m_arrow.set_color(AXIS_Z_COLOR);
render_axis(shader, Geometry::assemble_transform(m_origin));
shader->stop_using();
glsafe(::glDisable(GL_DEPTH_TEST));
}
//BBS: add part plate logic
bool Bed3D::set_shape(const Pointfs& printable_area, const double printable_height, std::vector<Pointfs> extruder_areas, std::vector<double> extruder_heights, const std::string& custom_model, bool force_as_custom,
const Vec2d position, bool with_reset)
{
/*auto check_texture = [](const std::string& texture) {
boost::system::error_code ec; // so the exists call does not throw (e.g. after a permission problem)
return !texture.empty() && (boost::algorithm::iends_with(texture, ".png") || boost::algorithm::iends_with(texture, ".svg")) && boost::filesystem::exists(texture, ec);
};*/
auto check_model = [](const std::string& model) {
boost::system::error_code ec;
return !model.empty() && boost::algorithm::iends_with(model, ".stl") && boost::filesystem::exists(model, ec);
};
Type type;
std::string model;
std::string texture;
if (force_as_custom)
type = Type::Custom;
else {
auto [new_type, system_model, system_texture] = detect_type(printable_area);
type = new_type;
model = system_model;
texture = system_texture;
}
/*std::string texture_filename = custom_texture.empty() ? texture : custom_texture;
if (! texture_filename.empty() && ! check_texture(texture_filename)) {
BOOST_LOG_TRIVIAL(error) << "Unable to load bed texture: " << texture_filename;
texture_filename.clear();
}*/
std::string model_filename = custom_model.empty() ? model : custom_model;
if (! model_filename.empty() && ! check_model(model_filename)) {
BOOST_LOG_TRIVIAL(error) << "Unable to load bed model: " << model_filename;
model_filename.clear();
}
//BBS: add position related logic
if (m_bed_shape == printable_area && m_build_volume.printable_height() == printable_height && m_type == type && m_model_filename == model_filename && position == m_position && m_extruder_shapes == extruder_areas && m_extruder_heights == extruder_heights)
// No change, no need to update the UI.
return false;
//BBS: add part plate logic, apply position to bed shape
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(":current position {%1%,%2%}, new position {%3%, %4%}") % m_position.x() % m_position.y() % position.x() % position.y();
m_position = position;
m_bed_shape = printable_area;
m_extruder_shapes = extruder_areas;
m_extruder_heights = extruder_heights;
if ((position(0) != 0) || (position(1) != 0)) {
Pointfs new_bed_shape;
for (const Vec2d& p : m_bed_shape) {
Vec2d point(p(0) + m_position.x(), p(1) + m_position.y());
new_bed_shape.push_back(point);
}
std::vector<Pointfs> new_extruder_shapes;
for (const std::vector<Vec2d>& shape : m_extruder_shapes) {
std::vector<Vec2d> new_extruder_shape;
for (const Vec2d& p : shape) {
Vec2d point(p(0) + m_position.x(), p(1) + m_position.y());
new_extruder_shape.push_back(point);
}
new_extruder_shapes.push_back(new_extruder_shape);
}
m_build_volume = BuildVolume { new_bed_shape, printable_height, new_extruder_shapes, m_extruder_heights };
}
else
m_build_volume = BuildVolume { printable_area, printable_height, m_extruder_shapes, m_extruder_heights };
m_type = type;
//m_texture_filename = texture_filename;
m_model_filename = model_filename;
//BBS add default bed
m_triangles.reset();
if (with_reset) {
//m_texture.reset();
m_model.reset();
}
//BBS: add part plate logic, always update model offset
update_model_offset();//include m_extended_bounding_box = this->calc_extended_bounding_box();
// Set the origin and size for rendering the coordinate system axes.
m_axes.set_origin({ 0.0, 0.0, static_cast<double>(GROUND_Z) });
m_axes.set_stem_length(0.1f * static_cast<float>(m_build_volume.bounding_volume().max_size()));
// unregister from picking
// BBS: remove the bed picking logic
// wxGetApp().plater()->canvas3D()->remove_raycasters_for_picking(SceneRaycaster::EType::Bed);
// Let the calee to update the UI.
return true;
}
//BBS: add api to set position for partplate related bed
void Bed3D::set_position(Vec2d& position)
{
set_shape(m_bed_shape, m_build_volume.printable_height(), m_extruder_shapes, m_extruder_heights, m_model_filename, false, position, false);
}
void Bed3D::set_axes_mode(bool origin)
{
if (origin) {
m_axes.set_origin({ 0.0, 0.0, static_cast<double>(GROUND_Z) });
}
else {
m_axes.set_origin({ m_position.x(), m_position.y(), static_cast<double>(GROUND_Z) });
}
}
/*bool Bed3D::contains(const Point& point) const
{
return m_polygon.contains(point);
}
Point Bed3D::point_projection(const Point& point) const
{
return m_polygon.point_projection(point);
}*/
void Bed3D::on_change_color_mode(bool is_dark)
{
m_is_dark = is_dark;
}
void Bed3D::render(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor, bool show_axes)
{
render_internal(canvas, view_matrix, projection_matrix, bottom, scale_factor, show_axes);
}
void Bed3D::render_internal(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, float scale_factor,
bool show_axes)
{
m_scale_factor = scale_factor;
if (show_axes)
render_axes();
glsafe(::glEnable(GL_DEPTH_TEST));
m_model.set_color(m_is_dark ? DEFAULT_MODEL_COLOR_DARK : DEFAULT_MODEL_COLOR);
switch (m_type)
{
case Type::System: { render_system(canvas, view_matrix, projection_matrix, bottom); break; }
default:
case Type::Custom: { render_custom(canvas, view_matrix, projection_matrix, bottom); break; }
}
render_gravity_arrow(view_matrix, projection_matrix);
glsafe(::glDisable(GL_DEPTH_TEST));
}
//BBS: add partplate related logic
// Calculate an extended bounding box from axes and current model for visualization purposes.
BoundingBoxf3 Bed3D::calc_printable_bounding_box() const
{
BoundingBoxf3 out{m_build_volume.bounding_volume()};
const Vec3d size = out.size();
// ensures that the bounding box is set as defined or the following calls to merge() will not work as intented
if (size.x() > 0.0 && size.y() > 0.0 && !out.defined)
out.defined = true;
// Reset the build volume Z, we don't want to zoom to the top of the build volume if it is empty.
out.min.z() = 0.0;
out.max.z() = 0.0;
// extend to contain axes
// BBS: add part plate related logic.
Vec3d offset{m_position.x(), m_position.y(), 0.f};
// out.merge(m_axes.get_origin() + offset + m_axes.get_total_length() * Vec3d::Ones());
out.merge(Vec3d(0.f, 0.f, GROUND_Z) + offset + m_axes.get_total_length() * Vec3d::Ones());
out.merge(out.min + Vec3d(-Axes::DefaultTipRadius, -Axes::DefaultTipRadius, out.max.z()));
return out;
}
BoundingBoxf3 Bed3D::calc_extended_bounding_box() const
{
BoundingBoxf3 out;
out.merge(m_printable_bounding_box);
BoundingBoxf3 model_bb = m_model.get_bounding_box();
if (model_bb.defined) {
model_bb.translate(m_model_offset);
out.merge(model_bb);
}
return out;
}
// Try to match the print bed shape with the shape of an active profile. If such a match exists,
// return the print bed model.
std::tuple<Bed3D::Type, std::string, std::string> Bed3D::detect_type(const Pointfs& shape)
{
auto bundle = wxGetApp().preset_bundle;
if (bundle != nullptr) {
const Preset* curr = &bundle->printers.get_selected_preset();
while (curr != nullptr) {
if (curr->config.has("printable_area")) {
std::string texture_filename, model_filename;
if (shape == make_counter_clockwise(dynamic_cast<const ConfigOptionPoints*>(curr->config.option("printable_area"))->values)) {
if (curr->is_system)
model_filename = PresetUtils::system_printer_bed_model(*curr);
else {
auto *printer_model = curr->config.opt<ConfigOptionString>("printer_model");
if (printer_model != nullptr && ! printer_model->value.empty()) {
model_filename = bundle->get_stl_model_for_printer_model(printer_model->value);
}
}
//std::string model_filename = PresetUtils::system_printer_bed_model(*curr);
//std::string texture_filename = PresetUtils::system_printer_bed_texture(*curr);
if (!model_filename.empty())
return { Type::System, model_filename, texture_filename };
}
}
curr = bundle->printers.get_preset_parent(*curr);
}
}
return { Type::Custom, {}, {} };
}
void Bed3D::render_axes()
{
if (m_build_volume.valid())
m_axes.render();
}
void Bed3D::render_system(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom)
{
if (!bottom)
render_model(view_matrix, projection_matrix);
/*if (show_texture)
render_texture(bottom, canvas);*/
}
/*void Bed3D::render_texture(bool bottom, GLCanvas3D& canvas)
{
GLTexture* texture = const_cast<GLTexture*>(&m_texture);
GLTexture* temp_texture = const_cast<GLTexture*>(&m_temp_texture);
if (m_texture_filename.empty()) {
texture->reset();
render_default(bottom, false);
return;
}
if (texture->get_id() == 0 || texture->get_source() != m_texture_filename) {
texture->reset();
if (boost::algorithm::iends_with(m_texture_filename, ".svg")) {
// use higher resolution images if graphic card and opengl version allow
GLint max_tex_size = OpenGLManager::get_gl_info().get_max_tex_size();
if (temp_texture->get_id() == 0 || temp_texture->get_source() != m_texture_filename) {
// generate a temporary lower resolution texture to show while no main texture levels have been compressed
if (!temp_texture->load_from_svg_file(m_texture_filename, false, false, false, max_tex_size / 8)) {
render_default(bottom, false);
return;
}
canvas.request_extra_frame();
}
// starts generating the main texture, compression will run asynchronously
if (!texture->load_from_svg_file(m_texture_filename, true, true, true, max_tex_size)) {
render_default(bottom, false);
return;
}
}
else if (boost::algorithm::iends_with(m_texture_filename, ".png")) {
// generate a temporary lower resolution texture to show while no main texture levels have been compressed
if (temp_texture->get_id() == 0 || temp_texture->get_source() != m_texture_filename) {
if (!temp_texture->load_from_file(m_texture_filename, false, GLTexture::None, false)) {
render_default(bottom, false);
return;
}
canvas.request_extra_frame();
}
// starts generating the main texture, compression will run asynchronously
if (!texture->load_from_file(m_texture_filename, true, GLTexture::MultiThreaded, true)) {
render_default(bottom, false);
return;
}
}
else {
render_default(bottom, false);
return;
}
}
else if (texture->unsent_compressed_data_available()) {
// sends to gpu the already available compressed levels of the main texture
texture->send_compressed_data_to_gpu();
// the temporary texture is not needed anymore, reset it
if (temp_texture->get_id() != 0)
temp_texture->reset();
canvas.request_extra_frame();
}
if (m_triangles.get_vertices_count() > 0) {
GLShaderProgram* shader = wxGetApp().get_shader("printbed");
if (shader != nullptr) {
shader->start_using();
const Camera& camera = wxGetApp().plater()->get_camera();
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
shader->set_uniform("transparent_background", bottom);
shader->set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg"));
unsigned int* vbo_id = const_cast<unsigned int*>(&m_vbo_id);
if (*vbo_id == 0) {
glsafe(::glGenBuffers(1, vbo_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, *vbo_id));
glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_triangles.get_vertices_data_size(), (const GLvoid*)m_triangles.get_vertices_data(), GL_STATIC_DRAW));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
glsafe(::glEnable(GL_DEPTH_TEST));
if (bottom)
glsafe(::glDepthMask(GL_FALSE));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
if (bottom)
glsafe(::glFrontFace(GL_CW));
unsigned int stride = m_triangles.get_vertex_data_size();
GLint position_id = shader->get_attrib_location("v_position");
GLint tex_coords_id = shader->get_attrib_location("v_tex_coords");
// show the temporary texture while no compressed data is available
GLuint tex_id = (GLuint)temp_texture->get_id();
if (tex_id == 0)
tex_id = (GLuint)texture->get_id();
glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, *vbo_id));
if (position_id != -1) {
glsafe(::glEnableVertexAttribArray(position_id));
glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_position_offset()));
}
if (tex_coords_id != -1) {
glsafe(::glEnableVertexAttribArray(tex_coords_id));
glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_tex_coords_offset()));
}
glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)m_triangles.get_vertices_count()));
if (tex_coords_id != -1)
glsafe(::glDisableVertexAttribArray(tex_coords_id));
if (position_id != -1)
glsafe(::glDisableVertexAttribArray(position_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
if (bottom)
glsafe(::glFrontFace(GL_CCW));
glsafe(::glDisable(GL_BLEND));
if (bottom)
glsafe(::glDepthMask(GL_TRUE));
shader->stop_using();
}
}
}*/
//BBS: add part plate related logic
void Bed3D::update_model_offset()
{
// move the model so that its origin (0.0, 0.0, 0.0) goes into the bed shape center and a bit down to avoid z-fighting with the texture quad
Vec3d shift = m_build_volume.bounding_volume().center();
shift(2) = -0.03;
Vec3d* model_offset_ptr = const_cast<Vec3d*>(&m_model_offset);
*model_offset_ptr = shift;
//BBS: TODO: hack for current stl for BBL printer
if (std::string::npos != m_model_filename.find("bbl-3dp-"))
{
(*model_offset_ptr)(0) -= m_bed_shape[2].x() / 2.0f;
(*model_offset_ptr)(1) -= m_bed_shape[2].y() / 2.0f;
}
(*model_offset_ptr)(2) = -0.41 + GROUND_Z;
// update extended bounding box
const_cast<BoundingBoxf3 &>(m_printable_bounding_box) = calc_printable_bounding_box();
const_cast<BoundingBoxf3 &>(m_extended_bounding_box) = calc_extended_bounding_box();
m_triangles.reset();
}
void Bed3D::update_bed_triangles()
{
if (m_triangles.is_initialized()) {
return;
}
Vec3d shift = m_extended_bounding_box.center();
shift(2) = -0.03;
Vec3d* model_offset_ptr = const_cast<Vec3d*>(&m_model_offset);
*model_offset_ptr = shift;
//BBS: TODO: hack for default bed
BoundingBoxf3 build_volume;
if (!m_build_volume.valid()) return;
auto bed_ext = get_extents(m_bed_shape);
(*model_offset_ptr)(0) = m_build_volume.bounding_volume2d().min.x() - bed_ext.min.x();
(*model_offset_ptr)(1) = m_build_volume.bounding_volume2d().min.y() - bed_ext.min.y();
(*model_offset_ptr)(2) = -0.41 + GROUND_Z;
std::vector<Vec2d> origin_bed_shape;
for (size_t i = 0; i < m_bed_shape.size(); i++) {
origin_bed_shape.push_back(m_bed_shape[i]);
}
std::vector<Vec2d> new_bed_shape; // offset to correct origin
for (auto point : origin_bed_shape) {
Vec2d new_point(point.x() + model_offset_ptr->x(), point.y() + model_offset_ptr->y());
new_bed_shape.push_back(new_point);
}
ExPolygon poly{ Polygon::new_scale(new_bed_shape) };
if (!init_model_from_poly(m_triangles, poly, GROUND_Z)) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":Unable to update plate triangles\n";
}
// update extended bounding box
const_cast<BoundingBoxf3 &>(m_printable_bounding_box) = calc_printable_bounding_box();
const_cast<BoundingBoxf3 &>(m_extended_bounding_box) = calc_extended_bounding_box();
}
void Bed3D::render_model(const Transform3d& view_matrix, const Transform3d& projection_matrix)
{
if (m_model_filename.empty())
return;
if (m_model.get_filename() != m_model_filename && m_model.init_from_file(m_model_filename)) {
m_model.set_color(m_is_dark ? DEFAULT_MODEL_COLOR_DARK : DEFAULT_MODEL_COLOR);
update_model_offset();
// BBS: remove the bed picking logic
//register_raycasters_for_picking(m_model.model.get_geometry(), Geometry::assemble_transform(m_model_offset));
}
if (!m_model.get_filename().empty()) {
const Camera & camera = wxGetApp().plater()->get_camera();
const Transform3d &view_matrix = camera.get_view_matrix();
const Transform3d &projection_matrix = camera.get_projection_matrix();
GLShaderProgram* shader = wxGetApp().get_shader("hotbed");
if (shader != nullptr) {
shader->start_using();
shader->set_uniform("emission_factor", 0.0f);
Transform3d model_matrix = Geometry::assemble_transform(m_model_offset);
shader->set_uniform("volume_world_matrix", model_matrix);
shader->set_uniform("view_model_matrix", view_matrix * model_matrix);
shader->set_uniform("projection_matrix", projection_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);
if (m_build_volume.get_extruder_area_count() > 0) {
const BuildVolume::BuildSharedVolume& shared_volume = m_build_volume.get_shared_volume();
std::array<float, 4> xy_data = shared_volume.data;
shader->set_uniform("print_volume.type", shared_volume.type);
shader->set_uniform("print_volume.xy_data", xy_data);
std::array<float, 2> zs = shared_volume.zs;
zs[0] = -1;
shader->set_uniform("print_volume.z_data", zs);
}
else {
//use -1 ad a invalid type
shader->set_uniform("print_volume.type", -1);
}
m_model.render();
shader->stop_using();
}
}
}
void Bed3D::render_custom(GLCanvas3D& canvas, const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom)
{
if (m_model_filename.empty()) {
render_default(bottom, view_matrix, projection_matrix);
return;
}
if (!bottom)
render_model(view_matrix, projection_matrix);
/*if (show_texture)
render_texture(bottom, canvas);*/
}
void Bed3D::render_gravity_arrow(const Transform3d& view_matrix, const Transform3d& projection_matrix)
{
// build_plate_tilt_{x,y} are kept in sync with the belt tilt (see TabPrinter), so
// reading them here covers both belt and non-belt tilted printers.
const Vec3d up_dir = build_plate_tilt_up_direction();
if (up_dir == Vec3d::UnitZ()) {
m_gravity_arrow.reset();
return;
}
// A plain line along the tilted "up" direction -- the way the layers lean, i.e.
// the gantry -- drawn like the bed axes (no tip: the other direction is not
// possible) and shorter than them, so it reads as a hint inside the YZ corner.
const float length = 0.6f * m_axes.get_total_length();
if (!m_gravity_arrow.is_initialized() || m_gravity_arrow_length != length) {
m_gravity_arrow.reset();
m_gravity_arrow.init_from(smooth_cylinder(16, /*Radius*/ length / 75.f, length));
m_gravity_arrow_length = length;
}
// The cylinder model points along +Z. Compute the rotation that aligns it with
// up_dir: rotation axis = cross(+Z, up_dir), angle = acos(dot(+Z, up_dir)).
Vec3d from = Vec3d::UnitZ();
Vec3d to = up_dir;
double dot = from.dot(to);
Transform3d rot = Transform3d::Identity();
if (dot < -0.9999) {
// Nearly opposite -- rotate 180 degrees around X
rot = Eigen::AngleAxisd(M_PI, Vec3d::UnitX()) * rot;
} else if (dot < 0.9999) {
Vec3d axis = from.cross(to).normalized();
double angle = std::acos(std::clamp(dot, -1.0, 1.0));
rot = Eigen::AngleAxisd(angle, axis) * rot;
}
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader == nullptr)
return;
glsafe(::glEnable(GL_DEPTH_TEST));
shader->start_using();
const Camera& camera = wxGetApp().plater()->get_camera();
Transform3d model_matrix = Eigen::Translation3d(m_axes.get_origin()) * rot;
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * model_matrix);
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
m_gravity_arrow.set_color({ 1.0f, 0.85f, 0.0f, 1.0f }); // yellow
m_gravity_arrow.render();
shader->stop_using();
}
void Bed3D::render_default(bool bottom, const Transform3d& view_matrix, const Transform3d& projection_matrix)
{
// m_texture.reset();
update_bed_triangles();
GLShaderProgram* shader = wxGetApp().get_shader("flat");
if (shader != nullptr) {
shader->start_using();
shader->set_uniform("view_model_matrix", view_matrix);
shader->set_uniform("projection_matrix", projection_matrix);
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
if (m_model.get_filename().empty() && !bottom) {
// draw background
glsafe(::glDepthMask(GL_FALSE));
ColorRGBA color = m_is_dark ? DEFAULT_MODEL_COLOR_DARK : DEFAULT_MODEL_COLOR; // ORCA add dark mode support
color = ColorRGBA(color[0] * 0.8f, color[1] * 0.8f,color[2] * 0.8f, color[3]); // ORCA shift color a darker tone to fix difference between flat / gouraud_light shader
m_triangles.set_color(color);
m_triangles.render();
glsafe(::glDepthMask(GL_TRUE));
}
/*if (!picking) {
// draw grid
glsafe(::glLineWidth(1.5f * m_scale_factor));
m_gridlines.set_color(picking ? DEFAULT_SOLID_GRID_COLOR : DEFAULT_TRANSPARENT_GRID_COLOR);
m_gridlines.render();
}*/
glsafe(::glDisable(GL_BLEND));
shader->stop_using();
}
}
// BBS: remove the bed picking logic
/*
void Bed3D::register_raycasters_for_picking(const GLModel::Geometry& geometry, const Transform3d& trafo)
{
assert(m_model.mesh_raycaster == nullptr);
indexed_triangle_set its;
its.vertices.reserve(geometry.vertices_count());
for (size_t i = 0; i < geometry.vertices_count(); ++i) {
its.vertices.emplace_back(geometry.extract_position_3(i));
}
its.indices.reserve(geometry.indices_count() / 3);
for (size_t i = 0; i < geometry.indices_count() / 3; ++i) {
const size_t tri_id = i * 3;
its.indices.emplace_back(geometry.extract_index(tri_id), geometry.extract_index(tri_id + 1), geometry.extract_index(tri_id + 2));
}
m_model.mesh_raycaster = std::make_unique<MeshRaycaster>(std::make_shared<const TriangleMesh>(std::move(its)));
wxGetApp().plater()->canvas3D()->add_raycaster_for_picking(SceneRaycaster::EType::Bed, 0, *m_model.mesh_raycaster, trafo);
}
*/
} // GUI
} // Slic3r