mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-15 20:01:31 +00:00
* Option: Show/ Hide inactive plates * Hide inactive plate volumes by bounds Switch inactive-plate volume hiding to use geometric overlap with the current plate bounding box instead of object and wipe tower ID mapping. This also guards against a missing current plate and keeps any active volume visible when its transformed bounds overlap the current plate footprint. * Hide inactive plates per instance and keep them unpickable Decide what is shown per object instance, from its footprint over the current plate, so the parts of an object are never split; a selected instance stays visible. Keep the hidden state between frames instead of restoring it after each one. GLVolume::on_hidden_plate records what this update hid, so it only shows that again and leaves the volumes gizmos hide alone, and the raycasts made while handling the mouse see the same scene as the frame. Nothing runs while the inactive plates are shown. Deactivate the raycasters of the other plates, whose icons still took clicks where nothing was drawn, move the axes to the current plate, and limit the option to Prepare, as Preview already shows the current plate alone. * Follow the selected plate with the camera while the others are hidden Selecting a plate in Prepare while the inactive plates are hidden moves the camera target over it and keeps the angle, distance and height, so the plate comes into view where the previous one was. A zoom to all plates, as after loading a project or adding a plate, frames the current plate instead. * Add a compact plate switcher while inactive plates are hidden The hidden plates can no longer be clicked, and the object list gets crowded on large projects. While they are hidden in Prepare, a column of numbered buttons in the top left corner, a compact counterpart of the Preview plate toolbar, selects the plate to show. It highlights the current plate, dims the empty ones and gives the plate name in the tooltip. * Simplify the inactive plates update and the plate switcher layout Decide and apply each volume's state in one pass instead of filling a per-volume vector first, and hoist the switcher's loop-invariant colors. Keep the height the bottom-left corner takes next to the constants it is made of, with the canvas toolbar's button spacing named, rather than repeating that layout in the switcher. * Rename the option to Focus mode The canvas menu entry reads Focus mode, lit while the other plates are hidden, and is off by default, so nothing changes until it is turned on. The app config key is focus_mode. Turning it on zooms to the current plate, so the plate fills the view. * Show plate thumbnails in the plate switcher The switcher shows the thumbnails of the Preview plate toolbar instead of numbered buttons, with rounded corners concentric with the window's. Plater::plate_toolbar_image_version() lets it refresh them without clearing the Preview toolbar's dirty flag. * Don't draw the sinking contours of inactive volumes GLVolume::render() skips an inactive volume, but its sinking contour was still drawn, so an object on a hidden plate left its outline behind. * Show the plate switcher's scrollbar only on hover, as in Preview The plate switcher now shares the Preview plate toolbar's scrollbar: the same width and colors, shown only while the mouse is over the column or drags the bar, with the mouse wheel scrolling it either way, and tighter window corners while it shows. Both toolbars draw it through the same helpers, so they cannot drift apart; the Preview toolbar behaves as before. Drop the frame style the switcher no longer uses since it draws thumbnails instead of buttons. * Make the plate switcher's thumbnails cheaper and match the Preview toolbar The thumbnail version moves after almost every edit, and the switcher redrew both thumbnails of every plate each time. It now redraws only the current plate's, the only one edited while the others are hidden, and the other plates' once their thumbnails are invalidated, through the new Plater::update_plate_thumbnails(). IMToolbarItem::set_thumbnail() uploads a plate thumbnail without keeping a copy of its pixels, a megabyte per plate, and replaces the item setup the Preview toolbar and the switcher each repeated. The switcher's hover border takes the Preview toolbar's color. --------- Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
2388 lines
106 KiB
C++
2388 lines
106 KiB
C++
#include <array>
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <cmath>
|
|
#include <glad/gl.h>
|
|
|
|
#include "3DScene.hpp"
|
|
#include "GLShader.hpp"
|
|
#include "GUI_App.hpp"
|
|
#include "GUI_Colors.hpp"
|
|
#include "Plater.hpp"
|
|
#include "BitmapCache.hpp"
|
|
#include "Camera.hpp"
|
|
|
|
#include "libslic3r/BuildVolume.hpp"
|
|
#include "libslic3r/ExtrusionEntity.hpp"
|
|
#include "libslic3r/ExtrusionEntityCollection.hpp"
|
|
#include "libslic3r/Geometry.hpp"
|
|
#include "libslic3r/Print.hpp"
|
|
#include "libslic3r/SLAPrint.hpp"
|
|
#include "libslic3r/Slicing.hpp"
|
|
#include "libslic3r/Format/STL.hpp"
|
|
#include "libslic3r/Utils.hpp"
|
|
#include "libslic3r/AppConfig.hpp"
|
|
#include "libslic3r/PresetBundle.hpp"
|
|
#include "libslic3r/ClipperUtils.hpp"
|
|
#include "libslic3r/GCode/WipeTower.hpp"
|
|
#include "libslic3r/GCode/WipeTowerEstimate.hpp"
|
|
#include "libslic3r/Tesselate.hpp"
|
|
#include "libslic3r/PrintConfig.hpp"
|
|
|
|
#include "libslic3r/BoundingBox.hpp"
|
|
#include "libslic3r/TriangleMeshSlicer.hpp"
|
|
#include "libslic3r/Polygon.hpp"
|
|
#include "libslic3r/Point.hpp"
|
|
#include "slic3r/GUI/ImGuiWrapper.hpp"
|
|
#include "libslic3r/Model.hpp"
|
|
#include "libslic3r/Technologies.hpp"
|
|
#include <optional>
|
|
#include "slic3r/GUI/OpenGLManager.hpp"
|
|
#include "libslic3r/Config.hpp"
|
|
#include <memory>
|
|
#include "slic3r/GUI/MeshUtils.hpp"
|
|
#include "libslic3r/TriangleMesh.hpp"
|
|
#include <math.h>
|
|
#include "libslic3r/ObjectID.hpp"
|
|
#include "libslic3r/libslic3r.h"
|
|
#include <map>
|
|
#include <set>
|
|
#include <iterator>
|
|
#include "libslic3r/Color.hpp"
|
|
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
|
#include "libslic3r/Line.hpp"
|
|
#include "libslic3r/Polyline.hpp"
|
|
#include "libslic3r/Exception.hpp"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
#include <boost/log/trivial.hpp>
|
|
|
|
#include <boost/filesystem/operations.hpp>
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
|
|
#include <Eigen/Dense>
|
|
#include <vector>
|
|
#include <utility>
|
|
#include <string>
|
|
|
|
#ifdef HAS_GLSAFE
|
|
void glAssertRecentCallImpl(const char* file_name, unsigned int line, const char* function_name)
|
|
{
|
|
#if defined(NDEBUG)
|
|
// In release mode, only show OpenGL errors if sufficiently high loglevel.
|
|
if (Slic3r::get_logging_level() < 5)
|
|
return;
|
|
#endif // NDEBUG
|
|
|
|
GLenum err = glGetError();
|
|
if (err == GL_NO_ERROR)
|
|
return;
|
|
const char* sErr = 0;
|
|
switch (err) {
|
|
case GL_INVALID_ENUM: sErr = "Invalid Enum"; break;
|
|
case GL_INVALID_VALUE: sErr = "Invalid Value"; break;
|
|
// be aware that GL_INVALID_OPERATION is generated if glGetError is executed between the execution of glBegin and the corresponding execution of glEnd
|
|
case GL_INVALID_OPERATION: sErr = "Invalid Operation"; break;
|
|
case GL_STACK_OVERFLOW: sErr = "Stack Overflow"; break;
|
|
case GL_STACK_UNDERFLOW: sErr = "Stack Underflow"; break;
|
|
case GL_OUT_OF_MEMORY: sErr = "Out Of Memory"; break;
|
|
default: sErr = "Unknown"; break;
|
|
}
|
|
BOOST_LOG_TRIVIAL(error) << "OpenGL error in " << file_name << ":" << line << ", function " << function_name << "() : " << (int)err << " - " << sErr;
|
|
assert(false);
|
|
}
|
|
#endif // HAS_GLSAFE
|
|
|
|
float FullyTransparentMaterialThreshold = 0.1f;
|
|
float FullTransparentModdifiedToFixAlpha = 0.3f;
|
|
// Be careful changing this value because it could break thumbnail color due to rounding error!
|
|
// The color rendering on BambuLab's "send to printer" screen relies on the assumption that this color can be accurately rendered by OpenGL,
|
|
// value like 0.18f could not because in C++ (int)(0.18f * 255) == 45 however in OpenGL it renders this as 46
|
|
// which breaks the `SelectMachineDialog::record_edge_pixels_data()` function!
|
|
float FULL_BLACK_THRESHOLD = 0.2f;
|
|
// Keep depth_tex away from texture unit 0 to avoid sampler-type aliasing with
|
|
// shadow/environment samplers when realistic view is disabled.
|
|
static constexpr int OUTLINE_DEPTH_TEX_UNIT = 5;
|
|
|
|
Slic3r::ColorRGBA adjust_color_for_rendering(const Slic3r::ColorRGBA &colors)
|
|
{
|
|
if (colors.a() < FullyTransparentMaterialThreshold) { // completely transparent
|
|
return {1, 1, 1, FullTransparentModdifiedToFixAlpha};
|
|
}
|
|
else if(colors.r() < FULL_BLACK_THRESHOLD && colors.g() < FULL_BLACK_THRESHOLD && colors.b() < FULL_BLACK_THRESHOLD) { // black
|
|
return {FULL_BLACK_THRESHOLD, FULL_BLACK_THRESHOLD, FULL_BLACK_THRESHOLD, colors.a()};
|
|
}
|
|
else
|
|
return colors;
|
|
}
|
|
|
|
namespace Slic3r {
|
|
|
|
|
|
const float GLVolume::SinkingContours::HalfWidth = 0.25f;
|
|
|
|
void GLVolume::SinkingContours::render()
|
|
{
|
|
update();
|
|
|
|
GLShaderProgram* shader = GUI::wxGetApp().get_current_shader();
|
|
if (shader == nullptr)
|
|
return;
|
|
|
|
const GUI::Camera& camera = GUI::wxGetApp().plater()->get_camera();
|
|
shader->set_uniform("view_model_matrix", camera.get_view_matrix() * Geometry::assemble_transform(m_shift));
|
|
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
|
|
m_model.render();
|
|
}
|
|
|
|
void GLVolume::SinkingContours::update()
|
|
{
|
|
const int object_idx = m_parent.object_idx();
|
|
const Model& model = GUI::wxGetApp().plater()->model();
|
|
|
|
if (0 <= object_idx && object_idx < int(model.objects.size()) && m_parent.is_sinking() && !m_parent.is_below_printbed()) {
|
|
const BoundingBoxf3& box = m_parent.transformed_convex_hull_bounding_box();
|
|
if (!m_old_box.size().isApprox(box.size()) || m_old_box.min.z() != box.min.z()) {
|
|
m_old_box = box;
|
|
m_shift = Vec3d::Zero();
|
|
|
|
const TriangleMesh& mesh = model.objects[object_idx]->volumes[m_parent.volume_idx()]->mesh();
|
|
|
|
m_model.reset();
|
|
GUI::GLModel::Geometry init_data;
|
|
init_data.format = { GUI::GLModel::Geometry::EPrimitiveType::Triangles, GUI::GLModel::Geometry::EVertexLayout::P3 };
|
|
init_data.color = ColorRGBA::WHITE();
|
|
unsigned int vertices_counter = 0;
|
|
MeshSlicingParams slicing_params;
|
|
slicing_params.trafo = m_parent.world_matrix();
|
|
const Polygons polygons = union_(slice_mesh(mesh.its, 0.0f, slicing_params));
|
|
for (const ExPolygon& expoly : diff_ex(expand(polygons, float(scale_(HalfWidth))), shrink(polygons, float(scale_(HalfWidth))))) {
|
|
const std::vector<Vec3d> triangulation = triangulate_expolygon_3d(expoly);
|
|
init_data.reserve_vertices(init_data.vertices_count() + triangulation.size());
|
|
init_data.reserve_indices(init_data.indices_count() + triangulation.size());
|
|
for (const Vec3d& v : triangulation) {
|
|
init_data.add_vertex((Vec3f)(v.cast<float>() + 0.015f * Vec3f::UnitZ())); // add a small positive z to avoid z-fighting
|
|
++vertices_counter;
|
|
if (vertices_counter % 3 == 0)
|
|
init_data.add_triangle(vertices_counter - 3, vertices_counter - 2, vertices_counter - 1);
|
|
}
|
|
}
|
|
m_model.init_from(std::move(init_data));
|
|
}
|
|
else
|
|
m_shift = box.center() - m_old_box.center();
|
|
}
|
|
else
|
|
m_model.reset();
|
|
}
|
|
|
|
ColorRGBA GLVolume::DISABLED_COLOR = ColorRGBA::DARK_GRAY();
|
|
ColorRGBA GLVolume::SLA_SUPPORT_COLOR = ColorRGBA::LIGHT_GRAY();
|
|
ColorRGBA GLVolume::SLA_PAD_COLOR = { 0.0f, 0.2f, 0.0f, 1.0f };
|
|
// BBS
|
|
ColorRGBA GLVolume::NEUTRAL_COLOR = { 0.8f, 0.8f, 0.8f, 1.0f };
|
|
ColorRGBA GLVolume::UNPRINTABLE_COLOR = { 0.0f, 0.0f, 0.0f, 0.5f };
|
|
|
|
ColorRGBA GLVolume::MODEL_MIDIFIER_COL = {1.0f, 1.0f, 0.0f, 0.6f};
|
|
ColorRGBA GLVolume::MODEL_NEGTIVE_COL = {0.3f, 0.3f, 0.3f, 0.4f};
|
|
ColorRGBA GLVolume::SUPPORT_ENFORCER_COL = {0.3f, 0.3f, 1.0f, 0.4f};
|
|
ColorRGBA GLVolume::SUPPORT_BLOCKER_COL = {1.0f, 0.3f, 0.3f, 0.4f};
|
|
|
|
ColorRGBA GLVolume::MODEL_HIDDEN_COL = {0.f, 0.f, 0.f, 0.3f};
|
|
|
|
// Precise Seam modifier colors. Center, Left and Right are deliberately close shades of one orange:
|
|
// all three are strong modifiers, and distinct hues per mode would turn the scene into a rainbow.
|
|
// The object list icons tell the modes apart.
|
|
ColorRGBA GLVolume::PRECISE_SEAM_CENTER_COL = {1.0f, 0.627f, 0.082f, 0.6f}; // FFA015 - orange
|
|
ColorRGBA GLVolume::PRECISE_SEAM_LEFT_COL = {1.0f, 0.753f, 0.0f, 0.6f}; // FFC000 - golden
|
|
ColorRGBA GLVolume::PRECISE_SEAM_RIGHT_COL = {1.0f, 0.514f, 0.0f, 0.6f}; // FF8300 - dark orange
|
|
ColorRGBA GLVolume::PRECISE_SEAM_ENFORCED_COL = {0.412f, 0.820f, 0.412f, 0.6f}; // 69D169 - green
|
|
ColorRGBA GLVolume::PRECISE_SEAM_NEUTRAL_COL = {0.655f, 0.655f, 0.655f, 0.6f}; // A7A7A7 - gray
|
|
ColorRGBA GLVolume::PRECISE_SEAM_BLOCKED_COL = {0.820f, 0.412f, 0.412f, 0.6f}; // D16969 - red
|
|
|
|
std::array<ColorRGBA, 5> GLVolume::MODEL_COLOR = { {
|
|
{ 1.0f, 1.0f, 0.0f, 1.f },
|
|
{ 1.0f, 0.5f, 0.5f, 1.f },
|
|
{ 0.5f, 1.0f, 0.5f, 1.f },
|
|
{ 0.5f, 0.5f, 1.0f, 1.f },
|
|
{ 1.0f, 1.0f, 0.0f, 1.f }
|
|
} };
|
|
|
|
void GLVolume::update_render_colors()
|
|
{
|
|
GLVolume::DISABLED_COLOR = GUI::ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Model_Disable]);
|
|
GLVolume::NEUTRAL_COLOR = GUI::ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Model_Neutral]);
|
|
GLVolume::MODEL_COLOR[0] = GUI::ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Modifier]);
|
|
GLVolume::MODEL_COLOR[1] = GUI::ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Negtive_Volume]);
|
|
GLVolume::MODEL_COLOR[2] = GUI::ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Support_Enforcer]);
|
|
GLVolume::MODEL_COLOR[3] = GUI::ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Support_Blocker]);
|
|
GLVolume::UNPRINTABLE_COLOR = GUI::ImGuiWrapper::from_ImVec4(RenderColor::colors[RenderCol_Model_Unprintable]);
|
|
|
|
}
|
|
|
|
void GLVolume::load_render_colors()
|
|
{
|
|
RenderColor::colors[RenderCol_Model_Disable] = GUI::ImGuiWrapper::to_ImVec4(GLVolume::DISABLED_COLOR);
|
|
RenderColor::colors[RenderCol_Model_Neutral] = GUI::ImGuiWrapper::to_ImVec4(GLVolume::NEUTRAL_COLOR);
|
|
RenderColor::colors[RenderCol_Modifier] = GUI::ImGuiWrapper::to_ImVec4(GLVolume::MODEL_COLOR[0]);
|
|
RenderColor::colors[RenderCol_Negtive_Volume] = GUI::ImGuiWrapper::to_ImVec4(GLVolume::MODEL_COLOR[1]);
|
|
RenderColor::colors[RenderCol_Support_Enforcer] = GUI::ImGuiWrapper::to_ImVec4(GLVolume::MODEL_COLOR[2]);
|
|
RenderColor::colors[RenderCol_Support_Blocker] = GUI::ImGuiWrapper::to_ImVec4(GLVolume::MODEL_COLOR[3]);
|
|
RenderColor::colors[RenderCol_Model_Unprintable] = GUI::ImGuiWrapper::to_ImVec4(GLVolume::UNPRINTABLE_COLOR);
|
|
}
|
|
|
|
ColorRGBA GLVolume::brighten_color(const ColorRGBA& color, float multiplier)
|
|
{
|
|
// Convert RGB to HSL, increase lightness, convert back
|
|
|
|
float r = color.r(), g = color.g(), b = color.b();
|
|
|
|
// RGB to HSL conversion
|
|
float max_val = std::max({r, g, b});
|
|
float min_val = std::min({r, g, b});
|
|
float l = (max_val + min_val) / 2.0f;
|
|
float h = 0.0f, s = 0.0f;
|
|
|
|
if (max_val != min_val) {
|
|
float delta = max_val - min_val;
|
|
s = l > 0.5f ? delta / (2.0f - max_val - min_val) : delta / (max_val + min_val);
|
|
|
|
if (max_val == r)
|
|
h = (g - b) / delta + (g < b ? 6.0f : 0.0f);
|
|
else if (max_val == g)
|
|
h = (b - r) / delta + 2.0f;
|
|
else
|
|
h = (r - g) / delta + 4.0f;
|
|
h /= 6.0f;
|
|
}
|
|
|
|
// Increase lightness by a fixed amount (0.25)
|
|
// Ensures even saturated colors become visibly brighter
|
|
l = std::min(l + 0.25f, 1.0f);
|
|
|
|
// HSL to RGB conversion
|
|
auto hue_to_rgb = [](float p, float q, float t) {
|
|
if (t < 0.0f) t += 1.0f;
|
|
if (t > 1.0f) t -= 1.0f;
|
|
if (t < 1.0f / 6.0f) return p + (q - p) * 6.0f * t;
|
|
if (t < 1.0f / 2.0f) return q;
|
|
if (t < 2.0f / 3.0f) return p + (q - p) * (2.0f / 3.0f - t) * 6.0f;
|
|
return p;
|
|
};
|
|
|
|
if (s == 0.0f) {
|
|
r = g = b = l; // achromatic (gray)
|
|
} else {
|
|
float q = l < 0.5f ? l * (1.0f + s) : l + s - l * s;
|
|
float p = 2.0f * l - q;
|
|
r = hue_to_rgb(p, q, h + 1.0f / 3.0f);
|
|
g = hue_to_rgb(p, q, h);
|
|
b = hue_to_rgb(p, q, h - 1.0f / 3.0f);
|
|
}
|
|
|
|
return ColorRGBA(r, g, b, color.a());
|
|
}
|
|
|
|
GLVolume::GLVolume(float r, float g, float b, float a)
|
|
: m_sla_shift_z(0.0)
|
|
, m_sinking_contours(*this)
|
|
// geometry_id == 0 -> invalid
|
|
, geometry_id(std::pair<size_t, size_t>(0, 0))
|
|
, extruder_id(0)
|
|
, selected(false)
|
|
, disabled(false)
|
|
, printable(true)
|
|
, visible(true)
|
|
, is_active(true)
|
|
, on_hidden_plate(false)
|
|
, zoom_to_volumes(true)
|
|
, shader_outside_printer_detection_enabled(false)
|
|
, is_outside(false)
|
|
, partly_inside(false)
|
|
, hover(HS_None)
|
|
, is_modifier(false)
|
|
, slice_error(false)
|
|
, is_wipe_tower(false)
|
|
, is_extrusion_path(false)
|
|
, force_transparent(false)
|
|
, force_native_color(false)
|
|
, force_neutral_color(false)
|
|
, force_sinking_contours(false)
|
|
, depth_bias(false)
|
|
, picking(false)
|
|
, tverts_range(0, size_t(-1))
|
|
{
|
|
color = { r, g, b, a };
|
|
set_render_color(color);
|
|
mmuseg_ts = 0;
|
|
}
|
|
|
|
|
|
// BBS
|
|
float GLVolume::explosion_ratio = 1.0;
|
|
float GLVolume::last_explosion_ratio = 1.0;
|
|
|
|
void GLVolume::set_render_color()
|
|
{
|
|
bool outside = is_outside || is_below_printbed();
|
|
|
|
if (force_native_color || force_neutral_color) {
|
|
#ifdef ENABBLE_OUTSIDE_COLOR
|
|
if (outside && shader_outside_printer_detection_enabled)
|
|
set_render_color(OUTSIDE_COLOR);
|
|
else {
|
|
#endif
|
|
if (force_native_color)
|
|
set_render_color(color);
|
|
else
|
|
set_render_color(NEUTRAL_COLOR);
|
|
#ifdef ENABLE_OUTSIDE_COLOR
|
|
}
|
|
#endif
|
|
}
|
|
else {
|
|
/* BBS
|
|
if (hover == HS_Select)
|
|
set_render_color(HOVER_SELECT_COLOR);
|
|
else if (hover == HS_Deselect)
|
|
set_render_color(HOVER_DESELECT_COLOR);
|
|
else if (selected)
|
|
set_render_color(outside ? SELECTED_OUTSIDE_COLOR : SELECTED_COLOR);
|
|
else if (disabled)
|
|
*/
|
|
// Determine base color first
|
|
ColorRGBA base_color;
|
|
|
|
if (disabled) {
|
|
base_color = DISABLED_COLOR;
|
|
}
|
|
#ifdef ENABLE_OUTSIDE_COLOR
|
|
else if (is_outside && shader_outside_printer_detection_enabled) {
|
|
base_color = OUTSIDE_COLOR;
|
|
}
|
|
#endif
|
|
else {
|
|
// to make black not too hard too see
|
|
base_color = adjust_color_for_rendering(color);
|
|
}
|
|
|
|
// Apply selection brightening AFTER determining base color
|
|
if (selected && !disabled) {
|
|
set_render_color(brighten_color(base_color, 1.25f));
|
|
}
|
|
else {
|
|
set_render_color(base_color);
|
|
}
|
|
}
|
|
|
|
if (force_transparent) {
|
|
if (color.a() < FullyTransparentMaterialThreshold) {
|
|
render_color.a(FullTransparentModdifiedToFixAlpha);
|
|
} else {
|
|
render_color.a(color.a());
|
|
}
|
|
}
|
|
|
|
//BBS set unprintable color
|
|
if (!printable) {
|
|
if (selected) {
|
|
render_color = brighten_color(UNPRINTABLE_COLOR, 1.25f);
|
|
} else {
|
|
render_color = UNPRINTABLE_COLOR;
|
|
}
|
|
}
|
|
|
|
//BBS set invisible color
|
|
if (!visible) {
|
|
render_color = MODEL_HIDDEN_COL;
|
|
}
|
|
}
|
|
|
|
ColorRGBA color_from_model_volume(const ModelVolume& model_volume)
|
|
{
|
|
ColorRGBA color;
|
|
if (model_volume.is_negative_volume())
|
|
return GLVolume::MODEL_NEGTIVE_COL;
|
|
else if (model_volume.is_precise_seam()) {
|
|
// Return color based on Precise Seam subtype.
|
|
// Exhaustive switch (no default) so -Wswitch flags any future PRECISE_SEAM_* additions.
|
|
switch (model_volume.type()) {
|
|
case ModelVolumeType::PRECISE_SEAM_CENTER: return GLVolume::PRECISE_SEAM_CENTER_COL;
|
|
case ModelVolumeType::PRECISE_SEAM_LEFT: return GLVolume::PRECISE_SEAM_LEFT_COL;
|
|
case ModelVolumeType::PRECISE_SEAM_RIGHT: return GLVolume::PRECISE_SEAM_RIGHT_COL;
|
|
case ModelVolumeType::PRECISE_SEAM_ENFORCED: return GLVolume::PRECISE_SEAM_ENFORCED_COL;
|
|
case ModelVolumeType::PRECISE_SEAM_NEUTRAL: return GLVolume::PRECISE_SEAM_NEUTRAL_COL;
|
|
case ModelVolumeType::PRECISE_SEAM_BLOCKED: return GLVolume::PRECISE_SEAM_BLOCKED_COL;
|
|
// Non-seam types are unreachable due to the outer is_precise_seam() guard;
|
|
// listed explicitly so this switch stays exhaustive over ModelVolumeType.
|
|
case ModelVolumeType::INVALID:
|
|
case ModelVolumeType::MODEL_PART:
|
|
case ModelVolumeType::NEGATIVE_VOLUME:
|
|
case ModelVolumeType::PARAMETER_MODIFIER:
|
|
case ModelVolumeType::SUPPORT_BLOCKER:
|
|
case ModelVolumeType::SUPPORT_ENFORCER:
|
|
break;
|
|
}
|
|
return GLVolume::MODEL_MIDIFIER_COL; // unreachable fallback
|
|
}
|
|
else if (model_volume.is_modifier())
|
|
#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
|
|
return GLVolume::MODEL_MIDIFIER_COL;
|
|
#else
|
|
color = { 0.2f, 1.0f, 0.2f, 1.0f };
|
|
#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
|
|
else if (model_volume.is_support_blocker())
|
|
return GLVolume::SUPPORT_BLOCKER_COL;
|
|
else if (model_volume.is_support_enforcer())
|
|
return GLVolume::SUPPORT_ENFORCER_COL;
|
|
return color;
|
|
}
|
|
|
|
Transform3d GLVolume::world_matrix() const
|
|
{
|
|
Transform3d m = m_instance_transformation.get_matrix() * m_volume_transformation.get_matrix();
|
|
Vec3d ofs2ass = m_offset_to_assembly * (GLVolume::explosion_ratio - 1.0);
|
|
Vec3d volofs2obj = m_volume_transformation.get_offset() * (GLVolume::explosion_ratio - 1.0);
|
|
|
|
m.translation()(2) += m_sla_shift_z;
|
|
m.translate(ofs2ass + volofs2obj);
|
|
return m;
|
|
}
|
|
|
|
bool GLVolume::is_left_handed() const
|
|
{
|
|
const Vec3d &m1 = m_instance_transformation.get_mirror();
|
|
const Vec3d &m2 = m_volume_transformation.get_mirror();
|
|
return m1.x() * m1.y() * m1.z() * m2.x() * m2.y() * m2.z() < 0.;
|
|
}
|
|
|
|
const BoundingBoxf3& GLVolume::transformed_bounding_box() const
|
|
{
|
|
if (!m_transformed_bounding_box.has_value() || last_explosion_ratio != explosion_ratio) {
|
|
const BoundingBoxf3& box = bounding_box();
|
|
assert(box.defined || box.min.x() >= box.max.x() || box.min.y() >= box.max.y() || box.min.z() >= box.max.z());
|
|
std::optional<BoundingBoxf3>* trans_box = const_cast<std::optional<BoundingBoxf3>*>(&m_transformed_bounding_box);
|
|
*trans_box = box.transformed(world_matrix());
|
|
last_explosion_ratio = explosion_ratio;
|
|
}
|
|
return *m_transformed_bounding_box;
|
|
}
|
|
|
|
const BoundingBoxf3& GLVolume::transformed_convex_hull_bounding_box() const
|
|
{
|
|
if (!m_transformed_convex_hull_bounding_box.has_value()) {
|
|
std::optional<BoundingBoxf3>* trans_box = const_cast<std::optional<BoundingBoxf3>*>(&m_transformed_convex_hull_bounding_box);
|
|
*trans_box = transformed_convex_hull_bounding_box(world_matrix());
|
|
}
|
|
return *m_transformed_convex_hull_bounding_box;
|
|
}
|
|
|
|
BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d &trafo) const
|
|
{
|
|
return (m_convex_hull && ! m_convex_hull->empty()) ?
|
|
m_convex_hull->transformed_bounding_box(trafo) :
|
|
bounding_box().transformed(trafo);
|
|
}
|
|
|
|
BoundingBoxf3 GLVolume::transformed_non_sinking_bounding_box(const Transform3d& trafo) const
|
|
{
|
|
return GUI::wxGetApp().plater()->model().objects[object_idx()]->volumes[volume_idx()]->mesh().transformed_bounding_box(trafo, 0.0);
|
|
}
|
|
|
|
const BoundingBoxf3& GLVolume::transformed_non_sinking_bounding_box() const
|
|
{
|
|
if (!m_transformed_non_sinking_bounding_box.has_value()) {
|
|
std::optional<BoundingBoxf3>* trans_box = const_cast<std::optional<BoundingBoxf3>*>(&m_transformed_non_sinking_bounding_box);
|
|
const Transform3d& trafo = world_matrix();
|
|
*trans_box = transformed_non_sinking_bounding_box(trafo);
|
|
}
|
|
return *m_transformed_non_sinking_bounding_box;
|
|
}
|
|
|
|
void GLVolume::set_range(double min_z, double max_z)
|
|
{
|
|
this->tverts_range.first = 0;
|
|
this->tverts_range.second = this->model.indices_count();
|
|
|
|
if (!this->print_zs.empty()) {
|
|
// The Z layer range is specified.
|
|
// First test whether the Z span of this object is not out of (min_z, max_z) completely.
|
|
if (this->print_zs.front() > max_z || this->print_zs.back() < min_z)
|
|
this->tverts_range.second = 0;
|
|
else {
|
|
// Then find the lowest layer to be displayed.
|
|
size_t i = 0;
|
|
for (; i < this->print_zs.size() && this->print_zs[i] < min_z; ++i);
|
|
if (i == this->print_zs.size())
|
|
// This shall not happen.
|
|
this->tverts_range.second = 0;
|
|
else {
|
|
// Remember start of the layer.
|
|
this->tverts_range.first = this->offsets[i];
|
|
// Some layers are above $min_z. Which?
|
|
for (; i < this->print_zs.size() && this->print_zs[i] <= max_z; ++i);
|
|
if (i < this->print_zs.size())
|
|
this->tverts_range.second = this->offsets[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GLVolume::render()
|
|
{
|
|
if (!is_active)
|
|
return;
|
|
|
|
GLShaderProgram *shader = GUI::wxGetApp().get_current_shader();
|
|
if (shader == nullptr)
|
|
return;
|
|
|
|
ModelObjectPtrs &model_objects = GUI::wxGetApp().model().objects;
|
|
std::vector<ColorRGBA> colors = GUI::wxGetApp().plater()->get_extruders_colors();
|
|
|
|
simple_render(shader, model_objects, colors);
|
|
}
|
|
|
|
//BBS: add outline related logic
|
|
void GLVolume::render_with_outline(const GUI::Size& cnv_size)
|
|
{
|
|
if (!is_active)
|
|
return;
|
|
|
|
GLShaderProgram *shader = GUI::wxGetApp().get_current_shader();
|
|
if (shader == nullptr)
|
|
return;
|
|
|
|
ModelObjectPtrs &model_objects = GUI::wxGetApp().model().objects;
|
|
std::vector<ColorRGBA> colors = GUI::wxGetApp().plater()->get_extruders_colors();
|
|
|
|
const GUI::OpenGLManager::EFramebufferType framebuffers_type = GUI::OpenGLManager::get_framebuffers_type();
|
|
if (framebuffers_type == GUI::OpenGLManager::EFramebufferType::Unknown) {
|
|
// No supported, degrade to normal rendering
|
|
simple_render(shader, model_objects, colors);
|
|
return;
|
|
}
|
|
// 0th. render pass, render the model using stencil buffer
|
|
glsafe(::glEnable(GL_STENCIL_TEST));
|
|
glsafe(::glStencilMask(0xFF));
|
|
glsafe(::glStencilOp(GL_KEEP, GL_REPLACE, GL_REPLACE));
|
|
glsafe(::glClearStencil(0));
|
|
glsafe(::glClear(GL_STENCIL_BUFFER_BIT));
|
|
glsafe(::glStencilFunc(GL_ALWAYS, 0xFF, 0xFF));
|
|
// This pass paints the visible surface, so it must go through simple_render() to keep
|
|
// per-triangle MMU paint colors; the later is_outline passes only draw the flat silhouette
|
|
// highlight and are fine using the single-color model.
|
|
simple_render(shader, model_objects, colors);
|
|
glsafe(::glStencilFunc(GL_NOTEQUAL, 0xFF, 0xFF));
|
|
glsafe(::glStencilMask(0x00));
|
|
shader->set_uniform("is_outline", true);
|
|
shader->set_uniform("screen_size", Vec2f{cnv_size.get_width(), cnv_size.get_height()});
|
|
if (tverts_range == std::make_pair<size_t, size_t>(0, -1))
|
|
model.render(shader);
|
|
else
|
|
model.render(this->tverts_range, shader);
|
|
shader->set_uniform("is_outline", false);
|
|
glsafe(::glStencilMask(0xFF));
|
|
glsafe(::glDisable(GL_STENCIL_TEST));
|
|
// render the outline using depth buffer and discard the pixels that are not on the outline
|
|
// The silhouette is resolved per sample in the shader (see DetectSilho in gouraud.fs/phong.fs).
|
|
// That needs the GL 3.2 entry points and a shader that declares depth_tex as sampler2DMS, which
|
|
// only the 140 ones do and only under GL_ARB_texture_multisample - so ask the compiled program
|
|
// rather than the GL version, or a sampler2D ends up bound to a multisample texture.
|
|
// Only the Arb branch below allocates a multisample texture, so keep the target consistent with it.
|
|
const bool use_msaa_outline = framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb &&
|
|
GUI::wxGetApp().is_gl_version_greater_or_equal_to(3, 2) &&
|
|
shader->get_uniform_location("msaa_samples") >= 0;
|
|
const GLenum depth_tex_target = use_msaa_outline ? GL_TEXTURE_2D_MULTISAMPLE : GL_TEXTURE_2D;
|
|
// Keep the depth texture off image unit 0. The object shaders leave shadow_map (and
|
|
// environment_tex) at the default sampler value 0 whenever the shadow pass is skipped - which is
|
|
// the case with realistic view off - and GL forbids two sampler types referring to the same image
|
|
// unit. A sampler2DMS on unit 0 then makes every draw fail with INVALID_OPERATION on drivers that
|
|
// enforce it (Mesa), i.e. the model disappears entirely. Unit 5 is unused (shadow_map takes 4).
|
|
const int depth_tex_unit = OUTLINE_DEPTH_TEX_UNIT;
|
|
int aa_samples = 1;
|
|
if (use_msaa_outline) {
|
|
if (const AppConfig* app_config = GUI::wxGetApp().app_config; app_config != nullptr) {
|
|
const std::string value = app_config->get(SETTING_OPENGL_AA_SAMPLES);
|
|
if (value == "2" || value == "4" || value == "8" || value == "16")
|
|
aa_samples = ::atoi(value.c_str());
|
|
}
|
|
// Never request more samples than the driver supports for depth textures (a 1-sample texture
|
|
// is used when MSAA is disabled, keeping a single code path for the sampler2DMS shader).
|
|
GLint max_samples = 1;
|
|
glsafe(::glGetIntegerv(GL_MAX_DEPTH_TEXTURE_SAMPLES, &max_samples));
|
|
if (aa_samples > max_samples)
|
|
aa_samples = max_samples < 1 ? 1 : max_samples;
|
|
if (aa_samples < 1)
|
|
aa_samples = 1;
|
|
}
|
|
// 1st. render pass, render the model into a separate render target that has only depth buffer
|
|
GLuint depth_fbo = 0;
|
|
GLuint depth_tex = 0;
|
|
if (framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb) {
|
|
glsafe(::glGenFramebuffers(1, &depth_fbo));
|
|
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, depth_fbo));
|
|
|
|
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
|
glsafe(::glGenTextures(1, &depth_tex));
|
|
glsafe(::glBindTexture(depth_tex_target, depth_tex));
|
|
if (use_msaa_outline) {
|
|
// Multisample textures do not take filter/wrap parameters.
|
|
glsafe(::glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, aa_samples, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), GL_TRUE));
|
|
} else {
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
|
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));
|
|
}
|
|
|
|
glsafe(::glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depth_tex_target, depth_tex, 0));
|
|
} else {
|
|
glsafe(::glGenFramebuffersEXT(1, &depth_fbo));
|
|
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, depth_fbo));
|
|
|
|
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
|
glsafe(::glGenTextures(1, &depth_tex));
|
|
glsafe(::glBindTexture(GL_TEXTURE_2D, depth_tex));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
|
|
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
|
|
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, cnv_size.get_width(), cnv_size.get_height(), 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr));
|
|
|
|
glsafe(::glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depth_tex, 0));
|
|
}
|
|
// Unbind before drawing: the texture is this framebuffer's depth attachment, so leaving it bound
|
|
// to a sampled unit would be a feedback loop.
|
|
glsafe(::glBindTexture(depth_tex_target, 0));
|
|
glsafe(::glActiveTexture(GL_TEXTURE0));
|
|
glsafe(::glClear(GL_DEPTH_BUFFER_BIT));
|
|
if (tverts_range == std::make_pair<size_t, size_t>(0, -1))
|
|
model.render(shader);
|
|
else
|
|
model.render(this->tverts_range, shader);
|
|
|
|
// 2nd. render pass, just a normal render with the depth buffer passed as a texture
|
|
if (framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb) {
|
|
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0));
|
|
} else if (framebuffers_type == GUI::OpenGLManager::EFramebufferType::Ext) {
|
|
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0));
|
|
}
|
|
shader->set_uniform("is_outline", true);
|
|
shader->set_uniform("screen_size", Vec2f{cnv_size.get_width(), cnv_size.get_height()});
|
|
shader->set_uniform("msaa_samples", aa_samples);
|
|
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
|
glsafe(::glBindTexture(depth_tex_target, depth_tex));
|
|
glsafe(::glActiveTexture(GL_TEXTURE0));
|
|
shader->set_uniform("depth_tex", depth_tex_unit);
|
|
simple_render(shader, model_objects, colors);
|
|
|
|
// Some clean up to do
|
|
glsafe(::glActiveTexture(GL_TEXTURE0 + depth_tex_unit));
|
|
glsafe(::glBindTexture(depth_tex_target, 0));
|
|
glsafe(::glActiveTexture(GL_TEXTURE0));
|
|
shader->set_uniform("is_outline", false);
|
|
if (framebuffers_type == GUI::OpenGLManager::EFramebufferType::Arb) {
|
|
glsafe(::glBindFramebuffer(GL_FRAMEBUFFER, 0));
|
|
if (depth_fbo != 0)
|
|
glsafe(::glDeleteFramebuffers(1, &depth_fbo));
|
|
} else if (framebuffers_type == GUI::OpenGLManager::EFramebufferType::Ext) {
|
|
glsafe(::glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0));
|
|
if (depth_fbo != 0)
|
|
glsafe(::glDeleteFramebuffersEXT(1, &depth_fbo));
|
|
}
|
|
if (depth_tex != 0)
|
|
glsafe(::glDeleteTextures(1, &depth_tex));
|
|
}
|
|
|
|
//BBS add render for simple case
|
|
void GLVolume::simple_render(GLShaderProgram* shader, ModelObjectPtrs& model_objects, std::vector<ColorRGBA>& extruder_colors, bool ban_light)
|
|
{
|
|
if (this->is_left_handed())
|
|
glFrontFace(GL_CW);
|
|
glsafe(::glCullFace(GL_BACK));
|
|
|
|
bool color_volume = false;
|
|
ModelObject* model_object = nullptr;
|
|
ModelVolume* model_volume = nullptr;
|
|
do {
|
|
if ((!printable) || object_idx() >= model_objects.size())
|
|
break;
|
|
model_object = model_objects[object_idx()];
|
|
|
|
if (volume_idx() >= model_object->volumes.size())
|
|
break;
|
|
model_volume = model_object->volumes[volume_idx()];
|
|
if (model_volume->mmu_segmentation_facets.empty())
|
|
break;
|
|
|
|
color_volume = true;
|
|
if (model_volume->mmu_segmentation_facets.timestamp() != mmuseg_ts) {
|
|
mmuseg_models.clear();
|
|
std::vector<indexed_triangle_set> its_per_color;
|
|
model_volume->mmu_segmentation_facets.get_facets(*model_volume, its_per_color);
|
|
mmuseg_models.resize(its_per_color.size());
|
|
for (int idx = 0; idx < its_per_color.size(); idx++) {
|
|
mmuseg_models[idx].init_from(its_per_color[idx]);
|
|
}
|
|
|
|
mmuseg_ts = model_volume->mmu_segmentation_facets.timestamp();
|
|
}
|
|
} while (0);
|
|
|
|
if (color_volume && !picking) {
|
|
const bool brighten_selected = selected && !disabled && !force_native_color && !force_neutral_color;
|
|
|
|
// when force_transparent, we need to keep the alpha
|
|
if (force_native_color && render_color.is_transparent()) {
|
|
for (auto &extruder_color : extruder_colors)
|
|
extruder_color.a(render_color.a());
|
|
}
|
|
|
|
for (int idx = 0; idx < mmuseg_models.size(); idx++) {
|
|
GUI::GLModel &m = mmuseg_models[idx];
|
|
if (!m.is_initialized())
|
|
continue;
|
|
|
|
if (shader) {
|
|
if (idx == 0) {
|
|
int extruder_id = model_volume->extruder_id();
|
|
// ORCA: extruder_id may be 0 (unset) or point past the colour list after a
|
|
// filament is deleted/remapped, so clamp the index instead of reading out of
|
|
// bounds.
|
|
if (!extruder_colors.empty()) {
|
|
int color_idx = std::clamp(extruder_id - 1, 0, int(extruder_colors.size()) - 1);
|
|
//to make black not too hard too see
|
|
ColorRGBA new_color = adjust_color_for_rendering(extruder_colors[color_idx]);
|
|
if (brighten_selected)
|
|
new_color = brighten_color(new_color, 1.25f);
|
|
if (ban_light) {
|
|
new_color[3] = (255 - color_idx)/255.0f;
|
|
}
|
|
m.set_color(new_color);
|
|
// shader->set_uniform("uniform_color", new_color);
|
|
}
|
|
}
|
|
else {
|
|
if (idx <= extruder_colors.size()) {
|
|
//to make black not too hard too see
|
|
ColorRGBA new_color = adjust_color_for_rendering(extruder_colors[idx - 1]);
|
|
if (brighten_selected)
|
|
new_color = brighten_color(new_color, 1.25f);
|
|
if (ban_light) {
|
|
new_color[3] = (255 - (idx - 1))/255.0f;
|
|
}
|
|
m.set_color(new_color);
|
|
// shader->set_uniform("uniform_color", new_color);
|
|
}
|
|
else {
|
|
//to make black not too hard too see
|
|
ColorRGBA new_color = adjust_color_for_rendering(extruder_colors[0]);
|
|
if (brighten_selected)
|
|
new_color = brighten_color(new_color, 1.25f);
|
|
if (ban_light) {
|
|
new_color[3] = (255 - 0) / 255.0f;
|
|
}
|
|
m.set_color(new_color);
|
|
// shader->set_uniform("uniform_color", new_color);
|
|
}
|
|
}
|
|
}
|
|
if (tverts_range == std::make_pair<size_t, size_t>(0, -1))
|
|
m.render(shader);
|
|
else
|
|
m.render(this->tverts_range, shader);
|
|
}
|
|
} else {
|
|
if (tverts_range == std::make_pair<size_t, size_t>(0, -1))
|
|
model.render(shader);
|
|
else
|
|
model.render(this->tverts_range, shader);
|
|
}
|
|
if (this->is_left_handed())
|
|
glFrontFace(GL_CCW);
|
|
}
|
|
|
|
bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == -int(slaposSupportTree); }
|
|
bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposPad); }
|
|
|
|
bool GLVolume::is_sinking() const
|
|
{
|
|
if (is_modifier || GUI::wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA)
|
|
return false;
|
|
const BoundingBoxf3& box = transformed_convex_hull_bounding_box();
|
|
return box.min.z() < SINKING_Z_THRESHOLD && box.max.z() >= SINKING_Z_THRESHOLD;
|
|
}
|
|
|
|
bool GLVolume::is_below_printbed() const
|
|
{
|
|
return transformed_convex_hull_bounding_box().max.z() < 0.0;
|
|
}
|
|
|
|
void GLVolume::render_sinking_contours()
|
|
{
|
|
if (!is_active)
|
|
return;
|
|
m_sinking_contours.render();
|
|
}
|
|
|
|
GLWipeTowerVolume::GLWipeTowerVolume(const std::vector<ColorRGBA>& colors)
|
|
: GLVolume()
|
|
{
|
|
m_colors = colors;
|
|
}
|
|
|
|
void GLWipeTowerVolume::render()
|
|
{
|
|
if (!is_active)
|
|
return;
|
|
|
|
if (m_colors.size() == 0 || m_colors.size() != model_per_colors.size())
|
|
return;
|
|
|
|
if (this->is_left_handed())
|
|
glFrontFace(GL_CW);
|
|
glsafe(::glCullFace(GL_BACK));
|
|
|
|
for (int i = 0; i < m_colors.size(); i++) {
|
|
if (!picking) {
|
|
ColorRGBA new_color = adjust_color_for_rendering(m_colors[i]);
|
|
this->model_per_colors[i].set_color(new_color);
|
|
} else {
|
|
this->model_per_colors[i].set_color(model.get_color());
|
|
}
|
|
this->model_per_colors[i].render();
|
|
}
|
|
|
|
if (this->is_left_handed())
|
|
glFrontFace(GL_CCW);
|
|
}
|
|
|
|
bool GLWipeTowerVolume::IsTransparent() {
|
|
for (size_t i = 0; i < m_colors.size(); i++) {
|
|
if (m_colors[i].is_transparent()) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::vector<int> GLVolumeCollection::load_object(
|
|
const ModelObject *model_object,
|
|
int obj_idx,
|
|
const std::vector<int> &instance_idxs,
|
|
const std::string &color_by,
|
|
bool opengl_initialized,
|
|
bool need_raycaster)
|
|
{
|
|
std::vector<int> volumes_idx;
|
|
for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++volume_idx)
|
|
for (int instance_idx : instance_idxs)
|
|
volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, obj_idx, volume_idx, instance_idx, color_by, opengl_initialized, false, false, need_raycaster));
|
|
return volumes_idx;
|
|
}
|
|
|
|
|
|
int GLVolumeCollection::load_object_volume(
|
|
const ModelObject *model_object,
|
|
int obj_idx,
|
|
int volume_idx,
|
|
int instance_idx,
|
|
const std::string &color_by,
|
|
bool opengl_initialized,
|
|
bool in_assemble_view,
|
|
bool use_loaded_id,
|
|
bool need_raycaster)
|
|
{
|
|
const ModelVolume *model_volume = model_object->volumes[volume_idx];
|
|
const int extruder_id = model_volume->extruder_id();
|
|
const ModelInstance *instance = model_object->instances[instance_idx];
|
|
auto color = GLVolume::MODEL_COLOR[((color_by == "volume") ? volume_idx : obj_idx) % 4];
|
|
color.a(model_volume->is_model_part() ? 0.7f : 0.4f);
|
|
|
|
std::shared_ptr<const TriangleMesh> mesh = model_volume->mesh_ptr();
|
|
this->volumes.emplace_back(new GLVolume(color));
|
|
GLVolume& v = *this->volumes.back();
|
|
v.set_color(color_from_model_volume(*model_volume));
|
|
v.name = model_volume->name;
|
|
|
|
v.model.init_from(*mesh);
|
|
if (need_raycaster) { v.mesh_raycaster = std::make_unique<GUI::MeshRaycaster>(mesh); }
|
|
v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx);
|
|
|
|
if (model_volume->is_model_part())
|
|
{
|
|
// GLVolume will reference a convex hull from model_volume!
|
|
v.set_convex_hull(model_volume->get_convex_hull_shared_ptr());
|
|
if (extruder_id != -1)
|
|
v.extruder_id = extruder_id;
|
|
}
|
|
v.is_modifier = !model_volume->is_model_part();
|
|
v.shader_outside_printer_detection_enabled = model_volume->is_model_part();
|
|
if (in_assemble_view) {
|
|
v.set_instance_transformation(instance->get_assemble_transformation());
|
|
v.set_offset_to_assembly(instance->get_offset_to_assembly());
|
|
}
|
|
else
|
|
v.set_instance_transformation(instance->get_transformation());
|
|
v.set_volume_transformation(model_volume->get_transformation());
|
|
//use object's instance id
|
|
if (use_loaded_id && (instance->loaded_id > 0))
|
|
v.model_object_ID = instance->loaded_id;
|
|
else
|
|
v.model_object_ID = instance->id().id;
|
|
|
|
return int(this->volumes.size() - 1);
|
|
}
|
|
|
|
// Load SLA auxiliary GLVolumes (for support trees or pad).
|
|
// This function produces volumes for multiple instances in a single shot,
|
|
// as some object specific mesh conversions may be expensive.
|
|
void GLVolumeCollection::load_object_auxiliary(
|
|
const SLAPrintObject* print_object,
|
|
int obj_idx,
|
|
// pairs of <instance_idx, print_instance_idx>
|
|
const std::vector<std::pair<size_t, size_t>>& instances,
|
|
SLAPrintObjectStep milestone,
|
|
// Timestamp of the last change of the milestone
|
|
size_t timestamp)
|
|
{
|
|
assert(print_object->is_step_done(milestone));
|
|
Transform3d mesh_trafo_inv = print_object->trafo().inverse();
|
|
// Get the support mesh.
|
|
TriangleMesh mesh = print_object->get_mesh(milestone);
|
|
mesh.transform(mesh_trafo_inv);
|
|
// Convex hull is required for out of print bed detection.
|
|
TriangleMesh convex_hull = mesh.convex_hull_3d();
|
|
for (const std::pair<size_t, size_t>& instance_idx : instances) {
|
|
const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first];
|
|
this->volumes.emplace_back(new GLVolume((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR));
|
|
GLVolume& v = *this->volumes.back();
|
|
v.model.init_from(mesh);
|
|
v.model.set_color((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR);
|
|
v.mesh_raycaster = std::make_unique<GUI::MeshRaycaster>(std::make_shared<const TriangleMesh>(mesh));
|
|
v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first);
|
|
v.geometry_id = std::pair<size_t, size_t>(timestamp, model_instance.id().id);
|
|
// Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance.
|
|
if (&instance_idx == &instances.back())
|
|
v.set_convex_hull(std::move(convex_hull));
|
|
else
|
|
v.set_convex_hull(convex_hull);
|
|
v.is_modifier = false;
|
|
v.shader_outside_printer_detection_enabled = (milestone == slaposSupportTree);
|
|
v.set_instance_transformation(model_instance.get_transformation());
|
|
// Leave the volume transformation at identity.
|
|
// v.set_volume_transformation(model_volume->get_transformation());
|
|
}
|
|
}
|
|
|
|
int GLVolumeCollection::load_wipe_tower_preview(
|
|
int obj_idx, float pos_x, float pos_y, float width, float depth, float height,
|
|
float rotation_angle, bool size_unknown, float brim_width)
|
|
{
|
|
int plate_idx = obj_idx - 1000;
|
|
|
|
if (depth < 0.01f)
|
|
return int(this->volumes.size() - 1);
|
|
if (height == 0.0f)
|
|
height = 0.1f;
|
|
|
|
std::vector<ColorRGBA> extruder_colors = GUI::wxGetApp().plater()->get_extruders_colors();
|
|
std::vector<ColorRGBA> colors;
|
|
GUI::PartPlateList& ppl = GUI::wxGetApp().plater()->get_partplate_list();
|
|
std::vector<int> plate_extruders = ppl.get_plate(plate_idx)->get_extruders(true);
|
|
TriangleMesh wipe_tower_shell = make_cube(width, depth, height);
|
|
// The brim is part of the printed footprint: draw it and fold it into the shell so the
|
|
// outside-bed shader and the drag clamp react to the true first-layer extent.
|
|
const bool show_brim = brim_width > 0.f;
|
|
const float brim_height = 0.2f; // one first layer, visual only
|
|
TriangleMesh brim_slab;
|
|
if (show_brim) {
|
|
// The brim follows the real first-layer outline: a Type2 cone-wall tower's base bulges
|
|
// past the body box. The wall type and angle are print settings, the planner a printer one.
|
|
const DynamicPrintConfig &print_cfg = GUI::wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
|
const DynamicPrintConfig &printer_cfg = GUI::wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
|
const Polygon outline = estimate_wipe_tower_first_layer_outline(print_cfg, resolve_wipe_tower_type(printer_cfg), width, depth, height);
|
|
const Polygons brim_outline = offset(outline, scaled(brim_width));
|
|
brim_slab = WipeTower::its_make_rib_brim(brim_outline.empty() ? outline : brim_outline.front(), brim_height);
|
|
wipe_tower_shell.merge(brim_slab);
|
|
}
|
|
for (int extruder_id : plate_extruders) {
|
|
if (extruder_id <= extruder_colors.size())
|
|
colors.push_back(extruder_colors[extruder_id - 1]);
|
|
else
|
|
colors.push_back(extruder_colors[0]);
|
|
}
|
|
|
|
// Orca: make it transparent
|
|
for(auto& color : colors)
|
|
color.a(0.66f);
|
|
const size_t slab_count = colors.size(); // per-filament body slabs; the brim part comes after
|
|
if (show_brim && !colors.empty())
|
|
colors.push_back(colors.front());
|
|
volumes.emplace_back(new GLWipeTowerVolume(colors));
|
|
GLWipeTowerVolume& v = *dynamic_cast<GLWipeTowerVolume*>(volumes.back());
|
|
v.model_per_colors.resize(colors.size());
|
|
for (size_t i = 0; i < slab_count; i++) {
|
|
TriangleMesh color_part = make_cube(width, depth / slab_count, height);
|
|
color_part.translate({ 0.f, depth * i / slab_count, 0. });
|
|
v.model_per_colors[i].init_from(color_part);
|
|
}
|
|
if (show_brim && !colors.empty())
|
|
v.model_per_colors[slab_count].init_from(brim_slab);
|
|
v.model.init_from(wipe_tower_shell);
|
|
v.mesh_raycaster = std::make_unique<GUI::MeshRaycaster>(std::make_shared<const TriangleMesh>(wipe_tower_shell));
|
|
v.set_convex_hull(wipe_tower_shell);
|
|
v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0));
|
|
v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle));
|
|
v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0);
|
|
v.geometry_id.first = 0;
|
|
v.geometry_id.second = wipe_tower_instance_id().id + (obj_idx - 1000);
|
|
v.is_wipe_tower = true;
|
|
v.shader_outside_printer_detection_enabled = !size_unknown;
|
|
return int(volumes.size() - 1);
|
|
}
|
|
|
|
int GLVolumeCollection::load_real_wipe_tower_preview(
|
|
int obj_idx, float pos_x, float pos_y, const TriangleMesh& wt_mesh,const TriangleMesh &brim_mesh,bool render_brim, float rotation_angle, bool size_unknown, bool opengl_initialized)
|
|
{
|
|
int plate_idx = obj_idx - 1000;
|
|
if (wt_mesh.its.vertices.empty()) return int(this->volumes.size() - 1);
|
|
|
|
std::vector<Slic3r::ColorRGBA> extruder_colors = GUI::wxGetApp().plater()->get_extruders_colors();
|
|
GUI::PartPlateList &ppl = GUI::wxGetApp().plater()->get_partplate_list();
|
|
std::vector<int> plate_extruders = ppl.get_plate(plate_idx)->get_extruders(true);
|
|
std::vector<Slic3r::ColorRGBA> colors;
|
|
if (!plate_extruders.empty()) {
|
|
if (plate_extruders.front() <= extruder_colors.size())
|
|
colors.push_back(extruder_colors[plate_extruders.front() - 1]);
|
|
else
|
|
colors.push_back(extruder_colors[0]);
|
|
}
|
|
if (colors.empty()) return int(this->volumes.size() - 1);
|
|
volumes.emplace_back(new GLWipeTowerVolume({colors}));
|
|
GLWipeTowerVolume &v = *dynamic_cast<GLWipeTowerVolume *>(volumes.back());
|
|
auto mesh = wt_mesh;
|
|
if (render_brim) {
|
|
mesh.merge(brim_mesh);
|
|
}
|
|
if (!colors.empty()) {
|
|
v.model_per_colors.resize(1);
|
|
v.model_per_colors[0].init_from(mesh);
|
|
}
|
|
TriangleMesh wipe_tower_shell = mesh.convex_hull_3d();
|
|
v.model.init_from(wipe_tower_shell);
|
|
v.mesh_raycaster = std::make_unique<GUI::MeshRaycaster>(std::make_shared<const TriangleMesh>(wipe_tower_shell));
|
|
v.set_convex_hull(wipe_tower_shell);
|
|
v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0));
|
|
v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle));
|
|
v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0);
|
|
v.geometry_id.first = 0;
|
|
v.geometry_id.second = wipe_tower_instance_id().id + (obj_idx - 1000);
|
|
v.is_wipe_tower = true;
|
|
v.shader_outside_printer_detection_enabled = !size_unknown;
|
|
return int(volumes.size() - 1);
|
|
}
|
|
|
|
|
|
GLVolume* GLVolumeCollection::new_toolpath_volume(const ColorRGBA& rgba)
|
|
{
|
|
GLVolume* out = new_nontoolpath_volume(rgba);
|
|
out->is_extrusion_path = true;
|
|
return out;
|
|
}
|
|
|
|
GLVolume* GLVolumeCollection::new_nontoolpath_volume(const ColorRGBA& rgba)
|
|
{
|
|
GLVolume* out = new GLVolume(rgba);
|
|
out->is_extrusion_path = false;
|
|
this->volumes.emplace_back(out);
|
|
return out;
|
|
}
|
|
|
|
GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCollection::ERenderType type, const Transform3d& view_matrix, std::function<bool(const GLVolume&)> filter_func)
|
|
{
|
|
GLVolumeWithIdAndZList list;
|
|
list.reserve(volumes.size());
|
|
|
|
for (unsigned int i = 0; i < (unsigned int)volumes.size(); ++i) {
|
|
GLVolume* volume = volumes[i];
|
|
bool is_transparent = volume->render_color.is_transparent();
|
|
auto tempGlwipeTowerVolume = dynamic_cast<GLWipeTowerVolume *>(volume);
|
|
if (tempGlwipeTowerVolume) {
|
|
is_transparent = tempGlwipeTowerVolume->IsTransparent();
|
|
}
|
|
if (((type == GLVolumeCollection::ERenderType::Opaque && !is_transparent) ||
|
|
(type == GLVolumeCollection::ERenderType::Transparent && is_transparent) ||
|
|
type == GLVolumeCollection::ERenderType::All) &&
|
|
(! filter_func || filter_func(*volume)))
|
|
list.emplace_back(std::make_pair(volume, std::make_pair(i, 0.0)));
|
|
}
|
|
|
|
if (type == GLVolumeCollection::ERenderType::Transparent && list.size() > 1) {
|
|
for (GLVolumeWithIdAndZ& volume : list) {
|
|
volume.second.second = volume.first->bounding_box().transformed(view_matrix * volume.first->world_matrix()).max(2);
|
|
}
|
|
|
|
std::sort(list.begin(), list.end(),
|
|
[](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool { return v1.second.second < v2.second.second; }
|
|
);
|
|
}
|
|
else if (type == GLVolumeCollection::ERenderType::Opaque && list.size() > 1) {
|
|
// Orca: nearest first after the selected ones, so the depth test skips shading hidden surfaces.
|
|
for (GLVolumeWithIdAndZ& volume : list) {
|
|
volume.second.second = volume.first->transformed_bounding_box().transformed(view_matrix).max(2);
|
|
}
|
|
|
|
std::sort(list.begin(), list.end(),
|
|
[](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool {
|
|
if (v1.first->selected != v2.first->selected)
|
|
return v1.first->selected;
|
|
return v1.second.second > v2.second.second;
|
|
}
|
|
);
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
// ORCA: Compute slope.normal_z for 3D overhang highlight directly from support settings.
|
|
// If support_threshold_angle is 0, use tree fallback angle (30 deg) for tree supports,
|
|
// and derive an equivalent angle from threshold overlap for normal supports.
|
|
float GLVolumeCollection::get_selection_support_normal_z() const
|
|
{
|
|
const DynamicPrintConfig& glb_cfg = GUI::wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
|
const auto& full_cfg = GUI::wxGetApp().preset_bundle->full_config();
|
|
const auto support_type = glb_cfg.opt_enum<SupportType>("support_type");
|
|
const int support_threshold_angle = glb_cfg.opt_int("support_threshold_angle");
|
|
double angle_rad;
|
|
|
|
if (support_threshold_angle > 0) {
|
|
// Match support generation: explicit threshold angles are treated as inclusive.
|
|
const int effective_support_threshold_angle = std::min(support_threshold_angle + 1, 89);
|
|
angle_rad = Geometry::deg2rad(static_cast<double>(effective_support_threshold_angle));
|
|
} else if (is_tree(support_type)) {
|
|
angle_rad = Geometry::deg2rad(30.0); // fallback value for tree supports
|
|
} else { // For normal supports, if the angle is set to 0, calculate normal_z from overlap.
|
|
const double layer_height = full_cfg.opt_float("layer_height");
|
|
const auto* nozzle_diameter_opt = full_cfg.option<ConfigOptionFloats>("nozzle_diameter");
|
|
const int wall_filament_id = full_cfg.opt_int("outer_wall_filament_id");
|
|
const size_t nozzle_count = nozzle_diameter_opt->values.size();
|
|
const size_t wall_extruder_idx = (wall_filament_id > 0 && wall_filament_id <= static_cast<int>(nozzle_count))
|
|
? static_cast<size_t>(wall_filament_id - 1)
|
|
: 0; // Invalid extruder index falls back to extruder 1.
|
|
|
|
// Use wall extruder's nozzle diameter for better estimation of external perimeter width,
|
|
// which is more relevant to overhang printing than the default nozzle diameter.
|
|
const double nozzle_diameter = nozzle_diameter_opt->values[wall_extruder_idx];
|
|
|
|
double external_perimeter_width = full_cfg.get_abs_value("outer_wall_line_width", nozzle_diameter);
|
|
if (external_perimeter_width <= 0.0) {
|
|
external_perimeter_width = full_cfg.get_abs_value("line_width", nozzle_diameter);
|
|
|
|
if (external_perimeter_width <= 0.0)
|
|
external_perimeter_width = nozzle_diameter;
|
|
}
|
|
|
|
const double overlap_width = full_cfg.get_abs_value("support_threshold_overlap", external_perimeter_width);
|
|
const double lower_layer_offset = std::max(0.0, external_perimeter_width - overlap_width);
|
|
|
|
angle_rad = lower_layer_offset <= EPSILON ? Geometry::deg2rad(89.0) : std::atan(layer_height / lower_layer_offset);
|
|
}
|
|
|
|
return static_cast<float>(-std::cos(std::clamp(angle_rad, 0.0, Geometry::deg2rad(89.0))));
|
|
}
|
|
|
|
//BBS: add outline drawing logic
|
|
void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
|
|
bool disable_cullface,
|
|
const Transform3d & view_matrix,
|
|
const Transform3d& projection_matrix,
|
|
const GUI::Size& cnv_size,
|
|
std::function<bool(const GLVolume &)> filter_func,
|
|
bool partly_inside_enable,
|
|
std::vector<double> * printable_heights) const
|
|
{
|
|
GLVolumeWithIdAndZList to_render = volumes_to_render(volumes, type, view_matrix, filter_func);
|
|
if (to_render.empty())
|
|
return;
|
|
|
|
GLShaderProgram* shader = GUI::wxGetApp().get_current_shader();
|
|
if (shader == nullptr)
|
|
return;
|
|
|
|
GLShaderProgram* sink_shader = GUI::wxGetApp().get_shader("flat");
|
|
#if SLIC3R_OPENGL_ES
|
|
GLShaderProgram* edges_shader = GUI::wxGetApp().get_shader("dashed_lines");
|
|
#else
|
|
GLShaderProgram* edges_shader = GUI::OpenGLManager::get_gl_info().is_core_profile() ? GUI::wxGetApp().get_shader("dashed_thick_lines") : GUI::wxGetApp().get_shader("flat");
|
|
#endif // SLIC3R_OPENGL_ES
|
|
|
|
if (type == ERenderType::Transparent) {
|
|
glsafe(::glEnable(GL_BLEND));
|
|
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
|
|
}
|
|
|
|
glsafe(::glCullFace(GL_BACK));
|
|
if (disable_cullface)
|
|
glsafe(::glDisable(GL_CULL_FACE));
|
|
|
|
const float support_normal_z = get_selection_support_normal_z();
|
|
|
|
// The outline passes below are driven by is_outline, which only the object shaders have; with an
|
|
// overlay one (wireframe, x-ray) bound they would just draw the volume again.
|
|
const bool shader_can_outline = shader->get_uniform_location("is_outline") >= 0;
|
|
|
|
// Prime depth_tex on every frame so non-outline draws do not keep the
|
|
// default sampler unit 0, which can conflict with other sampler types.
|
|
shader->set_uniform("depth_tex", OUTLINE_DEPTH_TEX_UNIT);
|
|
|
|
// Compute up direction accounting for build plate tilt. This is frame-invariant
|
|
// (config cannot change mid-render), so compute it once before the volume loop.
|
|
const Vec3f up_direction = GUI::build_plate_tilt_up_direction().cast<float>();
|
|
|
|
for (GLVolumeWithIdAndZ& volume : to_render) {
|
|
#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
|
|
if (type == ERenderType::Transparent) {
|
|
volume.first->force_transparent = true;
|
|
//BOOST_LOG_TRIVIAL(info) << boost::format("transparent rendering...");
|
|
}
|
|
//else
|
|
// BOOST_LOG_TRIVIAL(info) << boost::format("opaque rendering...");
|
|
#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
|
|
volume.first->set_render_color();
|
|
#if ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
|
|
if (type == ERenderType::Transparent)
|
|
volume.first->force_transparent = false;
|
|
#endif // ENABLE_MODIFIERS_ALWAYS_TRANSPARENT
|
|
|
|
// render sinking contours of non-hovered volumes
|
|
shader->stop_using();
|
|
if (sink_shader != nullptr) {
|
|
sink_shader->start_using();
|
|
if (m_show_sinking_contours) {
|
|
if (volume.first->is_sinking() && !volume.first->is_below_printbed() &&
|
|
volume.first->hover == GLVolume::HS_None && !volume.first->force_sinking_contours) {
|
|
volume.first->render_sinking_contours();
|
|
}
|
|
}
|
|
sink_shader->stop_using();
|
|
}
|
|
shader->start_using();
|
|
|
|
if (!volume.first->model.is_initialized())
|
|
shader->set_uniform("uniform_color", volume.first->render_color);
|
|
shader->set_uniform("z_range", m_z_range);
|
|
shader->set_uniform("clipping_plane", m_clipping_plane);
|
|
shader->set_uniform("use_color_clip_plane", m_use_color_clip_plane);
|
|
shader->set_uniform("color_clip_plane", m_color_clip_plane);
|
|
shader->set_uniform("uniform_color_clip_plane_1", m_color_clip_plane_colors[0]);
|
|
shader->set_uniform("uniform_color_clip_plane_2", m_color_clip_plane_colors[1]);
|
|
//BOOST_LOG_TRIVIAL(info) << boost::format("set uniform_color to {%1%, %2%, %3%, %4%}, with_outline=%5%, selected %6%")
|
|
// %volume.first->render_color[0]%volume.first->render_color[1]%volume.first->render_color[2]%volume.first->render_color[3]
|
|
// %with_outline%volume.first->selected;
|
|
|
|
//BBS set print_volume to render volume
|
|
//shader->set_uniform("print_volume.type", static_cast<int>(m_render_volume.type));
|
|
//shader->set_uniform("print_volume.xy_data", m_render_volume.data);
|
|
//shader->set_uniform("print_volume.z_data", m_render_volume.zs);
|
|
|
|
if (volume.first->partly_inside && partly_inside_enable) {
|
|
//only partly inside volume need to be painted with boundary check
|
|
shader->set_uniform("print_volume.type", static_cast<int>(m_print_volume.type));
|
|
shader->set_uniform("print_volume.xy_data", m_print_volume.data);
|
|
shader->set_uniform("print_volume.z_data", m_print_volume.zs);
|
|
}
|
|
else {
|
|
//use -1 ad a invalid type
|
|
shader->set_uniform("print_volume.type", -1);
|
|
}
|
|
|
|
// Per-extruder printable-height shading. The flag is set to
|
|
// 2.0 only for multi-extruder printers (two per-extruder heights); otherwise it is forced to 0.0
|
|
// on every render so no stale flag survives a multi->single-extruder plate switch, keeping the
|
|
// shared gouraud shader pixel-identical for single-extruder printers. When active the height
|
|
// branch reads print_volume.xy_data (the bed rect), so set it explicitly here.
|
|
std::array<float, 3> extruder_printable_heights = {0.0f, 0.0f, 0.0f};
|
|
if (printable_heights != nullptr && printable_heights->size() > 1) {
|
|
extruder_printable_heights[0] = 2.0f;
|
|
extruder_printable_heights[1] = static_cast<float>((*printable_heights)[0]);
|
|
extruder_printable_heights[2] = static_cast<float>((*printable_heights)[1]);
|
|
shader->set_uniform("extruder_printable_heights", extruder_printable_heights);
|
|
shader->set_uniform("print_volume.xy_data", m_print_volume.data);
|
|
}
|
|
else {
|
|
shader->set_uniform("extruder_printable_heights", extruder_printable_heights);
|
|
}
|
|
|
|
shader->set_uniform("volume_world_matrix", volume.first->world_matrix());
|
|
shader->set_uniform("slope.actived", m_slope.isGlobalActive && !volume.first->is_modifier && !volume.first->is_wipe_tower);
|
|
shader->set_uniform("slope.volume_world_normal_matrix", static_cast<Matrix3f>(volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast<float>()));
|
|
shader->set_uniform("slope.normal_z", support_normal_z);
|
|
shader->set_uniform("slope.up_direction", up_direction);
|
|
|
|
#if ENABLE_ENVIRONMENT_MAP
|
|
unsigned int environment_texture_id = GUI::wxGetApp().plater()->get_environment_texture_id();
|
|
bool use_environment_texture = environment_texture_id > 0 && GUI::wxGetApp().app_config->get("use_environment_map") == "1";
|
|
shader->set_uniform("use_environment_tex", use_environment_texture);
|
|
if (use_environment_texture)
|
|
glsafe(::glBindTexture(GL_TEXTURE_2D, environment_texture_id));
|
|
#endif // ENABLE_ENVIRONMENT_MAP
|
|
glcheck();
|
|
|
|
auto red_color = ColorRGBA{1.0f, 0.0f, 0.0f, 1.0f};//slice_error
|
|
volume.first->model.set_color(volume.first->slice_error ? red_color : volume.first->render_color);
|
|
const Transform3d model_matrix = volume.first->world_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 (volume.first->depth_bias) {
|
|
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
|
|
glsafe(::glPolygonOffset(1.0f, 1.0f));
|
|
}
|
|
//BBS: add outline related logic
|
|
if (volume.first->selected && shader_can_outline && GUI::wxGetApp().show_outline())
|
|
volume.first->render_with_outline(cnv_size);
|
|
else
|
|
volume.first->render();
|
|
if (volume.first->depth_bias)
|
|
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
|
|
|
|
#if ENABLE_ENVIRONMENT_MAP
|
|
if (use_environment_texture)
|
|
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
|
|
#endif // ENABLE_ENVIRONMENT_MAP
|
|
|
|
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
|
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
|
}
|
|
|
|
if (m_show_sinking_contours) {
|
|
shader->stop_using();
|
|
if (sink_shader != nullptr) {
|
|
sink_shader->start_using();
|
|
for (GLVolumeWithIdAndZ& volume : to_render) {
|
|
// render sinking contours of hovered/displaced volumes
|
|
if (volume.first->is_sinking() && !volume.first->is_below_printbed() &&
|
|
(volume.first->hover != GLVolume::HS_None || volume.first->force_sinking_contours)) {
|
|
glsafe(::glDepthFunc(GL_ALWAYS));
|
|
volume.first->render_sinking_contours();
|
|
glsafe(::glDepthFunc(GL_LESS));
|
|
}
|
|
}
|
|
sink_shader->start_using();
|
|
}
|
|
shader->start_using();
|
|
}
|
|
|
|
if (disable_cullface)
|
|
glsafe(::glEnable(GL_CULL_FACE));
|
|
|
|
if (type == ERenderType::Transparent)
|
|
glsafe(::glDisable(GL_BLEND));
|
|
}
|
|
|
|
bool GLVolumeCollection::check_wipe_tower_outside_state(const Slic3r::BuildVolume &build_volume, int plate_id) const
|
|
{
|
|
for (GLVolume *volume : this->volumes) {
|
|
if (volume->is_wipe_tower) {
|
|
int wipe_tower_plate_id = volume->composite_id.object_id - 1000;
|
|
if (wipe_tower_plate_id != plate_id)
|
|
continue;
|
|
const std::vector<Vec2d>& printable_area = build_volume.printable_area();
|
|
Polygon printable_poly = Polygon::new_scale(printable_area);
|
|
|
|
// multi-extruder
|
|
Polygons extruder_polys;
|
|
const std::vector<std::vector<Vec2d>> & extruder_areas = build_volume.extruder_areas();
|
|
if (!extruder_areas.empty()) {
|
|
for (size_t i = 0; i < extruder_areas.size(); ++i) {
|
|
extruder_polys.emplace_back(Polygon::new_scale(extruder_areas[i]));
|
|
}
|
|
extruder_polys = union_(extruder_polys);
|
|
if (extruder_polys.empty())
|
|
return false;
|
|
|
|
printable_poly = extruder_polys[0];
|
|
}
|
|
|
|
const BoundingBoxf3 &bbox = volume->transformed_convex_hull_bounding_box();
|
|
Polygon wipe_tower_polygon = bbox.polygon(true);
|
|
|
|
Polygons diff_res = diff(wipe_tower_polygon, printable_poly);
|
|
return diff_res.empty();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool GLVolumeCollection::check_outside_state(const BuildVolume &build_volume, ModelInstanceEPrintVolumeState *out_state, ObjectFilamentResults* object_results) const
|
|
{
|
|
if (GUI::wxGetApp().plater() == NULL)
|
|
{
|
|
if (out_state != nullptr)
|
|
*out_state = ModelInstancePVS_Inside;
|
|
return false;
|
|
}
|
|
|
|
const Model& model = GUI::wxGetApp().plater()->model();
|
|
auto volume_below = [](GLVolume& volume) -> bool
|
|
{ return volume.object_idx() != -1 && volume.volume_idx() != -1 && volume.is_below_printbed(); };
|
|
// Volume is partially below the print bed, thus a pre-calculated convex hull cannot be used.
|
|
auto volume_sinking = [](GLVolume& volume) -> bool
|
|
{ return volume.object_idx() != -1 && volume.volume_idx() != -1 && volume.is_sinking(); };
|
|
// Cached bounding box of a volume above the print bed.
|
|
auto volume_bbox = [volume_sinking](GLVolume& volume) -> BoundingBoxf3
|
|
{ return volume_sinking(volume) ? volume.transformed_non_sinking_bounding_box() : volume.transformed_convex_hull_bounding_box(); };
|
|
// Cached 3D convex hull of a volume above the print bed.
|
|
auto volume_convex_mesh = [volume_sinking, &model](GLVolume& volume) -> const TriangleMesh&
|
|
{ return volume_sinking(volume) ? model.objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh() : *volume.convex_hull(); };
|
|
|
|
ModelInstanceEPrintVolumeState overall_state = ModelInstancePVS_Fully_Outside;
|
|
bool contained_min_one = false;
|
|
|
|
//BBS: add instance judge logic, besides to original volume judge logic
|
|
//std::map<int64_t, ModelInstanceEPrintVolumeState> model_state;
|
|
|
|
GUI::PartPlate* curr_plate = GUI::wxGetApp().plater()->get_partplate_list().get_selected_plate();
|
|
const Pointfs& pp_bed_shape = curr_plate->get_shape();
|
|
BuildVolume plate_build_volume(pp_bed_shape, build_volume.printable_height(), build_volume.extruder_areas(), build_volume.extruder_heights());
|
|
const std::vector<BoundingBoxf3>& exclude_areas = curr_plate->get_exclude_areas();
|
|
|
|
std::map<ModelObject*, std::map<int, std::set<int>>> objects_unprintable_filaments;
|
|
int extruder_count = build_volume.get_extruder_area_count();
|
|
std::vector<std::set<int>> unprintable_filament_ids(extruder_count, std::set<int>());
|
|
std::set<ModelObject*> partly_objects_set;
|
|
const ModelObjectPtrs &model_objects = model.objects;
|
|
for (GLVolume* volume : this->volumes)
|
|
{
|
|
std::vector<bool> inside_extruders;
|
|
if (! volume->is_modifier && (volume->shader_outside_printer_detection_enabled || (! volume->is_wipe_tower && volume->composite_id.volume_id >= 0))) {
|
|
BuildVolume::ObjectState state;
|
|
if (volume_below(*volume))
|
|
state = BuildVolume::ObjectState::Below;
|
|
else {
|
|
switch (plate_build_volume.type()) {
|
|
case BuildVolume_Type::Rectangle: {
|
|
//FIXME this test does not evaluate collision of a build volume bounding box with non-convex objects.
|
|
const BoundingBoxf3& bb = volume_bbox(*volume);
|
|
state = plate_build_volume.volume_state_bbox(bb);
|
|
if ((state == BuildVolume::ObjectState::Inside) && (extruder_count > 1))
|
|
{
|
|
state = plate_build_volume.check_volume_bbox_state_with_extruder_areas(bb, inside_extruders);
|
|
}
|
|
break;
|
|
}
|
|
case BuildVolume_Type::Circle:
|
|
case BuildVolume_Type::Convex:
|
|
//FIXME doing test on convex hull until we learn to do test on non-convex polygons efficiently.
|
|
case BuildVolume_Type::Custom:
|
|
{
|
|
const indexed_triangle_set& convex_mesh_it = volume_convex_mesh(*volume).its;
|
|
const Transform3f trafo = volume->world_matrix().cast<float>();
|
|
state = plate_build_volume.object_state(convex_mesh_it, trafo, volume_sinking(*volume));
|
|
if ((state == BuildVolume::ObjectState::Inside) && (extruder_count > 1))
|
|
{
|
|
state = plate_build_volume.check_object_state_with_extruder_areas(convex_mesh_it, trafo, inside_extruders);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
// Ignore, don't produce any collision.
|
|
state = BuildVolume::ObjectState::Inside;
|
|
break;
|
|
}
|
|
assert(state != BuildVolume::ObjectState::Below);
|
|
|
|
if (state == BuildVolume::ObjectState::Limited) {
|
|
//unprintable_filament_ids.resize(inside_extruders.size());
|
|
ModelObject *model_object = model_objects[volume->object_idx()];
|
|
ModelVolume *model_volume = model_object->volumes[volume->volume_idx()];
|
|
for (size_t i = 0; i < inside_extruders.size(); ++i) {
|
|
if (!inside_extruders[i]) {
|
|
std::vector<int> filaments = model_volume->get_extruders();
|
|
unprintable_filament_ids[i].insert(filaments.begin(), filaments.end());
|
|
if (object_results) {
|
|
std::map<int, std::set<int>>& obj_extruder_filament_maps = objects_unprintable_filaments[model_object];
|
|
std::set<int>& obj_extruder_filaments = obj_extruder_filament_maps[i+1];
|
|
obj_extruder_filaments.insert(filaments.begin(), filaments.end());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//int64_t comp_id = ((int64_t)volume->composite_id.object_id << 32) | ((int64_t)volume->composite_id.instance_id);
|
|
volume->is_outside = (state != BuildVolume::ObjectState::Inside && state != BuildVolume::ObjectState::Limited);
|
|
volume->partly_inside = (state == BuildVolume::ObjectState::Colliding);
|
|
if (volume->printable) {
|
|
if (state == BuildVolume::ObjectState::Colliding)
|
|
{
|
|
overall_state = ModelInstancePVS_Partly_Outside;
|
|
partly_objects_set.emplace(model_objects[volume->object_idx()]);
|
|
}
|
|
else if ((state == BuildVolume::ObjectState::Limited) && (overall_state != ModelInstancePVS_Partly_Outside))
|
|
overall_state = ModelInstancePVS_Limited;
|
|
else if ((state == BuildVolume::ObjectState::Inside) && (overall_state == ModelInstancePVS_Fully_Outside)) {
|
|
overall_state = ModelInstancePVS_Fully_Outside;
|
|
}
|
|
contained_min_one |= !volume->is_outside;
|
|
}
|
|
|
|
/*ModelInstanceEPrintVolumeState volume_state;
|
|
//if (volume->is_outside && (plate_build_volume.bounding_volume().intersects(volume->bounding_box())))
|
|
if (volume->is_outside && (state == BuildVolume::ObjectState::Colliding))
|
|
volume_state = ModelInstancePVS_Partly_Outside;
|
|
else if (volume->is_outside)
|
|
volume_state = ModelInstancePVS_Fully_Outside;
|
|
else
|
|
volume_state = ModelInstancePVS_Inside;
|
|
|
|
if (model_state.find(comp_id) != model_state.end())
|
|
{
|
|
if (model_state[comp_id] != ModelInstancePVS_Partly_Outside)
|
|
{
|
|
if (volume_state == ModelInstancePVS_Partly_Outside)
|
|
model_state[comp_id] = ModelInstancePVS_Partly_Outside;
|
|
else if (model_state[comp_id] != volume_state)
|
|
{
|
|
model_state[comp_id] = ModelInstancePVS_Partly_Outside;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
model_state[comp_id] = volume_state;
|
|
}
|
|
|
|
if (model_state[comp_id] == ModelInstancePVS_Partly_Outside) {
|
|
overall_state = ModelInstancePVS_Partly_Outside;
|
|
BOOST_LOG_TRIVIAL(debug) << "instance includes " << volume->name << " is partially outside of bed";
|
|
}*/
|
|
}
|
|
}
|
|
|
|
std::vector<std::vector<int>> unprintable_filament_vec;
|
|
for (const std::set<int>& filamnt_ids : unprintable_filament_ids) {
|
|
unprintable_filament_vec.emplace_back(std::vector<int>(filamnt_ids.begin(), filamnt_ids.end()));
|
|
}
|
|
|
|
if (object_results && !partly_objects_set.empty()) {
|
|
object_results->partly_outside_objects = std::vector<ModelObject*>(partly_objects_set.begin(), partly_objects_set.end());
|
|
}
|
|
|
|
//check per-object error for extruder areas
|
|
if (object_results && (extruder_count > 1))
|
|
{
|
|
const auto& project_config = Slic3r::GUI::wxGetApp().preset_bundle->project_config;
|
|
object_results->mode = curr_plate->get_real_filament_map_mode(project_config);
|
|
if (object_results->mode < FilamentMapMode::fmmManual)
|
|
{
|
|
std::vector<int> conflict_filament_vector;
|
|
for (int index = 0; index < extruder_count; index++ )
|
|
{
|
|
if (!unprintable_filament_vec[index].empty())
|
|
{
|
|
std::sort (unprintable_filament_vec[index].begin(), unprintable_filament_vec[index].end());
|
|
if (index == 0)
|
|
conflict_filament_vector = unprintable_filament_vec[index];
|
|
else
|
|
{
|
|
std::vector<int> result_filaments;
|
|
//result_filaments.reserve(conflict_filaments.size());
|
|
std::set_intersection (conflict_filament_vector.begin(), conflict_filament_vector.end(), unprintable_filament_vec[index].begin(), unprintable_filament_vec[index].end(), std::insert_iterator<std::vector<int>>(result_filaments, result_filaments.begin()));
|
|
conflict_filament_vector = result_filaments;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
conflict_filament_vector.clear();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!conflict_filament_vector.empty())
|
|
{
|
|
std::set<int> conflict_filaments_set(conflict_filament_vector.begin(), conflict_filament_vector.end());
|
|
object_results->filaments = conflict_filament_vector;
|
|
|
|
for (auto& object_map: objects_unprintable_filaments)
|
|
{
|
|
ModelObject *model_object = object_map.first;
|
|
std::map<int, std::set<int>>& obj_extruder_filament_maps = object_map.second;
|
|
std::set<int> obj_filaments_set;
|
|
ObjectFilamentInfo object_filament_info;
|
|
object_filament_info.object = model_object;
|
|
|
|
for (std::map<int, std::set<int>>::iterator extruder_map_iter = obj_extruder_filament_maps.begin(); extruder_map_iter != obj_extruder_filament_maps.end(); extruder_map_iter++ )
|
|
{
|
|
int extruder_id = extruder_map_iter->first;
|
|
std::set<int>& filaments_set = extruder_map_iter->second;
|
|
|
|
for (int filament: filaments_set)
|
|
{
|
|
if (conflict_filaments_set.find(filament) != conflict_filaments_set.end())
|
|
{
|
|
obj_filaments_set.emplace(filament);
|
|
}
|
|
}
|
|
}
|
|
if (!obj_filaments_set.empty()) {
|
|
object_filament_info.auto_filaments = std::vector<int>(obj_filaments_set.begin(), obj_filaments_set.end());
|
|
object_results->object_filaments.push_back(std::move(object_filament_info));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::set<int> conflict_filaments_set;
|
|
const auto& project_config = Slic3r::GUI::wxGetApp().preset_bundle->project_config;
|
|
std::vector<int> filament_maps = curr_plate->get_real_filament_maps(project_config);
|
|
for (auto& object_map: objects_unprintable_filaments)
|
|
{
|
|
ModelObject *model_object = object_map.first;
|
|
std::map<int, std::set<int>>& obj_extruder_filament_maps = object_map.second;
|
|
ObjectFilamentInfo object_filament_info;
|
|
object_filament_info.object = model_object;
|
|
|
|
for (std::map<int, std::set<int>>::iterator extruder_map_iter = obj_extruder_filament_maps.begin(); extruder_map_iter != obj_extruder_filament_maps.end(); extruder_map_iter++ )
|
|
{
|
|
int extruder_id = extruder_map_iter->first;
|
|
std::set<int>& filaments_set = extruder_map_iter->second;
|
|
|
|
for (int filament: filaments_set)
|
|
{
|
|
if (filament_maps[filament - 1] == extruder_id)
|
|
{
|
|
object_filament_info.manual_filaments.emplace(filament, extruder_id);
|
|
object_results->filament_maps[filament] = extruder_id;
|
|
conflict_filaments_set.emplace(filament);
|
|
}
|
|
}
|
|
}
|
|
if (!object_filament_info.manual_filaments.empty())
|
|
{
|
|
object_results->object_filaments.push_back(std::move(object_filament_info));
|
|
}
|
|
}
|
|
if (!conflict_filaments_set.empty()) {
|
|
object_results->filaments = std::vector<int>(conflict_filaments_set.begin(), conflict_filaments_set.end());
|
|
}
|
|
}
|
|
}
|
|
|
|
/*for (GLVolume* volume : this->volumes)
|
|
{
|
|
if (! volume->is_modifier && (volume->shader_outside_printer_detection_enabled || (! volume->is_wipe_tower && volume->composite_id.volume_id >= 0)))
|
|
{
|
|
int64_t comp_id = ((int64_t)volume->composite_id.object_id << 32) | ((int64_t)volume->composite_id.instance_id);
|
|
if (model_state.find(comp_id) != model_state.end())
|
|
{
|
|
if (model_state[comp_id] == ModelInstancePVS_Partly_Outside) {
|
|
volume->partly_inside = true;
|
|
}
|
|
else
|
|
volume->partly_inside = false;
|
|
}
|
|
}
|
|
}*/
|
|
|
|
if (out_state != nullptr)
|
|
*out_state = overall_state;
|
|
|
|
return contained_min_one;
|
|
}
|
|
|
|
void GLVolumeCollection::reset_outside_state()
|
|
{
|
|
for (GLVolume* volume : this->volumes)
|
|
{
|
|
if (volume != nullptr) {
|
|
volume->is_outside = false;
|
|
volume->partly_inside = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig *config, bool is_update_alpha)
|
|
{
|
|
|
|
using ColorItem = std::pair<std::string, ColorRGBA>;
|
|
std::vector<ColorItem> colors;
|
|
|
|
if (config->has("printer_technology") && static_cast<PrinterTechnology>(config->opt_int("printer_technology")) == ptSLA) {
|
|
const std::string& txt_color = config->opt_string("material_colour").empty() ?
|
|
print_config_def.get("material_colour")->get_default_value<ConfigOptionString>()->value :
|
|
config->opt_string("material_colour");
|
|
ColorRGBA rgba;
|
|
if (decode_color(txt_color, rgba))
|
|
colors.push_back({ txt_color, rgba });
|
|
}
|
|
else {
|
|
if (!config->has("filament_colour")) {
|
|
return;
|
|
}
|
|
const ConfigOptionStrings* filamemts_opt = dynamic_cast<const ConfigOptionStrings*>(config->option("filament_colour"));
|
|
if (filamemts_opt == nullptr)
|
|
return;
|
|
|
|
size_t colors_count = (size_t)filamemts_opt->values.size();
|
|
if (colors_count == 0)
|
|
return;
|
|
colors.resize(colors_count);
|
|
|
|
for (unsigned int i = 0; i < colors_count; ++i) {
|
|
ColorRGBA rgba;
|
|
const std::string& fil_color = config->opt_string("filament_colour", i);
|
|
if (decode_color(fil_color, rgba))
|
|
colors[i] = { fil_color, rgba };
|
|
}
|
|
}
|
|
|
|
for (GLVolume* volume : volumes) {
|
|
if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || volume->volume_idx() < 0)
|
|
continue;
|
|
|
|
int extruder_id = volume->extruder_id - 1;
|
|
if (extruder_id < 0 || (int)colors.size() <= extruder_id)
|
|
extruder_id = 0;
|
|
|
|
const ColorItem& color = colors[extruder_id];
|
|
if (!color.first.empty()) {
|
|
if (!is_update_alpha) {
|
|
float old_a = volume->color.a();
|
|
volume->color = color.second;
|
|
volume->color.a(old_a);
|
|
} else {
|
|
volume->color = color.second;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GLVolumeCollection::set_transparency(float alpha)
|
|
{
|
|
for (GLVolume *volume : volumes) {
|
|
if (volume == nullptr || volume->is_modifier || volume->is_wipe_tower || (volume->volume_idx() < 0))
|
|
continue;
|
|
|
|
volume->color.a(alpha);
|
|
}
|
|
}
|
|
|
|
std::vector<double> GLVolumeCollection::get_current_print_zs(bool active_only) const
|
|
{
|
|
// Collect layer top positions of all volumes.
|
|
std::vector<double> print_zs;
|
|
for (GLVolume *vol : this->volumes)
|
|
{
|
|
if (!active_only || vol->is_active)
|
|
append(print_zs, vol->print_zs);
|
|
}
|
|
std::sort(print_zs.begin(), print_zs.end());
|
|
|
|
// Replace intervals of layers with similar top positions with their average value.
|
|
int n = int(print_zs.size());
|
|
int k = 0;
|
|
for (int i = 0; i < n;) {
|
|
int j = i + 1;
|
|
coordf_t zmax = print_zs[i] + EPSILON;
|
|
for (; j < n && print_zs[j] <= zmax; ++ j) ;
|
|
print_zs[k ++] = (j > i + 1) ? (0.5 * (print_zs[i] + print_zs[j - 1])) : print_zs[i];
|
|
i = j;
|
|
}
|
|
if (k < n)
|
|
print_zs.erase(print_zs.begin() + k, print_zs.end());
|
|
|
|
return print_zs;
|
|
}
|
|
|
|
size_t GLVolumeCollection::cpu_memory_used() const
|
|
{
|
|
size_t memsize = sizeof(*this) + this->volumes.capacity() * sizeof(GLVolume);
|
|
for (const GLVolume *volume : this->volumes)
|
|
memsize += volume->cpu_memory_used();
|
|
return memsize;
|
|
}
|
|
|
|
size_t GLVolumeCollection::gpu_memory_used() const
|
|
{
|
|
size_t memsize = 0;
|
|
for (const GLVolume *volume : this->volumes)
|
|
memsize += volume->gpu_memory_used();
|
|
return memsize;
|
|
}
|
|
|
|
std::string GLVolumeCollection::log_memory_info() const
|
|
{
|
|
return " (GLVolumeCollection RAM: " + format_memsize_MB(this->cpu_memory_used()) + " GPU: " + format_memsize_MB(this->gpu_memory_used()) + " Both: " + format_memsize_MB(this->gpu_memory_used()) + ")";
|
|
}
|
|
|
|
static void thick_lines_to_geometry(
|
|
const Lines& lines,
|
|
const std::vector<double>& widths,
|
|
const std::vector<double>& heights,
|
|
bool closed,
|
|
double top_z,
|
|
GUI::GLModel::Geometry& geometry)
|
|
{
|
|
assert(!lines.empty());
|
|
if (lines.empty())
|
|
return;
|
|
|
|
enum Direction : unsigned char
|
|
{
|
|
Left,
|
|
Right,
|
|
Top,
|
|
Bottom
|
|
};
|
|
|
|
// right, left, top, bottom
|
|
std::array<int, 4> idx_prev = { -1, -1, -1, -1 };
|
|
std::array<int, 4> idx_initial = { -1, -1, -1, -1 };
|
|
|
|
double bottom_z_prev = 0.0;
|
|
Vec2d b1_prev(Vec2d::Zero());
|
|
Vec2d v_prev(Vec2d::Zero());
|
|
double len_prev = 0.0;
|
|
double width_initial = 0.0;
|
|
double bottom_z_initial = 0.0;
|
|
|
|
// loop once more in case of closed loops
|
|
const size_t lines_end = closed ? (lines.size() + 1) : lines.size();
|
|
for (size_t ii = 0; ii < lines_end; ++ii) {
|
|
const size_t i = (ii == lines.size()) ? 0 : ii;
|
|
const Line& line = lines[i];
|
|
const double bottom_z = top_z - heights[i];
|
|
const double middle_z = 0.5 * (top_z + bottom_z);
|
|
const double width = widths[i];
|
|
|
|
const bool is_first = (ii == 0);
|
|
const bool is_last = (ii == lines_end - 1);
|
|
const bool is_closing = closed && is_last;
|
|
|
|
const Vec2d v = unscale(line.vector()).normalized();
|
|
const double len = unscale<double>(line.length());
|
|
|
|
const Vec2d a = unscale(line.a);
|
|
const Vec2d b = unscale(line.b);
|
|
Vec2d a1 = a;
|
|
Vec2d a2 = a;
|
|
Vec2d b1 = b;
|
|
Vec2d b2 = b;
|
|
{
|
|
const double dist = 0.5 * width; // scaled
|
|
const double dx = dist * v.x();
|
|
const double dy = dist * v.y();
|
|
a1 += Vec2d(+dy, -dx);
|
|
a2 += Vec2d(-dy, +dx);
|
|
b1 += Vec2d(+dy, -dx);
|
|
b2 += Vec2d(-dy, +dx);
|
|
}
|
|
|
|
// calculate new XY normals
|
|
const Vec2d xy_right_normal = unscale(line.normal()).normalized();
|
|
|
|
std::array<int, 4> idx_a = { 0, 0, 0, 0 };
|
|
std::array<int, 4> idx_b = { 0, 0, 0, 0 };
|
|
int idx_last = int(geometry.vertices_count());
|
|
|
|
const bool bottom_z_different = bottom_z_prev != bottom_z;
|
|
bottom_z_prev = bottom_z;
|
|
|
|
if (!is_first && bottom_z_different) {
|
|
// Found a change of the layer thickness -> Add a cap at the end of the previous segment.
|
|
geometry.add_triangle(idx_b[Bottom], idx_b[Left], idx_b[Top]);
|
|
geometry.add_triangle(idx_b[Bottom], idx_b[Top], idx_b[Right]);
|
|
}
|
|
|
|
// Share top / bottom vertices if possible.
|
|
if (is_first) {
|
|
idx_a[Top] = idx_last++;
|
|
geometry.add_vertex(Vec3f(a.x(), a.y(), top_z), Vec3f(0.0f, 0.0f, 1.0f));
|
|
}
|
|
else
|
|
idx_a[Top] = idx_prev[Top];
|
|
|
|
if (is_first || bottom_z_different) {
|
|
// Start of the 1st line segment or a change of the layer thickness while maintaining the print_z.
|
|
idx_a[Bottom] = idx_last++;
|
|
geometry.add_vertex(Vec3f(a.x(), a.y(), bottom_z), Vec3f(0.0f, 0.0f, -1.0f));
|
|
idx_a[Left] = idx_last++;
|
|
geometry.add_vertex(Vec3f(a2.x(), a2.y(), middle_z), Vec3f(-xy_right_normal.x(), -xy_right_normal.y(), 0.0f));
|
|
idx_a[Right] = idx_last++;
|
|
geometry.add_vertex(Vec3f(a1.x(), a1.y(), middle_z), Vec3f(xy_right_normal.x(), xy_right_normal.y(), 0.0f));
|
|
}
|
|
else
|
|
idx_a[Bottom] = idx_prev[Bottom];
|
|
|
|
if (is_first) {
|
|
// Start of the 1st line segment.
|
|
width_initial = width;
|
|
bottom_z_initial = bottom_z;
|
|
idx_initial = idx_a;
|
|
}
|
|
else {
|
|
// Continuing a previous segment.
|
|
// Share left / right vertices if possible.
|
|
const double v_dot = v_prev.dot(v);
|
|
// To reduce gpu memory usage, we try to reuse vertices
|
|
// To reduce the visual artifacts, due to averaged normals, we allow to reuse vertices only when any of two adjacent edges
|
|
// is longer than a fixed threshold.
|
|
// The following value is arbitrary, it comes from tests made on a bunch of models showing the visual artifacts
|
|
const double len_threshold = 2.5;
|
|
|
|
// Generate new vertices if the angle between adjacent edges is greater than 45 degrees or thresholds conditions are met
|
|
const bool sharp = (v_dot < 0.707) || (len_prev > len_threshold) || (len > len_threshold);
|
|
if (sharp) {
|
|
if (!bottom_z_different) {
|
|
// Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn.
|
|
idx_a[Right] = idx_last++;
|
|
geometry.add_vertex(Vec3f(a1.x(), a1.y(), middle_z), Vec3f(xy_right_normal.x(), xy_right_normal.y(), 0.0f));
|
|
idx_a[Left] = idx_last++;
|
|
geometry.add_vertex(Vec3f(a2.x(), a2.y(), middle_z), Vec3f(-xy_right_normal.x(), -xy_right_normal.y(), 0.0f));
|
|
if (cross2(v_prev, v) > 0.0) {
|
|
// Right turn. Fill in the right turn wedge.
|
|
geometry.add_triangle(idx_prev[Right], idx_a[Right], idx_prev[Top]);
|
|
geometry.add_triangle(idx_prev[Right], idx_prev[Bottom], idx_a[Right]);
|
|
}
|
|
else {
|
|
// Left turn. Fill in the left turn wedge.
|
|
geometry.add_triangle(idx_prev[Left], idx_prev[Top], idx_a[Left]);
|
|
geometry.add_triangle(idx_prev[Left], idx_a[Left], idx_prev[Bottom]);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (!bottom_z_different) {
|
|
// The two successive segments are nearly collinear.
|
|
idx_a[Left] = idx_prev[Left];
|
|
idx_a[Right] = idx_prev[Right];
|
|
}
|
|
}
|
|
if (is_closing) {
|
|
if (!sharp) {
|
|
if (!bottom_z_different) {
|
|
// Closing a loop with smooth transition. Unify the closing left / right vertices.
|
|
geometry.set_vertex(idx_initial[Left], geometry.extract_position_3(idx_prev[Left]), geometry.extract_normal_3(idx_prev[Left]));
|
|
geometry.set_vertex(idx_initial[Right], geometry.extract_position_3(idx_prev[Right]), geometry.extract_normal_3(idx_prev[Right]));
|
|
geometry.remove_vertex(geometry.vertices_count() - 1);
|
|
geometry.remove_vertex(geometry.vertices_count() - 1);
|
|
// Replace the left / right vertex indices to point to the start of the loop.
|
|
const size_t indices_count = geometry.indices_count();
|
|
for (size_t u = indices_count - 24; u < indices_count; ++u) {
|
|
const unsigned int id = geometry.extract_index(u);
|
|
if (id == (unsigned int)idx_prev[Left])
|
|
geometry.set_index(u, (unsigned int)idx_initial[Left]);
|
|
else if (id == (unsigned int)idx_prev[Right])
|
|
geometry.set_index(u, (unsigned int)idx_initial[Right]);
|
|
}
|
|
}
|
|
}
|
|
// This is the last iteration, only required to solve the transition.
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Only new allocate top / bottom vertices, if not closing a loop.
|
|
if (is_closing)
|
|
idx_b[Top] = idx_initial[Top];
|
|
else {
|
|
idx_b[Top] = idx_last++;
|
|
geometry.add_vertex(Vec3f(b.x(), b.y(), top_z), Vec3f(0.0f, 0.0f, 1.0f));
|
|
}
|
|
|
|
if (is_closing && width == width_initial && bottom_z == bottom_z_initial)
|
|
idx_b[Bottom] = idx_initial[Bottom];
|
|
else {
|
|
idx_b[Bottom] = idx_last++;
|
|
geometry.add_vertex(Vec3f(b.x(), b.y(), bottom_z), Vec3f(0.0f, 0.0f, -1.0f));
|
|
}
|
|
// Generate new vertices for the end of this line segment.
|
|
idx_b[Left] = idx_last++;
|
|
geometry.add_vertex(Vec3f(b2.x(), b2.y(), middle_z), Vec3f(-xy_right_normal.x(), -xy_right_normal.y(), 0.0f));
|
|
idx_b[Right] = idx_last++;
|
|
geometry.add_vertex(Vec3f(b1.x(), b1.y(), middle_z), Vec3f(xy_right_normal.x(), xy_right_normal.y(), 0.0f));
|
|
|
|
idx_prev = idx_b;
|
|
bottom_z_prev = bottom_z;
|
|
b1_prev = b1;
|
|
v_prev = v;
|
|
len_prev = len;
|
|
|
|
if (bottom_z_different && (closed || (!is_first && !is_last))) {
|
|
// Found a change of the layer thickness -> Add a cap at the beginning of this segment.
|
|
geometry.add_triangle(idx_a[Bottom], idx_a[Right], idx_a[Top]);
|
|
geometry.add_triangle(idx_a[Bottom], idx_a[Top], idx_a[Left]);
|
|
}
|
|
|
|
if (!closed) {
|
|
// Terminate open paths with caps.
|
|
if (is_first) {
|
|
geometry.add_triangle(idx_a[Bottom], idx_a[Right], idx_a[Top]);
|
|
geometry.add_triangle(idx_a[Bottom], idx_a[Top], idx_a[Left]);
|
|
}
|
|
// We don't use 'else' because both cases are true if we have only one line.
|
|
if (is_last) {
|
|
geometry.add_triangle(idx_b[Bottom], idx_b[Left], idx_b[Top]);
|
|
geometry.add_triangle(idx_b[Bottom], idx_b[Top], idx_b[Right]);
|
|
}
|
|
}
|
|
|
|
// Add quads for a straight hollow tube-like segment.
|
|
// bottom-right face
|
|
geometry.add_triangle(idx_a[Bottom], idx_b[Bottom], idx_b[Right]);
|
|
geometry.add_triangle(idx_a[Bottom], idx_b[Right], idx_a[Right]);
|
|
// top-right face
|
|
geometry.add_triangle(idx_a[Right], idx_b[Right], idx_b[Top]);
|
|
geometry.add_triangle(idx_a[Right], idx_b[Top], idx_a[Top]);
|
|
// top-left face
|
|
geometry.add_triangle(idx_a[Top], idx_b[Top], idx_b[Left]);
|
|
geometry.add_triangle(idx_a[Top], idx_b[Left], idx_a[Left]);
|
|
// bottom-left face
|
|
geometry.add_triangle(idx_a[Left], idx_b[Left], idx_b[Bottom]);
|
|
geometry.add_triangle(idx_a[Left], idx_b[Bottom], idx_a[Bottom]);
|
|
}
|
|
}
|
|
|
|
// caller is responsible for supplying NO lines with zero length
|
|
static void thick_lines_to_geometry(
|
|
const Lines3& lines,
|
|
const std::vector<double>& widths,
|
|
const std::vector<double>& heights,
|
|
bool closed,
|
|
GUI::GLModel::Geometry& geometry)
|
|
{
|
|
assert(!lines.empty());
|
|
if (lines.empty())
|
|
return;
|
|
|
|
enum Direction : unsigned char
|
|
{
|
|
Left,
|
|
Right,
|
|
Top,
|
|
Bottom
|
|
};
|
|
|
|
// left, right, top, bottom
|
|
std::array<int, 4> idx_prev = { -1, -1, -1, -1 };
|
|
std::array<int, 4> idx_initial = { -1, -1, -1, -1 };
|
|
|
|
double z_prev = 0.0;
|
|
double len_prev = 0.0;
|
|
Vec3d n_right_prev = Vec3d::Zero();
|
|
Vec3d n_top_prev = Vec3d::Zero();
|
|
Vec3d unit_v_prev = Vec3d::Zero();
|
|
double width_initial = 0.0;
|
|
|
|
// new vertices around the line endpoints
|
|
// left, right, top, bottom
|
|
std::array<Vec3d, 4> a = { Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() };
|
|
std::array<Vec3d, 4> b = { Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero(), Vec3d::Zero() };
|
|
|
|
// loop once more in case of closed loops
|
|
const size_t lines_end = closed ? (lines.size() + 1) : lines.size();
|
|
for (size_t ii = 0; ii < lines_end; ++ii) {
|
|
const size_t i = (ii == lines.size()) ? 0 : ii;
|
|
|
|
const Line3& line = lines[i];
|
|
const double height = heights[i];
|
|
const double width = widths[i];
|
|
|
|
const Vec3d unit_v = unscale(line.vector()).normalized();
|
|
const double len = unscale<double>(line.length());
|
|
|
|
Vec3d n_top = Vec3d::Zero();
|
|
Vec3d n_right = Vec3d::Zero();
|
|
|
|
if (line.a.x() == line.b.x() && line.a.y() == line.b.y()) {
|
|
// vertical segment
|
|
n_top = Vec3d::UnitY();
|
|
n_right = Vec3d::UnitX();
|
|
if (line.a.z() < line.b.z())
|
|
n_right = -n_right;
|
|
}
|
|
else {
|
|
// horizontal segment
|
|
n_right = unit_v.cross(Vec3d::UnitZ()).normalized();
|
|
n_top = n_right.cross(unit_v).normalized();
|
|
}
|
|
|
|
const Vec3d rl_displacement = 0.5 * width * n_right;
|
|
const Vec3d tb_displacement = 0.5 * height * n_top;
|
|
const Vec3d l_a = unscale(line.a);
|
|
const Vec3d l_b = unscale(line.b);
|
|
|
|
a[Right] = l_a + rl_displacement;
|
|
a[Left] = l_a - rl_displacement;
|
|
a[Top] = l_a + tb_displacement;
|
|
a[Bottom] = l_a - tb_displacement;
|
|
b[Right] = l_b + rl_displacement;
|
|
b[Left] = l_b - rl_displacement;
|
|
b[Top] = l_b + tb_displacement;
|
|
b[Bottom] = l_b - tb_displacement;
|
|
|
|
const Vec3d n_bottom = -n_top;
|
|
const Vec3d n_left = -n_right;
|
|
|
|
std::array<int, 4> idx_a = { 0, 0, 0, 0};
|
|
std::array<int, 4> idx_b = { 0, 0, 0, 0 };
|
|
int idx_last = int(geometry.vertices_count());
|
|
|
|
const bool z_different = (z_prev != l_a.z());
|
|
z_prev = l_b.z();
|
|
|
|
// Share top / bottom vertices if possible.
|
|
if (ii == 0) {
|
|
idx_a[Top] = idx_last++;
|
|
geometry.add_vertex((Vec3f)a[Top].cast<float>(), (Vec3f)n_top.cast<float>());
|
|
}
|
|
else
|
|
idx_a[Top] = idx_prev[Top];
|
|
|
|
if (ii == 0 || z_different) {
|
|
// Start of the 1st line segment or a change of the layer thickness while maintaining the print_z.
|
|
idx_a[Bottom] = idx_last++;
|
|
geometry.add_vertex((Vec3f)a[Bottom].cast<float>(), (Vec3f)n_bottom.cast<float>());
|
|
idx_a[Left] = idx_last++;
|
|
geometry.add_vertex((Vec3f)a[Left].cast<float>(), (Vec3f)n_left.cast<float>());
|
|
idx_a[Right] = idx_last++;
|
|
geometry.add_vertex((Vec3f)a[Right].cast<float>(), (Vec3f)n_right.cast<float>());
|
|
}
|
|
else
|
|
idx_a[Bottom] = idx_prev[Bottom];
|
|
|
|
if (ii == 0) {
|
|
// Start of the 1st line segment.
|
|
width_initial = width;
|
|
idx_initial = idx_a;
|
|
}
|
|
else {
|
|
// Continuing a previous segment.
|
|
// Share left / right vertices if possible.
|
|
const double v_dot = unit_v_prev.dot(unit_v);
|
|
const bool is_right_turn = n_top_prev.dot(unit_v_prev.cross(unit_v)) > 0.0;
|
|
|
|
// To reduce gpu memory usage, we try to reuse vertices
|
|
// To reduce the visual artifacts, due to averaged normals, we allow to reuse vertices only when any of two adjacent edges
|
|
// is longer than a fixed threshold.
|
|
// The following value is arbitrary, it comes from tests made on a bunch of models showing the visual artifacts
|
|
const double len_threshold = 2.5;
|
|
|
|
// Generate new vertices if the angle between adjacent edges is greater than 45 degrees or thresholds conditions are met
|
|
const bool is_sharp = v_dot < 0.707 || len_prev > len_threshold || len > len_threshold;
|
|
if (is_sharp) {
|
|
// Allocate new left / right points for the start of this segment as these points will receive their own normals to indicate a sharp turn.
|
|
idx_a[Right] = idx_last++;
|
|
geometry.add_vertex((Vec3f)a[Right].cast<float>(), (Vec3f)n_right.cast<float>());
|
|
idx_a[Left] = idx_last++;
|
|
geometry.add_vertex((Vec3f)a[Left].cast<float>(), (Vec3f)n_left.cast<float>());
|
|
|
|
if (is_right_turn) {
|
|
// Right turn. Fill in the right turn wedge.
|
|
geometry.add_triangle(idx_prev[Right], idx_a[Right], idx_prev[Top]);
|
|
geometry.add_triangle(idx_prev[Right], idx_prev[Bottom], idx_a[Right]);
|
|
}
|
|
else {
|
|
// Left turn. Fill in the left turn wedge.
|
|
geometry.add_triangle(idx_prev[Left], idx_prev[Top], idx_a[Left]);
|
|
geometry.add_triangle(idx_prev[Left], idx_a[Left], idx_prev[Bottom]);
|
|
}
|
|
}
|
|
else {
|
|
// The two successive segments are nearly collinear.
|
|
idx_a[Left] = idx_prev[Left];
|
|
idx_a[Right] = idx_prev[Right];
|
|
}
|
|
|
|
if (ii == lines.size()) {
|
|
if (!is_sharp) {
|
|
// Closing a loop with smooth transition. Unify the closing left / right vertices.
|
|
geometry.set_vertex(idx_initial[Left], geometry.extract_position_3(idx_prev[Left]), geometry.extract_normal_3(idx_prev[Left]));
|
|
geometry.set_vertex(idx_initial[Right], geometry.extract_position_3(idx_prev[Right]), geometry.extract_normal_3(idx_prev[Right]));
|
|
geometry.remove_vertex(geometry.vertices_count() - 1);
|
|
geometry.remove_vertex(geometry.vertices_count() - 1);
|
|
// Replace the left / right vertex indices to point to the start of the loop.
|
|
const size_t indices_count = geometry.indices_count();
|
|
for (size_t u = indices_count - 24; u < indices_count; ++u) {
|
|
const unsigned int id = geometry.extract_index(u);
|
|
if (id == (unsigned int)idx_prev[Left])
|
|
geometry.set_index(u, (unsigned int)idx_initial[Left]);
|
|
else if (id == (unsigned int)idx_prev[Right])
|
|
geometry.set_index(u, (unsigned int)idx_initial[Right]);
|
|
}
|
|
}
|
|
|
|
// This is the last iteration, only required to solve the transition.
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Only new allocate top / bottom vertices, if not closing a loop.
|
|
if (closed && ii + 1 == lines.size())
|
|
idx_b[Top] = idx_initial[Top];
|
|
else {
|
|
idx_b[Top] = idx_last++;
|
|
geometry.add_vertex((Vec3f)b[Top].cast<float>(), (Vec3f)n_top.cast<float>());
|
|
}
|
|
|
|
if (closed && ii + 1 == lines.size() && width == width_initial)
|
|
idx_b[Bottom] = idx_initial[Bottom];
|
|
else {
|
|
idx_b[Bottom] = idx_last++;
|
|
geometry.add_vertex((Vec3f)b[Bottom].cast<float>(), (Vec3f)n_bottom.cast<float>());
|
|
}
|
|
|
|
// Generate new vertices for the end of this line segment.
|
|
idx_b[Left] = idx_last++;
|
|
geometry.add_vertex((Vec3f)b[Left].cast<float>(), (Vec3f)n_left.cast<float>());
|
|
idx_b[Right] = idx_last++;
|
|
geometry.add_vertex((Vec3f)b[Right].cast<float>(), (Vec3f)n_right.cast<float>());
|
|
|
|
idx_prev = idx_b;
|
|
n_right_prev = n_right;
|
|
n_top_prev = n_top;
|
|
unit_v_prev = unit_v;
|
|
len_prev = len;
|
|
|
|
if (!closed) {
|
|
// Terminate open paths with caps.
|
|
if (i == 0) {
|
|
geometry.add_triangle(idx_a[Bottom], idx_a[Right], idx_a[Top]);
|
|
geometry.add_triangle(idx_a[Bottom], idx_a[Top], idx_a[Left]);
|
|
}
|
|
|
|
// We don't use 'else' because both cases are true if we have only one line.
|
|
if (i + 1 == lines.size()) {
|
|
geometry.add_triangle(idx_b[Bottom], idx_b[Left], idx_b[Top]);
|
|
geometry.add_triangle(idx_b[Bottom], idx_b[Top], idx_b[Right]);
|
|
}
|
|
}
|
|
|
|
// Add quads for a straight hollow tube-like segment.
|
|
// bottom-right face
|
|
geometry.add_triangle(idx_a[Bottom], idx_b[Bottom], idx_b[Right]);
|
|
geometry.add_triangle(idx_a[Bottom], idx_b[Right], idx_a[Right]);
|
|
// top-right face
|
|
geometry.add_triangle(idx_a[Right], idx_b[Right], idx_b[Top]);
|
|
geometry.add_triangle(idx_a[Right], idx_b[Top], idx_a[Top]);
|
|
// top-left face
|
|
geometry.add_triangle(idx_a[Top], idx_b[Top], idx_b[Left]);
|
|
geometry.add_triangle(idx_a[Top], idx_b[Left], idx_a[Left]);
|
|
// bottom-left face
|
|
geometry.add_triangle(idx_a[Left], idx_b[Left], idx_b[Bottom]);
|
|
geometry.add_triangle(idx_a[Left], idx_b[Bottom], idx_a[Bottom]);
|
|
}
|
|
}
|
|
|
|
void _3DScene::thick_lines_to_verts(
|
|
const Lines& lines,
|
|
const std::vector<double>& widths,
|
|
const std::vector<double>& heights,
|
|
bool closed,
|
|
double top_z,
|
|
GUI::GLModel::Geometry& geometry)
|
|
{
|
|
thick_lines_to_geometry(lines, widths, heights, closed, top_z, geometry);
|
|
}
|
|
|
|
void _3DScene::thick_lines_to_verts(
|
|
const Lines3& lines,
|
|
const std::vector<double>& widths,
|
|
const std::vector<double>& heights,
|
|
bool closed,
|
|
GUI::GLModel::Geometry& geometry)
|
|
{
|
|
thick_lines_to_geometry(lines, widths, heights, closed, geometry);
|
|
}
|
|
|
|
// Fill in the qverts and tverts with quads and triangles for the extrusion_path.
|
|
void _3DScene::extrusionentity_to_verts(const ExtrusionPath& extrusion_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry)
|
|
{
|
|
Polyline polyline = extrusion_path.polyline.to_polyline();
|
|
polyline.remove_duplicate_points();
|
|
polyline.translate(copy);
|
|
const Lines lines = polyline.lines();
|
|
std::vector<double> widths(lines.size(), extrusion_path.width);
|
|
std::vector<double> heights(lines.size(), extrusion_path.height);
|
|
thick_lines_to_verts(lines, widths, heights, false, print_z, geometry);
|
|
}
|
|
|
|
// Fill in the qverts and tverts with quads and triangles for the extrusion_loop.
|
|
void _3DScene::extrusionentity_to_verts(const ExtrusionLoop& extrusion_loop, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry)
|
|
{
|
|
Lines lines;
|
|
std::vector<double> widths;
|
|
std::vector<double> heights;
|
|
for (const ExtrusionPath& extrusion_path : extrusion_loop.paths) {
|
|
Polyline polyline = extrusion_path.polyline.to_polyline();
|
|
polyline.remove_duplicate_points();
|
|
polyline.translate(copy);
|
|
const Lines lines_this = polyline.lines();
|
|
append(lines, lines_this);
|
|
widths.insert(widths.end(), lines_this.size(), extrusion_path.width);
|
|
heights.insert(heights.end(), lines_this.size(), extrusion_path.height);
|
|
}
|
|
thick_lines_to_verts(lines, widths, heights, true, print_z, geometry);
|
|
}
|
|
|
|
// Fill in the qverts and tverts with quads and triangles for the extrusion_multi_path.
|
|
void _3DScene::extrusionentity_to_verts(const ExtrusionMultiPath& extrusion_multi_path, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry)
|
|
{
|
|
Lines lines;
|
|
std::vector<double> widths;
|
|
std::vector<double> heights;
|
|
for (const ExtrusionPath& extrusion_path : extrusion_multi_path.paths) {
|
|
Polyline polyline = extrusion_path.polyline.to_polyline();
|
|
polyline.remove_duplicate_points();
|
|
polyline.translate(copy);
|
|
const Lines lines_this = polyline.lines();
|
|
append(lines, lines_this);
|
|
widths.insert(widths.end(), lines_this.size(), extrusion_path.width);
|
|
heights.insert(heights.end(), lines_this.size(), extrusion_path.height);
|
|
}
|
|
thick_lines_to_verts(lines, widths, heights, false, print_z, geometry);
|
|
}
|
|
|
|
void _3DScene::extrusionentity_to_verts(const ExtrusionEntityCollection& extrusion_entity_collection, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry)
|
|
{
|
|
for (const ExtrusionEntity* extrusion_entity : extrusion_entity_collection.entities)
|
|
extrusionentity_to_verts(extrusion_entity, print_z, copy, geometry);
|
|
}
|
|
|
|
void _3DScene::extrusionentity_to_verts(const ExtrusionEntity* extrusion_entity, float print_z, const Point& copy, GUI::GLModel::Geometry& geometry)
|
|
{
|
|
if (extrusion_entity != nullptr) {
|
|
auto* extrusion_path = dynamic_cast<const ExtrusionPath*>(extrusion_entity);
|
|
if (extrusion_path != nullptr)
|
|
extrusionentity_to_verts(*extrusion_path, print_z, copy, geometry);
|
|
else {
|
|
auto* extrusion_loop = dynamic_cast<const ExtrusionLoop*>(extrusion_entity);
|
|
if (extrusion_loop != nullptr)
|
|
extrusionentity_to_verts(*extrusion_loop, print_z, copy, geometry);
|
|
else {
|
|
auto* extrusion_multi_path = dynamic_cast<const ExtrusionMultiPath*>(extrusion_entity);
|
|
if (extrusion_multi_path != nullptr)
|
|
extrusionentity_to_verts(*extrusion_multi_path, print_z, copy, geometry);
|
|
else {
|
|
auto* extrusion_entity_collection = dynamic_cast<const ExtrusionEntityCollection*>(extrusion_entity);
|
|
if (extrusion_entity_collection != nullptr)
|
|
extrusionentity_to_verts(*extrusion_entity_collection, print_z, copy, geometry);
|
|
else
|
|
throw Slic3r::RuntimeError("Unexpected extrusion_entity type in to_verts()");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace Slic3r
|