Merge branch 'lm_sla_gizmo_clipping_plane'

This commit is contained in:
bubnikv 2019-04-11 15:45:49 +02:00
commit 112f218c03
17 changed files with 442 additions and 113 deletions

View File

@ -8,16 +8,22 @@ varying vec2 intensity;
varying vec3 delta_box_min;
varying vec3 delta_box_max;
varying float world_z;
varying vec3 world_pos;
uniform vec4 uniform_color;
// x = min z, y = max z;
uniform vec2 z_range;
// clipping plane (general orientation):
uniform vec4 clipping_plane;
void main()
{
if ((world_z < z_range.x) || (z_range.y < world_z))
if ((world_pos.z < z_range.x) || (z_range.y < world_pos.z))
discard;
if (world_pos.x*clipping_plane.x + world_pos.y*clipping_plane.y + world_pos.z*clipping_plane.z + clipping_plane.w < 0.0 )
discard;
// if the fragment is outside the print volume -> use darker color

View File

@ -34,7 +34,7 @@ varying vec2 intensity;
varying vec3 delta_box_min;
varying vec3 delta_box_max;
varying float world_z;
varying vec3 world_pos;
void main()
{
@ -69,5 +69,5 @@ void main()
}
gl_Position = ftransform();
world_z = vec3(print_box.volume_world_matrix * gl_Vertex).z;
world_pos = vec3(print_box.volume_world_matrix * gl_Vertex);
}

View File

@ -43,11 +43,21 @@ typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> stl_normal;
static_assert(sizeof(stl_vertex) == 12, "size of stl_vertex incorrect");
static_assert(sizeof(stl_normal) == 12, "size of stl_normal incorrect");
typedef struct {
struct stl_facet {
stl_normal normal;
stl_vertex vertex[3];
char extra[2];
} stl_facet;
stl_facet rotated(const Eigen::Quaternion<float, Eigen::DontAlign> &rot) {
stl_facet out;
out.normal = rot * this->normal;
out.vertex[0] = rot * this->vertex[0];
out.vertex[1] = rot * this->vertex[1];
out.vertex[2] = rot * this->vertex[2];
return out;
}
};
#define SIZEOF_STL_FACET 50
static_assert(offsetof(stl_facet, normal) == 0, "stl_facet.normal has correct offset");

View File

@ -41,10 +41,12 @@ stl_open(stl_file *stl, const char *file) {
stl_count_facets(stl, file);
stl_allocate(stl);
stl_read(stl, 0, true);
if (!stl->error) fclose(stl->fp);
if (stl->fp != nullptr) {
fclose(stl->fp);
stl->fp = nullptr;
}
}
void
stl_initialize(stl_file *stl) {
memset(stl, 0, sizeof(stl_file));
@ -118,7 +120,7 @@ stl_count_facets(stl_file *stl, const char *file) {
}
/* Read the int following the header. This should contain # of facets */
bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, stl->fp);
bool header_num_faces_read = fread(&header_num_facets, sizeof(uint32_t), 1, stl->fp) != 0;
#ifndef BOOST_LITTLE_ENDIAN
// Convert from little endian to big endian.
stl_internal_reverse_quads((char*)&header_num_facets, 4);
@ -257,7 +259,6 @@ stl_reallocate(stl_file *stl) {
time running this for the stl and therefore we should reset our max and min stats. */
void stl_read(stl_file *stl, int first_facet, bool first) {
stl_facet facet;
int i;
if (stl->error) return;
@ -268,7 +269,7 @@ void stl_read(stl_file *stl, int first_facet, bool first) {
}
char normal_buf[3][32];
for(i = first_facet; i < stl->stats.number_of_facets; i++) {
for(uint32_t i = first_facet; i < stl->stats.number_of_facets; i++) {
if(stl->stats.type == binary)
/* Read a single facet from a binary .STL file */
{
@ -366,17 +367,19 @@ void stl_facet_stats(stl_file *stl, stl_facet facet, bool &first)
}
}
void
stl_close(stl_file *stl) {
if (stl->error) return;
void stl_close(stl_file *stl)
{
assert(stl->fp == nullptr);
assert(stl->heads == nullptr);
assert(stl->tail == nullptr);
if(stl->neighbors_start != NULL)
free(stl->neighbors_start);
if(stl->facet_start != NULL)
free(stl->facet_start);
if(stl->v_indices != NULL)
free(stl->v_indices);
if(stl->v_shared != NULL)
free(stl->v_shared);
if (stl->facet_start != NULL)
free(stl->facet_start);
if (stl->neighbors_start != NULL)
free(stl->neighbors_start);
if (stl->v_indices != NULL)
free(stl->v_indices);
if (stl->v_shared != NULL)
free(stl->v_shared);
memset(stl, 0, sizeof(stl_file));
}

View File

@ -897,13 +897,13 @@ BoundingBoxf3 ModelObject::raw_bounding_box() const
throw std::invalid_argument("Can't call raw_bounding_box() with no instances");
#endif // !ENABLE_GENERIC_SUBPARTS_PLACEMENT
TriangleMesh vol_mesh(v->mesh);
#if ENABLE_GENERIC_SUBPARTS_PLACEMENT
vol_mesh.transform(inst_matrix * v->get_matrix());
bb.merge(vol_mesh.bounding_box());
bb.merge(v->mesh.transformed_bounding_box(inst_matrix * v->get_matrix()));
#else
vol_mesh.transform(v->get_matrix());
bb.merge(this->instances.front()->transform_mesh_bounding_box(vol_mesh, true));
// unmaintaned
assert(false);
// vol_mesh.transform(v->get_matrix());
// bb.merge(this->instances.front()->transform_mesh_bounding_box(vol_mesh, true));
#endif // ENABLE_GENERIC_SUBPARTS_PLACEMENT
}
return bb;
@ -920,13 +920,13 @@ BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_
{
if (v->is_model_part())
{
TriangleMesh mesh(v->mesh);
#if ENABLE_GENERIC_SUBPARTS_PLACEMENT
mesh.transform(inst_matrix * v->get_matrix());
bb.merge(mesh.bounding_box());
bb.merge(v->mesh.transformed_bounding_box(inst_matrix * v->get_matrix()));
#else
mesh.transform(v->get_matrix());
bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(mesh, dont_translate));
// not maintained
assert(false);
//mesh.transform(v->get_matrix());
//bb.merge(this->instances[instance_idx]->transform_mesh_bounding_box(mesh, dont_translate));
#endif // ENABLE_GENERIC_SUBPARTS_PLACEMENT
}
}

View File

@ -2528,14 +2528,17 @@ void PrintConfigDef::init_sla_params()
def = this->add("pad_wall_height", coFloat);
def->label = L("Pad wall height");
def->tooltip = L("Defines the cavity depth. Set to zero to disable the cavity.");
def->tooltip = L("Defines the pad cavity depth. Set to zero to disable the cavity. "
"Be careful when enabling this feature, as some resins may "
"produce an extreme suction effect inside the cavity, "
"which makes pealing the print off the vat foil difficult.");
def->category = L("Pad");
// def->tooltip = L("");
def->sidetext = L("mm");
def->min = 0;
def->max = 30;
def->mode = comSimple;
def->default_value = new ConfigOptionFloat(5.0);
def->mode = comExpert;
def->default_value = new ConfigOptionFloat(0.);
def = this->add("pad_max_merge_distance", coFloat);
def->label = L("Max merge distance");

View File

@ -693,6 +693,16 @@ void TriangleMeshSlicer::init(TriangleMesh *_mesh, throw_on_cancel_callback_type
}
}
void TriangleMeshSlicer::set_up_direction(const Vec3f& up)
{
m_quaternion.setFromTwoVectors(up, Vec3f::UnitZ());
m_use_quaternion = true;
}
void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons>* layers, throw_on_cancel_callback_type throw_on_cancel) const
{
BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice";
@ -795,7 +805,7 @@ void TriangleMeshSlicer::slice(const std::vector<float> &z, std::vector<Polygons
void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector<IntersectionLines>* lines, boost::mutex* lines_mutex,
const std::vector<float> &z) const
{
const stl_facet &facet = this->mesh->stl.facet_start[facet_idx];
const stl_facet &facet = m_use_quaternion ? this->mesh->stl.facet_start[facet_idx].rotated(m_quaternion) : this->mesh->stl.facet_start[facet_idx];
// find facet extents
const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2)));
@ -860,26 +870,43 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet(
IntersectionPoint points[3];
size_t num_points = 0;
size_t point_on_layer = size_t(-1);
// Reorder vertices so that the first one is the one with lowest Z.
// This is needed to get all intersection lines in a consistent order
// (external on the right of the line)
const int *vertices = this->mesh->stl.v_indices[facet_idx].vertex;
int i = (facet.vertex[1].z() == min_z) ? 1 : ((facet.vertex[2].z() == min_z) ? 2 : 0);
// These are used only if the cut plane is tilted:
stl_vertex rotated_a;
stl_vertex rotated_b;
for (int j = i; j - i < 3; ++j) { // loop through facet edges
int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)];
int a_id = vertices[j % 3];
int b_id = vertices[(j+1) % 3];
const stl_vertex *a = &this->v_scaled_shared[a_id];
const stl_vertex *b = &this->v_scaled_shared[b_id];
const stl_vertex *a;
const stl_vertex *b;
if (m_use_quaternion) {
rotated_a = m_quaternion * this->v_scaled_shared[a_id];
rotated_b = m_quaternion * this->v_scaled_shared[b_id];
a = &rotated_a;
b = &rotated_b;
}
else {
a = &this->v_scaled_shared[a_id];
b = &this->v_scaled_shared[b_id];
}
// Is edge or face aligned with the cutting plane?
if (a->z() == slice_z && b->z() == slice_z) {
// Edge is horizontal and belongs to the current layer.
const stl_vertex &v0 = this->v_scaled_shared[vertices[0]];
const stl_vertex &v1 = this->v_scaled_shared[vertices[1]];
const stl_vertex &v2 = this->v_scaled_shared[vertices[2]];
const stl_normal &normal = this->mesh->stl.facet_start[facet_idx].normal;
// The following rotation of the three vertices may not be efficient, but this branch happens rarely.
const stl_vertex &v0 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[0]]) : this->v_scaled_shared[vertices[0]];
const stl_vertex &v1 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[1]]) : this->v_scaled_shared[vertices[1]];
const stl_vertex &v2 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[2]]) : this->v_scaled_shared[vertices[2]];
const stl_normal &normal = facet.normal;
// We may ignore this edge for slicing purposes, but we may still use it for object cutting.
FacetSliceType result = Slicing;
if (min_z == max_z) {
@ -995,7 +1022,9 @@ TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet(
if (i == line_out->a_id || i == line_out->b_id)
i = vertices[2];
assert(i != line_out->a_id && i != line_out->b_id);
line_out->edge_type = (this->v_scaled_shared[i].z() < slice_z) ? feTop : feBottom;
line_out->edge_type = ((m_use_quaternion ?
(m_quaternion * this->v_scaled_shared[i]).z()
: this->v_scaled_shared[i].z()) < slice_z) ? feTop : feBottom;
}
#endif
return Slicing;

View File

@ -25,9 +25,10 @@ public:
TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd> &facets);
TriangleMesh(const TriangleMesh &other) : repaired(false) { stl_initialize(&this->stl); *this = other; }
TriangleMesh(TriangleMesh &&other) : repaired(false) { stl_initialize(&this->stl); this->swap(other); }
~TriangleMesh() { stl_close(&this->stl); }
~TriangleMesh() { clear(); }
TriangleMesh& operator=(const TriangleMesh &other);
TriangleMesh& operator=(TriangleMesh &&other) { this->swap(other); return *this; }
void clear() { stl_close(&this->stl); this->repaired = false; }
void swap(TriangleMesh &other) { std::swap(this->stl, other.stl); std::swap(this->repaired, other.repaired); }
void ReadSTLFile(const char* input_file) { stl_open(&stl, input_file); }
void write_ascii(const char* output_file) { stl_write_ascii(&this->stl, output_file, ""); }
@ -171,6 +172,7 @@ public:
FacetSliceType slice_facet(float slice_z, const stl_facet &facet, const int facet_idx,
const float min_z, const float max_z, IntersectionLine *line_out) const;
void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const;
void set_up_direction(const Vec3f& up);
private:
const TriangleMesh *mesh;
@ -178,6 +180,10 @@ private:
std::vector<int> facets_edges;
// Scaled copy of this->mesh->stl.v_shared
std::vector<stl_vertex> v_scaled_shared;
// Quaternion that will be used to rotate every facet before the slicing
Eigen::Quaternion<float, Eigen::DontAlign> m_quaternion;
// Whether or not the above quaterion should be used
bool m_use_quaternion = false;
void _slice_do(size_t facet_idx, std::vector<IntersectionLines>* lines, boost::mutex* lines_mutex, const std::vector<float> &z) const;
void make_loops(std::vector<IntersectionLine> &lines, Polygons* loops) const;

View File

@ -769,6 +769,7 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool
glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, &current_program_id));
GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1;
GLint z_range_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "z_range") : -1;
GLint clipping_plane_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "clipping_plane") : -1;
GLint print_box_min_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.min") : -1;
GLint print_box_max_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.max") : -1;
GLint print_box_detection_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.volume_detection") : -1;
@ -784,7 +785,11 @@ void GLVolumeCollection::render_VBOs(GLVolumeCollection::ERenderType type, bool
if (z_range_id != -1)
glsafe(::glUniform2fv(z_range_id, 1, (const GLfloat*)z_range));
if (clipping_plane_id != -1)
glsafe(::glUniform4fv(clipping_plane_id, 1, (const GLfloat*)clipping_plane));
GLVolumesWithZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func);
for (GLVolumeWithZ& volume : to_render) {
volume.first->set_render_color();
volume.first->render_VBOs(color_id, print_box_detection_id, print_box_worldmatrix_id);

View File

@ -431,6 +431,9 @@ private:
// z range for clipping in shaders
float z_range[2];
// plane coeffs for clipping in shaders
float clipping_plane[4];
public:
GLVolumePtrs volumes;
@ -489,6 +492,7 @@ public:
}
void set_z_range(float min_z, float max_z) { z_range[0] = min_z; z_range[1] = max_z; }
void set_clipping_plane(const double* coeffs) { clipping_plane[0] = coeffs[0]; clipping_plane[1] = coeffs[1]; clipping_plane[2] = coeffs[2]; clipping_plane[3] = coeffs[3]; }
// returns true if all the volumes are completely contained in the print volume
// returns the containment state in the given out_state, if non-null

View File

@ -3496,7 +3496,15 @@ void GLCanvas3D::_picking_pass() const
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane();
if (! m_use_VBOs) {
::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data());
::glEnable(GL_CLIP_PLANE0);
}
_render_volumes(true);
if (! m_use_VBOs)
::glDisable(GL_CLIP_PLANE0);
m_gizmos.render_current_gizmo_for_picking_pass(m_selection);
if (m_multisample_allowed)
@ -3577,6 +3585,8 @@ void GLCanvas3D::_render_axes() const
m_bed.render_axes();
}
void GLCanvas3D::_render_objects() const
{
if (m_volumes.empty())
@ -3585,6 +3595,8 @@ void GLCanvas3D::_render_objects() const
glsafe(::glEnable(GL_LIGHTING));
glsafe(::glEnable(GL_DEPTH_TEST));
m_camera_clipping_plane = m_gizmos.get_sla_clipping_plane();
if (m_use_VBOs)
{
if (m_picking_enabled)
@ -3605,6 +3617,8 @@ void GLCanvas3D::_render_objects() const
else
m_volumes.set_z_range(-FLT_MAX, FLT_MAX);
m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data());
m_shader.start_using();
if (m_picking_enabled && m_layers_editing.is_enabled() && m_layers_editing.last_object_id != -1) {
int object_id = m_layers_editing.last_object_id;
@ -3625,13 +3639,17 @@ void GLCanvas3D::_render_objects() const
}
else
{
::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_camera_clipping_plane.get_data());
::glEnable(GL_CLIP_PLANE0);
if (m_use_clipping_planes)
{
glsafe(::glClipPlane(GL_CLIP_PLANE0, (GLdouble*)m_clipping_planes[0].get_data()));
glsafe(::glEnable(GL_CLIP_PLANE0));
glsafe(::glClipPlane(GL_CLIP_PLANE1, (GLdouble*)m_clipping_planes[1].get_data()));
glsafe(::glClipPlane(GL_CLIP_PLANE1, (GLdouble*)m_clipping_planes[0].get_data()));
glsafe(::glEnable(GL_CLIP_PLANE1));
glsafe(::glClipPlane(GL_CLIP_PLANE2, (GLdouble*)m_clipping_planes[1].get_data()));
glsafe(::glEnable(GL_CLIP_PLANE2));
}
// do not cull backfaces to show broken geometry, if any
m_volumes.render_legacy(GLVolumeCollection::Opaque, m_picking_enabled, m_camera.get_view_matrix(), [this](const GLVolume& volume) {
@ -3639,13 +3657,16 @@ void GLCanvas3D::_render_objects() const
});
m_volumes.render_legacy(GLVolumeCollection::Transparent, false, m_camera.get_view_matrix());
::glDisable(GL_CLIP_PLANE0);
if (m_use_clipping_planes)
{
glsafe(::glDisable(GL_CLIP_PLANE0));
glsafe(::glDisable(GL_CLIP_PLANE1));
glsafe(::glDisable(GL_CLIP_PLANE2));
}
}
m_camera_clipping_plane = ClippingPlane::ClipsNothing();
glsafe(::glDisable(GL_LIGHTING));
}

View File

@ -65,6 +65,35 @@ public:
void set_scale_factor(int height);
};
class ClippingPlane
{
double m_data[4];
public:
ClippingPlane()
{
m_data[0] = 0.0;
m_data[1] = 0.0;
m_data[2] = 1.0;
m_data[3] = 0.0;
}
ClippingPlane(const Vec3d& direction, double offset)
{
Vec3d norm_dir = direction.normalized();
m_data[0] = norm_dir(0);
m_data[1] = norm_dir(1);
m_data[2] = norm_dir(2);
m_data[3] = offset;
}
static ClippingPlane ClipsNothing() { return ClippingPlane(Vec3d(0., 0., 1.), DBL_MAX); }
const double* get_data() const { return m_data; }
};
wxDECLARE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent);
using Vec2dEvent = Event<Vec2d>;
@ -288,32 +317,6 @@ class GLCanvas3D
}
};
public:
class ClippingPlane
{
double m_data[4];
public:
ClippingPlane()
{
m_data[0] = 0.0;
m_data[1] = 0.0;
m_data[2] = 1.0;
m_data[3] = 0.0;
}
ClippingPlane(const Vec3d& direction, double offset)
{
Vec3d norm_dir = direction.normalized();
m_data[0] = norm_dir(0);
m_data[1] = norm_dir(1);
m_data[2] = norm_dir(2);
m_data[3] = offset;
}
const double* get_data() const { return m_data; }
};
private:
struct SlaCap
{
@ -405,6 +408,7 @@ private:
mutable GLGizmosManager m_gizmos;
mutable GLToolbar m_toolbar;
ClippingPlane m_clipping_planes[2];
mutable ClippingPlane m_camera_clipping_plane;
bool m_use_clipping_planes;
mutable SlaCap m_sla_caps[2];
std::string m_sidebar_field;

View File

@ -818,8 +818,8 @@ void Preview::on_sliders_scroll_changed(wxEvent& event)
}
else if (tech == ptSLA)
{
m_canvas->set_clipping_plane(0, GLCanvas3D::ClippingPlane(Vec3d::UnitZ(), -m_slider->GetLowerValueD()));
m_canvas->set_clipping_plane(1, GLCanvas3D::ClippingPlane(-Vec3d::UnitZ(), m_slider->GetHigherValueD()));
m_canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), -m_slider->GetLowerValueD()));
m_canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), m_slider->GetHigherValueD()));
m_canvas->set_use_clipping_planes(m_slider->GetHigherValue() != 0);
m_canvas_widget->Refresh();
}

View File

@ -10,6 +10,7 @@
#include "slic3r/GUI/GUI_ObjectSettings.hpp"
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/PresetBundle.hpp"
#include "libslic3r/Tesselate.hpp"
namespace Slic3r {
@ -55,6 +56,11 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S
if (model_object && selection.is_from_single_instance())
{
// Cache the bb - it's needed for dealing with the clipping plane quite often
// It could be done inside update_mesh but one has to account for scaling of the instance.
//FIXME calling ModelObject::instance_bounding_box() is expensive!
m_active_instance_bb_radius = m_model_object->instance_bounding_box(m_active_instance).radius();
if (is_mesh_update_necessary()) {
update_mesh();
editing_mode_reload_cache();
@ -83,12 +89,75 @@ void GLGizmoSlaSupports::on_render(const Selection& selection) const
glsafe(::glEnable(GL_BLEND));
glsafe(::glEnable(GL_DEPTH_TEST));
render_points(selection, false);
// we'll recover current look direction from the modelview matrix (in world coords):
Eigen::Matrix<double, 4, 4, Eigen::DontAlign> modelview_matrix;
::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data());
Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]);
m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z();
if (m_quadric != nullptr && selection.is_from_single_instance())
render_points(selection, direction_to_camera, false);
render_selection_rectangle();
render_clipping_plane(selection, direction_to_camera);
glsafe(::glDisable(GL_BLEND));
}
void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection, const Vec3d& direction_to_camera) const
{
if (m_clipping_plane_distance == 0.f)
return;
const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin());
Transform3f instance_matrix = vol->get_instance_transformation().get_matrix().cast<float>();
Transform3f instance_matrix_no_translation_no_scaling = vol->get_instance_transformation().get_matrix(true,false,true).cast<float>();
Vec3f scaling = vol->get_instance_scaling_factor().cast<float>();
Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * direction_to_camera.cast<float>();
Vec3f up = Vec3f(up_noscale(0)*scaling(0), up_noscale(1)*scaling(1), up_noscale(2)*scaling(2));
float height_mesh = (m_active_instance_bb_radius - m_clipping_plane_distance * 2*m_active_instance_bb_radius) * (up_noscale.norm()/up.norm());
if (m_clipping_plane_distance != m_old_clipping_plane_distance
|| m_old_direction_to_camera != direction_to_camera) {
std::vector<ExPolygons> list_of_expolys;
if (! m_tms) {
m_tms.reset(new TriangleMeshSlicer);
m_tms->init(const_cast<TriangleMesh*>(&m_mesh), [](){});
}
m_tms->set_up_direction(up);
m_tms->slice(std::vector<float>{height_mesh}, 0.f, &list_of_expolys, [](){});
m_triangles = triangulate_expolygons_2f(list_of_expolys[0]);
m_old_direction_to_camera = direction_to_camera;
m_old_clipping_plane_distance = m_clipping_plane_distance;
}
if (! m_triangles.empty()) {
::glPushMatrix();
::glTranslated(0.0, 0.0, m_z_shift);
::glMultMatrixf(instance_matrix.data());
Eigen::Quaternionf q;
q.setFromTwoVectors(Vec3f::UnitZ(), up);
Eigen::AngleAxisf aa(q);
::glRotatef(aa.angle() * (180./M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2));
::glTranslatef(0.f, 0.f, -0.001f); // to make sure the cut is safely beyond the near clipping plane
::glColor3f(1.0f, 0.37f, 0.0f);
::glBegin(GL_TRIANGLES);
::glColor3f(1.0f, 0.37f, 0.0f);
for (const Vec2f& point : m_triangles)
::glVertex3f(point(0), point(1), height_mesh);
::glEnd();
::glPopMatrix();
}
}
void GLGizmoSlaSupports::render_selection_rectangle() const
{
if (m_selection_rectangle_status == srOff)
@ -138,24 +207,25 @@ void GLGizmoSlaSupports::on_render_for_picking(const Selection& selection) const
{
glsafe(::glEnable(GL_DEPTH_TEST));
render_points(selection, true);
// we'll recover current look direction from the modelview matrix (in world coords):
Eigen::Matrix<double, 4, 4, Eigen::DontAlign> modelview_matrix;
::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data());
Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]);
render_points(selection, direction_to_camera, true);
}
void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const
void GLGizmoSlaSupports::render_points(const Selection& selection, const Vec3d& direction_to_camera, bool picking) const
{
if (m_quadric == nullptr || !selection.is_from_single_instance())
return;
if (!picking)
glsafe(::glEnable(GL_LIGHTING));
const GLVolume* vol = selection.get_volume(*selection.get_volume_idxs().begin());
double z_shift = vol->get_sla_shift_z();
const Transform3d& instance_scaling_matrix_inverse = vol->get_instance_transformation().get_matrix(true, true, false, true).inverse();
const Transform3d& instance_matrix = vol->get_instance_transformation().get_matrix();
glsafe(::glPushMatrix());
glsafe(::glTranslated(0.0, 0.0, z_shift));
glsafe(::glTranslated(0.0, 0.0, m_z_shift));
glsafe(::glMultMatrixd(instance_matrix.data()));
float render_color[3];
@ -164,6 +234,9 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
const sla::SupportPoint& support_point = m_editing_mode_cache[i].support_point;
const bool& point_selected = m_editing_mode_cache[i].selected;
if (is_point_clipped(support_point.pos.cast<double>(), direction_to_camera))
continue;
// First decide about the color of the point.
if (picking) {
std::array<float, 3> color = picking_color_component(i);
@ -233,6 +306,21 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
glsafe(::glPopMatrix());
}
bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point, const Vec3d& direction_to_camera) const
{
if (m_clipping_plane_distance == 0.f)
return false;
Vec3d transformed_point = m_model_object->instances.front()->get_transformation().get_matrix() * point;
transformed_point(2) += m_z_shift;
return direction_to_camera.dot(m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift)) + m_active_instance_bb_radius
- m_clipping_plane_distance * 2*m_active_instance_bb_radius < direction_to_camera.dot(transformed_point);
}
bool GLGizmoSlaSupports::is_mesh_update_necessary() const
{
return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty())
@ -244,10 +332,9 @@ void GLGizmoSlaSupports::update_mesh()
wxBusyCursor wait;
Eigen::MatrixXf& V = m_V;
Eigen::MatrixXi& F = m_F;
// Composite mesh of all instances in the world coordinate system.
// This mesh does not account for the possible Z up SLA offset.
TriangleMesh mesh = m_model_object->raw_mesh();
const stl_file& stl = mesh.stl;
m_mesh = m_model_object->raw_mesh();
const stl_file& stl = m_mesh.stl;
V.resize(3 * stl.stats.number_of_facets, 3);
F.resize(stl.stats.number_of_facets, 3);
for (unsigned int i=0; i<stl.stats.number_of_facets; ++i) {
@ -266,6 +353,8 @@ void GLGizmoSlaSupports::update_mesh()
m_AABB.init(m_V, m_F);
}
// Unprojects the mouse position on the mesh and return the hit point and normal of the facet.
// The function throws if no intersection if found.
std::pair<Vec3f, Vec3f> GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos)
{
// if the gizmo doesn't have the V, F structures for igl, calculate them first:
@ -282,31 +371,53 @@ std::pair<Vec3f, Vec3f> GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse
::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 0.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point1(0), &point1(1), &point1(2));
::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 1.f, modelview_matrix.data(), projection_matrix.data(), viewport.data(), &point2(0), &point2(1), &point2(2));
igl::Hit hit;
std::vector<igl::Hit> hits;
const Selection& selection = m_parent.get_selection();
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
double z_offset = volume->get_sla_shift_z();
point1(2) -= z_offset;
point2(2) -= z_offset;
// we'll recover current look direction from the modelview matrix (in world coords):
Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]);
point1(2) -= m_z_shift;
point2(2) -= m_z_shift;
Transform3d inv = volume->get_instance_transformation().get_matrix().inverse();
point1 = inv * point1;
point2 = inv * point2;
if (!m_AABB.intersect_ray(m_V, m_F, point1.cast<float>(), (point2-point1).cast<float>(), hit))
if (!m_AABB.intersect_ray(m_V, m_F, point1.cast<float>(), (point2-point1).cast<float>(), hits))
throw std::invalid_argument("unproject_on_mesh(): No intersection found.");
int fid = hit.id; // facet id
Vec3f bc(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit
Vec3f a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0)));
Vec3f b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0)));
std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
// Now let's iterate through the points and find the first that is not clipped:
unsigned int i=0;
Vec3f bc;
Vec3f a;
Vec3f b;
Vec3f result;
for (i=0; i<hits.size(); ++i) {
igl::Hit& hit = hits[i];
int fid = hit.id; // facet id
bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit
a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0)));
b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0)));
result = bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2));
if (m_clipping_plane_distance == 0.f || !is_point_clipped(result.cast<double>(), direction_to_camera))
break;
}
if (i==hits.size() || (hits.size()-i) % 2 != 0) {
// All hits are either clipped, or there is an odd number of unclipped
// hits - meaning the nearest must be from inside the mesh.
throw std::invalid_argument("unproject_on_mesh(): No intersection found.");
}
// Calculate and return both the point and the facet normal.
return std::make_pair(
bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2)),
result,
a.cross(b)
);
}
@ -377,36 +488,64 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
const Selection& selection = m_parent.get_selection();
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
double z_offset = volume->get_sla_shift_z();
// bounding box created from the rectangle corners - will take care of order of the corners
BoundingBox rectangle(Points{Point(m_selection_rectangle_start_corner.cast<int>()), Point(m_selection_rectangle_end_corner.cast<int>())});
const Transform3d& instance_matrix_no_translation = volume->get_instance_transformation().get_matrix(true);
const Transform3d& instance_matrix_no_translation_no_scaling = volume->get_instance_transformation().get_matrix(true,false,true);
// we'll recover current look direction from the modelview matrix (in world coords)...
Vec3f direction_to_camera = camera.get_dir_forward().cast<float>();
// ...and transform it to model coords.
direction_to_camera = (instance_matrix_no_translation.inverse().cast<float>() * direction_to_camera).normalized().eval();
Vec3f direction_to_camera_mesh = (instance_matrix_no_translation_no_scaling.inverse().cast<float>() * direction_to_camera).normalized().eval();
Vec3f scaling = volume->get_instance_scaling_factor().cast<float>();
direction_to_camera_mesh = Vec3f(direction_to_camera_mesh(0)*scaling(0), direction_to_camera_mesh(1)*scaling(1), direction_to_camera_mesh(2)*scaling(2));
// Iterate over all points, check if they're in the rectangle and if so, check that they are not obscured by the mesh:
for (unsigned int i=0; i<m_editing_mode_cache.size(); ++i) {
const sla::SupportPoint &support_point = m_editing_mode_cache[i].support_point;
Vec3f pos = instance_matrix.cast<float>() * support_point.pos;
pos(2) += z_offset;
pos(2) += m_z_shift;
GLdouble out_x, out_y, out_z;
::gluProject((GLdouble)pos(0), (GLdouble)pos(1), (GLdouble)pos(2), (GLdouble*)modelview_matrix.data(), (GLdouble*)projection_matrix.data(), (GLint*)viewport.data(), &out_x, &out_y, &out_z);
out_y = m_canvas_height - out_y;
if (rectangle.contains(Point(out_x, out_y))) {
if (rectangle.contains(Point(out_x, out_y)) && !is_point_clipped(support_point.pos.cast<double>(), direction_to_camera.cast<double>())) {
bool is_obscured = false;
// Cast a ray in the direction of the camera and look for intersection with the mesh:
std::vector<igl::Hit> hits;
// Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies.
if (m_AABB.intersect_ray(m_V, m_F, support_point.pos + direction_to_camera * (support_point.head_front_radius + EPSILON), direction_to_camera, hits))
if (m_AABB.intersect_ray(m_V, m_F, support_point.pos + direction_to_camera_mesh * (support_point.head_front_radius + EPSILON), direction_to_camera_mesh, hits)) {
std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; });
if (m_clipping_plane_distance != 0.f) {
// If the closest hit facet normal points in the same direction as the ray,
// we are looking through the mesh and should therefore discard the point:
int fid = hits.front().id; // facet id
Vec3f a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0)));
Vec3f b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0)));
if ((a.cross(b)).dot(direction_to_camera_mesh) > 0.f)
is_obscured = true;
// Eradicate all hits that are on clipped surfaces:
for (unsigned int j=0; j<hits.size(); ++j) {
const igl::Hit& hit = hits[j];
int fid = hit.id; // facet id
Vec3f bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit
Vec3f hit_pos = bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2));
if (is_point_clipped(hit_pos.cast<double>(), direction_to_camera.cast<double>())) {
hits.erase(hits.begin()+j);
--j;
}
}
}
// FIXME: the intersection could in theory be behind the camera, but as of now we only have camera direction.
// Also, the threshold is in mesh coordinates, not in actual dimensions.
if (hits.size() > 1 || hits.front().t > 0.001f)
if (!hits.empty())
is_obscured = true;
}
if (!is_obscured) {
if (m_selection_rectangle_status == srDeselect)
@ -564,6 +703,64 @@ void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const
ClippingPlane GLGizmoSlaSupports::get_sla_clipping_plane() const
{
if (!m_model_object)
return ClippingPlane::ClipsNothing();
Eigen::Matrix<GLdouble, 4, 4, Eigen::DontAlign> modelview_matrix;
::glGetDoublev(GL_MODELVIEW_MATRIX, modelview_matrix.data());
// we'll recover current look direction from the modelview matrix (in world coords):
Vec3d direction_to_camera(modelview_matrix.data()[2], modelview_matrix.data()[6], modelview_matrix.data()[10]);
float dist = direction_to_camera.dot(m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift));
return ClippingPlane(-direction_to_camera.normalized(),(dist - (-m_active_instance_bb_radius) - m_clipping_plane_distance * 2*m_active_instance_bb_radius));
}
/*
void GLGizmoSlaSupports::find_intersecting_facets(const igl::AABB<Eigen::MatrixXf, 3>* aabb, const Vec3f& normal, double offset, std::vector<unsigned int>& idxs) const
{
if (aabb->is_leaf()) { // this is a facet
// corner.dot(normal) - offset
idxs.push_back(aabb->m_primitive);
}
else { // not a leaf
using CornerType = Eigen::AlignedBox<float, 3>::CornerType;
bool sign = std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(0))));
for (unsigned int i=1; i<8; ++i)
if (std::signbit(offset - normal.dot(aabb->m_box.corner(CornerType(i)))) != sign) {
find_intersecting_facets(aabb->m_left, normal, offset, idxs);
find_intersecting_facets(aabb->m_right, normal, offset, idxs);
}
}
}
void GLGizmoSlaSupports::make_line_segments() const
{
TriangleMeshSlicer tms(&m_model_object->volumes.front()->mesh);
Vec3f normal(0.f, 1.f, 1.f);
double d = 0.;
std::vector<IntersectionLine> lines;
find_intersections(&m_AABB, normal, d, lines);
ExPolygons expolys;
tms.make_expolygons_simple(lines, &expolys);
SVG svg("slice_loops.svg", get_extents(expolys));
svg.draw(expolys);
//for (const IntersectionLine &l : lines[i])
// svg.draw(l, "red", 0);
//svg.draw_outline(expolygons, "black", "blue", 0);
svg.Close();
}
*/
void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_limit, const Selection& selection)
{
if (!m_model_object)
@ -681,6 +878,13 @@ RENDER_AGAIN:
(m_model_object->sla_points_status == sla::PointsStatus::Generating ? "Generation in progress..." : "UNKNOWN STATUS"))));
}
// Following is rendered in both editing and non-editing mode:
m_imgui->text("Clipping of view: ");
ImGui::SameLine();
ImGui::PushItemWidth(150.0f);
bool value_changed = ImGui::SliderFloat(" ", &m_clipping_plane_distance, 0.f, 1.f, "%.2f");
m_imgui->end();
if (m_editing_mode != m_old_editing_state) { // user toggled between editing/non-editing mode
@ -752,8 +956,8 @@ void GLGizmoSlaSupports::on_set_state()
m_new_point_head_diameter = static_cast<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value;
}
if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
wxGetApp().CallAfter([this]() {
// Following is called through CallAfter, because otherwise there was a problem
wxGetApp().CallAfter([this]() {
// Following is called through CallAfter, because otherwise there was a problem
// on OSX with the wxMessageDialog being shown several times when clicked into.
if (m_model_object) {
if (m_unsaved_changes) {
@ -765,10 +969,16 @@ void GLGizmoSlaSupports::on_set_state()
editing_mode_discard_changes();
}
}
m_parent.toggle_model_objects_visibility(true);
m_editing_mode = false; // so it is not active next time the gizmo opens
m_editing_mode_cache.clear();
m_clipping_plane_distance = 0.f;
// Release copy of the mesh, triangle slicer and the AABB spatial search structure.
m_mesh.clear();
m_AABB.deinit();
m_V = Eigen::MatrixXf();
m_F = Eigen::MatrixXi();
m_tms.reset(nullptr);
});
}
m_old_state = m_state;

View File

@ -17,12 +17,17 @@ namespace Slic3r {
namespace GUI {
class ClippingPlane;
class GLGizmoSlaSupports : public GLGizmoBase
{
private:
ModelObject* m_model_object = nullptr;
ModelID m_current_mesh_model_id = 0;
int m_active_instance = -1;
float m_active_instance_bb_radius; // to cache the bb
mutable float m_z_shift = 0.f;
std::pair<Vec3f, Vec3f> unproject_on_mesh(const Vec2d& mouse_pos);
const float RenderPointScale = 1.f;
@ -31,6 +36,8 @@ private:
Eigen::MatrixXf m_V; // vertices
Eigen::MatrixXi m_F; // facets indices
igl::AABB<Eigen::MatrixXf,3> m_AABB;
TriangleMesh m_mesh;
mutable std::vector<Vec2f> m_triangles;
class CacheEntry {
public:
@ -52,7 +59,7 @@ public:
void set_sla_support_data(ModelObject* model_object, const Selection& selection);
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
void delete_selected_points(bool force = false);
std::pair<float, float> get_sla_clipping_plane() const;
ClippingPlane get_sla_clipping_plane() const;
private:
bool on_init();
@ -61,7 +68,8 @@ private:
virtual void on_render_for_picking(const Selection& selection) const;
void render_selection_rectangle() const;
void render_points(const Selection& selection, bool picking = false) const;
void render_points(const Selection& selection, const Vec3d& direction_to_camera, bool picking = false) const;
void render_clipping_plane(const Selection& selection, const Vec3d& direction_to_camera) const;
bool is_mesh_update_necessary() const;
void update_mesh();
void update_cache_entry_normal(unsigned int i) const;
@ -74,6 +82,8 @@ private:
float m_density = 100.f;
mutable std::vector<CacheEntry> m_editing_mode_cache; // a support point and whether it is currently selected
float m_clipping_plane_distance = 0.f;
mutable float m_old_clipping_plane_distance = 0.f;
mutable Vec3d m_old_direction_to_camera;
enum SelectionRectangleStatus {
srOff = 0,
@ -90,7 +100,11 @@ private:
int m_canvas_width;
int m_canvas_height;
mutable std::unique_ptr<TriangleMeshSlicer> m_tms;
std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const;
bool is_point_clipped(const Vec3d& point, const Vec3d& direction_to_camera) const;
void find_intersecting_facets(const igl::AABB<Eigen::MatrixXf, 3>* aabb, const Vec3f& normal, double offset, std::vector<unsigned int>& out) const;
// Methods that do the model_object and editing cache synchronization,
// editing mode selection, etc:

View File

@ -467,6 +467,19 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
return false;
}
ClippingPlane GLGizmosManager::get_sla_clipping_plane() const
{
if (!m_enabled || m_current != SlaSupports)
return ClippingPlane::ClipsNothing();
GizmosMap::const_iterator it = m_gizmos.find(SlaSupports);
if (it != m_gizmos.end())
return reinterpret_cast<GLGizmoSlaSupports*>(it->second)->get_sla_clipping_plane();
return ClippingPlane::ClipsNothing();
}
void GLGizmosManager::render_current_gizmo(const Selection& selection) const
{
if (!m_enabled)

View File

@ -13,6 +13,7 @@ namespace GUI {
class Selection;
class GLGizmoBase;
class GLCanvas3D;
class ClippingPlane;
class Rect
{
@ -146,7 +147,7 @@ public:
void set_sla_support_data(ModelObject* model_object, const Selection& selection);
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position = Vec2d::Zero(), bool shift_down = false, bool alt_down = false, bool control_down = false);
ClippingPlane get_sla_clipping_plane() const;
void render_current_gizmo(const Selection& selection) const;
void render_current_gizmo_for_picking_pass(const Selection& selection) const;