diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 26f2190e53..1a58bdbbd9 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -188,6 +188,8 @@ add_library(libslic3r STATIC Time.cpp Time.hpp MTUtils.hpp + VoronoiOffset.cpp + VoronoiOffset.hpp Zipper.hpp Zipper.cpp MinAreaBoundingBox.hpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 998f8f992f..9a0c9eaa7d 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1298,7 +1298,28 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu m_placeholder_parser.set("has_wipe_tower", has_wipe_tower); m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming); m_placeholder_parser.set("total_toolchanges", std::max(0, print.wipe_tower_data().number_of_toolchanges)); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change). - + { + BoundingBoxf bbox(print.config().bed_shape.values); + m_placeholder_parser.set("print_bed_min", new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() })); + m_placeholder_parser.set("print_bed_max", new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() })); + m_placeholder_parser.set("print_bed_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() })); + } + { + // Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line. + // It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower. + // It does NOT encompass user extrusions generated by custom G-code, + // therefore it does NOT encompass the initial purge line. + // It does NOT encompass MMU/MMU2 starting (wipe) areas. + auto pts = std::make_unique(); + pts->values.reserve(print.first_layer_convex_hull().size()); + for (const Point &pt : print.first_layer_convex_hull().points) + pts->values.emplace_back(unscale(pt)); + BoundingBoxf bbox(pts->values); + m_placeholder_parser.set("first_layer_print_convex_hull", pts.release()); + m_placeholder_parser.set("first_layer_print_min", new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() })); + m_placeholder_parser.set("first_layer_print_max", new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() })); + m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() })); + } std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id); // Set bed temperature if the start G-code does not contain any bed temp control G-codes. this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true); diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 011420e71a..43f498b606 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -355,7 +355,7 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ max_layer_height = std::min(max_layer_height, mlh); } // The Prusa3D Fast (0.35mm layer height) print profile sets a higher layer height than what is normally allowed - // by the nozzle. This is a hack and it works by increasing extrusion width. + // by the nozzle. This is a hack and it works by increasing extrusion width. See GH #3919. max_layer_height = std::max(max_layer_height, max_object_layer_height); for (size_t i = 0; i + 1 < m_layer_tools.size(); ++ i) { @@ -400,47 +400,21 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ // and maybe other problems. We will therefore go through layer_tools and detect and fix this. // So, if there is a non-object layer starting with different extruder than the last one ended with (or containing more than one extruder), // we'll mark it with has_wipe tower. - assert(! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower); - if (! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower) { - for (size_t i = 0; i + 1 < m_layer_tools.size();) { - const LayerTools < = m_layer_tools[i]; - assert(lt.has_wipe_tower); - assert(! lt.extruders.empty()); - // Find the next layer with wipe tower or mark a layer as such. - size_t j = i + 1; - for (; j < m_layer_tools.size() && ! m_layer_tools[j].has_wipe_tower; ++ j) { - LayerTools <_next = m_layer_tools[j]; - if (lt_next.extruders.empty()) { - //FIXME Vojtech: Lukasi, proc? - j = m_layer_tools.size(); - break; - } - if (lt_next.extruders.front() != lt.extruders.back() || lt_next.extruders.size() > 1) { - // Support only layer, soluble layers? Otherwise the layer should have been already marked as having wipe tower. - assert(lt_next.has_support && ! lt_next.has_object); - lt_next.has_wipe_tower = true; - break; - } + for (unsigned int i=0; i+1 1)) + lt_next.has_wipe_tower = true; + // We should also check that the next wipe tower layer is no further than max_layer_height: + unsigned int j = i+1; + double last_wipe_tower_print_z = lt_next.print_z; + while (++j < m_layer_tools.size()-1 && !m_layer_tools[j].has_wipe_tower) + if (m_layer_tools[j+1].print_z - last_wipe_tower_print_z > max_layer_height + EPSILON) { + m_layer_tools[j].has_wipe_tower = true; + last_wipe_tower_print_z = m_layer_tools[j].print_z; } - if (j == m_layer_tools.size()) - // No wipe tower above layer i, therefore no need to add any wipe tower layer above i. - break; - // We should also check that the next wipe tower layer is no further than max_layer_height. - // This algorith may in theory create very thin wipe layer j if layer closely below j is marked as wipe tower. - // This may happen if printing with non-soluble break away supports. - // On the other side it should not hurt as there will be no wipe, just perimeter and sparse infill printed - // at that particular wipe tower layer without extruder change. - double last_wipe_tower_print_z = lt.print_z; - assert(m_layer_tools[j].has_wipe_tower); - for (size_t k = i + 1; k < j; ++k) { - assert(! m_layer_tools[k].has_wipe_tower); - if (m_layer_tools[k + 1].print_z - last_wipe_tower_print_z > max_layer_height + EPSILON) { - m_layer_tools[k].has_wipe_tower = true; - last_wipe_tower_print_z = m_layer_tools[k].print_z; - } - } - i = j; - } } // Calculate the wipe_tower_layer_height values. diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 40c622f04a..87fb0c9c72 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -252,8 +252,16 @@ bool arrange( // output Pointfs &positions); +class VoronoiDiagram : public boost::polygon::voronoi_diagram { +public: + typedef double coord_type; + typedef boost::polygon::point_data point_type; + typedef boost::polygon::segment_data segment_type; + typedef boost::polygon::rectangle_data rect_type; +}; + class MedialAxis { - public: +public: Lines lines; const ExPolygon* expolygon; double max_width; @@ -263,14 +271,8 @@ class MedialAxis { void build(ThickPolylines* polylines); void build(Polylines* polylines); - private: - class VD : public boost::polygon::voronoi_diagram { - public: - typedef double coord_type; - typedef boost::polygon::point_data point_type; - typedef boost::polygon::segment_data segment_type; - typedef boost::polygon::rectangle_data rect_type; - }; +private: + using VD = VoronoiDiagram; VD vd; std::set edges, valid_edges; std::map > thickness; diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 54e4baf2cc..c104d46da1 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -166,6 +166,7 @@ class SupportLayer : public Layer { public: // Polygons covered by the supports: base, interface and contact areas. + // Used to suppress retraction if moving for a support extrusion over these support_islands. ExPolygonCollection support_islands; // Extrusion paths for the support base and for the support interface and contacts. ExtrusionEntityCollection support_fills; diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index d75af4ab9d..0719cac8cf 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1480,9 +1480,6 @@ stl_stats ModelObject::get_object_stl_stats() const // fill full_stats from all objet's meshes for (ModelVolume* volume : this->volumes) { - if (volume->id() == this->volumes[0]->id()) - continue; - const stl_stats& stats = volume->mesh().stl.stats; // initialize full_stats (for repaired errors) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index caa2289d9f..0c8a11fcf0 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -244,7 +244,6 @@ bool Print::invalidate_step(PrintStep step) { bool invalidated = Inherited::invalidate_step(step); // Propagate to dependent steps. - //FIXME Why should skirt invalidate brim? Shouldn't it be vice versa? if (step == psSkirt) invalidated |= Inherited::invalidate_step(psBrim); if (step != psGCodeExport) @@ -1606,6 +1605,8 @@ void Print::process() } if (this->set_started(psSkirt)) { m_skirt.clear(); + m_skirt_convex_hull.clear(); + m_first_layer_convex_hull.points.clear(); if (this->has_skirt()) { this->set_status(88, L("Generating skirt")); this->_make_skirt(); @@ -1614,11 +1615,15 @@ void Print::process() } if (this->set_started(psBrim)) { m_brim.clear(); + m_first_layer_convex_hull.points.clear(); if (m_config.brim_width > 0) { this->set_status(88, L("Generating brim")); this->_make_brim(); } - this->set_done(psBrim); + // Brim depends on skirt (brim lines are trimmed by the skirt lines), therefore if + // the skirt gets invalidated, brim gets invalidated as well and the following line is called. + this->finalize_first_layer_convex_hull(); + this->set_done(psBrim); } BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info(); } @@ -1697,22 +1702,7 @@ void Print::_make_skirt() } // Include the wipe tower. - if (has_wipe_tower() && ! m_wipe_tower_data.tool_changes.empty()) { - double width = m_config.wipe_tower_width + 2*m_wipe_tower_data.brim_width; - double depth = m_wipe_tower_data.depth + 2*m_wipe_tower_data.brim_width; - Vec2d pt = Vec2d(-m_wipe_tower_data.brim_width, -m_wipe_tower_data.brim_width); - - std::vector pts; - pts.push_back(Vec2d(pt.x(), pt.y())); - pts.push_back(Vec2d(pt.x()+width, pt.y())); - pts.push_back(Vec2d(pt.x()+width, pt.y()+depth)); - pts.push_back(Vec2d(pt.x(), pt.y()+depth)); - for (Vec2d& pt : pts) { - pt = Eigen::Rotation2Dd(Geometry::deg2rad(m_config.wipe_tower_rotation_angle.value)) * pt; - pt += Vec2d(m_config.wipe_tower_x.value, m_config.wipe_tower_y.value); - points.push_back(Point(scale_(pt.x()), scale_(pt.y()))); - } - } + append(points, this->first_layer_wipe_tower_corners()); if (points.size() < 3) // At least three points required for a convex hull. @@ -1796,28 +1786,19 @@ void Print::_make_skirt() } // Brims were generated inside out, reverse to print the outmost contour first. m_skirt.reverse(); + + // Remember the outer edge of the last skirt line extruded as m_skirt_convex_hull. + for (Polygon &poly : offset(convex_hull, distance + 0.5f * float(scale_(spacing)), ClipperLib::jtRound, float(scale_(0.1)))) + append(m_skirt_convex_hull, std::move(poly.points)); } void Print::_make_brim() { // Brim is only printed on first layer and uses perimeter extruder. + Polygons islands = this->first_layer_islands(); + Polygons loops; Flow flow = this->brim_flow(); - Polygons islands; - for (PrintObject *object : m_objects) { - Polygons object_islands; - for (ExPolygon &expoly : object->m_layers.front()->lslices) - object_islands.push_back(expoly.contour); - if (! object->support_layers().empty()) - object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON)); - islands.reserve(islands.size() + object_islands.size() * object->instances().size()); - for (const PrintInstance &instance : object->instances()) - for (Polygon &poly : object_islands) { - islands.push_back(poly); - islands.back().translate(instance.shift); - } - } - Polygons loops; - size_t num_loops = size_t(floor(m_config.brim_width.value / flow.spacing())); + size_t num_loops = size_t(floor(m_config.brim_width.value / flow.spacing())); for (size_t i = 0; i < num_loops; ++ i) { this->throw_if_canceled(); islands = offset(islands, float(flow.scaled_spacing()), jtSquare); @@ -1828,6 +1809,11 @@ void Print::_make_brim() p.pop_back(); poly.points = std::move(p); } + if (i + 1 == num_loops) { + // Remember the outer edge of the last brim line extruded as m_first_layer_convex_hull. + for (Polygon &poly : islands) + append(m_first_layer_convex_hull.points, poly.points); + } polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing()))); } loops = union_pt_chained(loops, false); @@ -1967,6 +1953,58 @@ void Print::_make_brim() } } +Polygons Print::first_layer_islands() const +{ + Polygons islands; + for (PrintObject *object : m_objects) { + Polygons object_islands; + for (ExPolygon &expoly : object->m_layers.front()->lslices) + object_islands.push_back(expoly.contour); + if (! object->support_layers().empty()) + object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON)); + islands.reserve(islands.size() + object_islands.size() * object->instances().size()); + for (const PrintInstance &instance : object->instances()) + for (Polygon &poly : object_islands) { + islands.push_back(poly); + islands.back().translate(instance.shift); + } + } + return islands; +} + +std::vector Print::first_layer_wipe_tower_corners() const +{ + std::vector corners; + if (has_wipe_tower() && ! m_wipe_tower_data.tool_changes.empty()) { + double width = m_config.wipe_tower_width + 2*m_wipe_tower_data.brim_width; + double depth = m_wipe_tower_data.depth + 2*m_wipe_tower_data.brim_width; + Vec2d pt0(-m_wipe_tower_data.brim_width, -m_wipe_tower_data.brim_width); + for (Vec2d pt : { + pt0, + Vec2d(pt0.x()+width, pt0.y() ), + Vec2d(pt0.x()+width, pt0.y()+depth), + Vec2d(pt0.x(), pt0.y()+depth) + }) { + pt = Eigen::Rotation2Dd(Geometry::deg2rad(m_config.wipe_tower_rotation_angle.value)) * pt; + pt += Vec2d(m_config.wipe_tower_x.value, m_config.wipe_tower_y.value); + corners.emplace_back(Point(scale_(pt.x()), scale_(pt.y()))); + } + } + return corners; +} + +void Print::finalize_first_layer_convex_hull() +{ + append(m_first_layer_convex_hull.points, m_skirt_convex_hull); + if (m_first_layer_convex_hull.empty()) { + // Neither skirt nor brim was extruded. Collect points of printed objects from 1st layer. + for (Polygon &poly : this->first_layer_islands()) + append(m_first_layer_convex_hull.points, std::move(poly.points)); + } + append(m_first_layer_convex_hull.points, this->first_layer_wipe_tower_corners()); + m_first_layer_convex_hull = Geometry::convex_hull(m_first_layer_convex_hull.points); +} + // Wipe tower support. bool Print::has_wipe_tower() const { @@ -1991,7 +2029,6 @@ const WipeTowerData& Print::wipe_tower_data(size_t extruders_cnt, double first_l return m_wipe_tower_data; } - void Print::_make_wipe_tower() { m_wipe_tower_data.clear(); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 864b91e60e..9da85bfba5 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -402,6 +402,12 @@ public: const ExtrusionEntityCollection& skirt() const { return m_skirt; } const ExtrusionEntityCollection& brim() const { return m_brim; } + // Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line. + // It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower. + // It does NOT encompass user extrusions generated by custom G-code, + // therefore it does NOT encompass the initial purge line. + // It does NOT encompass MMU/MMU2 starting (wipe) areas. + const Polygon& first_layer_convex_hull() const { return m_first_layer_convex_hull; } const PrintStatistics& print_statistics() const { return m_print_statistics; } @@ -437,6 +443,12 @@ private: void _make_skirt(); void _make_brim(); void _make_wipe_tower(); + void finalize_first_layer_convex_hull(); + + // Islands of objects and their supports extruded at the 1st layer. + Polygons first_layer_islands() const; + // Return 4 wipe tower corners in the world coordinates (shifted and rotated), including the wipe tower brim. + std::vector first_layer_wipe_tower_corners() const; // Declared here to have access to Model / ModelObject / ModelInstance static void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_src); @@ -450,6 +462,13 @@ private: // Ordered collections of extrusion paths to build skirt loops and brim. ExtrusionEntityCollection m_skirt; ExtrusionEntityCollection m_brim; + // Convex hull of the 1st layer extrusions. + // It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower. + // It does NOT encompass user extrusions generated by custom G-code, + // therefore it does NOT encompass the initial purge line. + // It does NOT encompass MMU/MMU2 starting (wipe) areas. + Polygon m_first_layer_convex_hull; + Points m_skirt_convex_hull; // Following section will be consumed by the GCodeGenerator. ToolOrdering m_tool_ordering; diff --git a/src/libslic3r/SLA/EigenMesh3D.hpp b/src/libslic3r/SLA/EigenMesh3D.hpp index fd60c04edb..b932c0c18e 100644 --- a/src/libslic3r/SLA/EigenMesh3D.hpp +++ b/src/libslic3r/SLA/EigenMesh3D.hpp @@ -7,7 +7,7 @@ // There is an implementation of a hole-aware raycaster that was eventually // not used in production version. It is now hidden under following define // for possible future use. -#define SLIC3R_HOLE_RAYCASTER +// #define SLIC3R_HOLE_RAYCASTER #ifdef SLIC3R_HOLE_RAYCASTER #include "libslic3r/SLA/Hollowing.hpp" diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index 0c5720c6b7..e0d534e001 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -48,5 +48,8 @@ // Enable smoothing of objects normals #define ENABLE_SMOOTH_NORMALS (0 && ENABLE_2_3_0_ALPHA1) +// Enable error logging for OpenGL calls when SLIC3R_LOGLEVEL >= 5 +#define ENABLE_OPENGL_ERROR_LOGGING (1 && ENABLE_2_3_0_ALPHA1) + #endif // _prusaslicer_technologies_h_ diff --git a/src/libslic3r/VoronoiOffset.cpp b/src/libslic3r/VoronoiOffset.cpp new file mode 100644 index 0000000000..cd96e3cdc3 --- /dev/null +++ b/src/libslic3r/VoronoiOffset.cpp @@ -0,0 +1,393 @@ +// Polygon offsetting code inspired by OpenVoronoi by Anders Wallin +// https://github.com/aewallin/openvoronoi +// This offsetter uses results of boost::polygon Voronoi. + +#include "VoronoiOffset.hpp" + +#include + +namespace Slic3r { + +using VD = Geometry::VoronoiDiagram; + +namespace detail { + // Intersect a circle with a ray, return the two parameters + double first_circle_segment_intersection_parameter( + const Vec2d ¢er, const double r, const Vec2d &pt, const Vec2d &v) + { + const Vec2d d = pt - center; +#ifndef NDEBUG + double d0 = (pt - center).norm(); + double d1 = (pt + v - center).norm(); + assert(r < std::max(d0, d1) + EPSILON); +#endif /* NDEBUG */ + const double a = v.squaredNorm(); + const double b = 2. * d.dot(v); + const double c = d.squaredNorm() - r * r; + std::pair> out; + double u = b * b - 4. * a * c; + assert(u > - EPSILON); + double t; + if (u <= 0) { + // Degenerate to a single closest point. + t = - b / (2. * a); + assert(t >= - EPSILON && t <= 1. + EPSILON); + return Slic3r::clamp(0., 1., t); + } else { + u = sqrt(u); + out.first = 2; + double t0 = (- b - u) / (2. * a); + double t1 = (- b + u) / (2. * a); + // One of the intersections shall be found inside the segment. + assert((t0 >= - EPSILON && t0 <= 1. + EPSILON) || (t1 >= - EPSILON && t1 <= 1. + EPSILON)); + if (t1 < 0.) + return 0.; + if (t0 > 1.) + return 1.; + return (t0 > 0.) ? t0 : t1; + } + } + + Vec2d voronoi_edge_offset_point( + const VD &vd, + const Lines &lines, + // Distance of a VD vertex to the closest site (input polygon edge or vertex). + const std::vector &vertex_dist, + // Minium distance of a VD edge to the closest site (input polygon edge or vertex). + // For a parabolic segment the distance may be smaller than the distance of the two end points. + const std::vector &edge_dist, + // Edge for which to calculate the offset point. If the distance towards the input polygon + // is not monotonical, pick the offset point closer to edge.vertex0(). + const VD::edge_type &edge, + // Distance from the input polygon along the edge. + const double offset_distance) + { + const VD::vertex_type *v0 = edge.vertex0(); + const VD::vertex_type *v1 = edge.vertex1(); + const VD::cell_type *cell = edge.cell(); + const VD::cell_type *cell2 = edge.twin()->cell(); + const Line &line0 = lines[cell->source_index()]; + const Line &line1 = lines[cell2->source_index()]; + if (v0 == nullptr || v1 == nullptr) { + assert(edge.is_infinite()); + assert(v0 != nullptr || v1 != nullptr); + // Offsetting on an unconstrained edge. + assert(offset_distance > vertex_dist[(v0 ? v0 : v1) - &vd.vertices().front()] - EPSILON); + Vec2d pt, dir; + double t; + if (cell->contains_point() && cell2->contains_point()) { + const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + // Direction vector of this unconstrained Voronoi edge. + dir = Vec2d(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x())); + if (v0 == nullptr) { + v0 = v1; + dir = - dir; + } + pt = Vec2d(v0->x(), v0->y()); + t = detail::first_circle_segment_intersection_parameter(Vec2d(pt0.x(), pt0.y()), offset_distance, pt, dir); + } else { + // Infinite edges could not be created by two segment sites. + assert(cell->contains_point() != cell2->contains_point()); + // Linear edge goes through the endpoint of a segment. + assert(edge.is_linear()); + assert(edge.is_secondary()); + const Line &line = cell->contains_segment() ? line0 : line1; + const Point &ipt = cell->contains_segment() ? + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) : + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b); + assert(line.a == ipt || line.b == ipt); + pt = Vec2d(ipt.x(), ipt.y()); + dir = Vec2d(line.a.y() - line.b.y(), line.b.x() - line.a.x()); + assert(dir.norm() > 0.); + t = offset_distance / dir.norm(); + if (((line.a == ipt) == cell->contains_point()) == (v0 == nullptr)) + t = - t; + } + return pt + t * dir; + } else { + // Constrained edge. + Vec2d p0(v0->x(), v0->y()); + Vec2d p1(v1->x(), v1->y()); + double d0 = vertex_dist[v0 - &vd.vertices().front()]; + double d1 = vertex_dist[v1 - &vd.vertices().front()]; + if (cell->contains_segment() && cell2->contains_segment()) { + // This edge is a bisector of two line segments. Distance to the input polygon increases/decreases monotonically. + double ddif = d1 - d0; + assert(offset_distance > std::min(d0, d1) - EPSILON && offset_distance < std::max(d0, d1) + EPSILON); + double t = (ddif == 0) ? 0. : clamp(0., 1., (offset_distance - d0) / ddif); + return Slic3r::lerp(p0, p1, t); + } else { + // One cell contains a point, the other contains an edge or a point. + assert(cell->contains_point() || cell2->contains_point()); + const Point &ipt = cell->contains_point() ? + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) : + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b); + double t = detail::first_circle_segment_intersection_parameter( + Vec2d(ipt.x(), ipt.y()), offset_distance, p0, p1 - p0); + return Slic3r::lerp(p0, p1, t); + } + } + } +}; + +Polygons voronoi_offset(const VD &vd, const Lines &lines, double offset_distance, double discretization_error) +{ + // Distance of a VD vertex to the closest site (input polygon edge or vertex). + std::vector vertex_dist(vd.num_vertices(), std::numeric_limits::max()); + + // Minium distance of a VD edge to the closest site (input polygon edge or vertex). + // For a parabolic segment the distance may be smaller than the distance of the two end points. + std::vector edge_dist(vd.num_edges(), std::numeric_limits::max()); + + // Calculate minimum distance of input polygons to voronoi vertices and voronoi edges. + for (const VD::edge_type &edge : vd.edges()) { + const VD::vertex_type *v0 = edge.vertex0(); + const VD::vertex_type *v1 = edge.vertex1(); + const VD::cell_type *cell = edge.cell(); + const VD::cell_type *cell2 = edge.twin()->cell(); + const Line &line0 = lines[cell->source_index()]; + const Line &line1 = lines[cell2->source_index()]; + double d0, d1, dmin; + if (v0 == nullptr || v1 == nullptr) { + assert(edge.is_infinite()); + if (cell->contains_point() && cell2->contains_point()) { + const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + d0 = d1 = std::numeric_limits::max(); + if (v0 == nullptr && v1 == nullptr) { + dmin = (pt1.cast() - pt0.cast()).norm(); + } else { + Vec2d pt((pt0 + pt1).cast() * 0.5); + Vec2d dir(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x())); + Vec2d pt0d(pt0.x(), pt0.y()); + if (v0) { + Vec2d a(v0->x(), v0->y()); + d0 = (a - pt0d).norm(); + dmin = ((a - pt).dot(dir) < 0.) ? (a - pt0d).norm() : d0; + vertex_dist[v0 - &vd.vertices().front()] = d0; + } else { + Vec2d a(v1->x(), v1->y()); + d1 = (a - pt0d).norm(); + dmin = ((a - pt).dot(dir) < 0.) ? (a - pt0d).norm() : d1; + vertex_dist[v1 - &vd.vertices().front()] = d1; + } + } + } else { + // Infinite edges could not be created by two segment sites. + assert(cell->contains_point() != cell2->contains_point()); + // Linear edge goes through the endpoint of a segment. + assert(edge.is_linear()); + assert(edge.is_secondary()); +#ifndef NDEBUG + if (cell->contains_segment()) { + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + assert((pt1.x() == line0.a.x() && pt1.y() == line0.a.y()) || + (pt1.x() == line0.b.x() && pt1.y() == line0.b.y())); + } else { + const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; + assert((pt0.x() == line1.a.x() && pt0.y() == line1.a.y()) || + (pt0.x() == line1.b.x() && pt0.y() == line1.b.y())); + } + const Point &pt = cell->contains_segment() ? + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) : + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b); +#endif /* NDEBUG */ + if (v0) { + assert((Point(v0->x(), v0->y()) - pt).cast().norm() < SCALED_EPSILON); + d0 = dmin = 0.; + vertex_dist[v0 - &vd.vertices().front()] = d0; + } else { + assert((Point(v1->x(), v1->y()) - pt).cast().norm() < SCALED_EPSILON); + d1 = dmin = 0.; + vertex_dist[v1 - &vd.vertices().front()] = d1; + } + } + } else { + // Finite edge has valid points at both sides. + if (cell->contains_segment() && cell2->contains_segment()) { + // This edge is a bisector of two line segments. Project v0, v1 onto one of the line segments. + Vec2d pt(line0.a.cast()); + Vec2d dir(line0.b.cast() - pt); + Vec2d vec0 = Vec2d(v0->x(), v0->y()) - pt; + Vec2d vec1 = Vec2d(v1->x(), v1->y()) - pt; + double l2 = dir.squaredNorm(); + assert(l2 > 0.); + d0 = (dir * (vec0.dot(dir) / l2) - vec0).norm(); + d1 = (dir * (vec1.dot(dir) / l2) - vec1).norm(); + dmin = std::min(d0, d1); + } else { + assert(cell->contains_point() || cell2->contains_point()); + const Point &pt0 = cell->contains_point() ? + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) : + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b); + // Project p0 to line segment . + Vec2d p0(v0->x(), v0->y()); + Vec2d p1(v1->x(), v1->y()); + Vec2d px(pt0.x(), pt0.y()); + Vec2d v = p1 - p0; + d0 = (p0 - px).norm(); + d1 = (p1 - px).norm(); + double t = v.dot(px - p0); + double l2 = v.squaredNorm(); + if (t > 0. && t < l2) { + // Foot point on the line segment. + Vec2d foot = p0 + (t / l2) * v; + dmin = (foot - px).norm(); + } else + dmin = std::min(d0, d1); + } + vertex_dist[v0 - &vd.vertices().front()] = d0; + vertex_dist[v1 - &vd.vertices().front()] = d1; + } + edge_dist[&edge - &vd.edges().front()] = dmin; + } + + // Mark cells intersected by the offset curve. + std::vector seed_cells(vd.num_cells(), false); + for (const VD::cell_type &cell : vd.cells()) { + const VD::edge_type *first_edge = cell.incident_edge(); + const VD::edge_type *edge = first_edge; + do { + double dmin = edge_dist[edge - &vd.edges().front()]; + double dmax = std::numeric_limits::max(); + const VD::vertex_type *v0 = edge->vertex0(); + const VD::vertex_type *v1 = edge->vertex1(); + if (v0 != nullptr) + dmax = vertex_dist[v0 - &vd.vertices().front()]; + if (v1 != nullptr) + dmax = std::max(dmax, vertex_dist[v1 - &vd.vertices().front()]); + if (offset_distance >= dmin && offset_distance <= dmax) { + // This cell is being intersected by the offset curve. + seed_cells[&cell - &vd.cells().front()] = true; + break; + } + edge = edge->next(); + } while (edge != first_edge); + } + + auto edge_dir = [&vd, &vertex_dist, &edge_dist, offset_distance](const VD::edge_type *edge) { + const VD::vertex_type *v0 = edge->vertex0(); + const VD::vertex_type *v1 = edge->vertex1(); + double d0 = v0 ? vertex_dist[v0 - &vd.vertices().front()] : std::numeric_limits::max(); + double d1 = v1 ? vertex_dist[v1 - &vd.vertices().front()] : std::numeric_limits::max(); + if (d0 < offset_distance && offset_distance < d1) + return true; + else if (d1 < offset_distance && offset_distance < d0) + return false; + else { + assert(false); + return false; + } + }; + + /// \brief starting at e, find the next edge on the face that brackets t + /// + /// we can be in one of two modes. + /// if direction==false then we are looking for an edge where src_t < t < trg_t + /// if direction==true we are looning for an edge where trg_t < t < src_t + auto next_offset_edge = + [&vd, &vertex_dist, &edge_dist, offset_distance] + (const VD::edge_type *start_edge, bool direction) -> const VD::edge_type* { + const VD::edge_type *edge = start_edge; + do { + const VD::vertex_type *v0 = edge->vertex0(); + const VD::vertex_type *v1 = edge->vertex1(); + double d0 = v0 ? vertex_dist[v0 - &vd.vertices().front()] : std::numeric_limits::max(); + double d1 = v1 ? vertex_dist[v1 - &vd.vertices().front()] : std::numeric_limits::max(); + if (direction ? (d1 < offset_distance && offset_distance < d0) : (d0 < offset_distance && offset_distance < d1)) + return edge; + edge = edge->next(); + } while (edge != start_edge); + assert(false); + return nullptr; + }; + +#ifndef NDEBUG + auto dist_to_site = [&lines](const VD::cell_type &cell, const Vec2d &point) { + const Line &line = lines[cell.source_index()]; + return cell.contains_point() ? + (((cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line.a : line.b).cast() - point).norm() : + line.distance_to(point.cast()); + }; +#endif /* NDEBUG */ + + // Track the offset curves. + Polygons out; + double angle_step = 2. * acos((offset_distance - discretization_error) / offset_distance); + double sin_threshold = sin(angle_step) + EPSILON; + for (size_t seed_cell_idx = 0; seed_cell_idx < vd.num_cells(); ++ seed_cell_idx) + if (seed_cells[seed_cell_idx]) { + seed_cells[seed_cell_idx] = false; + // Initial direction should not matter, an offset curve shall intersect a cell at least at two points + // (if it is not just touching the cell at a single vertex), and such two intersection points shall have + // opposite direction. + bool direction = false; + // the first edge on the start-face + const VD::cell_type &cell = vd.cells()[seed_cell_idx]; + const VD::edge_type *start_edge = next_offset_edge(cell.incident_edge(), direction); + assert(start_edge->cell() == &cell); + const VD::edge_type *edge = start_edge; + Polygon poly; + do { + direction = edge_dir(edge); + // find the next edge + const VD::edge_type *next_edge = next_offset_edge(edge->next(), direction); + //std::cout << "offset-output: "; print_edge(edge); std::cout << " to "; print_edge(next_edge); std::cout << "\n"; + // Interpolate a circular segment or insert a linear segment between edge and next_edge. + const VD::cell_type *cell = edge->cell(); + Vec2d p1 = detail::voronoi_edge_offset_point(vd, lines, vertex_dist, edge_dist, *edge, offset_distance); + Vec2d p2 = detail::voronoi_edge_offset_point(vd, lines, vertex_dist, edge_dist, *next_edge, offset_distance); +#ifndef NDEBUG + { + double err = dist_to_site(*cell, p1) - offset_distance; + assert(std::abs(err) < SCALED_EPSILON); + err = dist_to_site(*cell, p2) - offset_distance; + assert(std::abs(err) < SCALED_EPSILON); + } +#endif /* NDEBUG */ + if (cell->contains_point()) { + // Discretize an arc from p1 to p2 with radius = offset_distance and discretization_error. + // The arc should cover angle < PI. + //FIXME we should be able to produce correctly oriented output curves based on the first edge taken! + const Line &line0 = lines[cell->source_index()]; + const Vec2d ¢er = ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b).cast(); + const Vec2d v1 = p1 - center; + const Vec2d v2 = p2 - center; + double orient = cross2(v1, v2); + double orient_norm = v1.norm() * v2.norm(); + bool ccw = orient > 0; + bool obtuse = v1.dot(v2) < 0.; + if (! ccw) + orient = - orient; + assert(orient != 0.); + if (obtuse || orient > orient_norm * sin_threshold) { + // Angle is bigger than the threshold, therefore the arc will be discretized. + double angle = asin(orient / orient_norm); + if (obtuse) + angle = M_PI - angle; + size_t n_steps = size_t(ceil(angle / angle_step)); + double astep = angle / n_steps; + if (! ccw) + astep *= -1.; + double a = astep; + for (size_t i = 1; i < n_steps; ++ i, a += astep) { + double c = cos(a); + double s = sin(a); + Vec2d p = center + Vec2d(c * v1.x() - s * v1.y(), s * v1.x() + c * v1.y()); + poly.points.emplace_back(Point(coord_t(p.x()), coord_t(p.y()))); + } + } + } + poly.points.emplace_back(Point(coord_t(p2.x()), coord_t(p2.y()))); + // although we may revisit current_face (if it is non-convex), it seems safe to mark it "done" here. + seed_cells[cell - &vd.cells().front()] = false; + edge = next_edge->twin(); + } while (edge != start_edge); + out.emplace_back(std::move(poly)); + } + + return out; +} + +} // namespace Slic3r diff --git a/src/libslic3r/VoronoiOffset.hpp b/src/libslic3r/VoronoiOffset.hpp new file mode 100644 index 0000000000..9f5485c00d --- /dev/null +++ b/src/libslic3r/VoronoiOffset.hpp @@ -0,0 +1,14 @@ +#ifndef slic3r_VoronoiOffset_hpp_ +#define slic3r_VoronoiOffset_hpp_ + +#include "libslic3r.h" + +#include "Geometry.hpp" + +namespace Slic3r { + +Polygons voronoi_offset(const Geometry::VoronoiDiagram &vd, const Lines &lines, double offset_distance, double discretization_error); + +} // namespace Slic3r + +#endif // slic3r_VoronoiOffset_hpp_ diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index dc57500c49..5dc0d430f1 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -40,12 +40,19 @@ #include #ifdef HAS_GLSAFE -void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name) +void glAssertRecentCallImpl(const char* file_name, unsigned int line, const char* function_name) { +#if defined(NDEBUG) && ENABLE_OPENGL_ERROR_LOGGING + // In release mode, if OpenGL debugging was forced by ENABLE_OPENGL_ERROR_LOGGING, only show + // OpenGL errors if sufficiently high loglevel. + if (Slic3r::get_logging_level() < 5) + return; +#endif // ENABLE_OPENGL_ERROR_LOGGING + GLenum err = glGetError(); if (err == GL_NO_ERROR) return; - const char *sErr = 0; + const char* sErr = 0; switch (err) { case GL_INVALID_ENUM: sErr = "Invalid Enum"; break; case GL_INVALID_VALUE: sErr = "Invalid Value"; break; @@ -56,10 +63,10 @@ void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char case GL_OUT_OF_MEMORY: sErr = "Out Of Memory"; break; default: sErr = "Unknown"; break; } - BOOST_LOG_TRIVIAL(error) << "OpenGL error in " << file_name << ":" << line << ", function " << function_name << "() : " << (int)err << " - " << sErr; + BOOST_LOG_TRIVIAL(error) << "OpenGL error in " << file_name << ":" << line << ", function " << function_name << "() : " << (int)err << " - " << sErr; assert(false); } -#endif +#endif // HAS_GLSAFE namespace Slic3r { diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index da111d7aa0..f935f0fb47 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -10,20 +10,20 @@ #include -#ifndef NDEBUG -#define HAS_GLSAFE +#if ENABLE_OPENGL_ERROR_LOGGING || ! defined(NDEBUG) + #define HAS_GLSAFE #endif #ifdef HAS_GLSAFE -extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name); -inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } -#define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) -#define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) -#else -inline void glAssertRecentCall() { } -#define glsafe(cmd) cmd -#define glcheck() -#endif + extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name); + inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } + #define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) + #define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) +#else // HAS_GLSAFE + inline void glAssertRecentCall() { } + #define glsafe(cmd) cmd + #define glcheck() +#endif // HAS_GLSAFE namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 71ae0f49e0..b4e672c4fb 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1534,9 +1534,8 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_retina_helper(nullptr) #endif , m_in_render(false) - , m_main_toolbar(GLToolbar::Normal, "Top") - , m_undoredo_toolbar(GLToolbar::Normal, "Top") - , m_collapse_toolbar(GLToolbar::Normal, "Top") + , m_main_toolbar(GLToolbar::Normal, "Main") + , m_undoredo_toolbar(GLToolbar::Normal, "Undo_Redo") , m_gizmos(*this) , m_use_clipping_planes(false) , m_sidebar_field("") @@ -1914,11 +1913,6 @@ void GLCanvas3D::enable_undoredo_toolbar(bool enable) m_undoredo_toolbar.set_enabled(enable); } -void GLCanvas3D::enable_collapse_toolbar(bool enable) -{ - m_collapse_toolbar.set_enabled(enable); -} - void GLCanvas3D::enable_dynamic_background(bool enable) { m_dynamic_background_enabled = enable; @@ -2112,7 +2106,7 @@ void GLCanvas3D::render() tooltip = m_undoredo_toolbar.get_tooltip(); if (tooltip.empty()) - tooltip = m_collapse_toolbar.get_tooltip(); + tooltip = wxGetApp().plater()->get_collapse_toolbar().get_tooltip(); if (tooltip.empty()) tooltip = wxGetApp().plater()->get_view_toolbar().get_tooltip(); @@ -2854,8 +2848,8 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) m_dirty |= m_main_toolbar.update_items_state(); m_dirty |= m_undoredo_toolbar.update_items_state(); - m_dirty |= m_collapse_toolbar.update_items_state(); m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state(); + m_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state(); bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera()); m_dirty |= mouse3d_controller_applied; @@ -3473,7 +3467,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) return; } - if (m_collapse_toolbar.on_mouse(evt, *this)) + if (wxGetApp().plater()->get_collapse_toolbar().on_mouse(evt, *this)) { if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) mouse_up_cleanup(); @@ -4187,7 +4181,7 @@ void GLCanvas3D::update_ui_from_settings() #endif // ENABLE_RETINA_GL bool enable_collapse = wxGetApp().app_config->get("show_collapse_button") == "1"; - enable_collapse_toolbar(enable_collapse); + wxGetApp().plater()->get_collapse_toolbar().set_enabled(enable_collapse); } @@ -5055,51 +5049,7 @@ bool GLCanvas3D::_init_view_toolbar() bool GLCanvas3D::_init_collapse_toolbar() { - if (!m_collapse_toolbar.is_enabled() && m_collapse_toolbar.get_items_count() > 0) - return true; - - BackgroundTexture::Metadata background_data; - background_data.filename = "toolbar_background.png"; - background_data.left = 16; - background_data.top = 16; - background_data.right = 16; - background_data.bottom = 16; - - if (!m_collapse_toolbar.init(background_data)) - { - // unable to init the toolbar texture, disable it - m_collapse_toolbar.set_enabled(false); - return true; - } - - m_collapse_toolbar.set_layout_type(GLToolbar::Layout::Vertical); - m_collapse_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right); - m_collapse_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); - m_collapse_toolbar.set_border(5.0f); - m_collapse_toolbar.set_separator_size(5); - m_collapse_toolbar.set_gap_size(2); - - GLToolbarItem::Data item; - - item.name = "collapse_sidebar"; - item.icon_filename = "collapse.svg"; - item.tooltip = wxGetApp().plater()->is_sidebar_collapsed() ? _utf8(L("Expand right panel")) : _utf8(L("Collapse right panel")); - item.sprite_id = 0; - item.left.action_callback = [this, item]() { - std::string new_tooltip = wxGetApp().plater()->is_sidebar_collapsed() ? - _utf8(L("Collapse right panel")) : _utf8(L("Expand right panel")); - - int id = m_collapse_toolbar.get_item_id("collapse_sidebar"); - m_collapse_toolbar.set_tooltip(id, new_tooltip); - set_tooltip(""); - - wxGetApp().plater()->collapse_sidebar(!wxGetApp().plater()->is_sidebar_collapsed()); - }; - - if (!m_collapse_toolbar.add_item(item)) - return false; - - return true; + return wxGetApp().plater()->init_collapse_toolbar(); } bool GLCanvas3D::_set_current() @@ -5427,20 +5377,21 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale() const float size = GLToolbar::Default_Icons_Size * scale; // Set current size for all top toolbars. It will be used for next calculations + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); #if ENABLE_RETINA_GL const float sc = m_retina_helper->get_scale_factor() * scale; m_main_toolbar.set_scale(sc); m_undoredo_toolbar.set_scale(sc); - m_collapse_toolbar.set_scale(sc); + collapse_toolbar.set_scale(sc); size *= m_retina_helper->get_scale_factor(); #else m_main_toolbar.set_icons_size(size); m_undoredo_toolbar.set_icons_size(size); - m_collapse_toolbar.set_icons_size(size); + collapse_toolbar.set_icons_size(size); #endif // ENABLE_RETINA_GL - float top_tb_width = m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + m_collapse_toolbar.get_width(); - int items_cnt = m_main_toolbar.get_visible_items_cnt() + m_undoredo_toolbar.get_visible_items_cnt() + m_collapse_toolbar.get_visible_items_cnt(); + float top_tb_width = m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar.get_width(); + int items_cnt = m_main_toolbar.get_visible_items_cnt() + m_undoredo_toolbar.get_visible_items_cnt() + collapse_toolbar.get_visible_items_cnt(); float noitems_width = top_tb_width - size * items_cnt; // width of separators and borders in top toolbars // calculate scale needed for items in all top toolbars @@ -5460,7 +5411,6 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale() const wxGetApp().set_auto_toolbar_icon_scale(new_scale); } - void GLCanvas3D::_render_overlays() const { glsafe(::glDisable(GL_DEPTH_TEST)); @@ -5485,12 +5435,12 @@ void GLCanvas3D::_render_overlays() const const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(/*true*/); m_main_toolbar.set_scale(scale); m_undoredo_toolbar.set_scale(scale); - m_collapse_toolbar.set_scale(scale); + wxGetApp().plater()->get_collapse_toolbar().set_scale(scale); #else const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(/*true*/)); m_main_toolbar.set_icons_size(size); m_undoredo_toolbar.set_icons_size(size); - m_collapse_toolbar.set_icons_size(size); + wxGetApp().plater()->get_collapse_toolbar().set_icons_size(size); #endif // ENABLE_RETINA_GL _render_main_toolbar(); @@ -5594,7 +5544,8 @@ void GLCanvas3D::_render_main_toolbar() const float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - float collapse_toolbar_width = m_collapse_toolbar.is_enabled() ? m_collapse_toolbar.get_width() : 0.0f; + const GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); + float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width) * inv_zoom; m_main_toolbar.set_position(top, left); @@ -5610,7 +5561,8 @@ void GLCanvas3D::_render_undoredo_toolbar() const float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - float collapse_toolbar_width = m_collapse_toolbar.is_enabled() ? m_collapse_toolbar.get_width() : 0.0f; + const GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); + float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; float left = (m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width)) * inv_zoom; m_undoredo_toolbar.set_position(top, left); m_undoredo_toolbar.render(*this); @@ -5618,8 +5570,7 @@ void GLCanvas3D::_render_undoredo_toolbar() const void GLCanvas3D::_render_collapse_toolbar() const { - if (!m_collapse_toolbar.is_enabled()) - return; + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); Size cnv_size = get_canvas_size(); float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); @@ -5627,10 +5578,10 @@ void GLCanvas3D::_render_collapse_toolbar() const float band = m_layers_editing.is_enabled() ? (wxGetApp().imgui()->get_style_scaling() * LayersEditing::THICKNESS_BAR_WIDTH) : 0.0; float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - float left = (0.5f * (float)cnv_size.get_width() - (float)m_collapse_toolbar.get_width() - band) * inv_zoom; + float left = (0.5f * (float)cnv_size.get_width() - (float)collapse_toolbar.get_width() - band) * inv_zoom; - m_collapse_toolbar.set_position(top, left); - m_collapse_toolbar.render(*this); + collapse_toolbar.set_position(top, left); + collapse_toolbar.render(*this); } void GLCanvas3D::_render_view_toolbar() const @@ -7164,9 +7115,10 @@ bool GLCanvas3D::_activate_search_toolbar_item() bool GLCanvas3D::_deactivate_collapse_toolbar_items() { - if (m_collapse_toolbar.is_item_pressed("print")) + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); + if (collapse_toolbar.is_item_pressed("print")) { - m_collapse_toolbar.force_left_action(m_collapse_toolbar.get_item_id("print"), *this); + collapse_toolbar.force_left_action(collapse_toolbar.get_item_id("print"), *this); return true; } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 2dc8dbecd3..c9433a10ea 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -452,7 +452,6 @@ private: mutable GLGizmosManager m_gizmos; mutable GLToolbar m_main_toolbar; mutable GLToolbar m_undoredo_toolbar; - mutable GLToolbar m_collapse_toolbar; ClippingPlane m_clipping_planes[2]; mutable ClippingPlane m_camera_clipping_plane; bool m_use_clipping_planes; @@ -588,7 +587,6 @@ public: void enable_selection(bool enable); void enable_main_toolbar(bool enable); void enable_undoredo_toolbar(bool enable); - void enable_collapse_toolbar(bool enable); void enable_dynamic_background(bool enable); void enable_labels(bool enable) { m_labels.enable(enable); } #if ENABLE_SLOPE_RENDERING diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 59401b11a1..4ab282b066 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -1238,7 +1238,7 @@ bool GLToolbar::generate_icons_texture() const } std::vector> states; - if (m_name == "Top") + if (m_type == Normal) { states.push_back({ 1, false }); // Normal states.push_back({ 0, false }); // Pressed @@ -1247,7 +1247,7 @@ bool GLToolbar::generate_icons_texture() const states.push_back({ 0, false }); // HoverPressed states.push_back({ 2, false }); // HoverDisabled } - else if (m_name == "View") + else { states.push_back({ 1, false }); // Normal states.push_back({ 1, true }); // Pressed diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b8f596e6bd..c11ed66ab8 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -2506,7 +2506,6 @@ void ObjectList::merge(bool to_multipart_object) // all objects, created from the instances will be added to the end of list int new_objects_cnt = 0; // count of this new objects -// std::vector obj_idxs; for (auto map_item : sel_map) { @@ -2569,22 +2568,45 @@ void ObjectList::merge(bool to_multipart_object) new_object->name = _u8L("Merged"); DynamicPrintConfig* config = &new_object->config; - int frst_obj_idx = obj_idxs.front(); - const Vec3d& main_offset = (*m_objects)[frst_obj_idx]->instances[0]->get_offset(); - for (int obj_idx : obj_idxs) { ModelObject* object = (*m_objects)[obj_idx]; - Vec3d offset = object->instances[0]->get_offset(); - if (object->id() == (*m_objects)[frst_obj_idx]->id()) - new_object->add_instance(*object->instances[0]); + const Geometry::Transformation& transformation = object->instances[0]->get_transformation(); + Vec3d scale = transformation.get_scaling_factor(); + Vec3d mirror = transformation.get_mirror(); + Vec3d rotation = transformation.get_rotation(); + + if (object->id() == (*m_objects)[obj_idxs.front()]->id()) + new_object->add_instance(); + Transform3d volume_offset_correction = new_object->instances[0]->get_transformation().get_matrix().inverse() * transformation.get_matrix(); + + // merge volumes + for (const ModelVolume* volume : object->volumes) { + ModelVolume* new_volume = new_object->add_volume(*volume); + + //set rotation + Vec3d vol_rot = new_volume->get_rotation() + rotation; + new_volume->set_rotation(vol_rot); + + // set scale + Vec3d vol_sc_fact = new_volume->get_scaling_factor().cwiseProduct(scale); + new_volume->set_scaling_factor(vol_sc_fact); + + // set mirror + Vec3d vol_mirror = new_volume->get_mirror().cwiseProduct(mirror); + new_volume->set_mirror(vol_mirror); + + // set offset + Vec3d vol_offset = volume_offset_correction* new_volume->get_offset(); + new_volume->set_offset(vol_offset); + } + + // merge settings auto new_opt_keys = config->keys(); - const DynamicPrintConfig& from_config = object->config; auto opt_keys = from_config.keys(); - // merge settings for (auto& opt_key : opt_keys) { if (find(new_opt_keys.begin(), new_opt_keys.end(), opt_key) == new_opt_keys.end()) { const ConfigOption* option = from_config.option(opt_key); @@ -2596,18 +2618,11 @@ void ObjectList::merge(bool to_multipart_object) config->set_key_value(opt_key, option->clone()); } } - - // merge volumes - for (const ModelVolume* volume : object->volumes) { - ModelVolume* new_volume = new_object->add_volume(*volume); - Vec3d vol_offset = offset - main_offset + new_volume->get_offset(); - new_volume->set_offset(vol_offset); - } // save extruder value if it was set if (object->volumes.size() == 1 && find(opt_keys.begin(), opt_keys.end(), "extruder") != opt_keys.end()) { ModelVolume* volume = new_object->volumes.back(); const ConfigOption* option = from_config.option("extruder"); - if (option) + if (option) volume->config.set_key_value("extruder", option->clone()); } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index ad222a4a80..aa5264b07d 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -13,6 +13,8 @@ #include "wxExtensions.hpp" #include "ObjectDataViewModel.hpp" +#include "libslic3r/PrintConfig.hpp" + class wxBoxSizer; class wxBitmapComboBox; class wxMenuItem; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 5084a7052c..51f5c8d2df 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -68,7 +68,6 @@ bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, Ba m_canvas->enable_selection(true); m_canvas->enable_main_toolbar(true); m_canvas->enable_undoredo_toolbar(true); - m_canvas->enable_collapse_toolbar(true); m_canvas->enable_labels(true); #if ENABLE_SLOPE_RENDERING m_canvas->enable_slope(true); @@ -222,7 +221,6 @@ bool Preview::init(wxWindow* parent, Model* model) m_canvas->set_process(m_process); m_canvas->enable_legend_texture(true); m_canvas->enable_dynamic_background(true); - m_canvas->enable_collapse_toolbar(true); m_double_slider_sizer = new wxBoxSizer(wxHORIZONTAL); create_double_slider(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index da56953978..cd42857247 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -54,18 +54,35 @@ bool GLGizmoFdmSupports::on_init() return true; } + +void GLGizmoFdmSupports::activate_internal_undo_redo_stack(bool activate) +{ + if (activate && ! m_internal_stack_active) { + Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned on")); + wxGetApp().plater()->enter_gizmos_stack(); + m_internal_stack_active = true; + } + if (! activate && m_internal_stack_active) { + wxGetApp().plater()->leave_gizmos_stack(); + Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned off")); + m_internal_stack_active = false; + } +} + void GLGizmoFdmSupports::set_fdm_support_data(ModelObject* model_object, const Selection& selection) { - const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr; - if (! mo) + if (m_state != On) return; + const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr; + if (mo && selection.is_from_single_instance() - && (mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size)) + && (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size)) { update_from_model_object(); m_old_mo_id = mo->id(); m_old_volumes_size = mo->volumes.size(); + m_schedule_update = false; } } @@ -131,8 +148,10 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const // Now render both enforcers and blockers. for (int i=0; i<2; ++i) { glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f)); - for (const GLIndexedVertexArray& iva : m_ivas[mesh_id][i]) - iva.render(); + for (const GLIndexedVertexArray& iva : m_ivas[mesh_id][i]) { + if (iva.has_VBOs()) + iva.render(); + } } glsafe(::glPopMatrix()); if (is_left_handed) @@ -493,6 +512,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous : (m_button_down == Button::Left ? _L("Add supports") : _L("Block supports")); + activate_internal_undo_redo_stack(true); Plater::TakeSnapshot(wxGetApp().plater(), action_name); update_model_object(); @@ -588,6 +608,8 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwr update_vertex_buffers(&mv->mesh(), mesh_id, FacetSupportType::BLOCKER); } + activate_internal_undo_redo_stack(true); + Plater::TakeSnapshot(wxGetApp().plater(), block ? _L("Block supports by angle") : _L("Add supports by angle")); update_model_object(); @@ -778,12 +800,9 @@ void GLGizmoFdmSupports::on_set_state() return; if (m_state == On && m_old_state != On) { // the gizmo was just turned on - { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("FDM gizmo turned on"))); - } if (! m_parent.get_gizmos_manager().is_serializing()) { - wxGetApp().CallAfter([]() { - wxGetApp().plater()->enter_gizmos_stack(); + wxGetApp().CallAfter([this]() { + activate_internal_undo_redo_stack(true); }); } } @@ -793,11 +812,7 @@ void GLGizmoFdmSupports::on_set_state() m_setting_angle = false; m_parent.use_slope(false); } - - wxGetApp().plater()->leave_gizmos_stack(); - { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("FDM gizmo turned off"))); - } + activate_internal_undo_redo_stack(false); m_old_mo_id = -1; m_ivas.clear(); m_selected_facets.clear(); @@ -820,14 +835,19 @@ void GLGizmoFdmSupports::on_stop_dragging() -void GLGizmoFdmSupports::on_load(cereal::BinaryInputArchive& ar) +void GLGizmoFdmSupports::on_load(cereal::BinaryInputArchive&) { - update_from_model_object(); + // We should update the gizmo from current ModelObject, but it is not + // possible at this point. That would require having updated selection and + // common gizmos data, which is not done at this point. Instead, save + // a flag to do the update in set_fdm_support_data, which will be called + // soon after. + m_schedule_update = true; } -void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive& ar) const +void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const { } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index d765a8da55..c4f5b153ec 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -64,6 +64,7 @@ private: void update_model_object() const; void update_from_model_object(); + void activate_internal_undo_redo_stack(bool activate); void select_facets_by_angle(float threshold, bool overwrite, bool block); bool m_overwrite_selected = false; @@ -74,6 +75,8 @@ private: float m_clipping_plane_distance = 0.f; std::unique_ptr m_clipping_plane; bool m_setting_angle = false; + bool m_internal_stack_active = false; + bool m_schedule_update = false; // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index 262b685a6d..c9e8b9d2ba 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -28,8 +28,7 @@ bool GLGizmoFlatten::on_init() void GLGizmoFlatten::on_set_state() { - if (m_state == On && is_plane_update_necessary()) - update_planes(); + } CommonGizmosDataID GLGizmoFlatten::on_get_requirements() const @@ -81,7 +80,8 @@ void GLGizmoFlatten::on_render() const else glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 0.5f)); - m_planes[i].vbo.render(); + if (m_planes[i].vbo.has_VBOs()) + m_planes[i].vbo.render(); } glsafe(::glPopMatrix()); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 1dffee6be0..658db64cab 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -59,7 +59,7 @@ void GLGizmoHollow::set_sla_support_data(ModelObject*, const Selection&) return; const ModelObject* mo = m_c->selection_info()->model_object(); - if (mo) { + if (m_state == On && mo) { if (m_old_mo_id != mo->id()) { reload_cache(); m_old_mo_id = mo->id(); @@ -810,11 +810,6 @@ void GLGizmoHollow::on_set_state() if (m_state == m_old_state) return; - if (m_state == On && m_old_state != On) { // the gizmo was just turned on - // we'll now reload support points: - if (m_c->selection_info()->model_object()) - reload_cache(); - } if (m_state == Off && m_old_state != Off) // the gizmo was just turned Off m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); m_old_state = m_state; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 268a15df35..908fe27b11 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -67,10 +67,11 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S ModelObject* mo = m_c->selection_info()->model_object(); - if (mo && mo->id() != m_old_mo_id) { + if (m_state == On && mo && mo->id() != m_old_mo_id) { disable_editing_mode(); reload_cache(); m_old_mo_id = mo->id(); + m_c->instances_hider()->show_supports(true); } // If we triggered autogeneration before, check backend and fetch results if they are there @@ -884,25 +885,23 @@ CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const void GLGizmoSlaSupports::on_set_state() { - const ModelObject* mo = m_c->selection_info()->model_object(); - if (m_state == m_old_state) return; if (m_state == On && m_old_state != On) { // the gizmo was just turned on - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned on"))); + // This function can be called from undo/redo, when selection (and hence + // common gizmos data are not yet deserialized. The CallAfter should put + // this off until after the update is done. + wxGetApp().CallAfter([this]() { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned on"))); - // we'll now reload support points: - if (mo) - reload_cache(); - - // Set default head diameter from config. - const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; - m_c->instances_hider()->show_supports(true); + // Set default head diameter from config. + const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; + }); } if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off - bool will_ask = mo && m_editing_mode && unsaved_changes(); + bool will_ask = m_editing_mode && unsaved_changes(); if (will_ask) { wxGetApp().CallAfter([this]() { // Following is called through CallAfter, because otherwise there was a problem @@ -922,7 +921,7 @@ void GLGizmoSlaSupports::on_set_state() disable_editing_mode(); // so it is not active next time the gizmo opens Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned off"))); m_normal_cache.clear(); - + m_old_mo_id = -1; } } m_old_state = m_state; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 9dd9c6565e..051e9cf880 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -8,6 +8,8 @@ #include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/PresetBundle.hpp" + #include namespace Slic3r { @@ -170,7 +172,8 @@ void InstancesHider::show_supports(bool show) { void HollowedMesh::on_update() { const ModelObject* mo = get_pool()->selection_info()->model_object(); - if (! mo) + bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; + if (! mo || ! is_sla) return; const GLCanvas3D* canvas = get_pool()->get_canvas(); @@ -376,7 +379,8 @@ void ObjectClipper::set_position(double pos, bool keep_normal) void SupportsClipper::on_update() { const ModelObject* mo = get_pool()->selection_info()->model_object(); - if (! mo) + bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; + if (! mo || ! is_sla) return; const GLCanvas3D* canvas = get_pool()->get_canvas(); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 355927fb13..511c68735c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -1104,9 +1104,16 @@ void GLGizmosManager::activate_gizmo(EType type) } m_current = type; - m_common_gizmos_data->update(get_current() - ? get_current()->get_requirements() - : CommonGizmosDataID(0)); + + // Updating common data should be left to the update_data function, which + // is always called after this one. activate_gizmo can be called by undo/redo, + // when selection is not yet deserialized, so the common data would update + // incorrectly (or crash if relying on unempty selection). Undo/redo stack + // will also call update_data, after selection is restored. + + //m_common_gizmos_data->update(get_current() + // ? get_current()->get_requirements() + // : CommonGizmosDataID(0)); if (type != Undefined) m_gizmos[type]->set_state(GLGizmoBase::On); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index e643c0b3b6..4ad46a2a92 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -141,11 +141,6 @@ public: EType new_current = m_current; m_current = old_current; - // Update common data. They should be updated when activate_gizmo is - // called, so it can be used in on_set_state which is called from there. - if (new_current != Undefined) - m_common_gizmos_data->update(m_gizmos[new_current]->get_requirements()); - // activate_gizmo call sets m_current and calls set_state for the gizmo // it does nothing in case the gizmo is already activated // it can safely be called for Undefined gizmo diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index c1a59b909c..cc599387d8 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -329,7 +329,7 @@ void MainFrame::init_tabpanel() Tab* tab = dynamic_cast(panel); // There shouldn't be a case, when we try to select a tab, which doesn't support a printer technology - if (panel == nullptr || (tab && tab->supports_printer_technology(m_plater->printer_technology()))) + if (panel == nullptr || (tab && ! tab->supports_printer_technology(m_plater->printer_technology()))) return; auto& tabs_list = wxGetApp().tabs_list; diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 413eedda59..581f50a882 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -49,7 +49,8 @@ void MeshClipper::render_cut() if (! m_triangles_valid) recalculate_triangles(); - m_vertex_array.render(); + if (m_vertex_array.has_VBOs()) + m_vertex_array.render(); } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7f0be3f2ed..8c3a90370a 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1589,6 +1589,7 @@ struct Plater::priv Mouse3DController mouse3d_controller; View3D* view3D; GLToolbar view_toolbar; + GLToolbar collapse_toolbar; Preview *preview; BackgroundSlicingProcess background_process; @@ -1683,6 +1684,7 @@ struct Plater::priv void reset_canvas_volumes(); bool init_view_toolbar(); + bool init_collapse_toolbar(); void reset_all_gizmos(); void update_ui_from_settings(); @@ -1878,6 +1880,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) , m_ui_jobs(this) , delayed_scene_refresh(false) , view_toolbar(GLToolbar::Radio, "View") + , collapse_toolbar(GLToolbar::Normal, "Collapse") , m_project_filename(wxEmptyString) { this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font()); @@ -3922,6 +3925,51 @@ bool Plater::priv::init_view_toolbar() return true; } +bool Plater::priv::init_collapse_toolbar() +{ + if (collapse_toolbar.get_items_count() > 0) + // already initialized + return true; + + BackgroundTexture::Metadata background_data; + background_data.filename = "toolbar_background.png"; + background_data.left = 16; + background_data.top = 16; + background_data.right = 16; + background_data.bottom = 16; + + if (!collapse_toolbar.init(background_data)) + return false; + + collapse_toolbar.set_layout_type(GLToolbar::Layout::Vertical); + collapse_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right); + collapse_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); + collapse_toolbar.set_border(5.0f); + collapse_toolbar.set_separator_size(5); + collapse_toolbar.set_gap_size(2); + + GLToolbarItem::Data item; + + item.name = "collapse_sidebar"; + item.icon_filename = "collapse.svg"; + item.tooltip = wxGetApp().plater()->is_sidebar_collapsed() ? _utf8(L("Expand right panel")) : _utf8(L("Collapse right panel")); + item.sprite_id = 0; + item.left.action_callback = [this, item]() { + std::string new_tooltip = wxGetApp().plater()->is_sidebar_collapsed() ? + _utf8(L("Collapse right panel")) : _utf8(L("Expand right panel")); + + int id = collapse_toolbar.get_item_id("collapse_sidebar"); + collapse_toolbar.set_tooltip(id, new_tooltip); + + wxGetApp().plater()->collapse_sidebar(!wxGetApp().plater()->is_sidebar_collapsed()); + }; + + if (!collapse_toolbar.add_item(item)) + return false; + + return true; +} + bool Plater::priv::can_set_instance_to_object() const { const int obj_idx = get_selected_object_idx(); @@ -5531,6 +5579,11 @@ bool Plater::init_view_toolbar() return p->init_view_toolbar(); } +bool Plater::init_collapse_toolbar() +{ + return p->init_collapse_toolbar(); +} + const Camera& Plater::get_camera() const { return p->camera; @@ -5574,6 +5627,16 @@ GLToolbar& Plater::get_view_toolbar() return p->view_toolbar; } +const GLToolbar& Plater::get_collapse_toolbar() const +{ + return p->collapse_toolbar; +} + +GLToolbar& Plater::get_collapse_toolbar() +{ + return p->collapse_toolbar; +} + const Mouse3DController& Plater::get_mouse3d_controller() const { return p->mouse3d_controller; diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 10b6b354e3..5d60e006b0 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -315,6 +315,7 @@ public: void sys_color_changed(); bool init_view_toolbar(); + bool init_collapse_toolbar(); const Camera& get_camera() const; Camera& get_camera(); @@ -330,6 +331,9 @@ public: const GLToolbar& get_view_toolbar() const; GLToolbar& get_view_toolbar(); + const GLToolbar& get_collapse_toolbar() const; + GLToolbar& get_collapse_toolbar(); + const Mouse3DController& get_mouse3d_controller() const; Mouse3DController& get_mouse3d_controller(); diff --git a/tests/libslic3r/test_voronoi.cpp b/tests/libslic3r/test_voronoi.cpp index 4615a93ae6..ef05119ad9 100644 --- a/tests/libslic3r/test_voronoi.cpp +++ b/tests/libslic3r/test_voronoi.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #define BOOST_VORONOI_USE_GMP 1 #include "boost/polygon/voronoi.hpp" @@ -16,12 +17,7 @@ using boost::polygon::voronoi_diagram; using namespace Slic3r; -struct VD : public boost::polygon::voronoi_diagram { - typedef double coord_type; - typedef boost::polygon::point_data point_type; - typedef boost::polygon::segment_data segment_type; - typedef boost::polygon::rectangle_data rect_type; -}; +using VD = Geometry::VoronoiDiagram; // #define VORONOI_DEBUG_OUT @@ -322,6 +318,7 @@ static inline void dump_voronoi_to_svg( /* const */ VD &vd, const Points &points, const Lines &lines, + const Polygons &offset_curves = Polygons(), const double scale = 0.7) // 0.2? { const std::string inputSegmentPointColor = "lightseagreen"; @@ -336,6 +333,9 @@ static inline void dump_voronoi_to_svg( const std::string voronoiArcColor = "red"; const coord_t voronoiLineWidth = coord_t(0.02 * scale / SCALING_FACTOR); + const std::string offsetCurveColor = "magenta"; + const coord_t offsetCurveLineWidth = coord_t(0.09 * scale / SCALING_FACTOR); + const bool internalEdgesOnly = false; const bool primaryEdgesOnly = false; @@ -408,6 +408,7 @@ static inline void dump_voronoi_to_svg( } #endif + svg.draw_outline(offset_curves, offsetCurveColor, offsetCurveLineWidth); svg.Close(); } #endif @@ -1585,6 +1586,32 @@ TEST_CASE("Voronoi NaN coordinates 12139", "[Voronoi][!hide][!mayfail]") #ifdef VORONOI_DEBUG_OUT dump_voronoi_to_svg(debug_out_path("voronoi-NaNs.svg").c_str(), - vd, Points(), lines, 0.015); + vd, Points(), lines, Polygons(), 0.015); #endif } + +TEST_CASE("Voronoi offset", "[VoronoiOffset]") +{ + Polygons poly_with_hole = { Polygon { + { 0, 10000000}, + { 700000, 0}, + { 700000, 9000000}, + { 9100000, 9000000}, + { 9100000, 0}, + {10000000, 10000000} + } + }; + + VD vd; + Lines lines = to_lines(poly_with_hole); + construct_voronoi(lines.begin(), lines.end(), &vd); + + Polygons offsetted_polygons = voronoi_offset(vd, lines, scale_(0.2), scale_(0.005)); + +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-offset.svg").c_str(), + vd, Points(), lines, offsetted_polygons); +#endif + + REQUIRE(offsetted_polygons.size() == 2); +} diff --git a/tests/sla_print/sla_raycast_tests.cpp b/tests/sla_print/sla_raycast_tests.cpp index 74c7994723..c82e4569a8 100644 --- a/tests/sla_print/sla_raycast_tests.cpp +++ b/tests/sla_print/sla_raycast_tests.cpp @@ -39,7 +39,7 @@ TEST_CASE("Raycaster - find intersections of a line and cylinder") REQUIRE(std::abs(out[1].first - std::sqrt(72.f)) < 0.001f); } - +#ifdef SLIC3R_HOLE_RAYCASTER // Create a simple scene with a 20mm cube and a big hole in the front wall // with 5mm radius. Then shoot rays from interesting positions and see where // they land. @@ -94,3 +94,4 @@ TEST_CASE("Raycaster with loaded drillholes", "[sla_raycast]") // Check for support tree correctness test_support_model_collision("20mm_cube.obj", {}, hcfg, holes); } +#endif diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 883e4268a8..1eaf796c00 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -105,8 +105,13 @@ void test_supports(const std::string &obj_filename, // Create the special index-triangle mesh with spatial indexing which // is the input of the support point and support mesh generators sla::EigenMesh3D emesh{mesh}; + +#ifdef SLIC3R_HOLE_RAYCASTER if (hollowingcfg.enabled) emesh.load_holes(drainholes); +#endif + + // TODO: do the cgal hole cutting... // Create the support point generator sla::SupportPointGenerator::Config autogencfg;