diff --git a/resources/shaders/gouraud_mod.fs b/resources/shaders/gouraud_mod.fs new file mode 100644 index 000000000..a1ba85b5c --- /dev/null +++ b/resources/shaders/gouraud_mod.fs @@ -0,0 +1,106 @@ +#version 110 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) + +#define INTENSITY_AMBIENT 0.3 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); +const vec3 GREEN = vec3(0.0, 0.7, 0.0); +const vec3 YELLOW = vec3(0.5, 0.7, 0.0); +const vec3 RED = vec3(0.7, 0.0, 0.0); +const vec3 WHITE = vec3(1.0, 1.0, 1.0); +const float EPSILON = 0.0001; +const float BANDS_WIDTH = 10.0; + +struct PrintVolumeDetection +{ + // 0 = rectangle, 1 = circle, 2 = custom, 3 = invalid + int type; + // type = 0 (rectangle): + // x = min.x, y = min.y, z = max.x, w = max.y + // type = 1 (circle): + // x = center.x, y = center.y, z = radius + vec4 xy_data; + // x = min z, y = max z + vec2 z_data; +}; + +struct SlopeDetection +{ + bool actived; + float normal_z; + mat3 volume_world_normal_matrix; +}; + +uniform vec4 uniform_color; +uniform SlopeDetection slope; + +uniform bool offset_depth_buffer; + +#ifdef ENABLE_ENVIRONMENT_MAP + uniform sampler2D environment_tex; + uniform bool use_environment_tex; +#endif // ENABLE_ENVIRONMENT_MAP + +varying vec3 clipping_planes_dots; + +// x = diffuse, y = specular; +varying vec2 intensity; + +uniform PrintVolumeDetection print_volume; + +varying vec4 model_pos; +varying vec4 world_pos; +varying float world_normal_z; +varying vec3 eye_normal; + +void main() +{ + if (any(lessThan(clipping_planes_dots, ZERO))) + discard; + vec3 color = uniform_color.rgb; + float alpha = uniform_color.a; + + if (slope.actived && world_normal_z < slope.normal_z - EPSILON) { + color = vec3(0.7, 0.7, 1.0); + alpha = 1.0; + } + + // if the fragment is outside the print volume -> use darker color + vec3 pv_check_min = ZERO; + vec3 pv_check_max = ZERO; + if (print_volume.type == 0) { + // rectangle + pv_check_min = world_pos.xyz - vec3(print_volume.xy_data.x, print_volume.xy_data.y, print_volume.z_data.x); + pv_check_max = world_pos.xyz - vec3(print_volume.xy_data.z, print_volume.xy_data.w, print_volume.z_data.y); + } + else if (print_volume.type == 1) { + // circle + float delta_radius = print_volume.xy_data.z - distance(world_pos.xy, print_volume.xy_data.xy); + pv_check_min = vec3(delta_radius, 0.0, world_pos.z - print_volume.z_data.x); + pv_check_max = vec3(0.0, 0.0, world_pos.z - print_volume.z_data.y); + } + color = (any(lessThan(pv_check_min, ZERO)) || any(greaterThan(pv_check_max, ZERO))) ? mix(color, ZERO, 0.3333) : color; + +#ifdef ENABLE_ENVIRONMENT_MAP + if (use_environment_tex) + gl_FragColor = vec4(0.45 * texture2D(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity.x, alpha); + else +#endif + gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha); + + // In the support painting gizmo and the seam painting gizmo are painted triangles rendered over the already + // rendered object. To resolved z-fighting between previously rendered object and painted triangles, values + // inside the depth buffer are offset by small epsilon for painted triangles inside those gizmos. + gl_FragDepth = gl_FragCoord.z - (offset_depth_buffer ? EPSILON : 0.0); +} diff --git a/resources/shaders/gouraud_mod.vs b/resources/shaders/gouraud_mod.vs new file mode 100644 index 000000000..79d7a63c0 --- /dev/null +++ b/resources/shaders/gouraud_mod.vs @@ -0,0 +1,73 @@ +#version 110 + +#define INTENSITY_CORRECTION 0.6 + +// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31) +const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929); +#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION) +#define LIGHT_TOP_SHININESS 20.0 + +// normalized values for (1./1.43, 0.2/1.43, 1./1.43) +const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074); +#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION) +//#define LIGHT_FRONT_SPECULAR (0.0 * INTENSITY_CORRECTION) +//#define LIGHT_FRONT_SHININESS 5.0 + +#define INTENSITY_AMBIENT 0.3 + +const vec3 ZERO = vec3(0.0, 0.0, 0.0); + +struct SlopeDetection +{ + bool actived; + float normal_z; + mat3 volume_world_normal_matrix; +}; + +uniform mat4 volume_world_matrix; +uniform SlopeDetection slope; + +// Clipping plane, x = min z, y = max z. Used by the FFF and SLA previews to clip with a top / bottom plane. +uniform vec2 z_range; +// Clipping plane - general orientation. Used by the SLA gizmo. +uniform vec4 clipping_plane; + +// x = diffuse, y = specular; +varying vec2 intensity; + +varying vec3 clipping_planes_dots; + +varying vec4 model_pos; +varying vec4 world_pos; +varying float world_normal_z; +varying vec3 eye_normal; + +void main() +{ + // First transform the normal into camera space and normalize the result. + eye_normal = normalize(gl_NormalMatrix * gl_Normal); + + // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. + // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); + + intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; + vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz; + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); + + // Perform the same lighting calculation for the 2nd light source (no specular applied). + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); + intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + + model_pos = gl_Vertex; + // Point in homogenous coordinates. + world_pos = volume_world_matrix * gl_Vertex; + + // z component of normal vector in world coordinate used for slope shading + world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * gl_Normal)).z : 0.0; + + gl_Position = ftransform(); + // Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded. + clipping_planes_dots = vec3(dot(world_pos, clipping_plane), world_pos.z - z_range.x, z_range.y - world_pos.z); +} diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 56bb7fc7c..6121fb7fd 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -720,6 +720,9 @@ void GCodeProcessor::UsedFilaments::process_caches(GCodeProcessor* processor) void GCodeProcessor::Result::reset() { moves = std::vector(); bed_shape = Pointfs(); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + max_print_height = 0.0f; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS settings_ids.reset(); extruders_count = 0; extruder_colors = std::vector(); @@ -734,6 +737,9 @@ void GCodeProcessor::Result::reset() { moves.clear(); lines_ends.clear(); bed_shape = Pointfs(); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + max_print_height = 0.0f; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS settings_ids.reset(); extruders_count = 0; extruder_colors = std::vector(); @@ -883,6 +889,10 @@ void GCodeProcessor::apply_config(const PrintConfig& config) const ConfigOptionFloatOrPercent* first_layer_height = config.option("first_layer_height"); if (first_layer_height != nullptr) m_first_layer_height = std::abs(first_layer_height->value); + +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + m_result.max_print_height = config.max_print_height; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } void GCodeProcessor::apply_config(const DynamicPrintConfig& config) @@ -1112,6 +1122,12 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) const ConfigOptionFloatOrPercent* first_layer_height = config.option("first_layer_height"); if (first_layer_height != nullptr) m_first_layer_height = std::abs(first_layer_height->value); + +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + const ConfigOptionFloat* max_print_height = config.option("max_print_height"); + if (max_print_height != nullptr) + m_result.max_print_height = max_print_height->value; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } void GCodeProcessor::enable_stealth_time_estimator(bool enabled) diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index fce888233..e7d602155 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -351,6 +351,9 @@ namespace Slic3r { // Positions of ends of lines of the final G-code this->filename after TimeProcessor::post_process() finalizes the G-code. std::vector lines_ends; Pointfs bed_shape; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + float max_print_height; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS SettingsIds settings_ids; size_t extruders_count; std::vector extruder_colors; diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index 420ab473f..4cf60dbe2 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -311,6 +311,15 @@ bool directions_parallel(double angle1, double angle2, double max_diff) return diff < max_diff || fabs(diff - PI) < max_diff; } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +bool directions_perpendicular(double angle1, double angle2, double max_diff) +{ + double diff = fabs(angle1 - angle2); + max_diff += EPSILON; + return fabs(diff - 0.5 * PI) < max_diff || fabs(diff - 1.5 * PI) < max_diff; +} +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + template bool contains(const std::vector &vector, const Point &point) { diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 505ff3d96..49886d4b8 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -84,6 +84,32 @@ static inline bool is_ccw(const Polygon &poly) return o == ORIENTATION_CCW; } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +// returns true if the given polygons are identical +static inline bool are_approx(const Polygon& lhs, const Polygon& rhs) +{ + if (lhs.points.size() != rhs.points.size()) + return false; + + size_t rhs_id = 0; + while (rhs_id < rhs.points.size()) { + if (rhs.points[rhs_id].isApprox(lhs.points.front())) + break; + ++rhs_id; + } + + if (rhs_id == rhs.points.size()) + return false; + + for (size_t i = 0; i < lhs.points.size(); ++i) { + if (!lhs.points[i].isApprox(rhs.points[(i + rhs_id) % lhs.points.size()])) + return false; + } + + return true; +} +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + inline bool ray_ray_intersection(const Vec2d &p1, const Vec2d &v1, const Vec2d &p2, const Vec2d &v2, Vec2d &res) { double denom = v1(0) * v2(1) - v2(0) * v1(1); @@ -336,6 +362,9 @@ Polygon convex_hull(Points points); Polygon convex_hull(const Polygons &polygons); bool directions_parallel(double angle1, double angle2, double max_diff = 0); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +bool directions_perpendicular(double angle1, double angle2, double max_diff = 0); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS template bool contains(const std::vector &vector, const Point &point); template T rad2deg(T angle) { return T(180.0) * angle / T(PI); } double rad2deg_dir(double angle); diff --git a/src/libslic3r/Line.cpp b/src/libslic3r/Line.cpp index 8a2a2875b..1a96b8b1f 100644 --- a/src/libslic3r/Line.cpp +++ b/src/libslic3r/Line.cpp @@ -63,6 +63,13 @@ bool Line::parallel_to(double angle) const return Slic3r::Geometry::directions_parallel(this->direction(), angle); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +bool Line::perpendicular_to(double angle) const +{ + return Slic3r::Geometry::directions_perpendicular(this->direction(), angle); +} +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + bool Line::intersection(const Line &l2, Point *intersection) const { const Line &l1 = *this; diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index bc902ed85..4a33c7c9c 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -105,6 +105,10 @@ public: double perp_distance_to(const Point &point) const; bool parallel_to(double angle) const; bool parallel_to(const Line &line) const { return this->parallel_to(line.direction()); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + bool perpendicular_to(double angle) const; + bool perpendicular_to(const Line& line) const { return this->perpendicular_to(line.direction()); } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS double atan2_() const { return atan2(this->b(1) - this->a(1), this->b(0) - this->a(0)); } double orientation() const; double direction() const; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 1ade36e36..b36618dd1 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -26,6 +26,35 @@ namespace Slic3r { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_shape, double print_volume_height, const Polygon& obj_hull_2d, double obj_min_z, double obj_max_z) +{ + if (!Geometry::intersects(printbed_shape, obj_hull_2d)) + return ModelInstancePVS_Fully_Outside; + + bool contained_xy = true; + for (const Point& p : obj_hull_2d) { + if (!printbed_shape.contains(p)) { + contained_xy = false; + break; + } + } + const bool contained_z = -1e10 < obj_min_z && obj_max_z < print_volume_height; + return (contained_xy && contained_z) ? ModelInstancePVS_Inside : ModelInstancePVS_Partly_Outside; +} + +ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_shape, double print_volume_height, const BoundingBoxf3& box) +{ + const Polygon box_hull_2d({ + { scale_(box.min.x()), scale_(box.min.y()) }, + { scale_(box.max.x()), scale_(box.min.y()) }, + { scale_(box.max.x()), scale_(box.max.y()) }, + { scale_(box.min.x()), scale_(box.max.y()) } + }); + return printbed_collision_state(printbed_shape, print_volume_height, box_hull_2d, box.min.z(), box.max.z()); +} +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + Model& Model::assign_copy(const Model &rhs) { this->copy_id(rhs); @@ -330,13 +359,23 @@ BoundingBoxf3 Model::bounding_box() const return bb; } -unsigned int Model::update_print_volume_state(const BoundingBoxf3 &print_volume) +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +unsigned int Model::update_print_volume_state(const Polygon& printbed_shape, double print_volume_height) +{ + unsigned int num_printable = 0; + for (ModelObject* model_object : this->objects) + num_printable += model_object->check_instances_print_volume_state(printbed_shape, print_volume_height); + return num_printable; +} +#else +unsigned int Model::update_print_volume_state(const BoundingBoxf3 &print_volume) { unsigned int num_printable = 0; for (ModelObject *model_object : this->objects) num_printable += model_object->check_instances_print_volume_state(print_volume); return num_printable; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS bool Model::center_instances_around_point(const Vec2d &point) { @@ -1529,6 +1568,38 @@ double ModelObject::get_instance_max_z(size_t instance_idx) const return max_z + inst->get_offset(Z); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +unsigned int ModelObject::check_instances_print_volume_state(const Polygon& printbed_shape, double print_volume_height) +{ + unsigned int num_printable = 0; + enum { + INSIDE = 1, + OUTSIDE = 2 + }; + for (ModelInstance* model_instance : this->instances) { + unsigned int inside_outside = 0; + for (const ModelVolume* vol : this->volumes) + if (vol->is_model_part()) { + const Transform3d matrix = model_instance->get_matrix() * vol->get_matrix(); + const BoundingBoxf3 bb = vol->mesh().transformed_bounding_box(matrix, 0.0); + const Polygon volume_hull_2d = its_convex_hull_2d_above(vol->mesh().its, matrix.cast(), 0.0f); + ModelInstanceEPrintVolumeState state = printbed_collision_state(printbed_shape, print_volume_height, volume_hull_2d, bb.min.z(), bb.max.z()); + if (state == ModelInstancePVS_Inside) + inside_outside |= INSIDE; + else if (state == ModelInstancePVS_Fully_Outside) + inside_outside |= OUTSIDE; + else + inside_outside |= INSIDE | OUTSIDE; + } + model_instance->print_volume_state = + (inside_outside == (INSIDE | OUTSIDE)) ? ModelInstancePVS_Partly_Outside : + (inside_outside == INSIDE) ? ModelInstancePVS_Inside : ModelInstancePVS_Fully_Outside; + if (inside_outside == INSIDE) + ++num_printable; + } + return num_printable; +} +#else unsigned int ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume) { unsigned int num_printable = 0; @@ -1556,6 +1627,7 @@ unsigned int ModelObject::check_instances_print_volume_state(const BoundingBoxf3 } return num_printable; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS void ModelObject::print_info() const { diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 11cbdc0cf..0a6a73cbc 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -367,7 +367,11 @@ public: double get_instance_max_z(size_t instance_idx) const; // Called by Print::validate() from the UI thread. +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + unsigned int check_instances_print_volume_state(const Polygon& printbed_shape, double print_volume_height); +#else unsigned int check_instances_print_volume_state(const BoundingBoxf3& print_volume); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // Print object statistics to console. void print_info() const; @@ -904,6 +908,14 @@ enum ModelInstanceEPrintVolumeState : unsigned char ModelInstanceNum_BedStates }; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +// return the state of the given object's volume (extrusion along z of obj_hull_2d from obj_min_z to obj_max_z) +// with respect to the given print volume (extrusion along z of printbed_shape from zero to print_volume_height) +ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_shape, double print_volume_height, const Polygon& obj_hull_2d, double obj_min_z, double obj_max_z); +// return the state of the given box +// with respect to the given print volume (extrusion along z of printbed_shape from zero to print_volume_height) +ModelInstanceEPrintVolumeState printbed_collision_state(const Polygon& printbed_shape, double print_volume_height, const BoundingBoxf3& box); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // A single instance of a ModelObject. // Knows the affine transformation of an object. @@ -1109,8 +1121,12 @@ public: BoundingBoxf3 bounding_box() const; // Set the print_volume_state of PrintObject::instances, // return total number of printable objects. +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + unsigned int update_print_volume_state(const Polygon& printbed_shape, double print_volume_height); +#else unsigned int update_print_volume_state(const BoundingBoxf3 &print_volume); - // Returns true if any ModelObject was modified. +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + // Returns true if any ModelObject was modified. bool center_instances_around_point(const Vec2d &point); void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); } TriangleMesh mesh() const; diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index acbb4731d..b22448a88 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -81,5 +81,9 @@ // Enable rendering modifiers and similar objects always as transparent #define ENABLE_MODIFIERS_ALWAYS_TRANSPARENT (1 && ENABLE_2_4_0_ALPHA4) +// Enable the fix for the detection of the out of bed state for sinking objects +// and detection of out of bed using the bed perimeter +#define ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS (1 && ENABLE_2_4_0_ALPHA4) + #endif // _prusaslicer_technologies_h_ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index bb3c9fc5c..09cb89372 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -435,6 +435,31 @@ BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) c return bbox; } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d& trafo, double world_min_z) const +{ + BoundingBoxf3 bbox; + const Transform3f ftrafo = trafo.cast(); + for (const stl_triangle_vertex_indices& tri : its.indices) { + const Vec3f pts[3] = { ftrafo * its.vertices[tri(0)], ftrafo * its.vertices[tri(1)], ftrafo * its.vertices[tri(2)] }; + int iprev = 2; + for (int iedge = 0; iedge < 3; ++iedge) { + const Vec3f& p1 = pts[iprev]; + const Vec3f& p2 = pts[iedge]; + if ((p1.z() < world_min_z && p2.z() > world_min_z) || (p2.z() < world_min_z && p1.z() > world_min_z)) { + // Edge crosses the z plane. Calculate intersection point with the plane. + const float t = (world_min_z - p1.z()) / (p2.z() - p1.z()); + bbox.merge(Vec3f(p1.x() + (p2.x() - p1.x()) * t, p1.y() + (p2.y() - p1.y()) * t, world_min_z).cast()); + } + if (p2.z() >= world_min_z) + bbox.merge(p2.cast()); + iprev = iedge; + } + } + return bbox; +} +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + TriangleMesh TriangleMesh::convex_hull_3d() const { // The qhull call: diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index 5223631c0..ec6401982 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -125,6 +125,10 @@ public: BoundingBoxf3 bounding_box() const; // Returns the bbox of this TriangleMesh transformed by the given transformation BoundingBoxf3 transformed_bounding_box(const Transform3d &trafo) const; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + // Variant returning the bbox of the part of this TriangleMesh above the given world_min_z + BoundingBoxf3 transformed_bounding_box(const Transform3d& trafo, double world_min_z) const; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // Return the size of the mesh in coordinates. Vec3d size() const { return m_stats.size.cast(); } /// Return the center of the related bounding box. diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 5b7218c87..18c017da1 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -7,11 +7,10 @@ #include "libslic3r/BoundingBox.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/Tesselate.hpp" +#include "libslic3r/PresetBundle.hpp" #include "GUI_App.hpp" -#include "libslic3r/PresetBundle.hpp" #include "GLCanvas3D.hpp" -#include "3DScene.hpp" #include @@ -154,7 +153,11 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c std::string model; std::string texture; if (force_as_custom) +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + type = EType::Custom; +#else type = Custom; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS else { auto [new_type, system_model, system_texture] = detect_type(shape); type = new_type; @@ -174,7 +177,12 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c model_filename.clear(); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + EShapeType shape_type = detect_shape_type(shape); + if (m_shape == shape && m_type == type && m_shape_type == shape_type && m_texture_filename == texture_filename && m_model_filename == model_filename) +#else if (m_shape == shape && m_type == type && m_texture_filename == texture_filename && m_model_filename == model_filename) +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // No change, no need to update the UI. return false; @@ -182,6 +190,9 @@ bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, c m_texture_filename = texture_filename; m_model_filename = model_filename; m_type = type; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + m_shape_type = shape_type; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS calc_bounding_boxes(); @@ -229,6 +240,84 @@ void Bed3D::render_for_picking(GLCanvas3D& canvas, bool bottom, float scale_fact render_internal(canvas, bottom, scale_factor, false, false, true); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +bool Bed3D::is_rectangle(const Pointfs& shape, Vec2d* min, Vec2d* max) +{ + const Lines lines = Polygon::new_scale(shape).lines(); + bool ret = lines.size() == 4 && lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3]) && lines[0].perpendicular_to(lines[1]); + if (ret) { + if (min != nullptr) { + *min = shape.front(); + for (const Vec2d& pt : shape) { + min->x() = std::min(min->x(), pt.x()); + min->y() = std::min(min->y(), pt.y()); + } + } + if (max != nullptr) { + *max = shape.front(); + for (const Vec2d& pt : shape) { + max->x() = std::max(max->x(), pt.x()); + max->y() = std::max(max->y(), pt.y()); + } + } + } + return ret; +} + +bool Bed3D::is_circle(const Pointfs& shape, Vec2d* center, double* radius) +{ + if (shape.size() < 3) + return false; + + // Analyze the array of points. + // Do they reside on a circle ? + const Vec2d box_center = BoundingBoxf(shape).center(); + std::vector vertex_distances; + double avg_dist = 0.0; + for (const Vec2d& pt : shape) { + double distance = (pt - box_center).norm(); + vertex_distances.push_back(distance); + avg_dist += distance; + } + + avg_dist /= vertex_distances.size(); + + double tolerance = avg_dist * 0.01; + + bool defined_value = true; + for (double el : vertex_distances) { + if (fabs(el - avg_dist) > tolerance) + defined_value = false; + break; + } + + if (center != nullptr) + *center = box_center; + + if (radius != nullptr) + *radius = avg_dist; + + return defined_value; +} + +bool Bed3D::is_convex(const Pointfs& shape) +{ + return Polygon::new_scale(shape).convex_points().size() == shape.size(); +} + +Bed3D::EShapeType Bed3D::detect_shape_type(const Pointfs& shape) +{ + if (shape.size() < 3) + return EShapeType::Invalid; + else if (is_rectangle(shape)) + return EShapeType::Rectangle; + else if (is_circle(shape)) + return EShapeType::Circle; + else + return EShapeType::Custom; +} +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + void Bed3D::render_internal(GLCanvas3D& canvas, bool bottom, float scale_factor, bool show_axes, bool show_texture, bool picking) { @@ -244,9 +333,15 @@ void Bed3D::render_internal(GLCanvas3D& canvas, bool bottom, float scale_factor, switch (m_type) { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + case EType::System: { render_system(canvas, bottom, show_texture); break; } + default: + case EType::Custom: { render_custom(canvas, bottom, show_texture, picking); break; } +#else case System: { render_system(canvas, bottom, show_texture); break; } default: case Custom: { render_custom(canvas, bottom, show_texture, picking); break; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } glsafe(::glDisable(GL_DEPTH_TEST)); @@ -320,7 +415,11 @@ std::tuple Bed3D::detect_type(const Poin std::string model_filename = PresetUtils::system_printer_bed_model(*curr); std::string texture_filename = PresetUtils::system_printer_bed_texture(*curr); if (!model_filename.empty() && !texture_filename.empty()) +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + return { EType::System, model_filename, texture_filename }; +#else return { System, model_filename, texture_filename }; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } } @@ -328,7 +427,11 @@ std::tuple Bed3D::detect_type(const Poin } } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + return { EType::Custom, "", "" }; +#else return { Custom, "", "" }; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } void Bed3D::render_axes() const diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index a2a643519..07b9f1758 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -62,15 +62,36 @@ class Bed3D }; public: +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + enum class EType : unsigned char + { + System, + Custom + }; + + enum class EShapeType : unsigned char + { + Rectangle, + Circle, + Custom, + Invalid + }; +#else enum EType : unsigned char { System, Custom, Num_Types }; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS private: +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + EType m_type{ EType::Custom }; + EShapeType m_shape_type{ EShapeType::Invalid }; +#else EType m_type{ Custom }; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS Pointfs m_shape; std::string m_texture_filename; std::string m_model_filename; @@ -94,16 +115,18 @@ public: ~Bed3D() { reset(); } EType get_type() const { return m_type; } - +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + EShapeType get_shape_type() const { return m_shape_type; } + bool is_custom() const { return m_type == EType::Custom; } +#else bool is_custom() const { return m_type == Custom; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS const Pointfs& get_shape() const { return m_shape; } // Return true if the bed shape changed, so the calee will update the UI. bool set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model, bool force_as_custom = false); - const BoundingBoxf3& get_bounding_box(bool extended) const { - return extended ? m_extended_bounding_box : m_bounding_box; - } + const BoundingBoxf3& get_bounding_box(bool extended) const { return extended ? m_extended_bounding_box : m_bounding_box; } bool contains(const Point& point) const; Point point_projection(const Point& point) const; @@ -113,6 +136,13 @@ public: void render_for_picking(GLCanvas3D& canvas, bool bottom, float scale_factor); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + static bool is_rectangle(const Pointfs& shape, Vec2d* min = nullptr, Vec2d* max = nullptr); + static bool is_circle(const Pointfs& shape, Vec2d* center = nullptr, double* radius = nullptr); + static bool is_convex(const Pointfs& shape); + static EShapeType detect_shape_type(const Pointfs& shape); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + private: void calc_bounding_boxes() const; void calc_triangles(const ExPolygon& poly); diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 98665177f..26a64fdb7 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -10,6 +10,10 @@ #include "GLShader.hpp" #include "GUI_App.hpp" #include "Plater.hpp" +#include "BitmapCache.hpp" +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +#include "3DBed.hpp" +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/ExtrusionEntityCollection.hpp" @@ -17,7 +21,6 @@ #include "libslic3r/Print.hpp" #include "libslic3r/SLAPrint.hpp" #include "libslic3r/Slicing.hpp" -#include "slic3r/GUI/BitmapCache.hpp" #include "libslic3r/Format/STL.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/AppConfig.hpp" @@ -37,6 +40,12 @@ #include +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +#include +#include +#include +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + #ifdef HAS_GLSAFE void glAssertRecentCallImpl(const char* file_name, unsigned int line, const char* function_name) { @@ -261,6 +270,12 @@ void GLIndexedVertexArray::render( const std::pair& tverts_range, const std::pair& qverts_range) const { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + // this method has been called before calling finalize() ? + if (this->vertices_and_normals_interleaved_VBO_id == 0 && !this->vertices_and_normals_interleaved.empty()) + return; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + assert(this->vertices_and_normals_interleaved_VBO_id != 0); assert(this->triangle_indices_VBO_id != 0 || this->quad_indices_VBO_id != 0); @@ -517,6 +532,23 @@ BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d & bounding_box().transformed(trafo); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +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* trans_box = const_cast*>(&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; +} +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + void GLVolume::set_range(double min_z, double max_z) { this->qverts_range.first = 0; @@ -591,6 +623,106 @@ void GLVolume::render_sinking_contours() m_sinking_contours.render(); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +void GLVolume::calc_convex_hull_3d() +{ + if (this->indexed_vertex_array.vertices_and_normals_interleaved.empty()) + return; + + TriangleMesh mesh; + for (size_t i = 0; i < this->indexed_vertex_array.vertices_and_normals_interleaved.size(); i += 6) { + const size_t v_id = 3 + i; + mesh.its.vertices.push_back({ this->indexed_vertex_array.vertices_and_normals_interleaved[v_id + 0], + this->indexed_vertex_array.vertices_and_normals_interleaved[v_id + 1], + this->indexed_vertex_array.vertices_and_normals_interleaved[v_id + 2] + }); + } + + const std::vector& vertices = mesh.its.vertices; + + // The qhull call: + orgQhull::Qhull qhull; + qhull.disableOutputStream(); // we want qhull to be quiet + std::vector src_vertices; + try + { +#if REALfloat + qhull.runQhull("", 3, (int)vertices.size(), (const realT*)(vertices.front().data()), "Qt"); +#else + src_vertices.reserve(vertices.size() * 3); + // We will now fill the vector with input points for computation: + for (const stl_vertex& v : vertices) + for (int i = 0; i < 3; ++i) + src_vertices.emplace_back(v(i)); + qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); +#endif + } + catch (...) + { + std::cout << "GLVolume::calc_convex_hull_3d() - Unable to create convex hull" << std::endl; + return ; + } + + // Let's collect results: + std::vector dst_vertices; + std::vector dst_facets; + // Map of QHull's vertex ID to our own vertex ID (pointing to dst_vertices). + std::vector map_dst_vertices; +#ifndef NDEBUG + Vec3f centroid = Vec3f::Zero(); + for (const auto& pt : vertices) + centroid += pt; + centroid /= float(vertices.size()); +#endif // NDEBUG + for (const orgQhull::QhullFacet& facet : qhull.facetList()) { + // Collect face vertices first, allocate unique vertices in dst_vertices based on QHull's vertex ID. + Vec3i indices; + int cnt = 0; + for (const orgQhull::QhullVertex vertex : facet.vertices()) { + const int id = vertex.id(); + assert(id >= 0); + if (id >= int(map_dst_vertices.size())) + map_dst_vertices.resize(next_highest_power_of_2(size_t(id + 1)), -1); + if (int i = map_dst_vertices[id]; i == -1) { + // Allocate a new vertex. + i = int(dst_vertices.size()); + map_dst_vertices[id] = i; + orgQhull::QhullPoint pt(vertex.point()); + dst_vertices.emplace_back(pt[0], pt[1], pt[2]); + indices[cnt] = i; + } + else + // Reuse existing vertex. + indices[cnt] = i; + + if (cnt++ == 3) + break; + } + assert(cnt == 3); + if (cnt == 3) { + // QHull sorts vertices of a face lexicographically by their IDs, not by face normals. + // Calculate face normal based on the order of vertices. + const Vec3f n = (dst_vertices[indices(1)] - dst_vertices[indices(0)]).cross(dst_vertices[indices(2)] - dst_vertices[indices(1)]); + auto* n2 = facet.getBaseT()->normal; + const auto d = n.x() * n2[0] + n.y() * n2[1] + n.z() * n2[2]; +#ifndef NDEBUG + const Vec3f n3 = (dst_vertices[indices(0)] - centroid); + const auto d3 = n.dot(n3); + assert((d < 0.f) == (d3 < 0.f)); +#endif // NDEBUG + // Get the face normal from QHull. + if (d < 0.f) + // Fix face orientation. + std::swap(indices[1], indices[2]); + dst_facets.emplace_back(indices); + } + } + + TriangleMesh out_mesh{ std::move(dst_vertices), std::move(dst_facets) }; + this->set_convex_hull(out_mesh); +} +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + std::vector GLVolumeCollection::load_object( const ModelObject *model_object, int obj_idx, @@ -748,7 +880,10 @@ int GLVolumeCollection::load_wipe_tower_preview( volumes.emplace_back(new GLVolume(color)); GLVolume& v = *volumes.back(); - v.indexed_vertex_array.load_mesh(mesh); + v.indexed_vertex_array.load_mesh(mesh); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + v.set_convex_hull(mesh.convex_hull_3d()); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS v.indexed_vertex_array.finalize_geometry(opengl_initialized); v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0)); v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle)); @@ -855,10 +990,17 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab shader->set_uniform("uniform_color", volume.first->render_color); shader->set_uniform("z_range", m_z_range, 2); shader->set_uniform("clipping_plane", m_clipping_plane, 4); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + shader->set_uniform("print_volume.type", static_cast(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); + shader->set_uniform("volume_world_matrix", volume.first->world_matrix()); +#else shader->set_uniform("print_box.min", m_print_box_min, 3); shader->set_uniform("print_box.max", m_print_box_max, 3); shader->set_uniform("print_box.actived", volume.first->shader_outside_printer_detection_enabled); shader->set_uniform("print_box.volume_world_matrix", volume.first->world_matrix()); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS shader->set_uniform("slope.actived", m_slope.active && !volume.first->is_modifier && !volume.first->is_wipe_tower); shader->set_uniform("slope.volume_world_normal_matrix", static_cast(volume.first->world_matrix().matrix().block(0, 0, 3, 3).inverse().transpose().cast())); shader->set_uniform("slope.normal_z", m_slope.normal_z); @@ -907,7 +1049,11 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab glsafe(::glDisable(GL_BLEND)); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state, bool as_toolpaths) const +#else bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state) const +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS { if (config == nullptr) return false; @@ -916,22 +1062,95 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M if (opt == nullptr) return false; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + const Polygon bed_poly = offset(Polygon::new_scale(opt->values), static_cast(scale_(BedEpsilon))).front(); + const float bed_height = config->opt_float("max_print_height"); + const BoundingBox bed_box_2D = get_extents(bed_poly); + BoundingBoxf3 print_volume({ unscale(bed_box_2D.min.x()), unscale(bed_box_2D.min.y()), -1e10 }, + { unscale(bed_box_2D.max.x()), unscale(bed_box_2D.max.y()), bed_height }); + + auto check_against_rectangular_bed = [&print_volume](GLVolume& volume, ModelInstanceEPrintVolumeState& state) { + const BoundingBoxf3* const bb = volume.is_sinking() ? &volume.transformed_non_sinking_bounding_box() : &volume.transformed_convex_hull_bounding_box(); + volume.is_outside = !print_volume.contains(*bb); + if (volume.printable) { + if (state == ModelInstancePVS_Inside && volume.is_outside) + state = ModelInstancePVS_Fully_Outside; + if (state == ModelInstancePVS_Fully_Outside && volume.is_outside && print_volume.intersects(*bb)) + state = ModelInstancePVS_Partly_Outside; + } + }; + + auto check_against_circular_bed = [](GLVolume& volume, ModelInstanceEPrintVolumeState& state, const Vec2d& center, double radius) { + const TriangleMesh* mesh = volume.is_sinking() ? &GUI::wxGetApp().plater()->model().objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh() : volume.convex_hull(); + const Polygon volume_hull_2d = its_convex_hull_2d_above(mesh->its, volume.world_matrix().cast(), 0.0f); + size_t outside_count = 0; + const double sq_radius = sqr(radius); + for (const Point& p : volume_hull_2d.points) { + if (sq_radius < (unscale(p) - center).squaredNorm()) + ++outside_count; + } + + volume.is_outside = outside_count > 0; + if (volume.printable) { + if (state == ModelInstancePVS_Inside && volume.is_outside) + state = ModelInstancePVS_Fully_Outside; + if (state == ModelInstancePVS_Fully_Outside && volume.is_outside && outside_count < volume_hull_2d.size()) + state = ModelInstancePVS_Partly_Outside; + } + }; + + auto check_against_convex_bed = [&bed_poly, bed_height](GLVolume& volume, ModelInstanceEPrintVolumeState& state) { + const TriangleMesh* mesh = volume.is_sinking() ? &GUI::wxGetApp().plater()->model().objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh() : volume.convex_hull(); + const Polygon volume_hull_2d = its_convex_hull_2d_above(mesh->its, volume.world_matrix().cast(), 0.0f); + const BoundingBoxf3* const bb = volume.is_sinking() ? &volume.transformed_non_sinking_bounding_box() : &volume.transformed_convex_hull_bounding_box(); + ModelInstanceEPrintVolumeState volume_state = printbed_collision_state(bed_poly, bed_height, volume_hull_2d, bb->min.z(), bb->max.z()); + bool contained = (volume_state == ModelInstancePVS_Inside); + bool intersects = (volume_state == ModelInstancePVS_Partly_Outside); + + volume.is_outside = !contained; + if (volume.printable) { + if (state == ModelInstancePVS_Inside && volume.is_outside) + state = ModelInstancePVS_Fully_Outside; + + if (state == ModelInstancePVS_Fully_Outside && volume.is_outside && intersects) + state = ModelInstancePVS_Partly_Outside; + } + }; +#else const BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); - BoundingBoxf3 print_volume({ unscale(bed_box_2D.min.x()), unscale(bed_box_2D.min.y()), 0.0 }, - { unscale(bed_box_2D.max.x()), unscale(bed_box_2D.max.y()), - config->opt_float("max_print_height") }); + BoundingBoxf3 print_volume({ unscale(bed_box_2D.min.x()), unscale(bed_box_2D.min.y()), 0.0 }, + { unscale(bed_box_2D.max.x()), unscale(bed_box_2D.max.y()), config->opt_float("max_print_height") }); // Allow the objects to protrude below the print bed - print_volume.min(2) = -1e10; - print_volume.min(0) -= BedEpsilon; - print_volume.min(1) -= BedEpsilon; - print_volume.max(0) += BedEpsilon; - print_volume.max(1) += BedEpsilon; - - ModelInstanceEPrintVolumeState state = ModelInstancePVS_Inside; + print_volume.min.z() = -1e10; + print_volume.min.x() -= BedEpsilon; + print_volume.min.y() -= BedEpsilon; + print_volume.max.x() += BedEpsilon; + print_volume.max.y() += BedEpsilon; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + ModelInstanceEPrintVolumeState overall_state = ModelInstancePVS_Inside; bool contained_min_one = false; for (GLVolume* volume : this->volumes) { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (as_toolpaths && !volume->is_extrusion_path) + continue; + else if (!as_toolpaths && (volume->is_modifier || (!volume->shader_outside_printer_detection_enabled && (volume->is_wipe_tower || volume->composite_id.volume_id < 0)))) + continue; + + if (GUI::Bed3D::is_rectangle(opt->values)) + check_against_rectangular_bed(*volume, overall_state); + else { + Vec2d center; + double radius; + if (GUI::Bed3D::is_circle(opt->values, ¢er, &radius)) + check_against_circular_bed(*volume, overall_state, center, radius); + else if (GUI::Bed3D::is_convex(opt->values)) + check_against_convex_bed(*volume, overall_state); + } + + contained_min_one |= !volume->is_outside; +#else if (volume->is_modifier || (!volume->shader_outside_printer_detection_enabled && (volume->is_wipe_tower || volume->composite_id.volume_id < 0))) continue; @@ -944,15 +1163,16 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M contained_min_one |= contained; - if (state == ModelInstancePVS_Inside && volume->is_outside) - state = ModelInstancePVS_Fully_Outside; + if (overall_state == ModelInstancePVS_Inside && volume->is_outside) + overall_state = ModelInstancePVS_Fully_Outside; - if (state == ModelInstancePVS_Fully_Outside && volume->is_outside && print_volume.intersects(bb)) - state = ModelInstancePVS_Partly_Outside; + if (overall_state == ModelInstancePVS_Fully_Outside && volume->is_outside && print_volume.intersects(bb)) + overall_state = ModelInstancePVS_Partly_Outside; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } if (out_state != nullptr) - *out_state = state; + *out_state = overall_state; return contained_min_one; } @@ -1008,35 +1228,28 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con std::vector colors(colors_count); unsigned char rgb[3]; - for (unsigned int i = 0; i < colors_count; ++i) - { + for (unsigned int i = 0; i < colors_count; ++i) { const std::string& txt_color = config->opt_string("extruder_colour", i); if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb)) - { colors[i].set(txt_color, rgb); - } - else - { + else { const std::string& txt_color = config->opt_string("filament_colour", i); if (Slic3r::GUI::BitmapCache::parse_color(txt_color, rgb)) colors[i].set(txt_color, rgb); } } - for (GLVolume* volume : volumes) - { - if ((volume == nullptr) || volume->is_modifier || volume->is_wipe_tower || (volume->volume_idx() < 0)) + 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)) + if (extruder_id < 0 || (int)colors.size() <= extruder_id) extruder_id = 0; const Color& color = colors[extruder_id]; - if (!color.text.empty()) - { - for (int i = 0; i < 3; ++i) - { + if (!color.text.empty()) { + for (int i = 0; i < 3; ++i) { volume->color[i] = (float)color.rgb[i] * inv_255; } } diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 78b9a96d9..14597f22a 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -41,7 +41,6 @@ enum ModelInstanceEPrintVolumeState : unsigned char; // Return appropriate color based on the ModelVolume. std::array color_from_model_volume(const ModelVolume& model_volume); - // A container for interleaved arrays of 3D vertices and normals, // possibly indexed by triangles and / or quads. class GLIndexedVertexArray { @@ -279,6 +278,10 @@ private: std::shared_ptr m_convex_hull; // Bounding box of this volume, in unscaled coordinates. std::optional m_transformed_convex_hull_bounding_box; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + // Bounding box of the non sinking part of this volume, in unscaled coordinates. + std::optional m_transformed_non_sinking_bounding_box; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS class SinkingContours { @@ -469,6 +472,12 @@ public: BoundingBoxf3 transformed_convex_hull_bounding_box(const Transform3d &trafo) const; // caching variant const BoundingBoxf3& transformed_convex_hull_bounding_box() const; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + // non-caching variant + BoundingBoxf3 transformed_non_sinking_bounding_box(const Transform3d& trafo) const; + // caching variant + const BoundingBoxf3& transformed_non_sinking_bounding_box() const; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // convex hull const TriangleMesh* convex_hull() const { return m_convex_hull.get(); } @@ -481,7 +490,15 @@ public: void finalize_geometry(bool opengl_initialized) { this->indexed_vertex_array.finalize_geometry(opengl_initialized); } void release_geometry() { this->indexed_vertex_array.release_geometry(); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + void set_bounding_boxes_as_dirty() { + m_transformed_bounding_box.reset(); + m_transformed_convex_hull_bounding_box.reset(); + m_transformed_non_sinking_bounding_box.reset(); + } +#else void set_bounding_boxes_as_dirty() { m_transformed_bounding_box.reset(); m_transformed_convex_hull_bounding_box.reset(); } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS bool is_sla_support() const; bool is_sla_pad() const; @@ -498,6 +515,12 @@ public: // Return an estimate of the memory held by GPU vertex buffers. size_t gpu_memory_used() const { return this->indexed_vertex_array.gpu_memory_used(); } size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); } + +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + // calculates the 3D convex hull from indexed_vertex_array.vertices_and_normals_interleaved + // must be called before calling indexed_vertex_array.finalize_geometry(); + void calc_convex_hull_3d(); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS }; typedef std::vector GLVolumePtrs; @@ -514,10 +537,30 @@ public: All }; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + struct PrintVolume + { + // see: Bed3D::EShapeType + int type{ 0 }; + // data contains: + // Rectangle: + // [0] = min.x, [1] = min.y, [2] = max.x, [3] = max.y + // Circle: + // [0] = center.x, [1] = center.y, [3] = radius + std::array data; + // [0] = min z, [1] = max z + std::array zs; + }; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + private: +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + PrintVolume m_print_volume; +#else // min and max vertex of the print box volume float m_print_box_min[3]; float m_print_box_max[3]; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // z range for clipping in shaders float m_z_range[2]; @@ -589,10 +632,14 @@ public: bool empty() const { return volumes.empty(); } void set_range(double low, double high) { for (GLVolume *vol : this->volumes) vol->set_range(low, high); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + void set_print_volume(const PrintVolume& print_volume) { m_print_volume = print_volume; } +#else void set_print_box(float min_x, float min_y, float min_z, float max_x, float max_y, float max_z) { m_print_box_min[0] = min_x; m_print_box_min[1] = min_y; m_print_box_min[2] = min_z; m_print_box_max[0] = max_x; m_print_box_max[1] = max_y; m_print_box_max[2] = max_z; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS void set_z_range(float min_z, float max_z) { m_z_range[0] = min_z; m_z_range[1] = max_z; } void set_clipping_plane(const double* coeffs) { m_clipping_plane[0] = coeffs[0]; m_clipping_plane[1] = coeffs[1]; m_clipping_plane[2] = coeffs[2]; m_clipping_plane[3] = coeffs[3]; } @@ -607,7 +654,11 @@ public: // 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 +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + bool check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state, bool as_toolpaths = false) const; +#else bool check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state) const; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS void reset_outside_state(); void update_colors_by_extruder(const DynamicPrintConfig* config); diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 40d9ee3b2..0b8e31e13 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -22,9 +22,25 @@ namespace GUI { BedShape::BedShape(const ConfigOptionPoints& points) { - auto polygon = Polygon::new_scale(points.values); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (points.size() < 3) { + m_type = Bed3D::EShapeType::Invalid; + return; + } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // is this a rectangle ? +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + Vec2d min; + Vec2d max; + if (Bed3D::is_rectangle(points.values, &min, &max)) { + m_type = Bed3D::EShapeType::Rectangle; + m_rectSize = max - min; + m_rectOrigin = -min; + return; + } +#else + Polygon polygon = Polygon::new_scale(points.values); if (points.size() == 4) { auto lines = polygon.lines(); if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) { @@ -48,8 +64,21 @@ BedShape::BedShape(const ConfigOptionPoints& points) return; } } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // is this a circle ? +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + Vec2d center; + double radius; + if (Bed3D::is_circle(points.values, ¢er, &radius)) { + m_type = Bed3D::EShapeType::Circle; + m_diameter = 2.0 * radius; + return; + } + + // This is a custom bed shape, use the polygon provided. + m_type = Bed3D::EShapeType::Custom; +#else { // Analyze the array of points.Do they reside on a circle ? auto center = polygon.bounding_box().center(); @@ -79,11 +108,12 @@ BedShape::BedShape(const ConfigOptionPoints& points) } } - if (points.size() < 3) + if (points.size() < 3) return; // This is a custom bed shape, use the polygon provided. m_type = Type::Custom; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } static std::string get_option_label(BedShape::Parameter param) @@ -134,31 +164,56 @@ void BedShape::append_option_line(ConfigOptionsGroupShp optgroup, Parameter para } } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +wxString BedShape::get_name(Bed3D::EShapeType type) +{ + switch (type) { + case Bed3D::EShapeType::Rectangle: { return _L("Rectangular"); } + case Bed3D::EShapeType::Circle: { return _L("Circular"); } + case Bed3D::EShapeType::Custom: { return _L("Custom"); } + case Bed3D::EShapeType::Invalid: + default: return _L("Invalid"); + } +} +#else wxString BedShape::get_name(Type type) { switch (type) { - case Type::Rectangular : return _L("Rectangular"); - case Type::Circular : return _L("Circular"); - case Type::Custom : return _L("Custom"); - case Type::Invalid : - default : return _L("Invalid"); + case Type::Rectangular: return _L("Rectangular"); + case Type::Circular: return _L("Circular"); + case Type::Custom: return _L("Custom"); + case Type::Invalid: + default: return _L("Invalid"); } } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS size_t BedShape::get_type() { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + return static_cast(m_type == Bed3D::EShapeType::Invalid ? Bed3D::EShapeType::Rectangle : m_type); +#else return static_cast(m_type == Type::Invalid ? Type::Rectangular : m_type); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } wxString BedShape::get_full_name_with_params() { wxString out = _L("Shape") + ": " + get_name(m_type); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (m_type == Bed3D::EShapeType::Rectangle) { +#else if (m_type == Type::Rectangular) { +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS out += "\n" + _(get_option_label(Parameter::RectSize)) + ": [" + ConfigOptionPoint(m_rectSize).serialize() + "]"; out += "\n" + _(get_option_label(Parameter::RectOrigin))+ ": [" + ConfigOptionPoint(m_rectOrigin).serialize() + "]"; } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + else if (m_type == Bed3D::EShapeType::Circle) +#else else if (m_type == Type::Circular) +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS out += "\n" + _L(get_option_label(Parameter::Diameter)) + ": [" + double_to_string(m_diameter) + "]"; return out; @@ -166,11 +221,19 @@ wxString BedShape::get_full_name_with_params() void BedShape::apply_optgroup_values(ConfigOptionsGroupShp optgroup) { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (m_type == Bed3D::EShapeType::Rectangle || m_type == Bed3D::EShapeType::Invalid) { +#else if (m_type == Type::Rectangular || m_type == Type::Invalid) { +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS optgroup->set_value("rect_size" , new ConfigOptionPoints{ m_rectSize }); optgroup->set_value("rect_origin" , new ConfigOptionPoints{ m_rectOrigin }); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + else if (m_type == Bed3D::EShapeType::Circle) +#else else if (m_type == Type::Circular) +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS optgroup->set_value("diameter", double_to_string(m_diameter)); } @@ -222,7 +285,7 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf m_custom_texture = custom_texture.value.empty() ? NONE : custom_texture.value; m_custom_model = custom_model.value.empty() ? NONE : custom_model.value; - auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _(L("Shape"))); + auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _L("Shape")); sbsizer->GetStaticBox()->SetFont(wxGetApp().bold_font()); wxGetApp().UpdateDarkUI(sbsizer->GetStaticBox()); @@ -232,16 +295,28 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf sbsizer->Add(m_shape_options_book); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + auto optgroup = init_shape_options_page(BedShape::get_name(Bed3D::EShapeType::Rectangle)); +#else auto optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Rectangular)); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS BedShape::append_option_line(optgroup, BedShape::Parameter::RectSize); BedShape::append_option_line(optgroup, BedShape::Parameter::RectOrigin); activate_options_page(optgroup); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + optgroup = init_shape_options_page(BedShape::get_name(Bed3D::EShapeType::Circle)); +#else optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Circular)); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS BedShape::append_option_line(optgroup, BedShape::Parameter::Diameter); activate_options_page(optgroup); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + optgroup = init_shape_options_page(BedShape::get_name(Bed3D::EShapeType::Custom)); +#else optgroup = init_shape_options_page(BedShape::get_name(BedShape::Type::Custom)); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS Line line{ "", "" }; line.full_width = 1; @@ -265,10 +340,7 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf wxPanel* texture_panel = init_texture_panel(); wxPanel* model_panel = init_model_panel(); - Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e) - { - update_shape(); - })); + Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e) { update_shape(); })); // right pane with preview canvas m_canvas = new Bed_2D(this); @@ -295,7 +367,7 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title) { wxPanel* panel = new wxPanel(m_shape_options_book); - ConfigOptionsGroupShp optgroup = std::make_shared(panel, _(L("Settings"))); + ConfigOptionsGroupShp optgroup = std::make_shared(panel, _L("Settings")); optgroup->label_width = 10; optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { @@ -319,7 +391,7 @@ wxPanel* BedShapePanel::init_texture_panel() { wxPanel* panel = new wxPanel(this); wxGetApp().UpdateDarkUI(panel, true); - ConfigOptionsGroupShp optgroup = std::make_shared(panel, _(L("Texture"))); + ConfigOptionsGroupShp optgroup = std::make_shared(panel, _L("Texture")); optgroup->label_width = 10; optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { @@ -329,7 +401,7 @@ wxPanel* BedShapePanel::init_texture_panel() Line line{ "", "" }; line.full_width = 1; line.widget = [this](wxWindow* parent) { - wxButton* load_btn = new wxButton(parent, wxID_ANY, _(L("Load..."))); + wxButton* load_btn = new wxButton(parent, wxID_ANY, _L("Load...")); wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL); load_sizer->Add(load_btn, 1, wxEXPAND); @@ -338,7 +410,7 @@ wxPanel* BedShapePanel::init_texture_panel() wxSizer* filename_sizer = new wxBoxSizer(wxHORIZONTAL); filename_sizer->Add(filename_lbl, 1, wxEXPAND); - wxButton* remove_btn = new wxButton(parent, wxID_ANY, _(L("Remove"))); + wxButton* remove_btn = new wxButton(parent, wxID_ANY, _L("Remove")); wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL); remove_sizer->Add(remove_btn, 1, wxEXPAND); @@ -347,31 +419,23 @@ wxPanel* BedShapePanel::init_texture_panel() sizer->Add(load_sizer, 1, wxEXPAND); sizer->Add(remove_sizer, 1, wxEXPAND | wxTOP, 2); - load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) - { - load_texture(); - })); - - remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) - { + load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) { load_texture(); })); + remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) { m_custom_texture = NONE; update_shape(); })); - filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) - { + filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) { e.SetText(_(boost::filesystem::path(m_custom_texture).filename().string())); wxStaticText* lbl = dynamic_cast(e.GetEventObject()); - if (lbl != nullptr) - { + if (lbl != nullptr) { bool exists = (m_custom_texture == NONE) || boost::filesystem::exists(m_custom_texture); lbl->SetForegroundColour(exists ? /*wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)*/wxGetApp().get_label_clr_default() : wxColor(*wxRED)); wxString tooltip_text = ""; - if (m_custom_texture != NONE) - { + if (m_custom_texture != NONE) { if (!exists) - tooltip_text += _(L("Not found:")) + " "; + tooltip_text += _L("Not found:") + " "; tooltip_text += _(m_custom_texture); } @@ -382,10 +446,7 @@ wxPanel* BedShapePanel::init_texture_panel() } })); - remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) - { - e.Enable(m_custom_texture != NONE); - })); + remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) { e.Enable(m_custom_texture != NONE); })); return sizer; }; @@ -401,7 +462,7 @@ wxPanel* BedShapePanel::init_model_panel() { wxPanel* panel = new wxPanel(this); wxGetApp().UpdateDarkUI(panel, true); - ConfigOptionsGroupShp optgroup = std::make_shared(panel, _(L("Model"))); + ConfigOptionsGroupShp optgroup = std::make_shared(panel, _L("Model")); optgroup->label_width = 10; optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { @@ -411,7 +472,7 @@ wxPanel* BedShapePanel::init_model_panel() Line line{ "", "" }; line.full_width = 1; line.widget = [this](wxWindow* parent) { - wxButton* load_btn = new wxButton(parent, wxID_ANY, _(L("Load..."))); + wxButton* load_btn = new wxButton(parent, wxID_ANY, _L("Load...")); wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL); load_sizer->Add(load_btn, 1, wxEXPAND); @@ -419,7 +480,7 @@ wxPanel* BedShapePanel::init_model_panel() wxSizer* filename_sizer = new wxBoxSizer(wxHORIZONTAL); filename_sizer->Add(filename_lbl, 1, wxEXPAND); - wxButton* remove_btn = new wxButton(parent, wxID_ANY, _(L("Remove"))); + wxButton* remove_btn = new wxButton(parent, wxID_ANY, _L("Remove")); wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL); remove_sizer->Add(remove_btn, 1, wxEXPAND); @@ -428,31 +489,24 @@ wxPanel* BedShapePanel::init_model_panel() sizer->Add(load_sizer, 1, wxEXPAND); sizer->Add(remove_sizer, 1, wxEXPAND | wxTOP, 2); - load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) - { - load_model(); - })); + load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) { load_model(); })); - remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) - { + remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) { m_custom_model = NONE; update_shape(); })); - filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) - { + filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) { e.SetText(_(boost::filesystem::path(m_custom_model).filename().string())); wxStaticText* lbl = dynamic_cast(e.GetEventObject()); - if (lbl != nullptr) - { + if (lbl != nullptr) { bool exists = (m_custom_model == NONE) || boost::filesystem::exists(m_custom_model); lbl->SetForegroundColour(exists ? /*wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)*/wxGetApp().get_label_clr_default() : wxColor(*wxRED)); wxString tooltip_text = ""; - if (m_custom_model != NONE) - { + if (m_custom_model != NONE) { if (!exists) - tooltip_text += _(L("Not found:")) + " "; + tooltip_text += _L("Not found:") + " "; tooltip_text += _(m_custom_model); } @@ -463,10 +517,7 @@ wxPanel* BedShapePanel::init_model_panel() } })); - remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) - { - e.Enable(m_custom_model != NONE); - })); + remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e) { e.Enable(m_custom_model != NONE); })); return sizer; }; @@ -511,10 +562,18 @@ void BedShapePanel::update_shape() auto page_idx = m_shape_options_book->GetSelection(); auto opt_group = m_optgroups[page_idx]; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + Bed3D::EShapeType page_type = static_cast(page_idx); +#else BedShape::Type page_type = static_cast(page_idx); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS - if (page_type == BedShape::Type::Rectangular) { - Vec2d rect_size(Vec2d::Zero()); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (page_type == Bed3D::EShapeType::Rectangle) { +#else + if (page_type == BedShape::Type::Rectangular) { +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + Vec2d rect_size(Vec2d::Zero()); Vec2d rect_origin(Vec2d::Zero()); try { rect_size = boost::any_cast(opt_group->get_value("rect_size")); } @@ -544,8 +603,12 @@ void BedShapePanel::update_shape() Vec2d(x1, y1), Vec2d(x0, y1) }; } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + else if (page_type == Bed3D::EShapeType::Circle) { +#else else if (page_type == BedShape::Type::Circular) { - double diameter; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + double diameter; try { diameter = boost::any_cast(opt_group->get_value("diameter")); } catch (const std::exception & /* e */) { return; } @@ -560,7 +623,11 @@ void BedShapePanel::update_shape() } m_shape = points; } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + else if (page_type == Bed3D::EShapeType::Custom) +#else else if (page_type == BedShape::Type::Custom) +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS m_shape = m_loaded_shape; update_preview(); @@ -569,14 +636,13 @@ void BedShapePanel::update_shape() // Loads an stl file, projects it to the XY plane and calculates a polygon. void BedShapePanel::load_stl() { - wxFileDialog dialog(this, _(L("Choose an STL file to import bed shape from:")), "", "", file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + wxFileDialog dialog(this, _L("Choose an STL file to import bed shape from:"), "", "", file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (dialog.ShowModal() != wxID_OK) return; std::string file_name = dialog.GetPath().ToUTF8().data(); - if (!boost::algorithm::iends_with(file_name, ".stl")) - { - show_error(this, _(L("Invalid file format."))); + if (!boost::algorithm::iends_with(file_name, ".stl")) { + show_error(this, _L("Invalid file format.")); return; } @@ -587,7 +653,7 @@ void BedShapePanel::load_stl() model = Model::read_from_file(file_name); } catch (std::exception &) { - show_error(this, _(L("Error! Invalid model"))); + show_error(this, _L("Error! Invalid model")); return; } @@ -595,11 +661,11 @@ void BedShapePanel::load_stl() auto expolygons = mesh.horizontal_projection(); if (expolygons.size() == 0) { - show_error(this, _(L("The selected file contains no geometry."))); + show_error(this, _L("The selected file contains no geometry.")); return; } if (expolygons.size() > 1) { - show_error(this, _(L("The selected file contains several disjoint areas. This is not supported."))); + show_error(this, _L("The selected file contains several disjoint areas. This is not supported.")); return; } @@ -614,7 +680,7 @@ void BedShapePanel::load_stl() void BedShapePanel::load_texture() { - wxFileDialog dialog(this, _(L("Choose a file to import bed texture from (PNG/SVG):")), "", "", + wxFileDialog dialog(this, _L("Choose a file to import bed texture from (PNG/SVG):"), "", "", file_wildcards(FT_TEX), wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (dialog.ShowModal() != wxID_OK) @@ -623,9 +689,8 @@ void BedShapePanel::load_texture() m_custom_texture = NONE; std::string file_name = dialog.GetPath().ToUTF8().data(); - if (!boost::algorithm::iends_with(file_name, ".png") && !boost::algorithm::iends_with(file_name, ".svg")) - { - show_error(this, _(L("Invalid file format."))); + if (!boost::algorithm::iends_with(file_name, ".png") && !boost::algorithm::iends_with(file_name, ".svg")) { + show_error(this, _L("Invalid file format.")); return; } @@ -637,7 +702,7 @@ void BedShapePanel::load_texture() void BedShapePanel::load_model() { - wxFileDialog dialog(this, _(L("Choose an STL file to import bed model from:")), "", "", + wxFileDialog dialog(this, _L("Choose an STL file to import bed model from:"), "", "", file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (dialog.ShowModal() != wxID_OK) @@ -646,9 +711,8 @@ void BedShapePanel::load_model() m_custom_model = NONE; std::string file_name = dialog.GetPath().ToUTF8().data(); - if (!boost::algorithm::iends_with(file_name, ".stl")) - { - show_error(this, _(L("Invalid file format."))); + if (!boost::algorithm::iends_with(file_name, ".stl")) { + show_error(this, _L("Invalid file format.")); return; } diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index 370129f2e..af84ffb95 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -5,6 +5,9 @@ #include "GUI_Utils.hpp" #include "2DBed.hpp" +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +#include "3DBed.hpp" +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #include "I18N.hpp" #include @@ -19,12 +22,14 @@ using ConfigOptionsGroupShp = std::shared_ptr; struct BedShape { +#if !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS enum class Type { Rectangular = 0, Circular, Custom, Invalid }; +#endif // !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS enum class Parameter { RectSize, @@ -34,10 +39,18 @@ struct BedShape BedShape(const ConfigOptionPoints& points); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + bool is_custom() { return m_type == Bed3D::EShapeType::Custom; } +#else bool is_custom() { return m_type == Type::Custom; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS static void append_option_line(ConfigOptionsGroupShp optgroup, Parameter param); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + static wxString get_name(Bed3D::EShapeType type); +#else static wxString get_name(Type type); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS // convert Type to size_t size_t get_type(); @@ -46,7 +59,11 @@ struct BedShape void apply_optgroup_values(ConfigOptionsGroupShp optgroup); private: +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + Bed3D::EShapeType m_type{ Bed3D::EShapeType::Invalid }; +#else Type m_type {Type::Invalid}; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS Vec2d m_rectSize {200, 200}; Vec2d m_rectOrigin {0, 0}; double m_diameter {0}; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index fd2fe9cbc..00fa066c9 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -6,10 +6,11 @@ #include "libslic3r/Model.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/LocalesUtils.hpp" +#include "libslic3r/PresetBundle.hpp" + #include "GUI_App.hpp" #include "MainFrame.hpp" #include "Plater.hpp" -#include "libslic3r/PresetBundle.hpp" #include "Camera.hpp" #include "I18N.hpp" #include "GUI_Utils.hpp" @@ -19,6 +20,10 @@ #include "GLToolbar.hpp" #include "GUI_Preview.hpp" #include "GUI_ObjectManipulation.hpp" +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +#include "3DBed.hpp" +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + #include #include @@ -674,6 +679,10 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& if (wxGetApp().is_gcode_viewer()) m_custom_gcode_per_print_z = gcode_result.custom_gcode_per_print_z; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + m_max_print_height = gcode_result.max_print_height; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + load_toolpaths(gcode_result); if (m_layers.empty()) @@ -819,6 +828,9 @@ void GCodeViewer::reset() m_paths_bounding_box = BoundingBoxf3(); m_max_bounding_box = BoundingBoxf3(); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + m_max_print_height = 0.0f; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS m_tool_colors = std::vector(); m_extruders_count = 0; m_extruder_ids = std::vector(); @@ -835,6 +847,9 @@ void GCodeViewer::reset() #if ENABLE_GCODE_VIEWER_STATISTICS m_statistics.reset_all(); #endif // ENABLE_GCODE_VIEWER_STATISTICS +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + m_contained_in_bed = true; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } void GCodeViewer::render() @@ -1554,7 +1569,49 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // set approximate max bounding box (take in account also the tool marker) m_max_bounding_box = m_paths_bounding_box; - m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size()[2] * Vec3d::UnitZ()); + m_max_bounding_box.merge(m_paths_bounding_box.max + m_sequential_view.marker.get_bounding_box().size().z() * Vec3d::UnitZ()); + +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (wxGetApp().is_editor()) { + const Bed3D::EShapeType bed_type = wxGetApp().plater()->get_bed().get_shape_type(); + if (bed_type == Bed3D::EShapeType::Rectangle) { + BoundingBoxf3 print_volume = wxGetApp().plater()->get_bed().get_bounding_box(false); + print_volume.min.z() = -1e10; + print_volume.max.z() = m_max_print_height; + print_volume.min -= Vec3f(BedEpsilon, BedEpsilon, 0.0f).cast(); + print_volume.max += Vec3f(BedEpsilon, BedEpsilon, 0.0f).cast(); + m_contained_in_bed = print_volume.contains(m_paths_bounding_box); + } + else if (bed_type == Bed3D::EShapeType::Circle) { + Vec2d center; + double radius; + Bed3D::is_circle(wxGetApp().plater()->get_bed().get_shape(), ¢er, &radius); + const double sq_radius = sqr(radius); + for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) { + if (move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.0f && move.height != 0.0f) { + if (sq_radius < (Vec2d(move.position.x(), move.position.y()) - center).squaredNorm()) { + m_contained_in_bed = false; + break; + } + } + } + } + else if (bed_type == Bed3D::EShapeType::Custom) { + const Pointfs& shape = wxGetApp().plater()->get_bed().get_shape(); + if (Bed3D::is_convex(shape)) { + const Polygon poly = Polygon::new_scale(shape); + for (const GCodeProcessor::MoveVertex& move : gcode_result.moves) { + if (move.type == EMoveType::Extrude && move.extrusion_role != erCustom && move.width != 0.0f && move.height != 0.0f) { + if (!poly.contains(Point::new_scale(Vec2d(move.position.x(), move.position.y())))) { + m_contained_in_bed = false; + break; + } + } + } + } + } + } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #if ENABLE_FIX_SEAMS_SYNCH m_sequential_view.gcode_ids.clear(); diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 1b5a53f9d..66fcba2bc 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -780,6 +780,9 @@ private: BoundingBoxf3 m_paths_bounding_box; // bounding box of toolpaths + marker tools BoundingBoxf3 m_max_bounding_box; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + float m_max_print_height{ 0.0f }; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS std::vector m_tool_colors; Layers m_layers; std::array m_layers_z_range; @@ -804,6 +807,10 @@ private: std::vector m_custom_gcode_per_print_z; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + bool m_contained_in_bed{ true }; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + public: GCodeViewer(); ~GCodeViewer() { reset(); } @@ -832,6 +839,10 @@ public: const SequentialView& get_sequential_view() const { return m_sequential_view; } void update_sequential_view_current(unsigned int first, unsigned int last); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + bool is_contained_in_bed() const { return m_contained_in_bed; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + EViewType get_view_type() const { return m_view_type; } void set_view_type(EViewType type) { if (type == EViewType::Count) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 37fb7995b..b0a83d93f 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1108,10 +1108,18 @@ void GLCanvas3D::reset_volumes() _set_warning_notification(EWarning::ObjectOutside, false); } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state(bool as_toolpaths) const +#else ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state() const +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS { ModelInstanceEPrintVolumeState state; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + m_volumes.check_outside_state(m_config, &state, as_toolpaths); +#else m_volumes.check_outside_state(m_config, &state); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS return state; } @@ -1828,8 +1836,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re assert(volume_idx_wipe_tower_old == -1); volume_idx_wipe_tower_old = (int)volume_id; } - if (!m_reload_delayed) - { + if (!m_reload_delayed) { deleted_volumes.emplace_back(volume, volume_id); delete volume; } @@ -2123,8 +2130,10 @@ void GLCanvas3D::load_sla_preview() // Release OpenGL data before generating new data. reset_volumes(); _load_sla_shells(); +#if !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS const BoundingBoxf3& bed_bb = wxGetApp().plater()->get_bed().get_bounding_box(false); m_volumes.set_print_box(float(bed_bb.min.x()) - BedEpsilon, float(bed_bb.min.y()) - BedEpsilon, 0.0f, float(bed_bb.max.x()) + BedEpsilon, float(bed_bb.max.y()) + BedEpsilon, (float)m_config->opt_float("max_print_height")); +#endif // !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS _update_sla_shells_outside_state(); _set_warning_notification_if_needed(EWarning::SlaSupportsOutside); } @@ -3216,6 +3225,11 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) } } else if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (evt.LeftUp()) + m_selection.stop_dragging(); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (m_layers_editing.state != LayersEditing::Unknown) { m_layers_editing.state = LayersEditing::Unknown; _stop_timer(); @@ -4984,6 +4998,7 @@ void GLCanvas3D::_rectangular_selection_picking_pass() _update_volumes_hover_state(); } +#if !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS static BoundingBoxf3 print_volume(const DynamicPrintConfig& config) { // tolerance to avoid false detection at bed edges @@ -5000,6 +5015,7 @@ static BoundingBoxf3 print_volume(const DynamicPrintConfig& config) } return ret; } +#endif // !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS void GLCanvas3D::_render_background() const { @@ -5010,11 +5026,16 @@ void GLCanvas3D::_render_background() const if (!m_volumes.empty()) use_error_color &= _is_any_volume_outside(); - else { + else +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + use_error_color &= m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); +#else + { const BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); const BoundingBoxf3& paths_volume = m_gcode_viewer.get_paths_bounding_box(); use_error_color &= (test_volume.radius() > 0.0 && paths_volume.radius() > 0.0) ? !test_volume.contains(paths_volume) : false; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } glsafe(::glPushMatrix()); @@ -5090,13 +5111,55 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) if (m_picking_enabled) { // Update the layer editing selection to the first object selected, update the current object maximum Z. m_layers_editing.select_object(*m_model, this->is_layers_editing_enabled() ? m_selection.get_object_idx() : -1); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS if (m_config != nullptr) { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + Bed3D::EShapeType type = wxGetApp().plater()->get_bed().get_shape_type(); + switch (type) + { + case Bed3D::EShapeType::Circle: { + Vec2d center; + double radius; + if (Bed3D::is_circle(wxGetApp().plater()->get_bed().get_shape(), ¢er, &radius)) { + m_volumes.set_print_volume({ static_cast(type), + { float(center.x()), float(center.y()), float(radius) + BedEpsilon, 0.0f }, + { 0.0f, float(m_config->opt_float("max_print_height")) } }); + } + break; + } + case Bed3D::EShapeType::Rectangle: { + const BoundingBoxf3& bed_bb = wxGetApp().plater()->get_bed().get_bounding_box(false); + m_volumes.set_print_volume({ static_cast(type), + { float(bed_bb.min.x()) - BedEpsilon, float(bed_bb.min.y()) - BedEpsilon, float(bed_bb.max.x()) + BedEpsilon, float(bed_bb.max.y()) + BedEpsilon }, + { 0.0f, float(m_config->opt_float("max_print_height")) } }); + break; + } + default: + case Bed3D::EShapeType::Custom: { + m_volumes.set_print_volume({ static_cast(type), + { 0.0f, 0.0f, 0.0f, 0.0f }, + { 0.0f, 0.0f } }); + } + } +#else const BoundingBoxf3& bed_bb = wxGetApp().plater()->get_bed().get_bounding_box(false); - m_volumes.set_print_box((float)bed_bb.min(0) - BedEpsilon, (float)bed_bb.min(1) - BedEpsilon, 0.0f, (float)bed_bb.max(0) + BedEpsilon, (float)bed_bb.max(1) + BedEpsilon, (float)m_config->opt_float("max_print_height")); + m_volumes.set_print_box((float)bed_bb.min.x() - BedEpsilon, (float)bed_bb.min.y() - BedEpsilon, 0.0f, (float)bed_bb.max.x() + BedEpsilon, (float)bed_bb.max.y() + BedEpsilon, (float)m_config->opt_float("max_print_height")); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (m_requires_check_outside_state) { + m_volumes.check_outside_state(m_config, nullptr); + m_requires_check_outside_state = false; + } +#else m_volumes.check_outside_state(m_config, nullptr); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } +#if !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } +#endif // !ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS if (m_use_clipping_planes) m_volumes.set_z_range(-m_clipping_planes[0].get_data()[3], m_clipping_planes[1].get_data()[3]); @@ -5106,7 +5169,11 @@ void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type) m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data()); m_volumes.set_show_sinking_contours(! m_gizmos.is_hiding_instances()); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + GLShaderProgram* shader = wxGetApp().get_shader("gouraud_mod"); +#else GLShaderProgram* shader = wxGetApp().get_shader("gouraud"); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS if (shader != nullptr) { shader->start_using(); @@ -5751,7 +5818,7 @@ void GLCanvas3D::_load_print_toolpaths() total_layer_count = std::max(total_layer_count, print_object->total_layer_count()); } size_t skirt_height = print->has_infinite_skirt() ? total_layer_count : std::min(print->config().skirt_height.value, total_layer_count); - if ((skirt_height == 0) && print->has_brim()) + if (skirt_height == 0 && print->has_brim()) skirt_height = 1; // Get first skirt_height layers. @@ -5784,6 +5851,9 @@ void GLCanvas3D::_load_print_toolpaths() reserve_new_volume_finalize_old_volume(*volume, vol, m_initialized); } } +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + volume->calc_convex_hull_3d(); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS volume->indexed_vertex_array.finalize_geometry(m_initialized); } @@ -6074,8 +6144,16 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), [](const GLVolume *volume) { return volume->empty(); }), m_volumes.volumes.end()); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { + GLVolume* v = m_volumes.volumes[i]; + v->calc_convex_hull_3d(); + v->indexed_vertex_array.finalize_geometry(m_initialized); + } +#else for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_initialized); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS BOOST_LOG_TRIVIAL(debug) << "Loading print object toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); } @@ -6083,7 +6161,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_tool_colors) { const Print *print = this->fff_print(); - if ((print == nullptr) || print->wipe_tower_data().tool_changes.empty()) + if (print == nullptr || print->wipe_tower_data().tool_changes.empty()) return; if (!print->is_step_done(psWipeTower)) @@ -6231,8 +6309,16 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector& str_ std::remove_if(m_volumes.volumes.begin() + volumes_cnt_initial, m_volumes.volumes.end(), [](const GLVolume *volume) { return volume->empty(); }), m_volumes.volumes.end()); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) { + GLVolume* v = m_volumes.volumes[i]; + v->calc_convex_hull_3d(); + v->indexed_vertex_array.finalize_geometry(m_initialized); + } +#else for (size_t i = volumes_cnt_initial; i < m_volumes.volumes.size(); ++i) m_volumes.volumes[i]->indexed_vertex_array.finalize_geometry(m_initialized); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS BOOST_LOG_TRIVIAL(debug) << "Loading wipe tower toolpaths in parallel - end" << m_volumes.log_memory_info() << log_memory_info(); } @@ -6294,18 +6380,26 @@ void GLCanvas3D::_load_sla_shells() void GLCanvas3D::_update_toolpath_volumes_outside_state() { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + check_volumes_outside_state(true); +#else BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); for (GLVolume* volume : m_volumes.volumes) { volume->is_outside = (test_volume.radius() > 0.0 && volume->is_extrusion_path) ? !test_volume.contains(volume->bounding_box()) : false; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } void GLCanvas3D::_update_sla_shells_outside_state() { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + check_volumes_outside_state(); +#else BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); for (GLVolume* volume : m_volumes.volumes) { volume->is_outside = (test_volume.radius() > 0.0 && volume->shader_outside_printer_detection_enabled) ? !test_volume.contains(volume->transformed_convex_hull_bounding_box()) : false; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) @@ -6316,12 +6410,18 @@ void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) show = _is_any_volume_outside(); else { if (wxGetApp().is_editor()) { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (current_printer_technology() != ptSLA) + show = m_gcode_viewer.has_data() && !m_gcode_viewer.is_contained_in_bed(); +#else BoundingBoxf3 test_volume = (m_config != nullptr) ? print_volume(*m_config) : BoundingBoxf3(); const BoundingBoxf3& paths_volume = m_gcode_viewer.get_paths_bounding_box(); if (test_volume.radius() > 0.0 && paths_volume.radius() > 0.0) show = !test_volume.contains(paths_volume); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } } + _set_warning_notification(warning, show); } @@ -6366,7 +6466,7 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) "Resolve the current problem to continue slicing."); error = ErrorType::PLATER_ERROR; break; -} + } auto& notification_manager = *wxGetApp().plater()->get_notification_manager(); switch (error) { @@ -6396,7 +6496,7 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) bool GLCanvas3D::_is_any_volume_outside() const { for (const GLVolume* volume : m_volumes.volumes) { - if ((volume != nullptr) && volume->is_outside) + if (volume != nullptr && volume->is_outside) return true; } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 387c41315..a18f1ec07 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -475,6 +475,9 @@ private: const DynamicPrintConfig* m_config; Model* m_model; BackgroundSlicingProcess *m_process; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + bool m_requires_check_outside_state{ false }; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS std::array m_old_size{ 0, 0 }; @@ -611,11 +614,18 @@ public: void post_event(wxEvent &&event); void set_as_dirty(); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + void requires_check_outside_state() { m_requires_check_outside_state = true; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS unsigned int get_volumes_count() const; const GLVolumeCollection& get_volumes() const { return m_volumes; } void reset_volumes(); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + ModelInstanceEPrintVolumeState check_volumes_outside_state(bool as_toolpaths = false) const; +#else ModelInstanceEPrintVolumeState check_volumes_outside_state() const; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #if ENABLE_SEAMS_USING_MODELS void init_gcode_viewer() { m_gcode_viewer.init(); } diff --git a/src/slic3r/GUI/GLShadersManager.cpp b/src/slic3r/GUI/GLShadersManager.cpp index 65b85564d..0214652b2 100644 --- a/src/slic3r/GUI/GLShadersManager.cpp +++ b/src/slic3r/GUI/GLShadersManager.cpp @@ -61,10 +61,15 @@ std::pair GLShadersManager::init() // used to render extrusion and travel paths as lines in gcode preview valid &= append_shader("toolpaths_lines", { "toolpaths_lines.vs", "toolpaths_lines.fs" }); // used to render objects in 3d editor +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + // When setting this technology to default rename the following from "gouraud_mod" to "gouraud" + valid &= append_shader("gouraud_mod", { "gouraud_mod.vs", "gouraud_mod.fs" } +#else valid &= append_shader("gouraud", { "gouraud.vs", "gouraud.fs" } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS #if ENABLE_ENVIRONMENT_MAP , { "ENABLE_ENVIRONMENT_MAP"sv } -#endif +#endif // ENABLE_ENVIRONMENT_MAP ); // used to render variable layers heights in 3d editor valid &= append_shader("variable_layer_height", { "variable_layer_height.vs", "variable_layer_height.fs" }); diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index d5a1a5659..09492db09 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -635,7 +635,6 @@ void ObjectManipulation::update_if_dirty() update(m_cache.rotation, m_cache.rotation_rounded, meRotation, m_new_rotation); } - if (selection.requires_uniform_scale()) { m_lock_bnt->SetLock(true); m_lock_bnt->SetToolTip(_L("You cannot use non-uniform scaling mode for multiple objects/parts selection")); @@ -658,8 +657,14 @@ void ObjectManipulation::update_if_dirty() else m_og->disable(); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + if (!selection.is_dragging()) { +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS update_reset_buttons_visibility(); update_mirror_buttons_visibility(); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS m_dirty = false; } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 0fda1b877..8cfb3fbca 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -66,12 +66,20 @@ GLGizmoPainterBase::ClippingPlaneDataWrapper GLGizmoPainterBase::get_clipping_pl void GLGizmoPainterBase::render_triangles(const Selection& selection) const { - auto *shader = wxGetApp().get_shader("gouraud"); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + auto* shader = wxGetApp().get_shader("gouraud_mod"); +#else + auto* shader = wxGetApp().get_shader("gouraud"); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS if (! shader) return; shader->start_using(); shader->set_uniform("slope.actived", false); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + shader->set_uniform("print_volume.type", 0); +#else shader->set_uniform("print_box.actived", false); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS shader->set_uniform("clipping_plane", this->get_clipping_plane_data().clp_dataf); ScopeGuard guard([shader]() { if (shader) shader->stop_using(); }); @@ -98,7 +106,11 @@ void GLGizmoPainterBase::render_triangles(const Selection& selection) const // to the shader input variable print_box.volume_world_matrix before // rendering the painted triangles. When this matrix is not set, the // wrong transformation matrix is used for "Clipping of view". +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + shader->set_uniform("volume_world_matrix", trafo_matrix); +#else shader->set_uniform("print_box.volume_world_matrix", trafo_matrix); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS m_triangle_selectors[mesh_id]->render(m_imgui); @@ -575,7 +587,11 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui) auto* shader = wxGetApp().get_current_shader(); if (! shader) return; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + assert(shader->get_name() == "gouraud_mod"); +#else assert(shader->get_name() == "gouraud"); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS ScopeGuard guard([shader]() { if (shader) shader->set_uniform("offset_depth_buffer", false);}); shader->set_uniform("offset_depth_buffer", true); for (auto iva : {std::make_pair(&m_iva_enforcers, enforcers_color), diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ba0f60839..1f5c2b6e7 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -3007,12 +3007,19 @@ void Plater::priv::schedule_background_process() void Plater::priv::update_print_volume_state() { +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + const ConfigOptionPoints* opt = dynamic_cast(this->config->option("bed_shape")); + const Polygon bed_poly = offset(Polygon::new_scale(opt->values), static_cast(scale_(BedEpsilon))).front(); + const float bed_height = this->config->opt_float("max_print_height"); + this->q->model().update_print_volume_state(bed_poly, bed_height); +#else BoundingBox bed_box_2D = get_extents(Polygon::new_scale(this->config->opt("bed_shape")->values)); BoundingBoxf3 print_volume(unscale(bed_box_2D.min(0), bed_box_2D.min(1), 0.0), unscale(bed_box_2D.max(0), bed_box_2D.max(1), scale_(this->config->opt_float("max_print_height")))); // Allow the objects to protrude below the print bed, only the part of the object above the print bed will be sliced. print_volume.offset(BedEpsilon); print_volume.min(2) = -1e10; this->q->model().update_print_volume_state(print_volume); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 6a82ca8d3..b15d08c01 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -112,6 +112,9 @@ Selection::Selection() , m_type(Empty) , m_valid(false) , m_scale_factor(1.0f) +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + , m_dragging(false) +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS { this->set_bounding_boxes_dirty(); } @@ -690,6 +693,10 @@ void Selection::start_dragging() if (!m_valid) return; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + m_dragging = true; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + set_caches(); } @@ -730,6 +737,9 @@ void Selection::translate(const Vec3d& displacement, bool local) ensure_not_below_bed(); set_bounding_boxes_dirty(); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } // Rotate an object around one of the axes. Only one rotation component is expected to be changing. @@ -842,7 +852,10 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ volume.set_volume_offset(volume.get_volume_offset() + center_local - center_local_new); } - this->set_bounding_boxes_dirty(); + set_bounding_boxes_dirty(); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } void Selection::flattening_rotate(const Vec3d& normal) @@ -941,6 +954,9 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type ensure_on_bed(); set_bounding_boxes_dirty(); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + wxGetApp().plater()->canvas3D()->requires_check_outside_state(); +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS } void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config) @@ -2140,10 +2156,10 @@ void Selection::ensure_not_below_bed() GLVolume* volume = (*m_volumes)[i]; if (!volume->is_wipe_tower && !volume->is_modifier) { const double max_z = volume->transformed_convex_hull_bounding_box().max.z(); - std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + const std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); InstancesToZMap::iterator it = instances_max_z.find(instance); if (it == instances_max_z.end()) - it = instances_max_z.insert(InstancesToZMap::value_type(instance, -DBL_MAX)).first; + it = instances_max_z.insert({ instance, -DBL_MAX }).first; it->second = std::max(it->second, max_z); } @@ -2152,17 +2168,17 @@ void Selection::ensure_not_below_bed() if (is_any_volume()) { for (unsigned int i : m_list) { GLVolume& volume = *(*m_volumes)[i]; - std::pair instance = std::make_pair(volume.object_idx(), volume.instance_idx()); - InstancesToZMap::iterator it = instances_max_z.find(instance); - double z_shift = SINKING_MIN_Z_THRESHOLD - it->second; + const std::pair instance = std::make_pair(volume.object_idx(), volume.instance_idx()); + InstancesToZMap::const_iterator it = instances_max_z.find(instance); + const double z_shift = SINKING_MIN_Z_THRESHOLD - it->second; if (it != instances_max_z.end() && z_shift > 0.0) volume.set_volume_offset(Z, volume.get_volume_offset(Z) + z_shift); } } else { for (GLVolume* volume : *m_volumes) { - std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); - InstancesToZMap::iterator it = instances_max_z.find(instance); + const std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); + InstancesToZMap::const_iterator it = instances_max_z.find(instance); if (it != instances_max_z.end() && it->second < SINKING_MIN_Z_THRESHOLD) volume->set_instance_offset(Z, volume->get_instance_offset(Z) + SINKING_MIN_Z_THRESHOLD - it->second); } diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index dea507511..cb8f38d50 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -220,6 +220,10 @@ private: float m_scale_factor; +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + bool m_dragging; +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + public: Selection(); @@ -312,6 +316,10 @@ public: const BoundingBoxf3& get_scaled_instance_bounding_box() const; void start_dragging(); +#if ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS + void stop_dragging() { m_dragging = false; } + bool is_dragging() const { return m_dragging; } +#endif // ENABLE_OUT_OF_BED_DETECTION_IMPROVEMENTS void translate(const Vec3d& displacement, bool local = false); void rotate(const Vec3d& rotation, TransformationType transformation_type);