diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs index 850d69b08..ed03bfe64 100644 --- a/resources/shaders/gouraud.fs +++ b/resources/shaders/gouraud.fs @@ -4,7 +4,9 @@ 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 SlopeDetection { @@ -15,6 +17,7 @@ struct SlopeDetection uniform vec4 uniform_color; uniform SlopeDetection slope; +uniform bool sinking; #ifdef ENABLE_ENVIRONMENT_MAP uniform sampler2D environment_tex; @@ -23,27 +26,38 @@ uniform SlopeDetection slope; varying vec3 clipping_planes_dots; -// x = tainted, y = specular; +// x = diffuse, y = specular; varying vec2 intensity; varying vec3 delta_box_min; varying vec3 delta_box_max; +varying vec4 model_pos; +varying float world_pos_z; varying float world_normal_z; varying vec3 eye_normal; +vec3 sinking_color(vec3 color) +{ + return (mod(model_pos.x + model_pos.y + model_pos.z, BANDS_WIDTH) < (0.5 * BANDS_WIDTH)) ? mix(color, ZERO, 0.6666) : color; +} + 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) { + 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 color = (any(lessThan(delta_box_min, ZERO)) || any(greaterThan(delta_box_max, ZERO))) ? mix(color, ZERO, 0.3333) : color; + // if the object is sinking, shade it with inclined bands or white around world z = 0 + if (sinking) + color = (abs(world_pos_z) < 0.05) ? WHITE : sinking_color(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); diff --git a/resources/shaders/gouraud.vs b/resources/shaders/gouraud.vs index ed7e3f56b..f2706b386 100644 --- a/resources/shaders/gouraud.vs +++ b/resources/shaders/gouraud.vs @@ -41,7 +41,7 @@ uniform vec2 z_range; // Clipping plane - general orientation. Used by the SLA gizmo. uniform vec4 clipping_plane; -// x = tainted, y = specular; +// x = diffuse, y = specular; varying vec2 intensity; varying vec3 delta_box_min; @@ -49,6 +49,8 @@ varying vec3 delta_box_max; varying vec3 clipping_planes_dots; +varying vec4 model_pos; +varying float world_pos_z; varying float world_normal_z; varying vec3 eye_normal; @@ -69,12 +71,16 @@ void main() NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; + model_pos = gl_Vertex; + // Point in homogenous coordinates. + vec4 world_pos = print_box.volume_world_matrix * gl_Vertex; + world_pos_z = world_pos.z; + // compute deltas for out of print volume detection (world coordinates) if (print_box.actived) { - vec3 v = (print_box.volume_world_matrix * gl_Vertex).xyz; - delta_box_min = v - print_box.min; - delta_box_max = v - print_box.max; + delta_box_min = world_pos.xyz - print_box.min; + delta_box_max = world_pos.xyz - print_box.max; } else { @@ -86,8 +92,6 @@ void main() world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * gl_Normal)).z : 0.0; gl_Position = ftransform(); - // Point in homogenous coordinates. - vec4 world_pos = print_box.volume_world_matrix * gl_Vertex; // 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/resources/shaders/variable_layer_height.fs b/resources/shaders/variable_layer_height.fs index f87e6627e..693c1c6a0 100644 --- a/resources/shaders/variable_layer_height.fs +++ b/resources/shaders/variable_layer_height.fs @@ -24,7 +24,7 @@ void main() float z_texture_col = object_z_row - z_texture_row; float z_blend = 0.25 * cos(min(M_PI, abs(M_PI * (object_z - z_cursor) * 1.8 / z_cursor_band_width))) + 0.25; // Calculate level of detail from the object Z coordinate. - // This makes the slowly sloping surfaces to be show with high detail (with stripes), + // This makes the slowly sloping surfaces to be shown with high detail (with stripes), // and the vertical surfaces to be shown with low detail (no stripes) float z_in_cells = object_z_row * 190.; // Gradient of Z projected on the screen. @@ -32,9 +32,10 @@ void main() float dy_vtc = dFdy(z_in_cells); float lod = clamp(0.5 * log2(max(dx_vtc * dx_vtc, dy_vtc * dy_vtc)), 0., 1.); // Sample the Z texture. Texture coordinates are normalized to <0, 1>. - vec4 color = mix(texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5 )), -10000.), - texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.), lod); - + vec4 color = vec4(0.25, 0.25, 0.25, 1.0); + if (z_texture_row >= 0.0) + color = mix(texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row + 0.5 )), -10000.), + texture2D(z_texture, vec2(z_texture_col, z_texture_row_to_normalized * (z_texture_row * 2. + 1.)), 10000.), lod); // Mix the final color. - gl_FragColor = vec4(intensity.y, intensity.y, intensity.y, 1.0) + intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend); + gl_FragColor = vec4(vec3(intensity.y), 1.0) + intensity.x * mix(color, vec4(1.0, 1.0, 0.0, 1.0), z_blend); } diff --git a/src/admesh/stl.h b/src/admesh/stl.h index e0f2865f0..f7b8937ad 100644 --- a/src/admesh/stl.h +++ b/src/admesh/stl.h @@ -135,7 +135,7 @@ struct stl_file { std::vector facet_start; std::vector neighbors_start; // Statistics - stl_stats stats; + stl_stats stats; }; struct indexed_triangle_set @@ -149,9 +149,9 @@ struct indexed_triangle_set } std::vector indices; - std::vector vertices; + std::vector vertices; //FIXME add normals once we get rid of the stl_file from TriangleMesh completely. - //std::vector normals + //std::vector normals }; extern bool stl_open(stl_file *stl, const char *file); diff --git a/src/clipper/clipper.cpp b/src/clipper/clipper.cpp index 0285d9167..e46a0797c 100644 --- a/src/clipper/clipper.cpp +++ b/src/clipper/clipper.cpp @@ -105,6 +105,15 @@ struct OutRec { //------------------------------------------------------------------------------ +inline IntPoint IntPoint2d(cInt x, cInt y) +{ + return IntPoint(x, y +#ifdef CLIPPERLIB_USE_XYZ + , 0 +#endif // CLIPPERLIB_USE_XYZ + ); +} + inline cInt Round(double val) { return static_cast((val < 0) ? (val - 0.5) : (val + 0.5)); @@ -2243,7 +2252,7 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) while (maxIt != m_Maxima.end() && *maxIt < e->Curr.x()) { if (horzEdge->OutIdx >= 0 && !IsOpen) - AddOutPt(horzEdge, IntPoint(*maxIt, horzEdge->Bot.y())); + AddOutPt(horzEdge, IntPoint2d(*maxIt, horzEdge->Bot.y())); ++maxIt; } } @@ -2252,7 +2261,7 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) while (maxRit != m_Maxima.rend() && *maxRit > e->Curr.x()) { if (horzEdge->OutIdx >= 0 && !IsOpen) - AddOutPt(horzEdge, IntPoint(*maxRit, horzEdge->Bot.y())); + AddOutPt(horzEdge, IntPoint2d(*maxRit, horzEdge->Bot.y())); ++maxRit; } } @@ -2297,12 +2306,12 @@ void Clipper::ProcessHorizontal(TEdge *horzEdge) if(dir == dLeftToRight) { - IntPoint Pt = IntPoint(e->Curr.x(), horzEdge->Curr.y()); + IntPoint Pt = IntPoint2d(e->Curr.x(), horzEdge->Curr.y()); IntersectEdges(horzEdge, e, Pt); } else { - IntPoint Pt = IntPoint(e->Curr.x(), horzEdge->Curr.y()); + IntPoint Pt = IntPoint2d(e->Curr.x(), horzEdge->Curr.y()); IntersectEdges( e, horzEdge, Pt); } TEdge* eNext = (dir == dLeftToRight) ? e->NextInAEL : e->PrevInAEL; @@ -3372,14 +3381,14 @@ void ClipperOffset::AddPath(const Path& path, JoinType joinType, EndType endType //if this path's lowest pt is lower than all the others then update m_lowest if (endType != etClosedPolygon) return; if (m_lowest.x() < 0) - m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); + m_lowest = IntPoint2d(m_polyNodes.ChildCount() - 1, k); else { IntPoint ip = m_polyNodes.Childs[(int)m_lowest.x()]->Contour[(int)m_lowest.y()]; if (newNode->Contour[k].y() > ip.y() || (newNode->Contour[k].y() == ip.y() && newNode->Contour[k].x() < ip.x())) - m_lowest = IntPoint(m_polyNodes.ChildCount() - 1, k); + m_lowest = IntPoint2d(m_polyNodes.ChildCount() - 1, k); } } //------------------------------------------------------------------------------ @@ -3427,10 +3436,10 @@ void ClipperOffset::Execute(Paths& solution, double delta) { IntRect r = clpr.GetBounds(); Path outer(4); - outer[0] = IntPoint(r.left - 10, r.bottom + 10); - outer[1] = IntPoint(r.right + 10, r.bottom + 10); - outer[2] = IntPoint(r.right + 10, r.top - 10); - outer[3] = IntPoint(r.left - 10, r.top - 10); + outer[0] = IntPoint2d(r.left - 10, r.bottom + 10); + outer[1] = IntPoint2d(r.right + 10, r.bottom + 10); + outer[2] = IntPoint2d(r.right + 10, r.top - 10); + outer[3] = IntPoint2d(r.left - 10, r.top - 10); clpr.AddPath(outer, ptSubject, true); clpr.ReverseSolution(true); @@ -3457,10 +3466,10 @@ void ClipperOffset::Execute(PolyTree& solution, double delta) { IntRect r = clpr.GetBounds(); Path outer(4); - outer[0] = IntPoint(r.left - 10, r.bottom + 10); - outer[1] = IntPoint(r.right + 10, r.bottom + 10); - outer[2] = IntPoint(r.right + 10, r.top - 10); - outer[3] = IntPoint(r.left - 10, r.top - 10); + outer[0] = IntPoint2d(r.left - 10, r.bottom + 10); + outer[1] = IntPoint2d(r.right + 10, r.bottom + 10); + outer[2] = IntPoint2d(r.right + 10, r.top - 10); + outer[3] = IntPoint2d(r.left - 10, r.top - 10); clpr.AddPath(outer, ptSubject, true); clpr.ReverseSolution(true); @@ -3536,7 +3545,7 @@ void ClipperOffset::DoOffset(double delta) double X = 1.0, Y = 0.0; for (cInt j = 1; j <= steps; j++) { - m_destPoly.push_back(IntPoint( + m_destPoly.push_back(IntPoint2d( Round(m_srcPoly[0].x() + X * delta), Round(m_srcPoly[0].y() + Y * delta))); double X2 = X; @@ -3549,7 +3558,7 @@ void ClipperOffset::DoOffset(double delta) double X = -1.0, Y = -1.0; for (int j = 0; j < 4; ++j) { - m_destPoly.push_back(IntPoint( + m_destPoly.push_back(IntPoint2d( Round(m_srcPoly[0].x() + X * delta), Round(m_srcPoly[0].y() + Y * delta))); if (X < 0) X = 1; @@ -3604,9 +3613,9 @@ void ClipperOffset::DoOffset(double delta) if (node.m_endtype == etOpenButt) { int j = len - 1; - pt1 = IntPoint(Round(m_srcPoly[j].x() + m_normals[j].x() * delta), Round(m_srcPoly[j].y() + m_normals[j].y() * delta)); + pt1 = IntPoint2d(Round(m_srcPoly[j].x() + m_normals[j].x() * delta), Round(m_srcPoly[j].y() + m_normals[j].y() * delta)); m_destPoly.push_back(pt1); - pt1 = IntPoint(Round(m_srcPoly[j].x() - m_normals[j].x() * delta), Round(m_srcPoly[j].y() - m_normals[j].y() * delta)); + pt1 = IntPoint2d(Round(m_srcPoly[j].x() - m_normals[j].x() * delta), Round(m_srcPoly[j].y() - m_normals[j].y() * delta)); m_destPoly.push_back(pt1); } else @@ -3631,9 +3640,9 @@ void ClipperOffset::DoOffset(double delta) if (node.m_endtype == etOpenButt) { - pt1 = IntPoint(Round(m_srcPoly[0].x() - m_normals[0].x() * delta), Round(m_srcPoly[0].y() - m_normals[0].y() * delta)); + pt1 = IntPoint2d(Round(m_srcPoly[0].x() - m_normals[0].x() * delta), Round(m_srcPoly[0].y() - m_normals[0].y() * delta)); m_destPoly.push_back(pt1); - pt1 = IntPoint(Round(m_srcPoly[0].x() + m_normals[0].x() * delta), Round(m_srcPoly[0].y() + m_normals[0].y() * delta)); + pt1 = IntPoint2d(Round(m_srcPoly[0].x() + m_normals[0].x() * delta), Round(m_srcPoly[0].y() + m_normals[0].y() * delta)); m_destPoly.push_back(pt1); } else @@ -3661,7 +3670,7 @@ void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) double cosA = (m_normals[k].x() * m_normals[j].x() + m_normals[j].y() * m_normals[k].y() ); if (cosA > 0) // angle => 0 degrees { - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta), + m_destPoly.push_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta), Round(m_srcPoly[j].y() + m_normals[k].y() * m_delta))); return; } @@ -3672,10 +3681,10 @@ void ClipperOffset::OffsetPoint(int j, int& k, JoinType jointype) if (m_sinA * m_delta < 0) { - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta), + m_destPoly.push_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[k].x() * m_delta), Round(m_srcPoly[j].y() + m_normals[k].y() * m_delta))); m_destPoly.push_back(m_srcPoly[j]); - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].x() + m_normals[j].x() * m_delta), + m_destPoly.push_back(IntPoint2d(Round(m_srcPoly[j].x() + m_normals[j].x() * m_delta), Round(m_srcPoly[j].y() + m_normals[j].y() * m_delta))); } else @@ -3699,10 +3708,10 @@ void ClipperOffset::DoSquare(int j, int k) { double dx = std::tan(std::atan2(m_sinA, m_normals[k].x() * m_normals[j].x() + m_normals[k].y() * m_normals[j].y()) / 4); - m_destPoly.push_back(IntPoint( + m_destPoly.push_back(IntPoint2d( Round(m_srcPoly[j].x() + m_delta * (m_normals[k].x() - m_normals[k].y() * dx)), Round(m_srcPoly[j].y() + m_delta * (m_normals[k].y() + m_normals[k].x() * dx)))); - m_destPoly.push_back(IntPoint( + m_destPoly.push_back(IntPoint2d( Round(m_srcPoly[j].x() + m_delta * (m_normals[j].x() + m_normals[j].y() * dx)), Round(m_srcPoly[j].y() + m_delta * (m_normals[j].y() - m_normals[j].x() * dx)))); } @@ -3711,7 +3720,7 @@ void ClipperOffset::DoSquare(int j, int k) void ClipperOffset::DoMiter(int j, int k, double r) { double q = m_delta / r; - m_destPoly.push_back(IntPoint(Round(m_srcPoly[j].x() + (m_normals[k].x() + m_normals[j].x()) * q), + m_destPoly.push_back(IntPoint2d(Round(m_srcPoly[j].x() + (m_normals[k].x() + m_normals[j].x()) * q), Round(m_srcPoly[j].y() + (m_normals[k].y() + m_normals[j].y()) * q))); } //------------------------------------------------------------------------------ @@ -3725,14 +3734,14 @@ void ClipperOffset::DoRound(int j, int k) double X = m_normals[k].x(), Y = m_normals[k].y(), X2; for (int i = 0; i < steps; ++i) { - m_destPoly.push_back(IntPoint( + m_destPoly.push_back(IntPoint2d( Round(m_srcPoly[j].x() + X * m_delta), Round(m_srcPoly[j].y() + Y * m_delta))); X2 = X; X = X * m_cos - m_sin * Y; Y = X2 * m_sin + Y * m_cos; } - m_destPoly.push_back(IntPoint( + m_destPoly.push_back(IntPoint2d( Round(m_srcPoly[j].x() + m_normals[j].x() * m_delta), Round(m_srcPoly[j].y() + m_normals[j].y() * m_delta))); } @@ -4001,7 +4010,7 @@ void Minkowski(const Path& poly, const Path& path, Path p; p.reserve(polyCnt); for (size_t j = 0; j < poly.size(); ++j) - p.push_back(IntPoint(path[i].x() + poly[j].x(), path[i].y() + poly[j].y())); + p.push_back(IntPoint2d(path[i].x() + poly[j].x(), path[i].y() + poly[j].y())); pp.push_back(p); } else @@ -4010,7 +4019,7 @@ void Minkowski(const Path& poly, const Path& path, Path p; p.reserve(polyCnt); for (size_t j = 0; j < poly.size(); ++j) - p.push_back(IntPoint(path[i].x() - poly[j].x(), path[i].y() - poly[j].y())); + p.push_back(IntPoint2d(path[i].x() - poly[j].x(), path[i].y() - poly[j].y())); pp.push_back(p); } @@ -4045,7 +4054,7 @@ void TranslatePath(const Path& input, Path& output, const IntPoint& delta) //precondition: input != output output.resize(input.size()); for (size_t i = 0; i < input.size(); ++i) - output[i] = IntPoint(input[i].x() + delta.x(), input[i].y() + delta.y()); + output[i] = IntPoint2d(input[i].x() + delta.x(), input[i].y() + delta.y()); } //------------------------------------------------------------------------------ diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 45a22a270..fbdbe50ac 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -199,6 +199,8 @@ add_library(libslic3r STATIC Tesselate.hpp TriangleMesh.cpp TriangleMesh.hpp + TriangleMeshSlicer.cpp + TriangleMeshSlicer.hpp TriangulateWall.hpp TriangulateWall.cpp utils.cpp diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 01740ca7b..d47d185a0 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -611,12 +611,47 @@ std::vector>> GCode::collec // free functions called by GCode::do_export() namespace DoExport { - static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics) +// static void update_print_estimated_times_stats(const GCodeProcessor& processor, PrintStatistics& print_statistics) +// { +// const GCodeProcessor::Result& result = processor.get_result(); +// print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time); +// print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? +// get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A"; +// } + + static void update_print_estimated_stats(const GCodeProcessor& processor, const std::vector& extruders, PrintStatistics& print_statistics) { const GCodeProcessor::Result& result = processor.get_result(); - print_statistics.estimated_normal_print_time = get_time_dhms(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time); + print_statistics.estimated_normal_print_time = get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time); print_statistics.estimated_silent_print_time = processor.is_stealth_time_estimator_enabled() ? - get_time_dhms(result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].time) : "N/A"; + get_time_dhms(result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].time) : "N/A"; + + // update filament statictics + double total_extruded_volume = 0.0; + double total_used_filament = 0.0; + double total_weight = 0.0; + double total_cost = 0.0; + for (auto volume : result.print_statistics.volumes_per_extruder) { + total_extruded_volume += volume.second; + + size_t extruder_id = volume.first; + auto extruder = std::find_if(extruders.begin(), extruders.end(), [extruder_id](const Extruder& extr) { return extr.id() == extruder_id; }); + if (extruder == extruders.end()) + continue; + + double s = PI * sqr(0.5* extruder->filament_diameter()); + double weight = volume.second * extruder->filament_density() * 0.001; + total_used_filament += volume.second/s; + total_weight += weight; + total_cost += weight * extruder->filament_cost() * 0.001; + } + + print_statistics.total_extruded_volume = total_extruded_volume; + print_statistics.total_used_filament = total_used_filament; + print_statistics.total_weight = total_weight; + print_statistics.total_cost = total_cost; + + print_statistics.filament_stats = result.print_statistics.volumes_per_extruder; } #if ENABLE_VALIDATE_CUSTOM_GCODE @@ -754,7 +789,8 @@ void GCode::do_export(Print* print, const char* path, GCodeProcessor::Result* re BOOST_LOG_TRIVIAL(debug) << "Start processing gcode, " << log_memory_info(); m_processor.process_file(path_tmp, true, [print]() { print->throw_if_canceled(); }); - DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); +// DoExport::update_print_estimated_times_stats(m_processor, print->m_print_statistics); + DoExport::update_print_estimated_stats(m_processor, m_writer.extruders(), print->m_print_statistics); #if ENABLE_GCODE_WINDOW if (result != nullptr) { *result = std::move(m_processor.extract_result()); @@ -957,7 +993,6 @@ namespace DoExport { dst.first += buf; ++ dst.second; }; - print_statistics.filament_stats.insert(std::pair{extruder.id(), (float)used_filament}); append(out_filament_used_mm, "%.2lf", used_filament); append(out_filament_used_cm3, "%.2lf", extruded_volume * 0.001); if (filament_weight > 0.) { diff --git a/src/libslic3r/GCode/GCodeProcessor.cpp b/src/libslic3r/GCode/GCodeProcessor.cpp index 232a51239..917f84f40 100644 --- a/src/libslic3r/GCode/GCodeProcessor.cpp +++ b/src/libslic3r/GCode/GCodeProcessor.cpp @@ -186,6 +186,72 @@ void GCodeProcessor::TimeMachine::CustomGCodeTime::reset() times = std::vector>(); } +void GCodeProcessor::UsedFilaments::reset() +{ + color_change_cache = 0.0f; + volumes_per_color_change = std::vector(); + + tool_change_cache = 0.0f; + volumes_per_extruder.clear(); + + role_cache = 0.0f; + filaments_per_role.clear(); +} + +void GCodeProcessor::UsedFilaments::increase_caches(double extruded_volume) +{ + color_change_cache += extruded_volume; + tool_change_cache += extruded_volume; + role_cache += extruded_volume; +} + +void GCodeProcessor::UsedFilaments::process_color_change_cache() +{ + if (color_change_cache != 0.0f) { + volumes_per_color_change.push_back(color_change_cache); + color_change_cache = 0.0f; + } +} + +void GCodeProcessor::UsedFilaments::process_extruder_cache(GCodeProcessor* processor) +{ + size_t active_extruder_id = processor->m_extruder_id; + if (tool_change_cache != 0.0f) { + if (volumes_per_extruder.find(active_extruder_id) != volumes_per_extruder.end()) + volumes_per_extruder[active_extruder_id] += tool_change_cache; + else + volumes_per_extruder[active_extruder_id] = tool_change_cache; + tool_change_cache = 0.0f; + } +} + +void GCodeProcessor::UsedFilaments::process_role_cache(GCodeProcessor* processor) +{ + if (role_cache != 0.0f) { + std::pair filament = { 0.0f, 0.0f }; + + double s = PI * sqr(0.5 * processor->m_filament_diameters[processor->m_extruder_id]); + filament.first = role_cache/s * 0.001; + filament.second = role_cache * processor->m_filament_densities[processor->m_extruder_id] * 0.001; + + ExtrusionRole active_role = processor->m_extrusion_role; + if (filaments_per_role.find(active_role) != filaments_per_role.end()) { + filaments_per_role[active_role].first += filament.first; + filaments_per_role[active_role].second += filament.second; + } + else + filaments_per_role[active_role] = filament; + role_cache = 0.0f; + } +} + +void GCodeProcessor::UsedFilaments::process_caches(GCodeProcessor* processor) +{ + process_color_change_cache(); + process_extruder_cache(processor); + process_role_cache(processor); +} + void GCodeProcessor::TimeMachine::reset() { enabled = false; @@ -348,10 +414,10 @@ void GCodeProcessor::TimeProcessor::reset() machine_limits = MachineEnvelopeConfig(); filament_load_times = std::vector(); filament_unload_times = std::vector(); - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { machines[i].reset(); } - machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].enabled = true; + machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].enabled = true; } #if ENABLE_GCODE_LINES_ID_IN_H_SLIDER @@ -416,19 +482,19 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) size_t g1_lines_counter = 0; // keeps track of last exported pair #if ENABLE_EXTENDED_M73_LINES - std::array, static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count)> last_exported_main; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + std::array, static_cast(PrintEstimatedStatistics::ETimeMode::Count)> last_exported_main; + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { last_exported_main[i] = { 0, time_in_minutes(machines[i].time) }; } // keeps track of last exported remaining time to next printer stop - std::array(PrintEstimatedTimeStatistics::ETimeMode::Count)> last_exported_stop; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + std::array(PrintEstimatedStatistics::ETimeMode::Count)> last_exported_stop; + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { last_exported_stop[i] = time_in_minutes(machines[i].time); } #else - std::array, static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count)> last_exported; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + std::array, static_cast(PrintEstimatedStatistics::ETimeMode::Count)> last_exported; + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { last_exported[i] = { 0, time_in_minutes(machines[i].time) }; } #endif // ENABLE_EXTENDED_M73_LINES @@ -451,7 +517,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) line = line.substr(1); if (export_remaining_time_enabled && (line == reserved_tag(ETags::First_Line_M73_Placeholder) || line == reserved_tag(ETags::Last_Line_M73_Placeholder))) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; if (machine.enabled) { #if ENABLE_EXTENDED_M73_LINES @@ -486,7 +552,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) else if (line == reserved_tag(ETags::Estimated_Printing_Time_Placeholder)) { #else if (export_remaining_time_enabled && (line == First_Line_M73_Placeholder_Tag || line == Last_Line_M73_Placeholder_Tag)) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; if (machine.enabled) { ret += format_line_M73(machine.line_m73_mask.c_str(), @@ -497,13 +563,13 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) } else if (line == Estimated_Printing_Time_Placeholder_Tag) { #endif // ENABLE_VALIDATE_CUSTOM_GCODE - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; - PrintEstimatedTimeStatistics::ETimeMode mode = static_cast(i); - if (mode == PrintEstimatedTimeStatistics::ETimeMode::Normal || machine.enabled) { + PrintEstimatedStatistics::ETimeMode mode = static_cast(i); + if (mode == PrintEstimatedStatistics::ETimeMode::Normal || machine.enabled) { char buf[128]; sprintf(buf, "; estimated printing time (%s mode) = %s\n", - (mode == PrintEstimatedTimeStatistics::ETimeMode::Normal) ? "normal" : "silent", + (mode == PrintEstimatedStatistics::ETimeMode::Normal) ? "normal" : "silent", get_time_dhms(machine.time).c_str()); ret += buf; } @@ -545,7 +611,7 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename) unsigned int exported_lines_count = 0; #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER if (export_remaining_time_enabled) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { const TimeMachine& machine = machines[i]; if (machine.enabled) { // export pair @@ -789,13 +855,13 @@ GCodeProcessor::GCodeProcessor() { reset(); #if ENABLE_EXTENDED_M73_LINES - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].line_m73_main_mask = "M73 P%s R%s\n"; - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].line_m73_stop_mask = "M73 C%s\n"; - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].line_m73_main_mask = "M73 Q%s S%s\n"; - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].line_m73_stop_mask = "M73 D%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_main_mask = "M73 P%s R%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_stop_mask = "M73 C%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].line_m73_main_mask = "M73 Q%s S%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].line_m73_stop_mask = "M73 D%s\n"; #else - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].line_m73_mask = "M73 P%s R%s\n"; - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].line_m73_mask = "M73 Q%s S%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].line_m73_mask = "M73 P%s R%s\n"; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].line_m73_mask = "M73 Q%s S%s\n"; #endif // ENABLE_EXTENDED_M73_LINES } @@ -826,6 +892,11 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_filament_diameters[i] = static_cast(config.filament_diameter.values[i]); } + m_filament_densities.resize(config.filament_density.values.size()); + for (size_t i = 0; i < config.filament_density.values.size(); ++i) { + m_filament_densities[i] = static_cast(config.filament_density.values[i]); + } + if ((m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware) && config.machine_limits_usage.value != MachineLimitsUsage::Ignore) { m_time_processor.machine_limits = reinterpret_cast(config); if (m_flavor == gcfMarlinLegacy) { @@ -846,7 +917,7 @@ void GCodeProcessor::apply_config(const PrintConfig& config) m_time_processor.filament_unload_times[i] = static_cast(config.filament_unload_time.values[i]); } - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); m_time_processor.machines[i].max_acceleration = max_acceleration; m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; @@ -896,6 +967,13 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) } } + const ConfigOptionFloats* filament_densities = config.option("filament_density"); + if (filament_densities != nullptr) { + for (double dens : filament_densities->values) { + m_filament_densities.push_back(static_cast(dens)); + } + } + m_result.extruders_count = config.option("nozzle_diameter")->values.size(); const ConfigOptionPoints* extruder_offset = config.option("extruder_offset"); @@ -1026,7 +1104,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) m_time_processor.machine_limits.machine_min_travel_rate.values = machine_min_travel_rate->values; } - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { float max_acceleration = get_option_value(m_time_processor.machine_limits.machine_max_acceleration_extruding, i); m_time_processor.machines[i].max_acceleration = max_acceleration; m_time_processor.machines[i].acceleration = (max_acceleration > 0.0f) ? max_acceleration : DEFAULT_ACCELERATION; @@ -1051,7 +1129,7 @@ void GCodeProcessor::apply_config(const DynamicPrintConfig& config) void GCodeProcessor::enable_stealth_time_estimator(bool enabled) { - m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled = enabled; + m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled = enabled; } void GCodeProcessor::reset() @@ -1096,6 +1174,7 @@ void GCodeProcessor::reset() } m_filament_diameters = std::vector(Min_Extruder_Count, 1.75f); + m_filament_densities = std::vector(Min_Extruder_Count, 1.245f); m_extruded_last_z = 0.0f; #if ENABLE_START_GCODE_VISUALIZATION m_first_layer_height = 0.0f; @@ -1109,6 +1188,7 @@ void GCodeProcessor::reset() m_producers_enabled = false; m_time_processor.reset(); + m_used_filaments.reset(); m_result.reset(); m_result.id = ++s_result_id; @@ -1186,7 +1266,7 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr } // process the time blocks - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; TimeMachine::CustomGCodeTime& gcode_time = machine.gcode_time; machine.calculate_time(); @@ -1194,6 +1274,8 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr gcode_time.times.push_back({ CustomGCode::ColorChange, gcode_time.cache }); } + m_used_filaments.process_caches(this); + update_estimated_times_stats(); // post-process to add M73 lines into the gcode @@ -1216,20 +1298,20 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr #endif // ENABLE_GCODE_VIEWER_STATISTICS } -float GCodeProcessor::get_time(PrintEstimatedTimeStatistics::ETimeMode mode) const +float GCodeProcessor::get_time(PrintEstimatedStatistics::ETimeMode mode) const { - return (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].time : 0.0f; + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].time : 0.0f; } -std::string GCodeProcessor::get_time_dhm(PrintEstimatedTimeStatistics::ETimeMode mode) const +std::string GCodeProcessor::get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const { - return (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)) : std::string("N/A"); + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? short_time(get_time_dhms(m_time_processor.machines[static_cast(mode)].time)) : std::string("N/A"); } -std::vector>> GCodeProcessor::get_custom_gcode_times(PrintEstimatedTimeStatistics::ETimeMode mode, bool include_remaining) const +std::vector>> GCodeProcessor::get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const { std::vector>> ret; - if (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) { + if (mode < PrintEstimatedStatistics::ETimeMode::Count) { const TimeMachine& machine = m_time_processor.machines[static_cast(mode)]; float total_time = 0.0f; for (const auto& [type, time] : machine.gcode_time.times) { @@ -1241,10 +1323,10 @@ std::vector>> GCodeProcesso return ret; } -std::vector> GCodeProcessor::get_moves_time(PrintEstimatedTimeStatistics::ETimeMode mode) const +std::vector> GCodeProcessor::get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const { std::vector> ret; - if (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) { + if (mode < PrintEstimatedStatistics::ETimeMode::Count) { for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].moves_time.size(); ++i) { float time = m_time_processor.machines[static_cast(mode)].moves_time[i]; if (time > 0.0f) @@ -1254,10 +1336,10 @@ std::vector> GCodeProcessor::get_moves_time(PrintEst return ret; } -std::vector> GCodeProcessor::get_roles_time(PrintEstimatedTimeStatistics::ETimeMode mode) const +std::vector> GCodeProcessor::get_roles_time(PrintEstimatedStatistics::ETimeMode mode) const { std::vector> ret; - if (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) { + if (mode < PrintEstimatedStatistics::ETimeMode::Count) { for (size_t i = 0; i < m_time_processor.machines[static_cast(mode)].roles_time.size(); ++i) { float time = m_time_processor.machines[static_cast(mode)].roles_time[i]; if (time > 0.0f) @@ -1267,9 +1349,9 @@ std::vector> GCodeProcessor::get_roles_time(Prin return ret; } -std::vector GCodeProcessor::get_layers_time(PrintEstimatedTimeStatistics::ETimeMode mode) const +std::vector GCodeProcessor::get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const { - return (mode < PrintEstimatedTimeStatistics::ETimeMode::Count) ? + return (mode < PrintEstimatedStatistics::ETimeMode::Count) ? m_time_processor.machines[static_cast(mode)].layers_time : std::vector(); } @@ -1461,6 +1543,7 @@ void GCodeProcessor::process_tags(const std::string_view comment) #if ENABLE_VALIDATE_CUSTOM_GCODE // extrusion role tag if (boost::starts_with(comment, reserved_tag(ETags::Role))) { + m_used_filaments.process_role_cache(this); m_extrusion_role = ExtrusionEntity::string_to_role(comment.substr(reserved_tag(ETags::Role).length())); #if ENABLE_SEAMS_VISUALIZATION if (m_extrusion_role == erExternalPerimeter) @@ -1546,7 +1629,8 @@ void GCodeProcessor::process_tags(const std::string_view comment) extruder_id = static_cast(eid); } - m_extruder_colors[extruder_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview + if (extruder_id < m_extruder_colors.size()) + m_extruder_colors[extruder_id] = static_cast(m_extruder_offsets.size()) + m_cp_color.counter; // color_change position in list of color for preview ++m_cp_color.counter; if (m_cp_color.counter == UCHAR_MAX) m_cp_color.counter = 0; @@ -1557,6 +1641,7 @@ void GCodeProcessor::process_tags(const std::string_view comment) } process_custom_gcode_time(CustomGCode::ColorChange); + process_filaments(CustomGCode::ColorChange); return; } @@ -2194,6 +2279,9 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) float volume_extruded_filament = area_filament_cross_section * delta_pos[E]; float area_toolpath_cross_section = volume_extruded_filament / delta_xyz; + // save extruded volume to the cache + m_used_filaments.increase_caches(volume_extruded_filament); + // volume extruded filament / tool displacement = area toolpath cross section m_mm3_per_mm = area_toolpath_cross_section; #if ENABLE_GCODE_VIEWER_DATA_CHECKING @@ -2254,7 +2342,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) assert(distance != 0.0f); float inv_distance = 1.0f / distance; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; if (!machine.enabled) continue; @@ -2264,8 +2352,8 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) std::vector& blocks = machine.blocks; curr.feedrate = (delta_pos[E] == 0.0f) ? - minimum_travel_feedrate(static_cast(i), m_feedrate) : - minimum_feedrate(static_cast(i), m_feedrate); + minimum_travel_feedrate(static_cast(i), m_feedrate) : + minimum_feedrate(static_cast(i), m_feedrate); TimeBlock block; block.move_type = type; @@ -2283,7 +2371,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) curr.abs_axis_feedrate[a] = std::abs(curr.axis_feedrate[a]); if (curr.abs_axis_feedrate[a] != 0.0f) { - float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a)); + float axis_max_feedrate = get_axis_max_feedrate(static_cast(i), static_cast(a)); if (axis_max_feedrate != 0.0f) min_feedrate_factor = std::min(min_feedrate_factor, axis_max_feedrate / curr.abs_axis_feedrate[a]); } @@ -2300,13 +2388,13 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // calculates block acceleration float acceleration = - (type == EMoveType::Travel) ? get_travel_acceleration(static_cast(i)) : + (type == EMoveType::Travel) ? get_travel_acceleration(static_cast(i)) : (is_extrusion_only_move(delta_pos) ? - get_retract_acceleration(static_cast(i)) : - get_acceleration(static_cast(i))); + get_retract_acceleration(static_cast(i)) : + get_acceleration(static_cast(i))); for (unsigned char a = X; a <= E; ++a) { - float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a)); + float axis_max_acceleration = get_axis_max_acceleration(static_cast(i), static_cast(a)); if (acceleration * std::abs(delta_pos[a]) * inv_distance > axis_max_acceleration) acceleration = axis_max_acceleration; } @@ -2317,7 +2405,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) curr.safe_feedrate = block.feedrate_profile.cruise; for (unsigned char a = X; a <= E; ++a) { - float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); + float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); if (curr.abs_axis_feedrate[a] > axis_max_jerk) curr.safe_feedrate = std::min(curr.safe_feedrate, axis_max_jerk); } @@ -2365,7 +2453,7 @@ void GCodeProcessor::process_G1(const GCodeReader::GCodeLine& line) // axis reversal std::max(-v_exit, v_entry)); - float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); + float axis_max_jerk = get_axis_max_jerk(static_cast(i), static_cast(a)); if (jerk > axis_max_jerk) { v_factor *= axis_max_jerk / jerk; limited = true; @@ -2650,8 +2738,8 @@ void GCodeProcessor::process_M201(const GCodeReader::GCodeLine& line) // see http://reprap.org/wiki/G-code#M201:_Set_max_printing_acceleration float factor = ((m_flavor != gcfRepRapSprinter && m_flavor != gcfRepRapFirmware) && m_units == EUnits::Inches) ? INCHES_TO_MM : 1.0f; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal || + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || m_time_processor.machine_envelope_processing_enabled) { if (line.has_x()) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_x, i, line.x() * factor); @@ -2678,8 +2766,8 @@ void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line) // http://smoothieware.org/supported-g-codes float factor = (m_flavor == gcfMarlinLegacy || m_flavor == gcfMarlinFirmware || m_flavor == gcfSmoothie) ? 1.0f : MMMIN_TO_MMSEC; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal || + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || m_time_processor.machine_envelope_processing_enabled) { if (line.has_x()) set_option_value(m_time_processor.machine_limits.machine_max_feedrate_x, i, line.x() * factor); @@ -2699,27 +2787,27 @@ void GCodeProcessor::process_M203(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line) { float value; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal || + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || m_time_processor.machine_envelope_processing_enabled) { if (line.has_value('S', value)) { // Legacy acceleration format. This format is used by the legacy Marlin, MK2 or MK3 firmware // It is also generated by PrusaSlicer to control acceleration per extrusion type // (perimeters, first layer etc) when 'Marlin (legacy)' flavor is used. - set_acceleration(static_cast(i), value); - set_travel_acceleration(static_cast(i), value); + set_acceleration(static_cast(i), value); + set_travel_acceleration(static_cast(i), value); if (line.has_value('T', value)) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value); } else { // New acceleration format, compatible with the upstream Marlin. if (line.has_value('P', value)) - set_acceleration(static_cast(i), value); + set_acceleration(static_cast(i), value); if (line.has_value('R', value)) set_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, i, value); if (line.has_value('T', value)) // Interpret the T value as the travel acceleration in the new Marlin format. - set_travel_acceleration(static_cast(i), value); + set_travel_acceleration(static_cast(i), value); } } } @@ -2727,8 +2815,8 @@ void GCodeProcessor::process_M204(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M205(const GCodeReader::GCodeLine& line) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { - if (static_cast(i) == PrintEstimatedTimeStatistics::ETimeMode::Normal || + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { + if (static_cast(i) == PrintEstimatedStatistics::ETimeMode::Normal || m_time_processor.machine_envelope_processing_enabled) { if (line.has_x()) { float max_jerk = line.x(); @@ -2761,7 +2849,7 @@ void GCodeProcessor::process_M221(const GCodeReader::GCodeLine& line) float value_t; if (line.has_value('S', value_s) && !line.has_value('T', value_t)) { value_s *= 0.01f; - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { m_time_processor.machines[i].extrude_factor_override_percentage = value_s; } } @@ -2812,7 +2900,7 @@ void GCodeProcessor::process_M402(const GCodeReader::GCodeLine& line) void GCodeProcessor::process_M566(const GCodeReader::GCodeLine& line) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { if (line.has_x()) set_option_value(m_time_processor.machine_limits.machine_max_jerk_x, i, line.x() * MMMIN_TO_MMSEC); @@ -2863,6 +2951,7 @@ void GCodeProcessor::process_T(const std::string_view command) BOOST_LOG_TRIVIAL(error) << "GCodeProcessor encountered an invalid toolchange, maybe from a custom gcode."; else { unsigned char old_extruder_id = m_extruder_id; + process_filaments(CustomGCode::ToolChange); m_extruder_id = id; m_cp_color.current = m_extruder_colors[id]; // Specific to the MK3 MMU2: @@ -2920,7 +3009,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type) #if ENABLE_EXTENDED_M73_LINES // stores stop time placeholders for later use if (type == EMoveType::Color_change || type == EMoveType::Pause_Print) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; if (!machine.enabled) continue; @@ -2931,7 +3020,7 @@ void GCodeProcessor::store_move_vertex(EMoveType type) #endif // ENABLE_EXTENDED_M73_LINES } -float GCodeProcessor::minimum_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const +float GCodeProcessor::minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const { if (m_time_processor.machine_limits.machine_min_extruding_rate.empty()) return feedrate; @@ -2939,7 +3028,7 @@ float GCodeProcessor::minimum_feedrate(PrintEstimatedTimeStatistics::ETimeMode m return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_extruding_rate, static_cast(mode))); } -float GCodeProcessor::minimum_travel_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const +float GCodeProcessor::minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const { if (m_time_processor.machine_limits.machine_min_travel_rate.empty()) return feedrate; @@ -2947,7 +3036,7 @@ float GCodeProcessor::minimum_travel_feedrate(PrintEstimatedTimeStatistics::ETim return std::max(feedrate, get_option_value(m_time_processor.machine_limits.machine_min_travel_rate, static_cast(mode))); } -float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const +float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const { switch (axis) { @@ -2959,7 +3048,7 @@ float GCodeProcessor::get_axis_max_feedrate(PrintEstimatedTimeStatistics::ETimeM } } -float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const +float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const { switch (axis) { @@ -2971,7 +3060,7 @@ float GCodeProcessor::get_axis_max_acceleration(PrintEstimatedTimeStatistics::ET } } -float GCodeProcessor::get_axis_max_jerk(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const +float GCodeProcessor::get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const { switch (axis) { @@ -2983,18 +3072,18 @@ float GCodeProcessor::get_axis_max_jerk(PrintEstimatedTimeStatistics::ETimeMode } } -float GCodeProcessor::get_retract_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const +float GCodeProcessor::get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const { return get_option_value(m_time_processor.machine_limits.machine_max_acceleration_retracting, static_cast(mode)); } -float GCodeProcessor::get_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const +float GCodeProcessor::get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const { size_t id = static_cast(mode); return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].acceleration : DEFAULT_ACCELERATION; } -void GCodeProcessor::set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value) +void GCodeProcessor::set_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value) { size_t id = static_cast(mode); if (id < m_time_processor.machines.size()) { @@ -3004,13 +3093,13 @@ void GCodeProcessor::set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mo } } -float GCodeProcessor::get_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const +float GCodeProcessor::get_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode) const { size_t id = static_cast(mode); return (id < m_time_processor.machines.size()) ? m_time_processor.machines[id].travel_acceleration : DEFAULT_TRAVEL_ACCELERATION; } -void GCodeProcessor::set_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value) +void GCodeProcessor::set_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value) { size_t id = static_cast(mode); if (id < m_time_processor.machines.size()) { @@ -3038,7 +3127,7 @@ float GCodeProcessor::get_filament_unload_time(size_t extruder_id) void GCodeProcessor::process_custom_gcode_time(CustomGCode::Type code) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { TimeMachine& machine = m_time_processor.machines[i]; if (!machine.enabled) continue; @@ -3055,17 +3144,26 @@ void GCodeProcessor::process_custom_gcode_time(CustomGCode::Type code) } } +void GCodeProcessor::process_filaments(CustomGCode::Type code) +{ + if (code == CustomGCode::ColorChange) + m_used_filaments.process_color_change_cache(); + + if (code == CustomGCode::ToolChange) + m_used_filaments.process_extruder_cache(this); +} + void GCodeProcessor::simulate_st_synchronize(float additional_time) { - for (size_t i = 0; i < static_cast(PrintEstimatedTimeStatistics::ETimeMode::Count); ++i) { + for (size_t i = 0; i < static_cast(PrintEstimatedStatistics::ETimeMode::Count); ++i) { m_time_processor.machines[i].simulate_st_synchronize(additional_time); } } void GCodeProcessor::update_estimated_times_stats() { - auto update_mode = [this](PrintEstimatedTimeStatistics::ETimeMode mode) { - PrintEstimatedTimeStatistics::Mode& data = m_result.time_statistics.modes[static_cast(mode)]; + auto update_mode = [this](PrintEstimatedStatistics::ETimeMode mode) { + PrintEstimatedStatistics::Mode& data = m_result.print_statistics.modes[static_cast(mode)]; data.time = get_time(mode); data.custom_gcode_times = get_custom_gcode_times(mode, true); data.moves_times = get_moves_time(mode); @@ -3073,11 +3171,15 @@ void GCodeProcessor::update_estimated_times_stats() data.layers_times = get_layers_time(mode); }; - update_mode(PrintEstimatedTimeStatistics::ETimeMode::Normal); - if (m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled) - update_mode(PrintEstimatedTimeStatistics::ETimeMode::Stealth); + update_mode(PrintEstimatedStatistics::ETimeMode::Normal); + if (m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled) + update_mode(PrintEstimatedStatistics::ETimeMode::Stealth); else - m_result.time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].reset(); + m_result.print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].reset(); + + m_result.print_statistics.volumes_per_color_change = m_used_filaments.volumes_per_color_change; + m_result.print_statistics.volumes_per_extruder = m_used_filaments.volumes_per_extruder; + m_result.print_statistics.used_filaments_per_role = m_used_filaments.filaments_per_role; } } /* namespace Slic3r */ diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 75ec1546b..8975255ec 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -36,7 +36,7 @@ namespace Slic3r { Count }; - struct PrintEstimatedTimeStatistics + struct PrintEstimatedStatistics { enum class ETimeMode : unsigned char { @@ -62,14 +62,21 @@ namespace Slic3r { } }; + std::vector volumes_per_color_change; + std::map volumes_per_extruder; + std::map> used_filaments_per_role; + std::array(ETimeMode::Count)> modes; - PrintEstimatedTimeStatistics() { reset(); } + PrintEstimatedStatistics() { reset(); } void reset() { for (auto m : modes) { m.reset(); } + volumes_per_color_change.clear(); + volumes_per_extruder.clear(); + used_filaments_per_role.clear(); } }; @@ -314,7 +321,7 @@ namespace Slic3r { // Additional load / unload times for a filament exchange sequence. std::vector filament_load_times; std::vector filament_unload_times; - std::array(PrintEstimatedTimeStatistics::ETimeMode::Count)> machines; + std::array(PrintEstimatedStatistics::ETimeMode::Count)> machines; void reset(); @@ -327,6 +334,30 @@ namespace Slic3r { #endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER }; + struct UsedFilaments // filaments per ColorChange + { + double color_change_cache; + std::vector volumes_per_color_change; + + double tool_change_cache; + std::map volumes_per_extruder; + + double role_cache; + // ExtrusionRole : + std::map> filaments_per_role; + + void reset(); + + void increase_caches(double extruded_volume); + + void process_color_change_cache(); + void process_extruder_cache(GCodeProcessor* processor); + void process_role_cache(GCodeProcessor* processor); + void process_caches(GCodeProcessor* processor); + + friend class GCodeProcessor; + }; + public: #if !ENABLE_GCODE_LINES_ID_IN_H_SLIDER struct MoveVertex @@ -372,7 +403,7 @@ namespace Slic3r { SettingsIds settings_ids; size_t extruders_count; std::vector extruder_colors; - PrintEstimatedTimeStatistics time_statistics; + PrintEstimatedStatistics print_statistics; #if ENABLE_GCODE_VIEWER_STATISTICS int64_t time{ 0 }; @@ -519,6 +550,7 @@ namespace Slic3r { ExtruderColors m_extruder_colors; ExtruderTemps m_extruder_temps; std::vector m_filament_diameters; + std::vector m_filament_densities; float m_extruded_last_z; #if ENABLE_START_GCODE_VISUALIZATION float m_first_layer_height; // mm @@ -550,6 +582,7 @@ namespace Slic3r { bool m_producers_enabled; TimeProcessor m_time_processor; + UsedFilaments m_used_filaments; Result m_result; static unsigned int s_result_id; @@ -566,7 +599,7 @@ namespace Slic3r { void apply_config(const PrintConfig& config); void enable_stealth_time_estimator(bool enabled); bool is_stealth_time_estimator_enabled() const { - return m_time_processor.machines[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Stealth)].enabled; + return m_time_processor.machines[static_cast(PrintEstimatedStatistics::ETimeMode::Stealth)].enabled; } void enable_machine_envelope_processing(bool enabled) { m_time_processor.machine_envelope_processing_enabled = enabled; } void enable_producers(bool enabled) { m_producers_enabled = enabled; } @@ -579,13 +612,13 @@ namespace Slic3r { // throws CanceledException through print->throw_if_canceled() (sent by the caller as callback). void process_file(const std::string& filename, bool apply_postprocess, std::function cancel_callback = nullptr); - float get_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; - std::string get_time_dhm(PrintEstimatedTimeStatistics::ETimeMode mode) const; - std::vector>> get_custom_gcode_times(PrintEstimatedTimeStatistics::ETimeMode mode, bool include_remaining) const; + float get_time(PrintEstimatedStatistics::ETimeMode mode) const; + std::string get_time_dhm(PrintEstimatedStatistics::ETimeMode mode) const; + std::vector>> get_custom_gcode_times(PrintEstimatedStatistics::ETimeMode mode, bool include_remaining) const; - std::vector> get_moves_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; - std::vector> get_roles_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; - std::vector get_layers_time(PrintEstimatedTimeStatistics::ETimeMode mode) const; + std::vector> get_moves_time(PrintEstimatedStatistics::ETimeMode mode) const; + std::vector> get_roles_time(PrintEstimatedStatistics::ETimeMode mode) const; + std::vector get_layers_time(PrintEstimatedStatistics::ETimeMode mode) const; private: void apply_config(const DynamicPrintConfig& config); @@ -701,20 +734,21 @@ namespace Slic3r { void store_move_vertex(EMoveType type); - float minimum_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const; - float minimum_travel_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, float feedrate) const; - float get_axis_max_feedrate(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const; - float get_axis_max_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const; - float get_axis_max_jerk(PrintEstimatedTimeStatistics::ETimeMode mode, Axis axis) const; - float get_retract_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const; - float get_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const; - void set_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value); - float get_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode) const; - void set_travel_acceleration(PrintEstimatedTimeStatistics::ETimeMode mode, float value); + float minimum_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const; + float minimum_travel_feedrate(PrintEstimatedStatistics::ETimeMode mode, float feedrate) const; + float get_axis_max_feedrate(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; + float get_axis_max_acceleration(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; + float get_axis_max_jerk(PrintEstimatedStatistics::ETimeMode mode, Axis axis) const; + float get_retract_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; + float get_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; + void set_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); + float get_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode) const; + void set_travel_acceleration(PrintEstimatedStatistics::ETimeMode mode, float value); float get_filament_load_time(size_t extruder_id); float get_filament_unload_time(size_t extruder_id); void process_custom_gcode_time(CustomGCode::Type code); + void process_filaments(CustomGCode::Type code); // Simulates firmware st_synchronize() call void simulate_st_synchronize(float additional_time = 0.0f); diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index c6f2b5b1b..775ccc985 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -3,6 +3,7 @@ #include "ModelArrange.hpp" #include "Geometry.hpp" #include "MTUtils.hpp" +#include "TriangleMeshSlicer.hpp" #include "TriangleSelector.hpp" #include "Format/AMF.hpp" @@ -893,6 +894,30 @@ Polygon ModelObject::convex_hull_2d(const Transform3d &trafo_instance) const Points pts; for (const ModelVolume *v : this->volumes) if (v->is_model_part()) { +#if ENABLE_ALLOW_NEGATIVE_Z + const Transform3d trafo = trafo_instance * v->get_matrix(); + const TriangleMesh& hull_3d = v->get_convex_hull(); + const indexed_triangle_set& its = hull_3d.its; + if (its.vertices.empty()) { + // Using the STL faces. + const stl_file& stl = hull_3d.stl; + for (const stl_facet& facet : stl.facet_start) { + for (size_t j = 0; j < 3; ++j) { + const Vec3d p = trafo * facet.vertex[j].cast(); + if (p.z() >= 0.0) + pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); + } + } + } + else { + // Using the shared vertices should be a bit quicker than using the STL faces. + for (size_t i = 0; i < its.vertices.size(); ++i) { + const Vec3d p = trafo * its.vertices[i].cast(); + if (p.z() >= 0.0) + pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); + } + } +#else Transform3d trafo = trafo_instance * v->get_matrix(); const indexed_triangle_set &its = v->mesh().its; if (its.vertices.empty()) { @@ -901,15 +926,16 @@ Polygon ModelObject::convex_hull_2d(const Transform3d &trafo_instance) const for (const stl_facet &facet : stl.facet_start) for (size_t j = 0; j < 3; ++ j) { Vec3d p = trafo * facet.vertex[j].cast(); - pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); + pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); } } else { // Using the shared vertices should be a bit quicker than using the STL faces. for (size_t i = 0; i < its.vertices.size(); ++ i) { Vec3d p = trafo * its.vertices[i].cast(); - pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); + pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y()))); } } +#endif // ENABLE_ALLOW_NEGATIVE_Z } std::sort(pts.begin(), pts.end(), [](const Point& a, const Point& b) { return a(0) < b(0) || (a(0) == b(0) && a(1) < b(1)); }); pts.erase(std::unique(pts.begin(), pts.end(), [](const Point& a, const Point& b) { return a(0) == b(0) && a(1) == b(1); }), pts.end()); @@ -942,30 +968,39 @@ void ModelObject::center_around_origin(bool include_modifiers) { // calculate the displacements needed to // center this object around the origin - BoundingBoxf3 bb = include_modifiers ? full_raw_mesh_bounding_box() : raw_mesh_bounding_box(); + const BoundingBoxf3 bb = include_modifiers ? full_raw_mesh_bounding_box() : raw_mesh_bounding_box(); // Shift is the vector from the center of the bounding box to the origin - Vec3d shift = -bb.center(); + const Vec3d shift = -bb.center(); this->translate(shift); this->origin_translation += shift; } +#if ENABLE_ALLOW_NEGATIVE_Z +void ModelObject::ensure_on_bed(bool allow_negative_z) +{ + const double min_z = get_min_z(); + if (!allow_negative_z || min_z > 0.0) + translate_instances({ 0.0, 0.0, -min_z }); +} +#else void ModelObject::ensure_on_bed() { - translate_instances(Vec3d(0.0, 0.0, -get_min_z())); + translate_instances({ 0.0, 0.0, -get_min_z() }); } +#endif // ENABLE_ALLOW_NEGATIVE_Z void ModelObject::translate_instances(const Vec3d& vector) { - for (size_t i = 0; i < instances.size(); ++i) - { + for (size_t i = 0; i < instances.size(); ++i) { translate_instance(i, vector); } } void ModelObject::translate_instance(size_t instance_idx, const Vec3d& vector) { + assert(instance_idx < instances.size()); ModelInstance* i = instances[instance_idx]; i->set_offset(i->get_offset() + vector); invalidate_bounding_box(); @@ -973,8 +1008,7 @@ void ModelObject::translate_instance(size_t instance_idx, const Vec3d& vector) void ModelObject::translate(double x, double y, double z) { - for (ModelVolume *v : this->volumes) - { + for (ModelVolume *v : this->volumes) { v->translate(x, y, z); } @@ -984,8 +1018,7 @@ void ModelObject::translate(double x, double y, double z) void ModelObject::scale(const Vec3d &versor) { - for (ModelVolume *v : this->volumes) - { + for (ModelVolume *v : this->volumes) { v->scale(versor); } this->invalidate_bounding_box(); @@ -993,41 +1026,34 @@ void ModelObject::scale(const Vec3d &versor) void ModelObject::rotate(double angle, Axis axis) { - for (ModelVolume *v : this->volumes) - { + for (ModelVolume *v : this->volumes) { v->rotate(angle, axis); } - center_around_origin(); this->invalidate_bounding_box(); } void ModelObject::rotate(double angle, const Vec3d& axis) { - for (ModelVolume *v : this->volumes) - { + for (ModelVolume *v : this->volumes) { v->rotate(angle, axis); } - center_around_origin(); this->invalidate_bounding_box(); } void ModelObject::mirror(Axis axis) { - for (ModelVolume *v : this->volumes) - { + for (ModelVolume *v : this->volumes) { v->mirror(axis); } - this->invalidate_bounding_box(); } // This method could only be called before the meshes of this ModelVolumes are not shared! void ModelObject::scale_mesh_after_creation(const Vec3d &versor) { - for (ModelVolume *v : this->volumes) - { + for (ModelVolume *v : this->volumes) { v->scale_geometry_after_creation(versor); v->set_offset(versor.cwiseProduct(v->get_offset())); } @@ -1187,32 +1213,32 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b } else if (! volume->mesh().empty()) { - TriangleMesh upper_mesh, lower_mesh; - // Transform the mesh by the combined transformation matrix. // Flip the triangles in case the composite transformation is left handed. TriangleMesh mesh(volume->mesh()); mesh.transform(instance_matrix * volume_matrix, true); volume->reset_mesh(); - - mesh.require_shared_vertices(); - - // Perform cut - TriangleMeshSlicer tms(&mesh); - tms.cut(float(z), &upper_mesh, &lower_mesh); - // Reset volume transformation except for offset const Vec3d offset = volume->get_offset(); volume->set_transformation(Geometry::Transformation()); volume->set_offset(offset); - if (keep_upper) { - upper_mesh.repair(); - upper_mesh.reset_repair_stats(); - } - if (keep_lower) { - lower_mesh.repair(); - lower_mesh.reset_repair_stats(); + // Perform cut + TriangleMesh upper_mesh, lower_mesh; + { + indexed_triangle_set upper_its, lower_its; + mesh.require_shared_vertices(); + cut_mesh(mesh.its, float(z), &upper_its, &lower_its); + if (keep_upper) { + upper_mesh = TriangleMesh(upper_its); + upper_mesh.repair(); + upper_mesh.reset_repair_stats(); + } + if (keep_lower) { + lower_mesh = TriangleMesh(lower_its); + lower_mesh.repair(); + lower_mesh.reset_repair_stats(); + } } if (keep_upper && upper_mesh.facets_count() > 0) { @@ -1418,11 +1444,9 @@ double ModelObject::get_min_z() const { if (instances.empty()) return 0.0; - else - { + else { double min_z = DBL_MAX; - for (size_t i = 0; i < instances.size(); ++i) - { + for (size_t i = 0; i < instances.size(); ++i) { min_z = std::min(min_z, get_instance_min_z(i)); } return min_z; @@ -1433,15 +1457,14 @@ double ModelObject::get_instance_min_z(size_t instance_idx) const { double min_z = DBL_MAX; - ModelInstance* inst = instances[instance_idx]; + const ModelInstance* inst = instances[instance_idx]; const Transform3d& mi = inst->get_matrix(true); - for (const ModelVolume* v : volumes) - { + for (const ModelVolume* v : volumes) { if (!v->is_model_part()) continue; - Transform3d mv = mi * v->get_matrix(); + const Transform3d mv = mi * v->get_matrix(); const TriangleMesh& hull = v->get_convex_hull(); for (const stl_facet &facet : hull.stl.facet_start) for (int i = 0; i < 3; ++ i) @@ -1906,12 +1929,19 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const Vec3d rotation = get_rotation(); rotation.z() = 0.; Transform3d trafo_instance = +#if ENABLE_ALLOW_NEGATIVE_Z + Geometry::assemble_transform(get_offset().z() * Vec3d::UnitZ(), rotation, + get_scaling_factor(), get_mirror()); +#else Geometry::assemble_transform(Vec3d::Zero(), rotation, get_scaling_factor(), get_mirror()); +#endif // ENABLE_ALLOW_NEGATIVE_Z Polygon p = get_object()->convex_hull_2d(trafo_instance); +#if !ENABLE_ALLOW_NEGATIVE_Z assert(!p.points.empty()); +#endif // !ENABLE_ALLOW_NEGATIVE_Z // if (!p.points.empty()) { // Polygons pp{p}; diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 7b782e87d..1b3265084 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -306,7 +306,11 @@ public: void center_around_origin(bool include_modifiers = true); +#if ENABLE_ALLOW_NEGATIVE_Z + void ensure_on_bed(bool allow_negative_z = false); +#else void ensure_on_bed(); +#endif // ENABLE_ALLOW_NEGATIVE_Z void translate_instances(const Vec3d& vector); void translate_instance(size_t instance_idx, const Vec3d& vector); void translate(const Vec3d &vector) { this->translate(vector(0), vector(1), vector(2)); } diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 92aa979e4..7a258182e 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -624,11 +624,17 @@ const std::vector& Preset::sla_printer_options() PresetCollection::PresetCollection(Preset::Type type, const std::vector &keys, const Slic3r::StaticPrintConfig &defaults, const std::string &default_name) : m_type(type), m_edited_preset(type, "", false), +#if ENABLE_PROJECT_DIRTY_STATE + m_saved_preset(type, "", false), +#endif // ENABLE_PROJECT_DIRTY_STATE m_idx_selected(0) { // Insert just the default preset. this->add_default_preset(keys, defaults, default_name); m_edited_preset.config.apply(m_presets.front().config); +#if ENABLE_PROJECT_DIRTY_STATE + update_saved_preset_from_current_preset(); +#endif // ENABLE_PROJECT_DIRTY_STATE } void PresetCollection::reset(bool delete_files) @@ -805,7 +811,10 @@ std::pair PresetCollection::load_external_preset( // The source config may contain keys from many possible preset types. Just copy those that relate to this preset. this->get_edited_preset().config.apply_only(combined_config, keys, true); this->update_dirty(); - assert(this->get_edited_preset().is_dirty); +#if ENABLE_PROJECT_DIRTY_STATE + update_saved_preset_from_current_preset(); +#endif // ENABLE_PROJECT_DIRTY_STATE + assert(this->get_edited_preset().is_dirty); return std::make_pair(&(*it), this->get_edited_preset().is_dirty); } if (inherits.empty()) { @@ -1215,6 +1224,9 @@ Preset& PresetCollection::select_preset(size_t idx) idx = first_visible_idx(); m_idx_selected = idx; m_edited_preset = m_presets[idx]; +#if ENABLE_PROJECT_DIRTY_STATE + update_saved_preset_from_current_preset(); +#endif // ENABLE_PROJECT_DIRTY_STATE bool default_visible = ! m_default_suppressed || m_idx_selected < m_num_default_presets; for (size_t i = 0; i < m_num_default_presets; ++i) m_presets[i].is_visible = default_visible; diff --git a/src/libslic3r/Preset.hpp b/src/libslic3r/Preset.hpp index 6e56ad911..8d407fb64 100644 --- a/src/libslic3r/Preset.hpp +++ b/src/libslic3r/Preset.hpp @@ -346,6 +346,11 @@ public: Preset& get_edited_preset() { return m_edited_preset; } const Preset& get_edited_preset() const { return m_edited_preset; } +#if ENABLE_PROJECT_DIRTY_STATE + // Return the last saved preset. + const Preset& get_saved_preset() const { return m_saved_preset; } +#endif // ENABLE_PROJECT_DIRTY_STATE + // Return vendor of the first parent profile, for which the vendor is defined, or null if such profile does not exist. PresetWithVendorProfile get_preset_with_vendor_profile(const Preset &preset) const; PresetWithVendorProfile get_edited_preset_with_vendor_profile() const { return this->get_preset_with_vendor_profile(this->get_edited_preset()); } @@ -365,8 +370,16 @@ public: // Return a preset by an index. If the preset is active, a temporary copy is returned. Preset& preset(size_t idx) { return (idx == m_idx_selected) ? m_edited_preset : m_presets[idx]; } const Preset& preset(size_t idx) const { return const_cast(this)->preset(idx); } +#if ENABLE_PROJECT_DIRTY_STATE + void discard_current_changes() { + m_presets[m_idx_selected].reset_dirty(); + m_edited_preset = m_presets[m_idx_selected]; + update_saved_preset_from_current_preset(); + } +#else void discard_current_changes() { m_presets[m_idx_selected].reset_dirty(); m_edited_preset = m_presets[m_idx_selected]; } - +#endif // ENABLE_PROJECT_DIRTY_STATE + // Return a preset by its name. If the preset is active, a temporary copy is returned. // If a preset is not found by its name, null is returned. Preset* find_preset(const std::string &name, bool first_visible_if_not_found = false); @@ -440,6 +453,16 @@ public: std::vector current_different_from_parent_options(const bool deep_compare = false) const { return dirty_options(&this->get_edited_preset(), this->get_selected_preset_parent(), deep_compare); } +#if ENABLE_PROJECT_DIRTY_STATE + // Compare the content of get_saved_preset() with get_edited_preset() configs, return true if they differ. + bool saved_is_dirty() const { return !this->saved_dirty_options().empty(); } + // Compare the content of get_saved_preset() with get_edited_preset() configs, return the list of keys where they differ. + std::vector saved_dirty_options(const bool deep_compare = false) const + { return dirty_options(&this->get_edited_preset(), &this->get_saved_preset(), deep_compare); } + // Copy edited preset into saved preset. + void update_saved_preset_from_current_preset() { m_saved_preset = m_edited_preset; } +#endif // ENABLE_PROJECT_DIRTY_STATE + // Return a sorted list of system preset names. // Used for validating the "inherits" flag when importing user's config bundles. // Returns names of all system presets including the former names of these presets. @@ -527,6 +550,11 @@ private: std::map m_map_system_profile_renamed; // Initially this preset contains a copy of the selected preset. Later on, this copy may be modified by the user. Preset m_edited_preset; +#if ENABLE_PROJECT_DIRTY_STATE + // Contains a copy of the last saved selected preset. + Preset m_saved_preset; +#endif // ENABLE_PROJECT_DIRTY_STATE + // Selected preset. size_t m_idx_selected; // Is the "- default -" preset suppressed? diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 538ee6009..99119f566 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -371,6 +371,15 @@ static inline bool sequential_print_horizontal_clearance_valid(const Print &prin // FIXME: Arrangement has different parameters for offsetting (jtMiter, limit 2) // which causes that the warning will be showed after arrangement with the // appropriate object distance. Even if I set this to jtMiter the warning still shows up. +#if ENABLE_ALLOW_NEGATIVE_Z + it_convex_hull = map_model_object_to_convex_hull.emplace_hint(it_convex_hull, model_object_id, + offset(print_object->model_object()->convex_hull_2d( + Geometry::assemble_transform({ 0.0, 0.0, model_instance0->get_offset().z() }, model_instance0->get_rotation(), model_instance0->get_scaling_factor(), model_instance0->get_mirror())), + // Shrink the extruder_clearance_radius a tiny bit, so that if the object arrangement algorithm placed the objects + // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. + float(scale_(0.5 * print.config().extruder_clearance_radius.value - EPSILON)), + jtRound, float(scale_(0.1))).front()); +#else it_convex_hull = map_model_object_to_convex_hull.emplace_hint(it_convex_hull, model_object_id, offset(print_object->model_object()->convex_hull_2d( Geometry::assemble_transform(Vec3d::Zero(), model_instance0->get_rotation(), model_instance0->get_scaling_factor(), model_instance0->get_mirror())), @@ -378,7 +387,8 @@ static inline bool sequential_print_horizontal_clearance_valid(const Print &prin // exactly by satisfying the extruder_clearance_radius, this test will not trigger collision. float(scale_(0.5 * print.config().extruder_clearance_radius.value - EPSILON)), jtRound, float(scale_(0.1))).front()); - } +#endif // ENABLE_ALLOW_NEGATIVE_Z + } // Make a copy, so it may be rotated for instances. Polygon convex_hull0 = it_convex_hull->second; double z_diff = Geometry::rotation_diff_z(model_instance0->get_rotation(), print_object->instances().front().model_instance->get_rotation()); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index cd3f84df5..44d2896aa 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -8,6 +8,7 @@ #include "Flow.hpp" #include "Point.hpp" #include "Slicing.hpp" +#include "TriangleMeshSlicer.hpp" #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" #include "GCode/ThumbnailData.hpp" @@ -24,7 +25,6 @@ class Print; class PrintObject; class ModelObject; class GCode; -enum class SlicingMode : uint32_t; class Layer; class SupportLayer; @@ -437,7 +437,7 @@ struct PrintStatistics double total_weight; double total_wipe_tower_cost; double total_wipe_tower_filament; - std::map filament_stats; + std::map filament_stats; // Config with the filled in print statistics. DynamicConfig config() const; diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index c23464eb8..b19a5b3a1 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -921,6 +921,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ ModelObjectStatusDB model_object_status_db; // 1) Synchronize model objects. + bool print_regions_reshuffled = false; if (model.id() != m_model.id()) { // Kill everything, initialize from scratch. // Stop background processing. @@ -932,6 +933,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ delete object; } m_objects.clear(); + print_regions_reshuffled = true; m_model.assign_copy(model); for (const ModelObject *model_object : m_model.objects) model_object_status_db.add(*model_object, ModelObjectStatus::New); @@ -1008,6 +1010,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ } for (ModelObject *model_object : model_objects_old) delete model_object; + print_regions_reshuffled = true; } } } @@ -1124,7 +1127,6 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_ } // 4) Generate PrintObjects from ModelObjects and their instances. - bool print_regions_reshuffled = false; { PrintObjectPtrs print_objects_new; print_objects_new.reserve(std::max(m_objects.size(), m_model.objects.size())); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 8fefd4beb..20c45f3cb 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -10,6 +10,7 @@ #include "Surface.hpp" #include "Slicing.hpp" #include "Tesselate.hpp" +#include "TriangleMeshSlicer.hpp" #include "Utils.hpp" #include "Fill/FillAdaptive.hpp" #include "Format/STL.hpp" @@ -1628,9 +1629,15 @@ PrintRegionConfig PrintObject::region_config_from_model_volume(const PrintRegion void PrintObject::update_slicing_parameters() { +#if ENABLE_ALLOW_NEGATIVE_Z + if (!m_slicing_params.valid) + m_slicing_params = SlicingParameters::create_from_config( + this->print()->config(), m_config, this->model_object()->bounding_box().max.z(), this->object_extruders()); +#else if (! m_slicing_params.valid) m_slicing_params = SlicingParameters::create_from_config( this->print()->config(), m_config, unscale(this->height()), this->object_extruders()); +#endif // ENABLE_ALLOW_NEGATIVE_Z } SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig& full_config, const ModelObject& model_object, float object_max_z) @@ -1692,6 +1699,15 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c updated = true; } +#if ENABLE_ALLOW_NEGATIVE_Z + // Verify the layer_height_profile. + if (!layer_height_profile.empty() && + // Must not be of even length. + ((layer_height_profile.size() & 1) != 0 || + // Last entry must be at the top of the object. + std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_parameters.object_print_z_max) > 1e-3)) + layer_height_profile.clear(); +#else // Verify the layer_height_profile. if (! layer_height_profile.empty() && // Must not be of even length. @@ -1699,6 +1715,7 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c // Last entry must be at the top of the object. std::abs(layer_height_profile[layer_height_profile.size() - 2] - slicing_parameters.object_print_z_height()) > 1e-3)) layer_height_profile.clear(); +#endif // ENABLE_ALLOW_NEGATIVE_Z if (layer_height_profile.empty()) { //layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_config_ranges, model_object.volumes); @@ -1745,7 +1762,7 @@ void PrintObject::_slice(const std::vector &layer_height_profile) } // Make sure all layers contain layer region objects for all regions. for (size_t region_id = 0; region_id < m_region_volumes.size(); ++ region_id) - layer->add_region(&this->print()->get_print_region(region_id)); + layer->add_region(&this->printing_region(region_id)); prev = layer; } } @@ -1782,7 +1799,7 @@ void PrintObject::_slice(const std::vector &layer_height_profile) bool clipped = false; bool upscaled = false; bool spiral_vase = this->print()->config().spiral_vase; - auto slicing_mode = spiral_vase ? SlicingMode::PositiveLargestContour : SlicingMode::Regular; + auto slicing_mode = spiral_vase ? MeshSlicingParams::SlicingMode::PositiveLargestContour : MeshSlicingParams::SlicingMode::Regular; if (! has_z_ranges && (! m_config.clip_multipart_objects.value || all_volumes_single_region >= 0)) { // Cheap path: Slice regions without mutual clipping. // The cheap path is possible if no clipping is allowed or if slicing volumes of just a single region. @@ -1793,12 +1810,12 @@ void PrintObject::_slice(const std::vector &layer_height_profile) if (spiral_vase) { // Slice the bottom layers with SlicingMode::Regular. // This needs to be in sync with LayerRegion::make_perimeters() spiral_vase! - const PrintRegionConfig &config = this->print()->get_print_region(region_id).config(); + const PrintRegionConfig &config = this->printing_region(region_id).config(); slicing_mode_normal_below_layer = size_t(config.bottom_solid_layers.value); for (; slicing_mode_normal_below_layer < slice_zs.size() && slice_zs[slicing_mode_normal_below_layer] < config.bottom_solid_min_thickness - EPSILON; ++ slicing_mode_normal_below_layer); } - std::vector expolygons_by_layer = this->slice_region(region_id, slice_zs, slicing_mode, slicing_mode_normal_below_layer, SlicingMode::Regular); + std::vector expolygons_by_layer = this->slice_region(region_id, slice_zs, slicing_mode, slicing_mode_normal_below_layer, MeshSlicingParams::SlicingMode::Regular); m_print->throw_if_canceled(); BOOST_LOG_TRIVIAL(debug) << "Slicing objects - append slices " << region_id << " start"; for (size_t layer_id = 0; layer_id < expolygons_by_layer.size(); ++ layer_id) @@ -2043,7 +2060,7 @@ end: } // To be used only if there are no layer span specific configurations applied, which would lead to z ranges being generated for this region. -std::vector PrintObject::slice_region(size_t region_id, const std::vector &z, SlicingMode mode, size_t slicing_mode_normal_below_layer, SlicingMode mode_below) const +std::vector PrintObject::slice_region(size_t region_id, const std::vector &z, MeshSlicingParams::SlicingMode mode, size_t slicing_mode_normal_below_layer, MeshSlicingParams::SlicingMode mode_below) const { std::vector volumes; if (region_id < m_region_volumes.size()) { @@ -2103,7 +2120,7 @@ std::vector PrintObject::slice_modifiers(size_t region_id, const std if (volume->is_modifier()) volumes.emplace_back(volume); } - out = this->slice_volumes(slice_zs, SlicingMode::Regular, volumes); + out = this->slice_volumes(slice_zs, MeshSlicingParams::SlicingMode::Regular, volumes); } else { // Some modifier in this region was split to layer spans. std::vector merge; @@ -2121,7 +2138,7 @@ std::vector PrintObject::slice_modifiers(size_t region_id, const std for (; j < volumes_and_ranges.volumes.size() && volume_id == volumes_and_ranges.volumes[j].volume_idx; ++ j) ranges.emplace_back(volumes_and_ranges.volumes[j].layer_height_range); // slicing in parallel - std::vector this_slices = this->slice_volume(slice_zs, ranges, SlicingMode::Regular, *model_volume); + std::vector this_slices = this->slice_volume(slice_zs, ranges, MeshSlicingParams::SlicingMode::Regular, *model_volume); // Variable this_slices could be empty if no value of slice_zs is within any of the ranges of this volume. if (out.empty()) { out = std::move(this_slices); @@ -2162,7 +2179,7 @@ std::vector PrintObject::slice_support_volumes(const ModelVolumeType zs.reserve(this->layers().size()); for (const Layer *l : this->layers()) zs.emplace_back((float)l->slice_z); - return this->slice_volumes(zs, SlicingMode::Regular, volumes); + return this->slice_volumes(zs, MeshSlicingParams::SlicingMode::Regular, volumes); } //FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it. @@ -2177,7 +2194,7 @@ static void fix_mesh_connectivity(TriangleMesh &mesh) std::vector PrintObject::slice_volumes( const std::vector &z, - SlicingMode mode, size_t slicing_mode_normal_below_layer, SlicingMode mode_below, + MeshSlicingParams::SlicingMode mode, size_t slicing_mode_normal_below_layer, MeshSlicingParams::SlicingMode mode_below, const std::vector &volumes) const { std::vector layers; @@ -2202,19 +2219,17 @@ std::vector PrintObject::slice_volumes( mesh.translate(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0); // perform actual slicing const Print *print = this->print(); - auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); // TriangleMeshSlicer needs shared vertices, also this calls the repair() function. mesh.require_shared_vertices(); - TriangleMeshSlicer mslicer; - mslicer.init(&mesh, callback); - mslicer.slice(z, mode, slicing_mode_normal_below_layer, mode_below, float(m_config.slice_closing_radius.value), &layers, callback); + MeshSlicingParamsEx params { { mode, slicing_mode_normal_below_layer, mode_below }, float(m_config.slice_closing_radius.value) }; + layers = slice_mesh_ex(mesh.its, z, params, [print]() { print->throw_if_canceled(); }); m_print->throw_if_canceled(); } } return layers; } -std::vector PrintObject::slice_volume(const std::vector &z, SlicingMode mode, const ModelVolume &volume) const +std::vector PrintObject::slice_volume(const std::vector &z, MeshSlicingParams::SlicingMode mode, const ModelVolume &volume) const { std::vector layers; if (! z.empty()) { @@ -2229,13 +2244,13 @@ std::vector PrintObject::slice_volume(const std::vector &z, S // apply XY shift mesh.translate(- unscale(m_center_offset.x()), - unscale(m_center_offset.y()), 0); // perform actual slicing - TriangleMeshSlicer mslicer; const Print *print = this->print(); - auto callback = TriangleMeshSlicer::throw_on_cancel_callback_type([print](){print->throw_if_canceled();}); // TriangleMeshSlicer needs the shared vertices. mesh.require_shared_vertices(); - mslicer.init(&mesh, callback); - mslicer.slice(z, mode, float(m_config.slice_closing_radius.value), &layers, callback); + MeshSlicingParamsEx params; + params.mode = mode; + params.closing_radius = float(m_config.slice_closing_radius.value); + layers = slice_mesh_ex(mesh.its, z, params, [print](){ print->throw_if_canceled(); }); m_print->throw_if_canceled(); } } @@ -2243,7 +2258,7 @@ std::vector PrintObject::slice_volume(const std::vector &z, S } // Filter the zs not inside the ranges. The ranges are closed at the bottom and open at the top, they are sorted lexicographically and non overlapping. -std::vector PrintObject::slice_volume(const std::vector &z, const std::vector &ranges, SlicingMode mode, const ModelVolume &volume) const +std::vector PrintObject::slice_volume(const std::vector &z, const std::vector &ranges, MeshSlicingParams::SlicingMode mode, const ModelVolume &volume) const { std::vector out; if (! z.empty() && ! ranges.empty()) { diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index b38784521..1d3016bde 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -295,11 +296,8 @@ void cut_drainholes(std::vector & obj_slices, if (mesh.empty()) return; mesh.require_shared_vertices(); - - TriangleMeshSlicer slicer(&mesh); - - std::vector hole_slices; - slicer.slice(slicegrid, SlicingMode::Regular, closing_radius, &hole_slices, thr); + + std::vector hole_slices = slice_mesh_ex(mesh.its, slicegrid, closing_radius, thr); if (obj_slices.size() != hole_slices.size()) BOOST_LOG_TRIVIAL(warning) diff --git a/src/libslic3r/SLA/Pad.cpp b/src/libslic3r/SLA/Pad.cpp index e11914a1c..bf2b4cf54 100644 --- a/src/libslic3r/SLA/Pad.cpp +++ b/src/libslic3r/SLA/Pad.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "ConcaveHull.hpp" @@ -476,10 +477,9 @@ void pad_blueprint(const TriangleMesh & mesh, ThrowOnCancel thrfn) { if (mesh.empty()) return; - TriangleMeshSlicer slicer(&mesh); - auto out = reserve_vector(heights.size()); - slicer.slice(heights, SlicingMode::Regular, 0.f, &out, thrfn); + assert(mesh.has_shared_vertices()); + std::vector out = slice_mesh_ex(mesh.its, heights, thrfn); size_t count = 0; for(auto& o : out) count += o.size(); diff --git a/src/libslic3r/SLA/RasterBase.hpp b/src/libslic3r/SLA/RasterBase.hpp index bbb83b5a0..1cd688ccb 100644 --- a/src/libslic3r/SLA/RasterBase.hpp +++ b/src/libslic3r/SLA/RasterBase.hpp @@ -72,7 +72,8 @@ public: size_t width_px = 0; size_t height_px = 0; - Resolution(size_t w = 0, size_t h = 0) : width_px(w), height_px(h) {} + Resolution() = default; + Resolution(size_t w, size_t h) : width_px(w), height_px(h) {} size_t pixels() const { return width_px * height_px; } }; @@ -81,7 +82,8 @@ public: double w_mm = 1.; double h_mm = 1.; - PixelDim(double px_width_mm = 0.0, double px_height_mm = 0.0) + PixelDim() = default; + PixelDim(double px_width_mm, double px_height_mm) : w_mm(px_width_mm), h_mm(px_height_mm) {} }; diff --git a/src/libslic3r/SLA/SupportTree.cpp b/src/libslic3r/SLA/SupportTree.cpp index 1bb4cfab7..14a4dc360 100644 --- a/src/libslic3r/SLA/SupportTree.cpp +++ b/src/libslic3r/SLA/SupportTree.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -44,9 +45,8 @@ std::vector SupportTree::slice( if (!sup_mesh.empty()) { slices.emplace_back(); - - TriangleMeshSlicer sup_slicer(&sup_mesh); - sup_slicer.slice(grid, SlicingMode::Regular, cr, &slices.back(), ctl().cancelfn); + assert(sup_mesh.has_shared_vertices()); + slices.back() = slice_mesh_ex(sup_mesh.its, grid, cr, ctl().cancelfn); } if (!pad_mesh.empty()) { @@ -59,8 +59,8 @@ std::vector SupportTree::slice( auto padgrid = reserve_vector(size_t(cap > 0 ? cap : 0)); std::copy(grid.begin(), maxzit, std::back_inserter(padgrid)); - TriangleMeshSlicer pad_slicer(&pad_mesh); - pad_slicer.slice(padgrid, SlicingMode::Regular, cr, &slices.back(), ctl().cancelfn); + assert(pad_mesh.has_shared_vertices()); + slices.back() = slice_mesh_ex(pad_mesh.its, padgrid, cr, ctl().cancelfn); } size_t len = grid.size(); diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 108159b89..46064c55b 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -3,6 +3,7 @@ #include #include #include +#include // Need the cylinder method for the the drainholes in hollowing step #include @@ -198,7 +199,7 @@ static std::vector create_exclude_mask( std::vector exclude_mask(its.indices.size(), false); std::vector< std::vector > neighbor_index = - create_neighbor_index(its); + create_vertex_faces_index(its); auto exclude_neighbors = [&neighbor_index, &exclude_mask](const Vec3i &face) { @@ -470,13 +471,12 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) for(auto it = slindex_it; it != po.m_slice_index.end(); ++it) po.m_model_height_levels.emplace_back(it->slice_level()); - TriangleMeshSlicer slicer(&mesh); - po.m_model_slices.clear(); float closing_r = float(po.config().slice_closing_radius.value); auto thr = [this]() { m_print->throw_if_canceled(); }; auto &slice_grid = po.m_model_height_levels; - slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &po.m_model_slices, thr); + assert(mesh.has_shared_vertices()); + po.m_model_slices = slice_mesh_ex(mesh.its, slice_grid, closing_r, thr); sla::Interior *interior = po.m_hollowing_data ? po.m_hollowing_data->interior.get() : @@ -486,9 +486,7 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po) TriangleMesh interiormesh = sla::get_mesh(*interior); interiormesh.repaired = false; interiormesh.repair(true); - TriangleMeshSlicer interior_slicer(&interiormesh); - std::vector interior_slices; - interior_slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &interior_slices, thr); + std::vector interior_slices = slice_mesh_ex(interiormesh.its, slice_grid, closing_r, thr); sla::ccr::for_each(size_t(0), interior_slices.size(), [&po, &interior_slices] (size_t i) { diff --git a/src/libslic3r/SVG.cpp b/src/libslic3r/SVG.cpp index 6bc334eec..6a743b1eb 100644 --- a/src/libslic3r/SVG.cpp +++ b/src/libslic3r/SVG.cpp @@ -83,12 +83,6 @@ void SVG::draw(const Lines &lines, std::string stroke, coordf_t stroke_width) this->draw(l, stroke, stroke_width); } -void SVG::draw(const IntersectionLines &lines, std::string stroke) -{ - for (const IntersectionLine &il : lines) - this->draw((Line)il, stroke); -} - void SVG::draw(const ExPolygon &expolygon, std::string fill, const float fill_opacity) { this->fill = fill; diff --git a/src/libslic3r/SVG.hpp b/src/libslic3r/SVG.hpp index da9dca093..314fae9b9 100644 --- a/src/libslic3r/SVG.hpp +++ b/src/libslic3r/SVG.hpp @@ -43,8 +43,7 @@ public: void draw(const Line &line, std::string stroke = "black", coordf_t stroke_width = 0); void draw(const ThickLine &line, const std::string &fill, const std::string &stroke, coordf_t stroke_width = 0); void draw(const Lines &lines, std::string stroke = "black", coordf_t stroke_width = 0); - void draw(const IntersectionLines &lines, std::string stroke = "black"); - + void draw(const ExPolygon &expolygon, std::string fill = "grey", const float fill_opacity=1.f); void draw_outline(const ExPolygon &polygon, std::string stroke_outer = "black", std::string stroke_holes = "blue", coordf_t stroke_width = 0); void draw(const ExPolygons &expolygons, std::string fill = "grey", const float fill_opacity=1.f); diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index a28d23bcc..e00af1851 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -41,15 +41,9 @@ //==================== #define ENABLE_2_4_0_ALPHA0 1 -// Enable splitting of vertex buffers used to render toolpaths -#define ENABLE_SPLITTED_VERTEX_BUFFER (1 && ENABLE_2_4_0_ALPHA0) -// Enable rendering only starting and final caps for toolpaths -#define ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS (1 && ENABLE_SPLITTED_VERTEX_BUFFER) // Enable reload from disk command for 3mf files #define ENABLE_RELOAD_FROM_DISK_FOR_3MF (1 && ENABLE_2_4_0_ALPHA0) -// Removes obsolete warning texture code -#define ENABLE_WARNING_TEXTURE_REMOVAL (1 && ENABLE_2_4_0_ALPHA0) -// Enable showing gcode line numbers in previeww horizontal slider +// Enable showing gcode line numbers in preview horizontal slider #define ENABLE_GCODE_LINES_ID_IN_H_SLIDER (1 && ENABLE_2_4_0_ALPHA0) // Enable validation of custom gcode against gcode processor reserved keywords #define ENABLE_VALIDATE_CUSTOM_GCODE (1 && ENABLE_2_4_0_ALPHA0) @@ -59,10 +53,19 @@ #define ENABLE_EXTENDED_M73_LINES (1 && ENABLE_VALIDATE_CUSTOM_GCODE) // Enable a modified version of automatic downscale on load of objects too big #define ENABLE_MODIFIED_DOWNSCALE_ON_LOAD_OBJECTS_TOO_BIG (1 && ENABLE_2_4_0_ALPHA0) +// Enable scrollable legend in preview +#define ENABLE_SCROLLABLE_LEGEND (1 && ENABLE_2_4_0_ALPHA0) // Enable visualization of start gcode as regular toolpaths #define ENABLE_START_GCODE_VISUALIZATION (1 && ENABLE_2_4_0_ALPHA0) // Enable visualization of seams in preview #define ENABLE_SEAMS_VISUALIZATION (1 && ENABLE_2_4_0_ALPHA0) +// Enable project dirty state manager +#define ENABLE_PROJECT_DIRTY_STATE (1 && ENABLE_2_4_0_ALPHA0) +// Enable project dirty state manager debug window +#define ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW (0 && ENABLE_PROJECT_DIRTY_STATE) +// Enable to push object instances under the bed +#define ENABLE_ALLOW_NEGATIVE_Z (1 && ENABLE_2_4_0_ALPHA0) +#define DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA (1 && ENABLE_ALLOW_NEGATIVE_Z) #endif // _prusaslicer_technologies_h_ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index 7b5c40466..4a1039b56 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -1,46 +1,28 @@ #include "Exception.hpp" #include "TriangleMesh.hpp" +#include "TriangleMeshSlicer.hpp" #include "ClipperUtils.hpp" #include "Geometry.hpp" -#include "Tesselate.hpp" + #include #include #include + #include #include #include -#include #include -#include #include #include -#include #include #include -#include - #include #include -// for SLIC3R_DEBUG_SLICE_PROCESSING -#include "libslic3r.h" - -#if 0 - #define DEBUG - #define _DEBUG - #undef NDEBUG - #define SLIC3R_DEBUG -// #define SLIC3R_TRIANGLEMESH_DEBUG -#endif - #include -#if defined(SLIC3R_DEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING) -#include "SVG.hpp" -#endif - namespace Slic3r { TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector &facets) : repaired(false) @@ -53,7 +35,7 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector &fac stl.stats.original_num_facets = stl.stats.number_of_facets; stl_allocate(&stl); - for (uint32_t i = 0; i < stl.stats.number_of_facets; ++ i) { + for (uint32_t i = 0; i < stl.stats.number_of_facets; ++ i) { stl_facet facet; facet.vertex[0] = points[facets[i](0)].cast(); facet.vertex[1] = points[facets[i](1)].cast(); @@ -104,23 +86,23 @@ TriangleMesh::TriangleMesh(const indexed_triangle_set &M) : repaired(false) void TriangleMesh::repair(bool update_shared_vertices) { if (this->repaired) { - if (update_shared_vertices) - this->require_shared_vertices(); - return; + if (update_shared_vertices) + this->require_shared_vertices(); + return; } // admesh fails when repairing empty meshes if (this->stl.stats.number_of_facets == 0) - return; + return; BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() started"; // checking exact #ifdef SLIC3R_TRACE_REPAIR - BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_exact"; + BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_exact"; #endif /* SLIC3R_TRACE_REPAIR */ - assert(stl_validate(&this->stl)); - stl_check_facets_exact(&stl); + assert(stl_validate(&this->stl)); + stl_check_facets_exact(&stl); assert(stl_validate(&this->stl)); stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge); stl.stats.facets_w_2_bad_edge = (stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge); @@ -128,17 +110,17 @@ void TriangleMesh::repair(bool update_shared_vertices) // checking nearby //int last_edges_fixed = 0; - float tolerance = (float)stl.stats.shortest_edge; - float increment = (float)stl.stats.bounding_diameter / 10000.0f; + float tolerance = (float)stl.stats.shortest_edge; + float increment = (float)stl.stats.bounding_diameter / 10000.0f; int iterations = 2; if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { for (int i = 0; i < iterations; i++) { if (stl.stats.connected_facets_3_edge < (int)stl.stats.number_of_facets) { //printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); #ifdef SLIC3R_TRACE_REPAIR - BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_nearby"; + BOOST_LOG_TRIVIAL(trace) << "\tstl_check_faces_nearby"; #endif /* SLIC3R_TRACE_REPAIR */ - stl_check_facets_nearby(&stl, tolerance); + stl_check_facets_nearby(&stl, tolerance); //printf(" Fixed %d edges.\n", stl.stats.edges_fixed - last_edges_fixed); //last_edges_fixed = stl.stats.edges_fixed; tolerance += increment; @@ -155,7 +137,7 @@ void TriangleMesh::repair(bool update_shared_vertices) BOOST_LOG_TRIVIAL(trace) << "\tstl_remove_unconnected_facets"; #endif /* SLIC3R_TRACE_REPAIR */ stl_remove_unconnected_facets(&stl); - assert(stl_validate(&this->stl)); + assert(stl_validate(&this->stl)); } // fill_holes @@ -207,7 +189,7 @@ void TriangleMesh::repair(bool update_shared_vertices) // and it is risky to generate such a structure once the meshes are shared. Do it now. this->its.clear(); if (update_shared_vertices) - this->require_shared_vertices(); + this->require_shared_vertices(); } float TriangleMesh::volume() @@ -273,18 +255,18 @@ void TriangleMesh::WriteOBJFile(const char* output_file) const void TriangleMesh::scale(float factor) { stl_scale(&(this->stl), factor); - for (stl_vertex& v : this->its.vertices) - v *= factor; + for (stl_vertex& v : this->its.vertices) + v *= factor; } void TriangleMesh::scale(const Vec3d &versor) { stl_scale_versor(&this->stl, versor.cast()); - for (stl_vertex& v : this->its.vertices) { - v.x() *= versor.x(); - v.y() *= versor.y(); - v.z() *= versor.z(); - } + for (stl_vertex& v : this->its.vertices) { + v.x() *= versor.x(); + v.y() *= versor.y(); + v.z() *= versor.z(); + } } void TriangleMesh::translate(float x, float y, float z) @@ -292,9 +274,9 @@ void TriangleMesh::translate(float x, float y, float z) if (x == 0.f && y == 0.f && z == 0.f) return; stl_translate_relative(&(this->stl), x, y, z); - stl_vertex shift(x, y, z); - for (stl_vertex& v : this->its.vertices) - v += shift; + stl_vertex shift(x, y, z); + for (stl_vertex& v : this->its.vertices) + v += shift; } void TriangleMesh::translate(const Vec3f &displacement) @@ -339,15 +321,15 @@ void TriangleMesh::mirror(const Axis &axis) if (axis == X) { stl_mirror_yz(&this->stl); for (stl_vertex &v : this->its.vertices) - v(0) *= -1.0; + v(0) *= -1.0; } else if (axis == Y) { stl_mirror_xz(&this->stl); for (stl_vertex &v : this->its.vertices) - v(1) *= -1.0; + v(1) *= -1.0; } else if (axis == Z) { stl_mirror_xy(&this->stl); for (stl_vertex &v : this->its.vertices) - v(2) *= -1.0; + v(2) *= -1.0; } } @@ -355,8 +337,8 @@ void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed) { stl_transform(&stl, t); its_transform(its, t); - if (fix_left_handed && t.matrix().block(0, 0, 3, 3).determinant() < 0.) { - // Left handed transformation is being applied. It is a good idea to flip the faces and their normals. + if (fix_left_handed && t.matrix().block(0, 0, 3, 3).determinant() < 0.) { + // Left handed transformation is being applied. It is a good idea to flip the faces and their normals. // As for the assert: the repair function would fix the normals, reversing would // break them again. The caller should provide a mesh that does not need repair. // The repair call is left here so things don't break more than they were. @@ -365,7 +347,7 @@ void TriangleMesh::transform(const Transform3d& t, bool fix_left_handed) stl_reverse_all_facets(&stl); this->its.clear(); this->require_shared_vertices(); - } + } } void TriangleMesh::transform(const Matrix3d& m, bool fix_left_handed) @@ -522,7 +504,7 @@ ExPolygons TriangleMesh::horizontal_projection() const auto delta = scaled(0.01); std::vector deltas { delta, delta, delta }; paths.reserve(this->stl.stats.number_of_facets); - for (const stl_facet &facet : this->stl.facet_start) { + for (const stl_facet &facet : this->stl.facet_start) { p.points[0] = Point::new_scale(facet.vertex[0](0), facet.vertex[0](1)); p.points[1] = Point::new_scale(facet.vertex[1](0), facet.vertex[1](1)); p.points[2] = Point::new_scale(facet.vertex[2](0), facet.vertex[2](1)); @@ -560,12 +542,12 @@ BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d &trafo) c BoundingBoxf3 bbox; if (this->its.vertices.empty()) { // Using the STL faces. - for (const stl_facet &facet : this->stl.facet_start) + for (const stl_facet &facet : this->stl.facet_start) for (size_t j = 0; j < 3; ++ j) bbox.merge(trafo * facet.vertex[j].cast()); } else { // Using the shared vertices should be a bit quicker than using the STL faces. - for (const stl_vertex &v : this->its.vertices) + for (const stl_vertex &v : this->its.vertices) bbox.merge(trafo * v.cast()); } return bbox; @@ -576,29 +558,29 @@ TriangleMesh TriangleMesh::convex_hull_3d() const // The qhull call: orgQhull::Qhull qhull; qhull.disableOutputStream(); // we want qhull to be quiet - std::vector src_vertices; - try + std::vector src_vertices; + try { - if (this->has_shared_vertices()) { + if (this->has_shared_vertices()) { #if REALfloat - qhull.runQhull("", 3, (int)this->its.vertices.size(), (const realT*)(this->its.vertices.front().data()), "Qt"); + qhull.runQhull("", 3, (int)this->its.vertices.size(), (const realT*)(this->its.vertices.front().data()), "Qt"); #else - src_vertices.reserve(this->its.vertices.size() * 3); - // We will now fill the vector with input points for computation: - for (const stl_vertex &v : this->its.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"); + src_vertices.reserve(this->its.vertices.size() * 3); + // We will now fill the vector with input points for computation: + for (const stl_vertex &v : this->its.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 - } else { - src_vertices.reserve(this->stl.facet_start.size() * 9); - // We will now fill the vector with input points for computation: - for (const stl_facet &f : this->stl.facet_start) - for (int i = 0; i < 3; ++ i) - for (int j = 0; j < 3; ++ j) - src_vertices.emplace_back(f.vertex[i](j)); - qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); - } + } else { + src_vertices.reserve(this->stl.facet_start.size() * 9); + // We will now fill the vector with input points for computation: + for (const stl_facet &f : this->stl.facet_start) + for (int i = 0; i < 3; ++ i) + for (int j = 0; j < 3; ++ j) + src_vertices.emplace_back(f.vertex[i](j)); + qhull.runQhull("", 3, (int)src_vertices.size() / 3, src_vertices.data(), "Qt"); + } } catch (...) { @@ -633,10 +615,8 @@ std::vector TriangleMesh::slice(const std::vector &z) { // convert doubles to floats std::vector z_f(z.begin(), z.end()); - TriangleMeshSlicer mslicer(this); - std::vector layers; - mslicer.slice(z_f, SlicingMode::Regular, 0.0004f, &layers, [](){}); - return layers; + assert(this->has_shared_vertices()); + return slice_mesh_ex(this->its, z_f, 0.0004f); } void TriangleMesh::require_shared_vertices() @@ -655,49 +635,62 @@ void TriangleMesh::require_shared_vertices() size_t TriangleMesh::memsize() const { - size_t memsize = 8 + this->stl.memsize() + this->its.memsize(); - return memsize; + size_t memsize = 8 + this->stl.memsize() + this->its.memsize(); + return memsize; } // Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released. size_t TriangleMesh::release_optional() { - size_t memsize_released = sizeof(stl_neighbors) * this->stl.neighbors_start.size() + this->its.memsize(); - // The indexed triangle set may be recalculated using the stl_generate_shared_vertices() function. - this->its.clear(); - // The neighbors structure may be recalculated using the stl_check_facets_exact() function. - this->stl.neighbors_start.clear(); - return memsize_released; + size_t memsize_released = sizeof(stl_neighbors) * this->stl.neighbors_start.size() + this->its.memsize(); + // The indexed triangle set may be recalculated using the stl_generate_shared_vertices() function. + this->its.clear(); + // The neighbors structure may be recalculated using the stl_check_facets_exact() function. + this->stl.neighbors_start.clear(); + return memsize_released; } // Restore optional data possibly released by release_optional(). void TriangleMesh::restore_optional() { - if (! this->stl.facet_start.empty()) { - // Save the old stats before calling stl_check_faces_exact, as it may modify the statistics. - stl_stats stats = this->stl.stats; - if (this->stl.neighbors_start.empty()) { - stl_reallocate(&this->stl); - stl_check_facets_exact(&this->stl); - } - if (this->its.vertices.empty()) - stl_generate_shared_vertices(&this->stl, this->its); - // Restore the old statistics. - this->stl.stats = stats; - } + if (! this->stl.facet_start.empty()) { + // Save the old stats before calling stl_check_faces_exact, as it may modify the statistics. + stl_stats stats = this->stl.stats; + if (this->stl.neighbors_start.empty()) { + stl_reallocate(&this->stl); + stl_check_facets_exact(&this->stl); + } + if (this->its.vertices.empty()) + stl_generate_shared_vertices(&this->stl, this->its); + // Restore the old statistics. + this->stl.stats = stats; + } } -void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callback_type throw_on_cancel) +std::vector> create_vertex_faces_index(const indexed_triangle_set &its) { - mesh = _mesh; - if (! mesh->has_shared_vertices()) - throw Slic3r::InvalidArgument("TriangleMeshSlicer was passed a mesh without shared vertices."); + std::vector> index; - throw_on_cancel(); - facets_edges.assign(_mesh->stl.stats.number_of_facets * 3, -1); - v_scaled_shared.assign(_mesh->its.vertices.size(), stl_vertex()); - for (size_t i = 0; i < v_scaled_shared.size(); ++ i) - this->v_scaled_shared[i] = _mesh->its.vertices[i] / float(SCALING_FACTOR); + if (! its.vertices.empty()) { + size_t res = its.indices.size() / its.vertices.size(); + index.assign(its.vertices.size(), reserve_vector(res)); + for (size_t fi = 0; fi < its.indices.size(); ++fi) { + auto &face = its.indices[fi]; + index[face(0)].emplace_back(fi); + index[face(1)].emplace_back(fi); + index[face(2)].emplace_back(fi); + } + } + + return index; +} + +// Map from a face edge to a unique edge identifier or -1 if no neighbor exists. +// Two neighbor faces share a unique edge identifier even if they are flipped. +template +static inline std::vector create_face_neighbors_index_impl(const indexed_triangle_set &its, ThrowOnCancelCallback throw_on_cancel) +{ + std::vector out(its.indices.size(), Vec3i(-1, -1, -1)); // Create a mapping from triangle edge into face. struct EdgeToFace { @@ -713,12 +706,12 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac bool operator<(const EdgeToFace &other) const { return vertex_low < other.vertex_low || (vertex_low == other.vertex_low && vertex_high < other.vertex_high); } }; std::vector edges_map; - edges_map.assign(this->mesh->stl.stats.number_of_facets * 3, EdgeToFace()); - for (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) + edges_map.assign(its.indices.size() * 3, EdgeToFace()); + for (uint32_t facet_idx = 0; facet_idx < its.indices.size(); ++ facet_idx) for (int i = 0; i < 3; ++ i) { - EdgeToFace &e2f = edges_map[facet_idx*3+i]; - e2f.vertex_low = this->mesh->its.indices[facet_idx][i]; - e2f.vertex_high = this->mesh->its.indices[facet_idx][(i + 1) % 3]; + EdgeToFace &e2f = edges_map[facet_idx * 3 + i]; + e2f.vertex_low = its.indices[facet_idx][i]; + e2f.vertex_high = its.indices[facet_idx][(i + 1) % 3]; e2f.face = facet_idx; // 1 based indexing, to be always strictly positive. e2f.face_edge = i + 1; @@ -761,10 +754,10 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac } } // Assign an edge index to the 1st face. - this->facets_edges[edge_i.face * 3 + std::abs(edge_i.face_edge) - 1] = num_edges; + out[edge_i.face](std::abs(edge_i.face_edge) - 1) = num_edges; if (found) { EdgeToFace &edge_j = edges_map[j]; - this->facets_edges[edge_j.face * 3 + std::abs(edge_j.face_edge) - 1] = num_edges; + out[edge_j.face](std::abs(edge_j.face_edge) - 1) = num_edges; // Mark the edge as connected. edge_j.face = -1; } @@ -772,1179 +765,138 @@ void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callbac if ((i & 0x0ffff) == 0) throw_on_cancel(); } -} - - -void TriangleMeshSlicer::set_up_direction(const Vec3f& up) -{ - m_quaternion.setFromTwoVectors(up, Vec3f::UnitZ()); - m_use_quaternion = true; -} - -void TriangleMeshSlicer::slice( - const std::vector &z, - SlicingMode mode, size_t alternate_mode_first_n_layers, SlicingMode alternate_mode, - std::vector* layers, throw_on_cancel_callback_type throw_on_cancel) const -{ - BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice"; - - /* - This method gets called with a list of unscaled Z coordinates and outputs - a vector pointer having the same number of items as the original list. - Each item is a vector of polygons created by slicing our mesh at the - given heights. - - This method should basically combine the behavior of the existing - Perl methods defined in lib/Slic3r/TriangleMesh.pm: - - - analyze(): this creates the 'facets_edges' and the 'edges_facets' - tables (we don't need the 'edges' table) - - - slice_facet(): this has to be done for each facet. It generates - intersection lines with each plane identified by the Z list. - The get_layer_range() binary search used to identify the Z range - of the facet is already ported to C++ (see Object.xsp) - - - make_loops(): this has to be done for each layer. It creates polygons - from the lines generated by the previous step. - - At the end, we free the tables generated by analyze() as we don't - need them anymore. - - NOTE: this method accepts a vector of floats because the mesh coordinate - type is float. - */ - - BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::_slice_do"; - std::vector lines(z.size()); - { - boost::mutex lines_mutex; - tbb::parallel_for( - tbb::blocked_range(0,this->mesh->stl.stats.number_of_facets), - [&lines, &lines_mutex, &z, throw_on_cancel, this](const tbb::blocked_range& range) { - for (int facet_idx = range.begin(); facet_idx < range.end(); ++ facet_idx) { - if ((facet_idx & 0x0ffff) == 0) - throw_on_cancel(); - this->_slice_do(facet_idx, &lines, &lines_mutex, z); - } - } - ); - } - throw_on_cancel(); - - // v_scaled_shared could be freed here - - // build loops - BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::_make_loops_do"; - layers->resize(z.size()); - tbb::parallel_for( - tbb::blocked_range(0, z.size()), - [&lines, &layers, mode, alternate_mode_first_n_layers, alternate_mode, throw_on_cancel, this](const tbb::blocked_range& range) { - for (size_t line_idx = range.begin(); line_idx < range.end(); ++ line_idx) { - if ((line_idx & 0x0ffff) == 0) - throw_on_cancel(); - - Polygons &polygons = (*layers)[line_idx]; - this->make_loops(lines[line_idx], &polygons); - - auto this_mode = line_idx < alternate_mode_first_n_layers ? alternate_mode : mode; - if (! polygons.empty()) { - if (this_mode == SlicingMode::Positive) { - // Reorient all loops to be CCW. - for (Polygon& p : polygons) - p.make_counter_clockwise(); - } else if (this_mode == SlicingMode::PositiveLargestContour) { - // Keep just the largest polygon, make it CCW. - double max_area = 0.; - Polygon* max_area_polygon = nullptr; - for (Polygon& p : polygons) { - double a = p.area(); - if (std::abs(a) > std::abs(max_area)) { - max_area = a; - max_area_polygon = &p; - } - } - assert(max_area_polygon != nullptr); - if (max_area < 0.) - max_area_polygon->reverse(); - Polygon p(std::move(*max_area_polygon)); - polygons.clear(); - polygons.emplace_back(std::move(p)); - } - } - } - } - ); - BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::slice finished"; - -#ifdef SLIC3R_DEBUG - { - static int iRun = 0; - for (size_t i = 0; i < z.size(); ++ i) { - Polygons &polygons = (*layers)[i]; - ExPolygons expolygons = union_ex(polygons, true); - SVG::export_expolygons(debug_out_path("slice_%d_%d.svg", iRun, i).c_str(), expolygons); - { - BoundingBox bbox; - for (const IntersectionLine &l : lines[i]) { - bbox.merge(l.a); - bbox.merge(l.b); - } - SVG svg(debug_out_path("slice_loops_%d_%d.svg", iRun, i).c_str(), bbox); - svg.draw(expolygons); - for (const IntersectionLine &l : lines[i]) - svg.draw(l, "red", 0); - svg.draw_outline(expolygons, "black", "blue", 0); - svg.Close(); - } -#if 0 -//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. - for (Polygon &poly : polygons) { - for (size_t i = 1; i < poly.points.size(); ++ i) - assert(poly.points[i-1] != poly.points[i]); - assert(poly.points.front() != poly.points.back()); - } -#endif - } - ++ iRun; - } -#endif -} - -void TriangleMeshSlicer::_slice_do(size_t facet_idx, std::vector* lines, boost::mutex* lines_mutex, - const std::vector &z) const -{ - const stl_facet &facet = m_use_quaternion ? (this->mesh->stl.facet_start.data() + facet_idx)->rotated(m_quaternion) : *(this->mesh->stl.facet_start.data() + facet_idx); - - // find facet extents - const float min_z = fminf(facet.vertex[0](2), fminf(facet.vertex[1](2), facet.vertex[2](2))); - const float max_z = fmaxf(facet.vertex[0](2), fmaxf(facet.vertex[1](2), facet.vertex[2](2))); - - #ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf("\n==> FACET %d (%f,%f,%f - %f,%f,%f - %f,%f,%f):\n", facet_idx, - facet.vertex[0](0), facet.vertex[0](1), facet.vertex[0](2), - facet.vertex[1](0), facet.vertex[1](1), facet.vertex[1](2), - facet.vertex[2](0), facet.vertex[2](1), facet.vertex[2](2)); - printf("z: min = %.2f, max = %.2f\n", min_z, max_z); - #endif /* SLIC3R_TRIANGLEMESH_DEBUG */ - - // find layer extents - std::vector::const_iterator min_layer, max_layer; - min_layer = std::lower_bound(z.begin(), z.end(), min_z); // first layer whose slice_z is >= min_z - max_layer = std::upper_bound(min_layer, z.end(), max_z); // first layer whose slice_z is > max_z - #ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf("layers: min = %d, max = %d\n", (int)(min_layer - z.begin()), (int)(max_layer - z.begin())); - #endif /* SLIC3R_TRIANGLEMESH_DEBUG */ - - for (std::vector::const_iterator it = min_layer; it != max_layer; ++ it) { - std::vector::size_type layer_idx = it - z.begin(); - IntersectionLine il; - if (this->slice_facet(*it / SCALING_FACTOR, facet, facet_idx, min_z, max_z, &il) == TriangleMeshSlicer::Slicing) { - boost::lock_guard l(*lines_mutex); - if (il.edge_type == feHorizontal) { - // Ignore horizontal triangles. Any valid horizontal triangle must have a vertical triangle connected, otherwise the part has zero volume. - } else - (*lines)[layer_idx].emplace_back(il); - } - } -} - -void TriangleMeshSlicer::slice( - const std::vector &z, SlicingMode mode, size_t alternate_mode_first_n_layers, SlicingMode alternate_mode, const float closing_radius, - std::vector* layers, throw_on_cancel_callback_type throw_on_cancel) const -{ - std::vector layers_p; - this->slice(z, - (mode == SlicingMode::PositiveLargestContour) ? SlicingMode::Positive : mode, - alternate_mode_first_n_layers, - (alternate_mode == SlicingMode::PositiveLargestContour) ? SlicingMode::Positive : alternate_mode, - &layers_p, throw_on_cancel); - - BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - start"; - layers->resize(z.size()); - tbb::parallel_for( - tbb::blocked_range(0, z.size()), - [&layers_p, mode, alternate_mode_first_n_layers, alternate_mode, closing_radius, layers, throw_on_cancel, this] - (const tbb::blocked_range& range) { - for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { -#ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf("Layer %zu (slice_z = %.2f):\n", layer_id, z[layer_id]); -#endif - throw_on_cancel(); - ExPolygons &expolygons = (*layers)[layer_id]; - this->make_expolygons(layers_p[layer_id], closing_radius, &expolygons); - const auto this_mode = layer_id < alternate_mode_first_n_layers ? alternate_mode : mode; - if (this_mode == SlicingMode::PositiveLargestContour) - keep_largest_contour_only(expolygons); - } - }); - BOOST_LOG_TRIVIAL(debug) << "TriangleMeshSlicer::make_expolygons in parallel - end"; -} - -// Return true, if the facet has been sliced and line_out has been filled. -TriangleMeshSlicer::FacetSliceType TriangleMeshSlicer::slice_facet( - float slice_z, const stl_facet &facet, const int facet_idx, - const float min_z, const float max_z, - IntersectionLine *line_out) const -{ - IntersectionPoint points[3]; - size_t num_points = 0; - size_t point_on_layer = size_t(-1); - - // Reorder vertices so that the first one is the one with lowest Z. - // This is needed to get all intersection lines in a consistent order - // (external on the right of the line) - const stl_triangle_vertex_indices &vertices = this->mesh->its.indices[facet_idx]; - int i = (facet.vertex[1].z() == min_z) ? 1 : ((facet.vertex[2].z() == min_z) ? 2 : 0); - - // These are used only if the cut plane is tilted: - stl_vertex rotated_a; - stl_vertex rotated_b; - - for (int j = i; j - i < 3; ++j) { // loop through facet edges - int edge_id = this->facets_edges[facet_idx * 3 + (j % 3)]; - int a_id = vertices[j % 3]; - int b_id = vertices[(j+1) % 3]; - - const stl_vertex *a; - const stl_vertex *b; - if (m_use_quaternion) { - rotated_a = m_quaternion * this->v_scaled_shared[a_id]; - rotated_b = m_quaternion * this->v_scaled_shared[b_id]; - a = &rotated_a; - b = &rotated_b; - } - else { - a = &this->v_scaled_shared[a_id]; - b = &this->v_scaled_shared[b_id]; - } - - // Is edge or face aligned with the cutting plane? - if (a->z() == slice_z && b->z() == slice_z) { - // Edge is horizontal and belongs to the current layer. - // The following rotation of the three vertices may not be efficient, but this branch happens rarely. - const stl_vertex &v0 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[0]]) : this->v_scaled_shared[vertices[0]]; - const stl_vertex &v1 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[1]]) : this->v_scaled_shared[vertices[1]]; - const stl_vertex &v2 = m_use_quaternion ? stl_vertex(m_quaternion * this->v_scaled_shared[vertices[2]]) : this->v_scaled_shared[vertices[2]]; - const stl_normal &normal = facet.normal; - // We may ignore this edge for slicing purposes, but we may still use it for object cutting. - FacetSliceType result = Slicing; - if (min_z == max_z) { - // All three vertices are aligned with slice_z. - line_out->edge_type = feHorizontal; - result = Cutting; - if (normal.z() < 0) { - // If normal points downwards this is a bottom horizontal facet so we reverse its point order. - std::swap(a, b); - std::swap(a_id, b_id); - } - } else { - // Two vertices are aligned with the cutting plane, the third vertex is below or above the cutting plane. - // Is the third vertex below the cutting plane? - bool third_below = v0.z() < slice_z || v1.z() < slice_z || v2.z() < slice_z; - // Two vertices on the cutting plane, the third vertex is below the plane. Consider the edge to be part of the slice - // only if it is the upper edge. - // (the bottom most edge resp. vertex of a triangle is not owned by the triangle, but the top most edge resp. vertex is part of the triangle - // in respect to the cutting plane). - result = third_below ? Slicing : Cutting; - if (third_below) { - line_out->edge_type = feTop; - std::swap(a, b); - std::swap(a_id, b_id); - } else - line_out->edge_type = feBottom; - } - line_out->a.x() = a->x(); - line_out->a.y() = a->y(); - line_out->b.x() = b->x(); - line_out->b.y() = b->y(); - line_out->a_id = a_id; - line_out->b_id = b_id; - assert(line_out->a != line_out->b); - return result; - } - - if (a->z() == slice_z) { - // Only point a alings with the cutting plane. - if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) { - point_on_layer = num_points; - IntersectionPoint &point = points[num_points ++]; - point.x() = a->x(); - point.y() = a->y(); - point.point_id = a_id; - } - } else if (b->z() == slice_z) { - // Only point b alings with the cutting plane. - if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) { - point_on_layer = num_points; - IntersectionPoint &point = points[num_points ++]; - point.x() = b->x(); - point.y() = b->y(); - point.point_id = b_id; - } - } else if ((a->z() < slice_z && b->z() > slice_z) || (b->z() < slice_z && a->z() > slice_z)) { - // A general case. The face edge intersects the cutting plane. Calculate the intersection point. - assert(a_id != b_id); - // Sort the edge to give a consistent answer. - if (a_id > b_id) { - std::swap(a_id, b_id); - std::swap(a, b); - } - IntersectionPoint &point = points[num_points]; - double t = (double(slice_z) - double(b->z())) / (double(a->z()) - double(b->z())); - if (t <= 0.) { - if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) { - point.x() = a->x(); - point.y() = a->y(); - point_on_layer = num_points ++; - point.point_id = a_id; - } - } else if (t >= 1.) { - if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) { - point.x() = b->x(); - point.y() = b->y(); - point_on_layer = num_points ++; - point.point_id = b_id; - } - } else { - point.x() = coord_t(floor(double(b->x()) + (double(a->x()) - double(b->x())) * t + 0.5)); - point.y() = coord_t(floor(double(b->y()) + (double(a->y()) - double(b->y())) * t + 0.5)); - point.edge_id = edge_id; - ++ num_points; - } - } - } - - // Facets must intersect each plane 0 or 2 times, or it may touch the plane at a single vertex only. - assert(num_points < 3); - if (num_points == 2) { - line_out->edge_type = feGeneral; - line_out->a = (Point)points[1]; - line_out->b = (Point)points[0]; - line_out->a_id = points[1].point_id; - line_out->b_id = points[0].point_id; - line_out->edge_a_id = points[1].edge_id; - line_out->edge_b_id = points[0].edge_id; - // Not a zero lenght edge. - //FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. - //assert(line_out->a != line_out->b); - // The plane cuts at least one edge in a general position. - assert(line_out->a_id == -1 || line_out->b_id == -1); - assert(line_out->edge_a_id != -1 || line_out->edge_b_id != -1); - // General slicing position, use the segment for both slicing and object cutting. -#if 0 - if (line_out->a_id != -1 && line_out->b_id != -1) { - // Solving a degenerate case, where both the intersections snapped to an edge. - // Correctly classify the face as below or above based on the position of the 3rd point. - int i = vertices[0]; - if (i == line_out->a_id || i == line_out->b_id) - i = vertices[1]; - if (i == line_out->a_id || i == line_out->b_id) - i = vertices[2]; - assert(i != line_out->a_id && i != line_out->b_id); - line_out->edge_type = ((m_use_quaternion ? - (m_quaternion * this->v_scaled_shared[i]).z() - : this->v_scaled_shared[i].z()) < slice_z) ? feTop : feBottom; - } -#endif - return Slicing; - } - return NoSlice; -} - -#if 0 -//FIXME Should this go away? For valid meshes the function slice_facet() returns Slicing -// and sets edges of vertical triangles to produce only a single edge per pair of neighbor faces. -// So the following code makes only sense now to handle degenerate meshes with more than two faces -// sharing a single edge. -static inline void remove_tangent_edges(std::vector &lines) -{ - std::vector by_vertex_pair; - by_vertex_pair.reserve(lines.size()); - for (IntersectionLine& line : lines) - if (line.edge_type != feGeneral && line.a_id != -1) - // This is a face edge. Check whether there is its neighbor stored in lines. - by_vertex_pair.emplace_back(&line); - auto edges_lower_sorted = [](const IntersectionLine *l1, const IntersectionLine *l2) { - // Sort vertices of l1, l2 lexicographically - int l1a = l1->a_id; - int l1b = l1->b_id; - int l2a = l2->a_id; - int l2b = l2->b_id; - if (l1a > l1b) - std::swap(l1a, l1b); - if (l2a > l2b) - std::swap(l2a, l2b); - // Lexicographical "lower" operator on lexicographically sorted vertices should bring equal edges together when sored. - return l1a < l2a || (l1a == l2a && l1b < l2b); - }; - std::sort(by_vertex_pair.begin(), by_vertex_pair.end(), edges_lower_sorted); - for (auto line = by_vertex_pair.begin(); line != by_vertex_pair.end(); ++ line) { - IntersectionLine &l1 = **line; - if (! l1.skip()) { - // Iterate as long as line and line2 edges share the same end points. - for (auto line2 = line + 1; line2 != by_vertex_pair.end() && ! edges_lower_sorted(*line, *line2); ++ line2) { - // Lines must share the end points. - assert(! edges_lower_sorted(*line, *line2)); - assert(! edges_lower_sorted(*line2, *line)); - IntersectionLine &l2 = **line2; - if (l2.skip()) - continue; - if (l1.a_id == l2.a_id) { - assert(l1.b_id == l2.b_id); - l2.set_skip(); - // If they are both oriented upwards or downwards (like a 'V'), - // then we can remove both edges from this layer since it won't - // affect the sliced shape. - // If one of them is oriented upwards and the other is oriented - // downwards, let's only keep one of them (it doesn't matter which - // one since all 'top' lines were reversed at slicing). - if (l1.edge_type == l2.edge_type) { - l1.set_skip(); - break; - } - } else { - assert(l1.a_id == l2.b_id && l1.b_id == l2.a_id); - // If this edge joins two horizontal facets, remove both of them. - if (l1.edge_type == feHorizontal && l2.edge_type == feHorizontal) { - l1.set_skip(); - l2.set_skip(); - break; - } - } - } - } - } -} -#endif - -struct OpenPolyline { - OpenPolyline() {}; - OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) : - start(start), end(end), points(std::move(points)), consumed(false) { this->length = Slic3r::length(this->points); } - void reverse() { - std::swap(start, end); - std::reverse(points.begin(), points.end()); - } - IntersectionReference start; - IntersectionReference end; - Points points; - double length; - bool consumed; -}; - -// called by TriangleMeshSlicer::make_loops() to connect sliced triangles into closed loops and open polylines by the triangle connectivity. -// Only connects segments crossing triangles of the same orientation. -static void chain_lines_by_triangle_connectivity(std::vector &lines, Polygons &loops, std::vector &open_polylines) -{ - // Build a map of lines by edge_a_id and a_id. - std::vector by_edge_a_id; - std::vector by_a_id; - by_edge_a_id.reserve(lines.size()); - by_a_id.reserve(lines.size()); - for (IntersectionLine &line : lines) { - if (! line.skip()) { - if (line.edge_a_id != -1) - by_edge_a_id.emplace_back(&line); - if (line.a_id != -1) - by_a_id.emplace_back(&line); - } - } - auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; }; - auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; }; - std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower); - std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower); - // Chain the segments with a greedy algorithm, collect the loops and unclosed polylines. - IntersectionLines::iterator it_line_seed = lines.begin(); - for (;;) { - // take first spare line and start a new loop - IntersectionLine *first_line = nullptr; - for (; it_line_seed != lines.end(); ++ it_line_seed) - if (it_line_seed->is_seed_candidate()) { - //if (! it_line_seed->skip()) { - first_line = &(*it_line_seed ++); - break; - } - if (first_line == nullptr) - break; - first_line->set_skip(); - Points loop_pts; - loop_pts.emplace_back(first_line->a); - IntersectionLine *last_line = first_line; - - /* - printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", - first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id, - first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y); - */ - - IntersectionLine key; - for (;;) { - // find a line starting where last one finishes - IntersectionLine* next_line = nullptr; - if (last_line->edge_b_id != -1) { - key.edge_a_id = last_line->edge_b_id; - auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower); - if (it_begin != by_edge_a_id.end()) { - auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower); - for (auto it_line = it_begin; it_line != it_end; ++ it_line) - if (! (*it_line)->skip()) { - next_line = *it_line; - break; - } - } - } - if (next_line == nullptr && last_line->b_id != -1) { - key.a_id = last_line->b_id; - auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower); - if (it_begin != by_a_id.end()) { - auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower); - for (auto it_line = it_begin; it_line != it_end; ++ it_line) - if (! (*it_line)->skip()) { - next_line = *it_line; - break; - } - } - } - if (next_line == nullptr) { - // Check whether we closed this loop. - if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) || - (first_line->a_id != -1 && first_line->a_id == last_line->b_id)) { - // The current loop is complete. Add it to the output. - loops.emplace_back(std::move(loop_pts)); - #ifdef SLIC3R_TRIANGLEMESH_DEBUG - printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); - #endif - } else { - // This is an open polyline. Add it to the list of open polylines. These open polylines will processed later. - loop_pts.emplace_back(last_line->b); - open_polylines.emplace_back(OpenPolyline( - IntersectionReference(first_line->a_id, first_line->edge_a_id), - IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts))); - } - break; - } - /* - printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", - next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id, - next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y); - */ - loop_pts.emplace_back(next_line->a); - last_line = next_line; - next_line->set_skip(); - } - } -} - -std::vector open_polylines_sorted(std::vector &open_polylines, bool update_lengths) -{ - std::vector out; - out.reserve(open_polylines.size()); - for (OpenPolyline &opl : open_polylines) - if (! opl.consumed) { - if (update_lengths) - opl.length = Slic3r::length(opl.points); - out.emplace_back(&opl); - } - std::sort(out.begin(), out.end(), [](const OpenPolyline *lhs, const OpenPolyline *rhs){ return lhs->length > rhs->length; }); return out; } -// called by TriangleMeshSlicer::make_loops() to connect remaining open polylines across shared triangle edges and vertices. -// Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation. -static void chain_open_polylines_exact(std::vector &open_polylines, Polygons &loops, bool try_connect_reversed) +std::vector create_face_neighbors_index(const indexed_triangle_set &its) { - // Store the end points of open_polylines into vectors sorted - struct OpenPolylineEnd { - OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} - OpenPolyline *polyline; - // Is it the start or end point? - bool start; - const IntersectionReference& ipref() const { return start ? polyline->start : polyline->end; } - // Return a unique ID for the intersection point. - // Return a positive id for a point, or a negative id for an edge. - int id() const { const IntersectionReference &r = ipref(); return (r.point_id >= 0) ? r.point_id : - r.edge_id; } - bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } - }; - auto by_id_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.id() < ope2.id(); }; - std::vector by_id; - by_id.reserve(2 * open_polylines.size()); - for (OpenPolyline &opl : open_polylines) { - if (opl.start.point_id != -1 || opl.start.edge_id != -1) - by_id.emplace_back(OpenPolylineEnd(&opl, true)); - if (try_connect_reversed && (opl.end.point_id != -1 || opl.end.edge_id != -1)) - by_id.emplace_back(OpenPolylineEnd(&opl, false)); - } - std::sort(by_id.begin(), by_id.end(), by_id_lower); - // Find an iterator to by_id_lower for the particular end of OpenPolyline (by comparing the OpenPolyline pointer and the start attribute). - auto find_polyline_end = [&by_id, by_id_lower](const OpenPolylineEnd &end) -> std::vector::iterator { - for (auto it = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); - it != by_id.end() && it->id() == end.id(); ++ it) - if (*it == end) - return it; - return by_id.end(); - }; - // Try to connect the loops. - std::vector sorted_by_length = open_polylines_sorted(open_polylines, false); - for (OpenPolyline *opl : sorted_by_length) { - if (opl->consumed) - continue; - opl->consumed = true; - OpenPolylineEnd end(opl, false); - for (;;) { - // find a line starting where last one finishes - auto it_next_start = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); - for (; it_next_start != by_id.end() && it_next_start->id() == end.id(); ++ it_next_start) - if (! it_next_start->polyline->consumed) - goto found; - // The current loop could not be closed. Unmark the segment. - opl->consumed = false; - break; - found: - // Attach this polyline to the end of the initial polyline. - if (it_next_start->start) { - auto it = it_next_start->polyline->points.begin(); - std::copy(++ it, it_next_start->polyline->points.end(), back_inserter(opl->points)); - } else { - auto it = it_next_start->polyline->points.rbegin(); - std::copy(++ it, it_next_start->polyline->points.rend(), back_inserter(opl->points)); - } - opl->length += it_next_start->polyline->length; - // Mark the next polyline as consumed. - it_next_start->polyline->points.clear(); - it_next_start->polyline->length = 0.; - it_next_start->polyline->consumed = true; - if (try_connect_reversed) { - // Running in a mode, where the polylines may be connected by mixing their orientations. - // Update the end point lookup structure after the end point of the current polyline was extended. - auto it_end = find_polyline_end(end); - auto it_next_end = find_polyline_end(OpenPolylineEnd(it_next_start->polyline, !it_next_start->start)); - // Swap the end points of the current and next polyline, but keep the polyline ptr and the start flag. - std::swap(opl->end, it_next_end->start ? it_next_end->polyline->start : it_next_end->polyline->end); - // Swap the positions of OpenPolylineEnd structures in the sorted array to match their respective end point positions. - std::swap(*it_end, *it_next_end); - } - // Check whether we closed this loop. - if ((opl->start.edge_id != -1 && opl->start.edge_id == opl->end.edge_id) || - (opl->start.point_id != -1 && opl->start.point_id == opl->end.point_id)) { - // The current loop is complete. Add it to the output. - //assert(opl->points.front().point_id == opl->points.back().point_id); - //assert(opl->points.front().edge_id == opl->points.back().edge_id); - // Remove the duplicate last point. - opl->points.pop_back(); - if (opl->points.size() >= 3) { - if (try_connect_reversed && area(opl->points) < 0) - // The closed polygon is patched from pieces with messed up orientation, therefore - // the orientation of the patched up polygon is not known. - // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. - std::reverse(opl->points.begin(), opl->points.end()); - loops.emplace_back(std::move(opl->points)); - } - opl->points.clear(); - break; - } - // Continue with the current loop. - } - } + return create_face_neighbors_index_impl(its, [](){}); } -// called by TriangleMeshSlicer::make_loops() to connect remaining open polylines across shared triangle edges and vertices, -// possibly closing small gaps. -// Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation. -static void chain_open_polylines_close_gaps(std::vector &open_polylines, Polygons &loops, double max_gap, bool try_connect_reversed) +std::vector create_face_neighbors_index(const indexed_triangle_set &its, std::function throw_on_cancel_callback) { - const coord_t max_gap_scaled = (coord_t)scale_(max_gap); - - // Sort the open polylines by their length, so the new loops will be seeded from longer chains. - // Update the polyline lengths, return only not yet consumed polylines. - std::vector sorted_by_length = open_polylines_sorted(open_polylines, true); - - // Store the end points of open_polylines into ClosestPointInRadiusLookup. - struct OpenPolylineEnd { - OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} - OpenPolyline *polyline; - // Is it the start or end point? - bool start; - const Point& point() const { return start ? polyline->points.front() : polyline->points.back(); } - bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } - }; - struct OpenPolylineEndAccessor { - const Point* operator()(const OpenPolylineEnd &pt) const { return pt.polyline->consumed ? nullptr : &pt.point(); } - }; - typedef ClosestPointInRadiusLookup ClosestPointLookupType; - ClosestPointLookupType closest_end_point_lookup(max_gap_scaled); - for (OpenPolyline *opl : sorted_by_length) { - closest_end_point_lookup.insert(OpenPolylineEnd(opl, true)); - if (try_connect_reversed) - closest_end_point_lookup.insert(OpenPolylineEnd(opl, false)); - } - // Try to connect the loops. - for (OpenPolyline *opl : sorted_by_length) { - if (opl->consumed) - continue; - OpenPolylineEnd end(opl, false); - if (try_connect_reversed) - // The end point of this polyline will be modified, thus the following entry will become invalid. Remove it. - closest_end_point_lookup.erase(end); - opl->consumed = true; - size_t n_segments_joined = 1; - for (;;) { - // Find a line starting where last one finishes, only return non-consumed open polylines (OpenPolylineEndAccessor returns null for consumed). - std::pair next_start_and_dist = closest_end_point_lookup.find(end.point()); - const OpenPolylineEnd *next_start = next_start_and_dist.first; - // Check whether we closed this loop. - double current_loop_closing_distance2 = (opl->points.back() - opl->points.front()).cast().squaredNorm(); - bool loop_closed = current_loop_closing_distance2 < coordf_t(max_gap_scaled) * coordf_t(max_gap_scaled); - if (next_start != nullptr && loop_closed && current_loop_closing_distance2 < next_start_and_dist.second) { - // Heuristics to decide, whether to close the loop, or connect another polyline. - // One should avoid closing loops shorter than max_gap_scaled. - loop_closed = sqrt(current_loop_closing_distance2) < 0.3 * length(opl->points); - } - if (loop_closed) { - // Remove the start point of the current polyline from the lookup. - // Mark the current segment as not consumed, otherwise the closest_end_point_lookup.erase() would fail. - opl->consumed = false; - closest_end_point_lookup.erase(OpenPolylineEnd(opl, true)); - if (current_loop_closing_distance2 == 0.) { - // Remove the duplicate last point. - opl->points.pop_back(); - } else { - // The end points are different, keep both of them. - } - if (opl->points.size() >= 3) { - if (try_connect_reversed && n_segments_joined > 1 && area(opl->points) < 0) - // The closed polygon is patched from pieces with messed up orientation, therefore - // the orientation of the patched up polygon is not known. - // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. - std::reverse(opl->points.begin(), opl->points.end()); - loops.emplace_back(std::move(opl->points)); - } - opl->points.clear(); - opl->consumed = true; - break; - } - if (next_start == nullptr) { - // The current loop could not be closed. Unmark the segment. - opl->consumed = false; - if (try_connect_reversed) - // Re-insert the end point. - closest_end_point_lookup.insert(OpenPolylineEnd(opl, false)); - break; - } - // Attach this polyline to the end of the initial polyline. - if (next_start->start) { - auto it = next_start->polyline->points.begin(); - if (*it == opl->points.back()) - ++ it; - std::copy(it, next_start->polyline->points.end(), back_inserter(opl->points)); - } else { - auto it = next_start->polyline->points.rbegin(); - if (*it == opl->points.back()) - ++ it; - std::copy(it, next_start->polyline->points.rend(), back_inserter(opl->points)); - } - ++ n_segments_joined; - // Remove the end points of the consumed polyline segment from the lookup. - OpenPolyline *opl2 = next_start->polyline; - closest_end_point_lookup.erase(OpenPolylineEnd(opl2, true)); - if (try_connect_reversed) - closest_end_point_lookup.erase(OpenPolylineEnd(opl2, false)); - opl2->points.clear(); - opl2->consumed = true; - // Continue with the current loop. - } - } + return create_face_neighbors_index_impl(its, throw_on_cancel_callback); } -void TriangleMeshSlicer::make_loops(std::vector &lines, Polygons* loops) const +// Merge duplicate vertices, return number of vertices removed. +int its_merge_vertices(indexed_triangle_set &its, bool shrink_to_fit) { -#if 0 -//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. -//#ifdef _DEBUG - for (const Line &l : lines) - assert(l.a != l.b); -#endif /* _DEBUG */ + // 1) Sort indices to vertices lexicographically by coordinates AND vertex index. + auto sorted = reserve_vector(its.vertices.size()); + for (int i = 0; i < int(its.vertices.size()); ++ i) + sorted.emplace_back(i); + std::sort(sorted.begin(), sorted.end(), [&its](int il, int ir) { + const Vec3f &l = its.vertices[il]; + const Vec3f &r = its.vertices[ir]; + // Sort lexicographically by coordinates AND vertex index. + return l.x() < r.x() || (l.x() == r.x() && (l.y() < r.y() || (l.y() == r.y() && (l.z() < r.z() || (l.z() == r.z() && il < ir))))); + }); - // There should be no tangent edges, as the horizontal triangles are ignored and if two triangles touch at a cutting plane, - // only the bottom triangle is considered to be cutting the plane. -// remove_tangent_edges(lines); - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - BoundingBox bbox_svg; - { - static int iRun = 0; - for (const Line &line : lines) { - bbox_svg.merge(line.a); - bbox_svg.merge(line.b); - } - SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-raw_lines-%d.svg", iRun ++).c_str(), bbox_svg); - for (const Line &line : lines) - svg.draw(line); - svg.Close(); + // 2) Map duplicate vertices to the one with the lowest vertex index. + // The vertex to stay will have a map_vertices[...] == -1 index assigned, the other vertices will point to it. + std::vector map_vertices(its.vertices.size(), -1); + for (int i = 0; i < int(sorted.size());) { + const int u = sorted[i]; + const Vec3f &p = its.vertices[u]; + int j = i; + for (++ j; j < int(sorted.size()); ++ j) { + const int v = sorted[j]; + const Vec3f &q = its.vertices[v]; + if (p != q) + break; + assert(v > u); + map_vertices[v] = u; } -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - - std::vector open_polylines; - chain_lines_by_triangle_connectivity(lines, *loops, open_polylines); - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - static int iRun = 0; - SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-%d.svg", iRun ++).c_str(), bbox_svg); - svg.draw(union_ex(*loops)); - for (const OpenPolyline &pl : open_polylines) - svg.draw(Polyline(pl.points), "red"); - svg.Close(); - } -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - - // Now process the open polylines. - // Do it in two rounds, first try to connect in the same direction only, - // then try to connect the open polylines in reversed order as well. - chain_open_polylines_exact(open_polylines, *loops, false); - chain_open_polylines_exact(open_polylines, *loops, true); - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - static int iRun = 0; - SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines2-%d.svg", iRun++).c_str(), bbox_svg); - svg.draw(union_ex(*loops)); - for (const OpenPolyline &pl : open_polylines) { - if (pl.points.empty()) - continue; - svg.draw(Polyline(pl.points), "red"); - svg.draw(pl.points.front(), "blue"); - svg.draw(pl.points.back(), "blue"); - } - svg.Close(); + i = j; } -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - // Try to close gaps. - // Do it in two rounds, first try to connect in the same direction only, - // then try to connect the open polylines in reversed order as well. -#if 0 - for (double max_gap : { EPSILON, 0.001, 0.1, 1., 2. }) { - chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, false); - chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, true); - } -#else - const double max_gap = 2.; //mm - chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, false); - chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, true); -#endif - -#ifdef SLIC3R_DEBUG_SLICE_PROCESSING - { - static int iRun = 0; - SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-final-%d.svg", iRun++).c_str(), bbox_svg); - svg.draw(union_ex(*loops)); - for (const OpenPolyline &pl : open_polylines) { - if (pl.points.empty()) - continue; - svg.draw(Polyline(pl.points), "red"); - svg.draw(pl.points.front(), "blue"); - svg.draw(pl.points.back(), "blue"); - } - svg.Close(); - } -#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ -} - -// Only used to cut the mesh into two halves. -void TriangleMeshSlicer::make_expolygons_simple(std::vector &lines, ExPolygons* slices) const -{ - assert(slices->empty()); - - Polygons loops; - this->make_loops(lines, &loops); - - Polygons holes; - for (Polygons::const_iterator loop = loops.begin(); loop != loops.end(); ++ loop) { - if (loop->area() >= 0.) { - ExPolygon ex; - ex.contour = *loop; - slices->emplace_back(ex); + // 3) Shrink its.vertices, update map_vertices with the new vertex indices. + int k = 0; + for (int i = 0; i < int(its.vertices.size()); ++ i) { + if (map_vertices[i] == -1) { + map_vertices[i] = k; + if (k < i) + its.vertices[k] = its.vertices[i]; + ++ k; } else { - holes.emplace_back(*loop); + assert(map_vertices[i] < i); + map_vertices[i] = map_vertices[map_vertices[i]]; } } - // If there are holes, then there should also be outer contours. - assert(holes.empty() || ! slices->empty()); - if (slices->empty()) - return; - - // Assign holes to outer contours. - for (Polygons::const_iterator hole = holes.begin(); hole != holes.end(); ++ hole) { - // Find an outer contour to a hole. - int slice_idx = -1; - double current_contour_area = std::numeric_limits::max(); - for (ExPolygons::iterator slice = slices->begin(); slice != slices->end(); ++ slice) { - if (slice->contour.contains(hole->points.front())) { - double area = slice->contour.area(); - if (area < current_contour_area) { - slice_idx = slice - slices->begin(); - current_contour_area = area; - } - } - } - // assert(slice_idx != -1); - if (slice_idx == -1) - // Ignore this hole. - continue; - assert(current_contour_area < std::numeric_limits::max() && current_contour_area >= -hole->area()); - (*slices)[slice_idx].holes.emplace_back(std::move(*hole)); + int num_erased = int(its.vertices.size()) - k; + + if (num_erased) { + // Shrink the vertices. + its.vertices.erase(its.vertices.begin() + k, its.vertices.end()); + // Remap face indices. + for (stl_triangle_vertex_indices &face : its.indices) + for (int i = 0; i < 3; ++ i) + face(i) = map_vertices[face(i)]; + // Optionally shrink to fit (reallocate) vertices. + if (shrink_to_fit) + its.vertices.shrink_to_fit(); } -#if 0 - // If the input mesh is not valid, the holes may intersect with the external contour. - // Rather subtract them from the outer contour. - Polygons poly; - for (auto it_slice = slices->begin(); it_slice != slices->end(); ++ it_slice) { - if (it_slice->holes.empty()) { - poly.emplace_back(std::move(it_slice->contour)); - } else { - Polygons contours; - contours.emplace_back(std::move(it_slice->contour)); - for (auto it = it_slice->holes.begin(); it != it_slice->holes.end(); ++ it) - it->reverse(); - polygons_append(poly, diff(contours, it_slice->holes)); - } - } - // If the input mesh is not valid, the input contours may intersect. - *slices = union_ex(poly); -#endif - -#if 0 - // If the input mesh is not valid, the holes may intersect with the external contour. - // Rather subtract them from the outer contour. - ExPolygons poly; - for (auto it_slice = slices->begin(); it_slice != slices->end(); ++ it_slice) { - Polygons contours; - contours.emplace_back(std::move(it_slice->contour)); - for (auto it = it_slice->holes.begin(); it != it_slice->holes.end(); ++ it) - it->reverse(); - expolygons_append(poly, diff_ex(contours, it_slice->holes)); - } - // If the input mesh is not valid, the input contours may intersect. - *slices = std::move(poly); -#endif + return num_erased; } -void TriangleMeshSlicer::make_expolygons(const Polygons &loops, const float closing_radius, ExPolygons* slices) const +int its_remove_degenerate_faces(indexed_triangle_set &its, bool shrink_to_fit) { - /* - Input loops are not suitable for evenodd nor nonzero fill types, as we might get - two consecutive concentric loops having the same winding order - and we have to - respect such order. In that case, evenodd would create wrong inversions, and nonzero - would ignore holes inside two concentric contours. - So we're ordering loops and collapse consecutive concentric loops having the same - winding order. - TODO: find a faster algorithm for this, maybe with some sort of binary search. - If we computed a "nesting tree" we could also just remove the consecutive loops - having the same winding order, and remove the extra one(s) so that we could just - supply everything to offset() instead of performing several union/diff calls. - - we sort by area assuming that the outermost loops have larger area; - the previous sorting method, based on $b->contains($a->[0]), failed to nest - loops correctly in some edge cases when original model had overlapping facets - */ - - /* The following lines are commented out because they can generate wrong polygons, - see for example issue #661 */ - - //std::vector area; - //std::vector sorted_area; // vector of indices - //for (Polygons::const_iterator loop = loops.begin(); loop != loops.end(); ++ loop) { - // area.emplace_back(loop->area()); - // sorted_area.emplace_back(loop - loops.begin()); - //} - // - //// outer first - //std::sort(sorted_area.begin(), sorted_area.end(), - // [&area](size_t a, size_t b) { return std::abs(area[a]) > std::abs(area[b]); }); - - //// we don't perform a safety offset now because it might reverse cw loops - //Polygons p_slices; - //for (std::vector::const_iterator loop_idx = sorted_area.begin(); loop_idx != sorted_area.end(); ++ loop_idx) { - // /* we rely on the already computed area to determine the winding order - // of the loops, since the Orientation() function provided by Clipper - // would do the same, thus repeating the calculation */ - // Polygons::const_iterator loop = loops.begin() + *loop_idx; - // if (area[*loop_idx] > +EPSILON) - // p_slices.emplace_back(*loop); - // else if (area[*loop_idx] < -EPSILON) - // //FIXME This is arbitrary and possibly very slow. - // // If the hole is inside a polygon, then there is no need to diff. - // // If the hole intersects a polygon boundary, then diff it, but then - // // there is no guarantee of an ordering of the loops. - // // Maybe we can test for the intersection before running the expensive diff algorithm? - // p_slices = diff(p_slices, *loop); - //} - - // Perform a safety offset to merge very close facets (TODO: find test case for this) - // 0.0499 comes from https://github.com/slic3r/Slic3r/issues/959 -// double safety_offset = scale_(0.0499); - // 0.0001 is set to satisfy GH #520, #1029, #1364 - double safety_offset = scale_(closing_radius); - - /* The following line is commented out because it can generate wrong polygons, - see for example issue #661 */ - //ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset); - - #ifdef SLIC3R_TRIANGLEMESH_DEBUG - size_t holes_count = 0; - for (ExPolygons::const_iterator e = ex_slices.begin(); e != ex_slices.end(); ++ e) - holes_count += e->holes.size(); - printf("%zu surface(s) having %zu holes detected from %zu polylines\n", - ex_slices.size(), holes_count, loops.size()); - #endif - - // append to the supplied collection - if (safety_offset > 0) - expolygons_append(*slices, offset2_ex(union_ex(loops), +safety_offset, -safety_offset)); - else - expolygons_append(*slices, union_ex(loops)); + int last = 0; + for (int i = 0; i < int(its.indices.size()); ++ i) { + const stl_triangle_vertex_indices &face = its.indices[i]; + if (face(0) != face(1) && face(0) != face(2) && face(1) != face(2)) { + if (last < i) + its.indices[last] = its.indices[i]; + ++ last; + } + } + int removed = int(its.indices.size()) - last; + if (removed) { + its.indices.erase(its.indices.begin() + last, its.indices.end()); + // Optionally shrink the vertices. + if (shrink_to_fit) + its.indices.shrink_to_fit(); + } + return removed; } -void TriangleMeshSlicer::make_expolygons(std::vector &lines, const float closing_radius, ExPolygons* slices) const +int its_compactify_vertices(indexed_triangle_set &its, bool shrink_to_fit) { - Polygons pp; - this->make_loops(lines, &pp); - this->make_expolygons(pp, closing_radius, slices); + // First used to mark referenced vertices, later used for mapping old vertex index to a new one. + std::vector vertex_map(its.vertices.size(), 0); + // Mark referenced vertices. + for (const stl_triangle_vertex_indices &face : its.indices) + for (int i = 0; i < 3; ++ i) + vertex_map[face(i)] = 1; + // Compactify vertices, update map from old vertex index to a new one. + int last = 0; + for (int i = 0; i < int(vertex_map.size()); ++ i) + if (vertex_map[i]) { + if (last < i) + its.vertices[last] = its.vertices[i]; + vertex_map[i] = last ++; + } + int removed = int(its.vertices.size()) - last; + if (removed) { + its.vertices.erase(its.vertices.begin() + last, its.vertices.end()); + // Update faces with the new vertex indices. + for (stl_triangle_vertex_indices &face : its.indices) + for (int i = 0; i < 3; ++ i) + face(i) = vertex_map[face(i)]; + // Optionally shrink the vertices. + if (shrink_to_fit) + its.vertices.shrink_to_fit(); + } + return removed; } -void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower) const +void its_shrink_to_fit(indexed_triangle_set &its) { - IntersectionLines upper_lines, lower_lines; - - BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - slicing object"; - float scaled_z = scale_(z); - for (uint32_t facet_idx = 0; facet_idx < this->mesh->stl.stats.number_of_facets; ++ facet_idx) { - const stl_facet* facet = &this->mesh->stl.facet_start[facet_idx]; - - // find facet extents - float min_z = std::min(facet->vertex[0](2), std::min(facet->vertex[1](2), facet->vertex[2](2))); - float max_z = std::max(facet->vertex[0](2), std::max(facet->vertex[1](2), facet->vertex[2](2))); - - // intersect facet with cutting plane - IntersectionLine line; - if (this->slice_facet(scaled_z, *facet, facet_idx, min_z, max_z, &line) != TriangleMeshSlicer::NoSlice) { - // Save intersection lines for generating correct triangulations. - if (line.edge_type == feTop) { - lower_lines.emplace_back(line); - } else if (line.edge_type == feBottom) { - upper_lines.emplace_back(line); - } else if (line.edge_type != feHorizontal) { - lower_lines.emplace_back(line); - upper_lines.emplace_back(line); - } - } - - if (min_z > z || (min_z == z && max_z > z)) { - // facet is above the cut plane and does not belong to it - if (upper != nullptr) - stl_add_facet(&upper->stl, facet); - } else if (max_z < z || (max_z == z && min_z < z)) { - // facet is below the cut plane and does not belong to it - if (lower != nullptr) - stl_add_facet(&lower->stl, facet); - } else if (min_z < z && max_z > z) { - // Facet is cut by the slicing plane. - - // look for the vertex on whose side of the slicing plane there are no other vertices - int isolated_vertex; - if ( (facet->vertex[0](2) > z) == (facet->vertex[1](2) > z) ) { - isolated_vertex = 2; - } else if ( (facet->vertex[1](2) > z) == (facet->vertex[2](2) > z) ) { - isolated_vertex = 0; - } else { - isolated_vertex = 1; - } - - // get vertices starting from the isolated one - const stl_vertex &v0 = facet->vertex[isolated_vertex]; - const stl_vertex &v1 = facet->vertex[(isolated_vertex+1) % 3]; - const stl_vertex &v2 = facet->vertex[(isolated_vertex+2) % 3]; - - // intersect v0-v1 and v2-v0 with cutting plane and make new vertices - stl_vertex v0v1, v2v0; - v0v1(0) = v1(0) + (v0(0) - v1(0)) * (z - v1(2)) / (v0(2) - v1(2)); - v0v1(1) = v1(1) + (v0(1) - v1(1)) * (z - v1(2)) / (v0(2) - v1(2)); - v0v1(2) = z; - v2v0(0) = v2(0) + (v0(0) - v2(0)) * (z - v2(2)) / (v0(2) - v2(2)); - v2v0(1) = v2(1) + (v0(1) - v2(1)) * (z - v2(2)) / (v0(2) - v2(2)); - v2v0(2) = z; - - // build the triangular facet - stl_facet triangle; - triangle.normal = facet->normal; - triangle.vertex[0] = v0; - triangle.vertex[1] = v0v1; - triangle.vertex[2] = v2v0; - - // build the facets forming a quadrilateral on the other side - stl_facet quadrilateral[2]; - quadrilateral[0].normal = facet->normal; - quadrilateral[0].vertex[0] = v1; - quadrilateral[0].vertex[1] = v2; - quadrilateral[0].vertex[2] = v0v1; - quadrilateral[1].normal = facet->normal; - quadrilateral[1].vertex[0] = v2; - quadrilateral[1].vertex[1] = v2v0; - quadrilateral[1].vertex[2] = v0v1; - - if (v0(2) > z) { - if (upper != nullptr) - stl_add_facet(&upper->stl, &triangle); - if (lower != nullptr) { - stl_add_facet(&lower->stl, &quadrilateral[0]); - stl_add_facet(&lower->stl, &quadrilateral[1]); - } - } else { - if (upper != nullptr) { - stl_add_facet(&upper->stl, &quadrilateral[0]); - stl_add_facet(&upper->stl, &quadrilateral[1]); - } - if (lower != nullptr) - stl_add_facet(&lower->stl, &triangle); - } - } - } - - if (upper != nullptr) { - BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating upper part"; - ExPolygons section; - this->make_expolygons_simple(upper_lines, §ion); - Pointf3s triangles = triangulate_expolygons_3d(section, z, true); - stl_facet facet; - facet.normal = stl_normal(0, 0, -1.f); - for (size_t i = 0; i < triangles.size(); ) { - for (size_t j = 0; j < 3; ++ j) - facet.vertex[j] = triangles[i ++].cast(); - stl_add_facet(&upper->stl, &facet); - } - } - - if (lower != nullptr) { - BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating lower part"; - ExPolygons section; - this->make_expolygons_simple(lower_lines, §ion); - Pointf3s triangles = triangulate_expolygons_3d(section, z, false); - stl_facet facet; - facet.normal = stl_normal(0, 0, -1.f); - for (size_t i = 0; i < triangles.size(); ) { - for (size_t j = 0; j < 3; ++ j) - facet.vertex[j] = triangles[i ++].cast(); - stl_add_facet(&lower->stl, &facet); - } - } - - BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - updating object sizes"; - stl_get_size(&upper->stl); - stl_get_size(&lower->stl); + its.indices.shrink_to_fit(); + its.vertices.shrink_to_fit(); } // Generate the vertex list for a cube solid of arbitrary size in X/Y/Z. @@ -1962,8 +914,8 @@ TriangleMesh make_cube(double x, double y, double z) {1, 7, 6}, {1, 6, 2}, {2, 6, 5}, {2, 5, 3}, {4, 0, 3}, {4, 3, 5} }); - mesh.repair(); - return mesh; + mesh.repair(); + return mesh; } // Generate the mesh for a cylinder and return it, using @@ -1971,13 +923,13 @@ TriangleMesh make_cube(double x, double y, double z) // Default is 360 sides, angle fa is in radians. TriangleMesh make_cylinder(double r, double h, double fa) { - size_t n_steps = (size_t)ceil(2. * PI / fa); - double angle_step = 2. * PI / n_steps; + size_t n_steps = (size_t)ceil(2. * PI / fa); + double angle_step = 2. * PI / n_steps; - Pointf3s vertices; - std::vector facets; - vertices.reserve(2 * n_steps + 2); - facets.reserve(4 * n_steps); + Pointf3s vertices; + std::vector facets; + vertices.reserve(2 * n_steps + 2); + facets.reserve(4 * n_steps); // 2 special vertices, top and bottom center, rest are relative to this vertices.emplace_back(Vec3d(0.0, 0.0, 0.0)); @@ -1987,29 +939,29 @@ TriangleMesh make_cylinder(double r, double h, double fa) // circle, generate four points and four facets (2 for the wall, 2 for the // top and bottom. // Special case: Last line shares 2 vertices with the first line. - Vec2d p = Eigen::Rotation2Dd(0.) * Eigen::Vector2d(0, r); - vertices.emplace_back(Vec3d(p(0), p(1), 0.)); - vertices.emplace_back(Vec3d(p(0), p(1), h)); - for (size_t i = 1; i < n_steps; ++i) { + Vec2d p = Eigen::Rotation2Dd(0.) * Eigen::Vector2d(0, r); + vertices.emplace_back(Vec3d(p(0), p(1), 0.)); + vertices.emplace_back(Vec3d(p(0), p(1), h)); + for (size_t i = 1; i < n_steps; ++i) { p = Eigen::Rotation2Dd(angle_step * i) * Eigen::Vector2d(0, r); vertices.emplace_back(Vec3d(p(0), p(1), 0.)); vertices.emplace_back(Vec3d(p(0), p(1), h)); int id = (int)vertices.size() - 1; facets.emplace_back( 0, id - 1, id - 3); // top facets.emplace_back(id, 1, id - 2); // bottom - facets.emplace_back(id, id - 2, id - 3); // upper-right of side + facets.emplace_back(id, id - 2, id - 3); // upper-right of side facets.emplace_back(id, id - 3, id - 1); // bottom-left of side } // Connect the last set of vertices with the first. - int id = (int)vertices.size() - 1; + int id = (int)vertices.size() - 1; facets.emplace_back( 0, 2, id - 1); facets.emplace_back( 3, 1, id); - facets.emplace_back(id, 2, 3); + facets.emplace_back(id, 2, 3); facets.emplace_back(id, id - 1, 2); - TriangleMesh mesh(std::move(vertices), std::move(facets)); - mesh.repair(); - return mesh; + TriangleMesh mesh(std::move(vertices), std::move(facets)); + mesh.repair(); + return mesh; } // Generates mesh for a sphere centered about the origin, using the generated angle @@ -2018,74 +970,56 @@ TriangleMesh make_cylinder(double r, double h, double fa) //FIXME better to discretize an Icosahedron recursively http://www.songho.ca/opengl/gl_sphere.html TriangleMesh make_sphere(double radius, double fa) { - int sectorCount = int(ceil(2. * M_PI / fa)); - int stackCount = int(ceil(M_PI / fa)); - float sectorStep = float(2. * M_PI / sectorCount); - float stackStep = float(M_PI / stackCount); + int sectorCount = int(ceil(2. * M_PI / fa)); + int stackCount = int(ceil(M_PI / fa)); + float sectorStep = float(2. * M_PI / sectorCount); + float stackStep = float(M_PI / stackCount); - Pointf3s vertices; - vertices.reserve((stackCount - 1) * sectorCount + 2); - for (int i = 0; i <= stackCount; ++ i) { - // from pi/2 to -pi/2 - double stackAngle = 0.5 * M_PI - stackStep * i; - double xy = radius * cos(stackAngle); - double z = radius * sin(stackAngle); - if (i == 0 || i == stackCount) - vertices.emplace_back(Vec3d(xy, 0., z)); - else - for (int j = 0; j < sectorCount; ++ j) { - // from 0 to 2pi - double sectorAngle = sectorStep * j; - vertices.emplace_back(Vec3d(xy * cos(sectorAngle), xy * sin(sectorAngle), z)); - } - } - - std::vector facets; - facets.reserve(2 * (stackCount - 1) * sectorCount); - for (int i = 0; i < stackCount; ++ i) { - // Beginning of current stack. - int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount); - int k1_first = k1; - // Beginning of next stack. - int k2 = (i == 0) ? 1 : (k1 + sectorCount); - int k2_first = k2; - for (int j = 0; j < sectorCount; ++ j) { - // 2 triangles per sector excluding first and last stacks - int k1_next = k1; - int k2_next = k2; - if (i != 0) { - k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1); - facets.emplace_back(k1, k2, k1_next); - } - if (i + 1 != stackCount) { - k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1); - facets.emplace_back(k1_next, k2, k2_next); - } - k1 = k1_next; - k2 = k2_next; - } - } - TriangleMesh mesh(std::move(vertices), std::move(facets)); - mesh.repair(); - return mesh; -} - -std::vector > create_neighbor_index(const indexed_triangle_set &its) -{ - if (its.vertices.empty()) return {}; - - size_t res = its.indices.size() / its.vertices.size(); - std::vector< std::vector > index(its.vertices.size(), - reserve_vector(res)); - - for (size_t fi = 0; fi < its.indices.size(); ++fi) { - auto &face = its.indices[fi]; - index[face(0)].emplace_back(fi); - index[face(1)].emplace_back(fi); - index[face(2)].emplace_back(fi); + Pointf3s vertices; + vertices.reserve((stackCount - 1) * sectorCount + 2); + for (int i = 0; i <= stackCount; ++ i) { + // from pi/2 to -pi/2 + double stackAngle = 0.5 * M_PI - stackStep * i; + double xy = radius * cos(stackAngle); + double z = radius * sin(stackAngle); + if (i == 0 || i == stackCount) + vertices.emplace_back(Vec3d(xy, 0., z)); + else + for (int j = 0; j < sectorCount; ++ j) { + // from 0 to 2pi + double sectorAngle = sectorStep * j; + vertices.emplace_back(Vec3d(xy * cos(sectorAngle), xy * sin(sectorAngle), z)); + } } - return index; + std::vector facets; + facets.reserve(2 * (stackCount - 1) * sectorCount); + for (int i = 0; i < stackCount; ++ i) { + // Beginning of current stack. + int k1 = (i == 0) ? 0 : (1 + (i - 1) * sectorCount); + int k1_first = k1; + // Beginning of next stack. + int k2 = (i == 0) ? 1 : (k1 + sectorCount); + int k2_first = k2; + for (int j = 0; j < sectorCount; ++ j) { + // 2 triangles per sector excluding first and last stacks + int k1_next = k1; + int k2_next = k2; + if (i != 0) { + k1_next = (j + 1 == sectorCount) ? k1_first : (k1 + 1); + facets.emplace_back(k1, k2, k1_next); + } + if (i + 1 != stackCount) { + k2_next = (j + 1 == sectorCount) ? k2_first : (k2 + 1); + facets.emplace_back(k1_next, k2, k2_next); + } + k1 = k1_next; + k2 = k2_next; + } + } + TriangleMesh mesh(std::move(vertices), std::move(facets)); + mesh.repair(); + return mesh; } } diff --git a/src/libslic3r/TriangleMesh.hpp b/src/libslic3r/TriangleMesh.hpp index e6f6dc84b..5d3c15ec1 100644 --- a/src/libslic3r/TriangleMesh.hpp +++ b/src/libslic3r/TriangleMesh.hpp @@ -5,7 +5,6 @@ #include #include #include -#include #include "BoundingBox.hpp" #include "Line.hpp" #include "Point.hpp" @@ -24,7 +23,7 @@ public: TriangleMesh() : repaired(false) {} TriangleMesh(const Pointf3s &points, const std::vector &facets); explicit TriangleMesh(const indexed_triangle_set &M); - void clear() { this->stl.clear(); this->its.clear(); this->repaired = false; } + void clear() { this->stl.clear(); this->its.clear(); this->repaired = false; } bool ReadSTLFile(const char* input_file) { return stl_open(&stl, input_file); } bool write_ascii(const char* output_file) { return stl_write_ascii(&this->stl, output_file, ""); } bool write_binary(const char* output_file) { return stl_write_binary(&this->stl, output_file, ""); } @@ -47,7 +46,7 @@ public: void mirror_y() { this->mirror(Y); } void mirror_z() { this->mirror(Z); } void transform(const Transform3d& t, bool fix_left_handed = false); - void transform(const Matrix3d& t, bool fix_left_handed = false); + void transform(const Matrix3d& t, bool fix_left_handed = false); void align_to_origin(); void rotate(double angle, Point* center); TriangleMeshPtrs split() const; @@ -62,7 +61,7 @@ public: // Return the size of the mesh in coordinates. Vec3d size() const { return stl.stats.size.cast(); } /// Return the center of the related bounding box. - Vec3d center() const { return this->bounding_box().center(); } + Vec3d center() const { return this->bounding_box().center(); } // Returns the convex hull of this TriangleMesh TriangleMesh convex_hull_3d() const; // Slice this mesh at the provided Z levels and return the vector @@ -78,8 +77,8 @@ public: size_t memsize() const; // Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released. size_t release_optional(); - // Restore optional data possibly released by release_optional(). - void restore_optional(); + // Restore optional data possibly released by release_optional(). + void restore_optional(); stl_file stl; indexed_triangle_set its; @@ -92,160 +91,27 @@ private: // Create an index of faces belonging to each vertex. The returned vector can // be indexed with vertex indices and contains a list of face indices for each // vertex. -std::vector< std::vector > -create_neighbor_index(const indexed_triangle_set &its); +std::vector> create_vertex_faces_index(const indexed_triangle_set &its); -enum FacetEdgeType { - // A general case, the cutting plane intersect a face at two different edges. - feGeneral, - // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane. - feTop, - // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. - feBottom, - // All three vertices of a face are aligned with the cutting plane. - feHorizontal -}; +// Map from a face edge to a unique edge identifier or -1 if no neighbor exists. +// Two neighbor faces share a unique edge identifier even if they are flipped. +// Used for chaining slice lines into polygons. +std::vector create_face_neighbors_index(const indexed_triangle_set &its); +std::vector create_face_neighbors_index(const indexed_triangle_set &its, std::function throw_on_cancel_callback); -class IntersectionReference -{ -public: - IntersectionReference() : point_id(-1), edge_id(-1) {} - IntersectionReference(int point_id, int edge_id) : point_id(point_id), edge_id(edge_id) {} - // Where is this intersection point located? On mesh vertex or mesh edge? - // Only one of the following will be set, the other will remain set to -1. - // Index of the mesh vertex. - int point_id; - // Index of the mesh edge. - int edge_id; -}; +// Merge duplicate vertices, return number of vertices removed. +// This function will happily create non-manifolds if more than two faces share the same vertex position +// or more than two faces share the same edge position! +int its_merge_vertices(indexed_triangle_set &its, bool shrink_to_fit = true); -class IntersectionPoint : public Point, public IntersectionReference -{ -public: - IntersectionPoint() {} - IntersectionPoint(int point_id, int edge_id, const Point &pt) : IntersectionReference(point_id, edge_id), Point(pt) {} - IntersectionPoint(const IntersectionReference &ir, const Point &pt) : IntersectionReference(ir), Point(pt) {} - // Inherits coord_t x, y -}; +// Remove degenerate faces, return number of faces removed. +int its_remove_degenerate_faces(indexed_triangle_set &its, bool shrink_to_fit = true); -class IntersectionLine : public Line -{ -public: - IntersectionLine() : a_id(-1), b_id(-1), edge_a_id(-1), edge_b_id(-1), edge_type(feGeneral), flags(0) {} +// Remove vertices, which none of the faces references. Return number of freed vertices. +int its_compactify_vertices(indexed_triangle_set &its, bool shrink_to_fit = true); - bool skip() const { return (this->flags & SKIP) != 0; } - void set_skip() { this->flags |= SKIP; } - - bool is_seed_candidate() const { return (this->flags & NO_SEED) == 0 && ! this->skip(); } - void set_no_seed(bool set) { if (set) this->flags |= NO_SEED; else this->flags &= ~NO_SEED; } - - // Inherits Point a, b - // For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1. - // Vertex indices of the line end points. - int a_id; - int b_id; - // Source mesh edges of the line end points. - int edge_a_id; - int edge_b_id; - // feGeneral, feTop, feBottom, feHorizontal - FacetEdgeType edge_type; - // Used by TriangleMeshSlicer::slice() to skip duplicate edges. - enum { - // Triangle edge added, because it has no neighbor. - EDGE0_NO_NEIGHBOR = 0x001, - EDGE1_NO_NEIGHBOR = 0x002, - EDGE2_NO_NEIGHBOR = 0x004, - // Triangle edge added, because it makes a fold with another horizontal edge. - EDGE0_FOLD = 0x010, - EDGE1_FOLD = 0x020, - EDGE2_FOLD = 0x040, - // The edge cannot be a seed of a greedy loop extraction (folds are not safe to become seeds). - NO_SEED = 0x100, - SKIP = 0x200, - }; - uint32_t flags; -}; -typedef std::vector IntersectionLines; -typedef std::vector IntersectionLinePtrs; - -enum class SlicingMode : uint32_t { - // Regular slicing, maintain all contours and their orientation. - Regular, - // Maintain all contours, orient all contours CCW, therefore all holes are being closed. - Positive, - // Orient all contours CCW and keep only the contour with the largest area. - // This mode is useful for slicing complex objects in vase mode. - PositiveLargestContour, -}; - -class TriangleMeshSlicer -{ -public: - typedef std::function throw_on_cancel_callback_type; - TriangleMeshSlicer() : mesh(nullptr) {} - TriangleMeshSlicer(const TriangleMesh* mesh) { this->init(mesh, [](){}); } - void init(const TriangleMesh *mesh, throw_on_cancel_callback_type throw_on_cancel); - void slice( - const std::vector &z, SlicingMode mode, size_t alternate_mode_first_n_layers, SlicingMode alternate_mode, - std::vector* layers, throw_on_cancel_callback_type throw_on_cancel) const; - void slice(const std::vector &z, SlicingMode mode, std::vector* layers, throw_on_cancel_callback_type throw_on_cancel) const - { return this->slice(z, mode, 0, mode, layers, throw_on_cancel); } - void slice( - const std::vector &z, SlicingMode mode, size_t alternate_mode_first_n_layers, SlicingMode alternate_mode, const float closing_radius, - std::vector* layers, throw_on_cancel_callback_type throw_on_cancel) const; - void slice(const std::vector &z, SlicingMode mode, const float closing_radius, - std::vector* layers, throw_on_cancel_callback_type throw_on_cancel) const - { this->slice(z, mode, 0, mode, closing_radius, layers, throw_on_cancel); } - enum FacetSliceType { - NoSlice = 0, - Slicing = 1, - Cutting = 2 - }; - FacetSliceType slice_facet(float slice_z, const stl_facet &facet, const int facet_idx, - const float min_z, const float max_z, IntersectionLine *line_out) const; - void cut(float z, TriangleMesh* upper, TriangleMesh* lower) const; - void set_up_direction(const Vec3f& up); - -private: - const TriangleMesh *mesh; - // Map from a facet to an edge index. - std::vector facets_edges; - // Scaled copy of this->mesh->stl.v_shared - std::vector v_scaled_shared; - // Quaternion that will be used to rotate every facet before the slicing - Eigen::Quaternion m_quaternion; - // Whether or not the above quaterion should be used - bool m_use_quaternion = false; - - void _slice_do(size_t facet_idx, std::vector* lines, boost::mutex* lines_mutex, const std::vector &z) const; - void make_loops(std::vector &lines, Polygons* loops) const; - void make_expolygons(const Polygons &loops, const float closing_radius, ExPolygons* slices) const; - void make_expolygons_simple(std::vector &lines, ExPolygons* slices) const; - void make_expolygons(std::vector &lines, const float closing_radius, ExPolygons* slices) const; -}; - -inline void slice_mesh( - const TriangleMesh & mesh, - const std::vector & z, - std::vector & layers, - TriangleMeshSlicer::throw_on_cancel_callback_type thr = nullptr) -{ - if (mesh.empty()) return; - TriangleMeshSlicer slicer(&mesh); - slicer.slice(z, SlicingMode::Regular, &layers, thr); -} - -inline void slice_mesh( - const TriangleMesh & mesh, - const std::vector & z, - std::vector & layers, - float closing_radius, - TriangleMeshSlicer::throw_on_cancel_callback_type thr = nullptr) -{ - if (mesh.empty()) return; - TriangleMeshSlicer slicer(&mesh); - slicer.slice(z, SlicingMode::Regular, closing_radius, &layers, thr); -} +// Shrink the vectors of its.vertices and its.faces to a minimum size by reallocating the two vectors. +void its_shrink_to_fit(indexed_triangle_set &its); TriangleMesh make_cube(double x, double y, double z); @@ -259,21 +125,21 @@ TriangleMesh make_sphere(double rho, double fa=(2*PI/360)); // Serialization through the Cereal library #include namespace cereal { - template struct specialize {}; - template void load(Archive &archive, Slic3r::TriangleMesh &mesh) { + template struct specialize {}; + template void load(Archive &archive, Slic3r::TriangleMesh &mesh) { stl_file &stl = mesh.stl; stl.stats.type = inmemory; - archive(stl.stats.number_of_facets, stl.stats.original_num_facets); + archive(stl.stats.number_of_facets, stl.stats.original_num_facets); stl_allocate(&stl); - archive.loadBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50); + archive.loadBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50); stl_get_size(&stl); mesh.repair(); - } - template void save(Archive &archive, const Slic3r::TriangleMesh &mesh) { - const stl_file& stl = mesh.stl; - archive(stl.stats.number_of_facets, stl.stats.original_num_facets); - archive.saveBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50); - } + } + template void save(Archive &archive, const Slic3r::TriangleMesh &mesh) { + const stl_file& stl = mesh.stl; + archive(stl.stats.number_of_facets, stl.stats.original_num_facets); + archive.saveBinary((char*)stl.facet_start.data(), stl.facet_start.size() * 50); + } } #endif diff --git a/src/libslic3r/TriangleMeshSlicer.cpp b/src/libslic3r/TriangleMeshSlicer.cpp new file mode 100644 index 000000000..7d0c2516c --- /dev/null +++ b/src/libslic3r/TriangleMeshSlicer.cpp @@ -0,0 +1,1422 @@ +#include "ClipperUtils.hpp" +#include "Geometry.hpp" +#include "Tesselate.hpp" +#include "TriangleMesh.hpp" +#include "TriangleMeshSlicer.hpp" + +#include +#include +#include +#include +#include + +#include + +#include + +#if 0 + #define DEBUG + #define _DEBUG + #undef NDEBUG + #define SLIC3R_DEBUG +// #define SLIC3R_TRIANGLEMESH_DEBUG +#endif + +#include + +#if defined(SLIC3R_DEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING) +#include "SVG.hpp" +#endif + +namespace Slic3r { + +class IntersectionReference +{ +public: + IntersectionReference() = default; + IntersectionReference(int point_id, int edge_id) : point_id(point_id), edge_id(edge_id) {} + // Where is this intersection point located? On mesh vertex or mesh edge? + // Only one of the following will be set, the other will remain set to -1. + // Index of the mesh vertex. + int point_id { -1 }; + // Index of the mesh edge. + int edge_id { -1 }; +}; + +class IntersectionPoint : public Point, public IntersectionReference +{ +public: + IntersectionPoint() = default; + IntersectionPoint(int point_id, int edge_id, const Point &pt) : IntersectionReference(point_id, edge_id), Point(pt) {} + IntersectionPoint(const IntersectionReference &ir, const Point &pt) : IntersectionReference(ir), Point(pt) {} + // Inherits coord_t x, y +}; + +class IntersectionLine : public Line +{ +public: + IntersectionLine() = default; + + bool skip() const { return (this->flags & SKIP) != 0; } + void set_skip() { this->flags |= SKIP; } + + bool is_seed_candidate() const { return (this->flags & NO_SEED) == 0 && ! this->skip(); } + void set_no_seed(bool set) { if (set) this->flags |= NO_SEED; else this->flags &= ~NO_SEED; } + + // Inherits Point a, b + // For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1. + // Vertex indices of the line end points. + int a_id { -1 }; + int b_id { -1 }; + // Source mesh edges of the line end points. + int edge_a_id { -1 }; + int edge_b_id { -1 }; + + enum class FacetEdgeType { + // A general case, the cutting plane intersect a face at two different edges. + General, + // Two vertices are aligned with the cutting plane, the third vertex is below the cutting plane. + Top, + // Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane. + Bottom, + // All three vertices of a face are aligned with the cutting plane. + Horizontal + }; + + // feGeneral, feTop, feBottom, feHorizontal + FacetEdgeType edge_type { FacetEdgeType::General }; + // Used to skip duplicate edges. + enum { + // Triangle edge added, because it has no neighbor. + EDGE0_NO_NEIGHBOR = 0x001, + EDGE1_NO_NEIGHBOR = 0x002, + EDGE2_NO_NEIGHBOR = 0x004, + // Triangle edge added, because it makes a fold with another horizontal edge. + EDGE0_FOLD = 0x010, + EDGE1_FOLD = 0x020, + EDGE2_FOLD = 0x040, + // The edge cannot be a seed of a greedy loop extraction (folds are not safe to become seeds). + NO_SEED = 0x100, + SKIP = 0x200, + }; + uint32_t flags { 0 }; +}; + +using IntersectionLines = std::vector; + +enum class FacetSliceType { + NoSlice = 0, + Slicing = 1, + Cutting = 2 +}; + +// Return true, if the facet has been sliced and line_out has been filled. +static FacetSliceType slice_facet( + // Z height of the slice in XY plane. Scaled or unscaled (same as vertices[].z()). + float slice_z, + // 3 vertices of the triangle, XY scaled. Z scaled or unscaled (same as slice_z). + const stl_vertex *vertices, + const stl_triangle_vertex_indices &indices, + const Vec3i &edge_neighbor, + const int idx_vertex_lowest, + const bool horizontal, + IntersectionLine &line_out) +{ + IntersectionPoint points[3]; + size_t num_points = 0; + auto point_on_layer = size_t(-1); + + // Reorder vertices so that the first one is the one with lowest Z. + // This is needed to get all intersection lines in a consistent order + // (external on the right of the line) + for (int j = 0; j < 3; ++ j) { // loop through facet edges + int edge_id; + const stl_vertex *a, *b; + int a_id, b_id; + { + int k = (idx_vertex_lowest + j) % 3; + int l = (k + 1) % 3; + edge_id = edge_neighbor(k); + a_id = indices[k]; + a = vertices + k; + b_id = indices[l]; + b = vertices + l; + } + + // Is edge or face aligned with the cutting plane? + if (a->z() == slice_z && b->z() == slice_z) { + // Edge is horizontal and belongs to the current layer. + // The following rotation of the three vertices may not be efficient, but this branch happens rarely. + const stl_vertex &v0 = vertices[0]; + const stl_vertex &v1 = vertices[1]; + const stl_vertex &v2 = vertices[2]; + // We may ignore this edge for slicing purposes, but we may still use it for object cutting. + FacetSliceType result = FacetSliceType::Slicing; + if (horizontal) { + // All three vertices are aligned with slice_z. + line_out.edge_type = IntersectionLine::FacetEdgeType::Horizontal; + result = FacetSliceType::Cutting; + double normal = (v1.x() - v0.x()) * (v2.y() - v1.y()) - (v1.y() - v0.y()) * (v2.x() - v1.x()); + if (normal < 0) { + // If normal points downwards this is a bottom horizontal facet so we reverse its point order. + std::swap(a, b); + std::swap(a_id, b_id); + } + } else { + // Two vertices are aligned with the cutting plane, the third vertex is below or above the cutting plane. + // Is the third vertex below the cutting plane? + bool third_below = v0.z() < slice_z || v1.z() < slice_z || v2.z() < slice_z; + // Two vertices on the cutting plane, the third vertex is below the plane. Consider the edge to be part of the slice + // only if it is the upper edge. + // (the bottom most edge resp. vertex of a triangle is not owned by the triangle, but the top most edge resp. vertex is part of the triangle + // in respect to the cutting plane). + result = third_below ? FacetSliceType::Slicing : FacetSliceType::Cutting; + if (third_below) { + line_out.edge_type = IntersectionLine::FacetEdgeType::Top; + std::swap(a, b); + std::swap(a_id, b_id); + } else + line_out.edge_type = IntersectionLine::FacetEdgeType::Bottom; + } + line_out.a.x() = a->x(); + line_out.a.y() = a->y(); + line_out.b.x() = b->x(); + line_out.b.y() = b->y(); + line_out.a_id = a_id; + line_out.b_id = b_id; + assert(line_out.a != line_out.b); + return result; + } + + if (a->z() == slice_z) { + // Only point a alings with the cutting plane. + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) { + point_on_layer = num_points; + IntersectionPoint &point = points[num_points ++]; + point.x() = a->x(); + point.y() = a->y(); + point.point_id = a_id; + } + } else if (b->z() == slice_z) { + // Only point b alings with the cutting plane. + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) { + point_on_layer = num_points; + IntersectionPoint &point = points[num_points ++]; + point.x() = b->x(); + point.y() = b->y(); + point.point_id = b_id; + } + } else if ((a->z() < slice_z && b->z() > slice_z) || (b->z() < slice_z && a->z() > slice_z)) { + // A general case. The face edge intersects the cutting plane. Calculate the intersection point. + assert(a_id != b_id); + // Sort the edge to give a consistent answer. + if (a_id > b_id) { + std::swap(a_id, b_id); + std::swap(a, b); + } + IntersectionPoint &point = points[num_points]; + double t = (double(slice_z) - double(b->z())) / (double(a->z()) - double(b->z())); + if (t <= 0.) { + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != a_id) { + point.x() = a->x(); + point.y() = a->y(); + point_on_layer = num_points ++; + point.point_id = a_id; + } + } else if (t >= 1.) { + if (point_on_layer == size_t(-1) || points[point_on_layer].point_id != b_id) { + point.x() = b->x(); + point.y() = b->y(); + point_on_layer = num_points ++; + point.point_id = b_id; + } + } else { + point.x() = coord_t(floor(double(b->x()) + (double(a->x()) - double(b->x())) * t + 0.5)); + point.y() = coord_t(floor(double(b->y()) + (double(a->y()) - double(b->y())) * t + 0.5)); + point.edge_id = edge_id; + ++ num_points; + } + } + } + + // Facets must intersect each plane 0 or 2 times, or it may touch the plane at a single vertex only. + assert(num_points < 3); + if (num_points == 2) { + line_out.edge_type = IntersectionLine::FacetEdgeType::General; + line_out.a = static_cast(points[1]); + line_out.b = static_cast(points[0]); + line_out.a_id = points[1].point_id; + line_out.b_id = points[0].point_id; + line_out.edge_a_id = points[1].edge_id; + line_out.edge_b_id = points[0].edge_id; + // Not a zero lenght edge. + //FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. + //assert(line_out.a != line_out.b); + // The plane cuts at least one edge in a general position. + assert(line_out.a_id == -1 || line_out.b_id == -1); + assert(line_out.edge_a_id != -1 || line_out.edge_b_id != -1); + // General slicing position, use the segment for both slicing and object cutting. +#if 0 + if (line_out.a_id != -1 && line_out.b_id != -1) { + // Solving a degenerate case, where both the intersections snapped to an edge. + // Correctly classify the face as below or above based on the position of the 3rd point. + int i = indices[0]; + if (i == line_out.a_id || i == line_out.b_id) + i = indices[1]; + if (i == line_out.a_id || i == line_out.b_id) + i = indices[2]; + assert(i != line_out.a_id && i != line_out.b_id); + line_out.edge_type = ((m_use_quaternion ? + (m_quaternion * this->v_scaled_shared[i]).z() + : this->v_scaled_shared[i].z()) < slice_z) ? IntersectionLine::FacetEdgeType::Top : IntersectionLine::FacetEdgeType::Bottom; + } +#endif + return FacetSliceType::Slicing; + } + return FacetSliceType::NoSlice; +} + +template +void slice_facet_at_zs( + // Scaled or unscaled vertices. transform_vertex_fn may scale zs. + const std::vector &mesh_vertices, + const TransformVertex &transform_vertex_fn, + const stl_triangle_vertex_indices &indices, + const Vec3i &facet_neighbors, + // Scaled or unscaled zs. If vertices have their zs scaled or transform_vertex_fn scales them, then zs have to be scaled as well. + const std::vector &zs, + std::vector &lines, + boost::mutex &lines_mutex) +{ + stl_vertex vertices[3] { transform_vertex_fn(mesh_vertices[indices(0)]), transform_vertex_fn(mesh_vertices[indices(1)]), transform_vertex_fn(mesh_vertices[indices(2)]) }; + + // find facet extents + const float min_z = fminf(vertices[0].z(), fminf(vertices[1].z(), vertices[2].z())); + const float max_z = fmaxf(vertices[0].z(), fmaxf(vertices[1].z(), vertices[2].z())); + + // find layer extents + auto min_layer = std::lower_bound(zs.begin(), zs.end(), min_z); // first layer whose slice_z is >= min_z + auto max_layer = std::upper_bound(min_layer, zs.end(), max_z); // first layer whose slice_z is > max_z + + for (auto it = min_layer; it != max_layer; ++ it) { + IntersectionLine il; + int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0); + if (slice_facet(*it, vertices, indices, facet_neighbors, idx_vertex_lowest, min_z == max_z, il) == FacetSliceType::Slicing && + il.edge_type != IntersectionLine::FacetEdgeType::Horizontal) { + // Ignore horizontal triangles. Any valid horizontal triangle must have a vertical triangle connected, otherwise the part has zero volume. + boost::lock_guard l(lines_mutex); + lines[it - zs.begin()].emplace_back(il); + } + } +} + +template +inline std::vector slice_make_lines( + const std::vector &vertices, + const TransformVertex &transform_vertex_fn, + const std::vector &indices, + const std::vector &face_neighbors, + const std::vector &zs, + const ThrowOnCancel throw_on_cancel_fn) +{ + std::vector lines(zs.size(), IntersectionLines()); + boost::mutex lines_mutex; + tbb::parallel_for( + tbb::blocked_range(0, int(indices.size())), + [&vertices, &transform_vertex_fn, &indices, &face_neighbors, &zs, &lines, &lines_mutex, throw_on_cancel_fn](const tbb::blocked_range &range) { + for (int face_idx = range.begin(); face_idx < range.end(); ++ face_idx) { + if ((face_idx & 0x0ffff) == 0) + throw_on_cancel_fn(); + slice_facet_at_zs(vertices, transform_vertex_fn, indices[face_idx], face_neighbors[face_idx], zs, lines, lines_mutex); + } + } + ); + return lines; +} + +#if 0 +//FIXME Should this go away? For valid meshes the function slice_facet() returns Slicing +// and sets edges of vertical triangles to produce only a single edge per pair of neighbor faces. +// So the following code makes only sense now to handle degenerate meshes with more than two faces +// sharing a single edge. +static inline void remove_tangent_edges(std::vector &lines) +{ + std::vector by_vertex_pair; + by_vertex_pair.reserve(lines.size()); + for (IntersectionLine& line : lines) + if (line.edge_type != IntersectionLine::FacetEdgeType::General && line.a_id != -1) + // This is a face edge. Check whether there is its neighbor stored in lines. + by_vertex_pair.emplace_back(&line); + auto edges_lower_sorted = [](const IntersectionLine *l1, const IntersectionLine *l2) { + // Sort vertices of l1, l2 lexicographically + int l1a = l1->a_id; + int l1b = l1->b_id; + int l2a = l2->a_id; + int l2b = l2->b_id; + if (l1a > l1b) + std::swap(l1a, l1b); + if (l2a > l2b) + std::swap(l2a, l2b); + // Lexicographical "lower" operator on lexicographically sorted vertices should bring equal edges together when sored. + return l1a < l2a || (l1a == l2a && l1b < l2b); + }; + std::sort(by_vertex_pair.begin(), by_vertex_pair.end(), edges_lower_sorted); + for (auto line = by_vertex_pair.begin(); line != by_vertex_pair.end(); ++ line) { + IntersectionLine &l1 = **line; + if (! l1.skip()) { + // Iterate as long as line and line2 edges share the same end points. + for (auto line2 = line + 1; line2 != by_vertex_pair.end() && ! edges_lower_sorted(*line, *line2); ++ line2) { + // Lines must share the end points. + assert(! edges_lower_sorted(*line, *line2)); + assert(! edges_lower_sorted(*line2, *line)); + IntersectionLine &l2 = **line2; + if (l2.skip()) + continue; + if (l1.a_id == l2.a_id) { + assert(l1.b_id == l2.b_id); + l2.set_skip(); + // If they are both oriented upwards or downwards (like a 'V'), + // then we can remove both edges from this layer since it won't + // affect the sliced shape. + // If one of them is oriented upwards and the other is oriented + // downwards, let's only keep one of them (it doesn't matter which + // one since all 'top' lines were reversed at slicing). + if (l1.edge_type == l2.edge_type) { + l1.set_skip(); + break; + } + } else { + assert(l1.a_id == l2.b_id && l1.b_id == l2.a_id); + // If this edge joins two horizontal facets, remove both of them. + if (l1.edge_type == IntersectionLine::FacetEdgeType::Horizontal && l2.edge_type == IntersectionLine::FacetEdgeType::Horizontal) { + l1.set_skip(); + l2.set_skip(); + break; + } + } + } + } + } +} +#endif + +struct OpenPolyline { + OpenPolyline() = default; + OpenPolyline(const IntersectionReference &start, const IntersectionReference &end, Points &&points) : + start(start), end(end), points(std::move(points)), consumed(false) { this->length = Slic3r::length(this->points); } + void reverse() { + std::swap(start, end); + std::reverse(points.begin(), points.end()); + } + IntersectionReference start; + IntersectionReference end; + Points points; + double length; + bool consumed; +}; + +// called by make_loops() to connect sliced triangles into closed loops and open polylines by the triangle connectivity. +// Only connects segments crossing triangles of the same orientation. +static void chain_lines_by_triangle_connectivity(IntersectionLines &lines, Polygons &loops, std::vector &open_polylines) +{ + // Build a map of lines by edge_a_id and a_id. + std::vector by_edge_a_id; + std::vector by_a_id; + by_edge_a_id.reserve(lines.size()); + by_a_id.reserve(lines.size()); + for (IntersectionLine &line : lines) { + if (! line.skip()) { + if (line.edge_a_id != -1) + by_edge_a_id.emplace_back(&line); + if (line.a_id != -1) + by_a_id.emplace_back(&line); + } + } + auto by_edge_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->edge_a_id < il2->edge_a_id; }; + auto by_vertex_lower = [](const IntersectionLine* il1, const IntersectionLine *il2) { return il1->a_id < il2->a_id; }; + std::sort(by_edge_a_id.begin(), by_edge_a_id.end(), by_edge_lower); + std::sort(by_a_id.begin(), by_a_id.end(), by_vertex_lower); + // Chain the segments with a greedy algorithm, collect the loops and unclosed polylines. + IntersectionLines::iterator it_line_seed = lines.begin(); + for (;;) { + // take first spare line and start a new loop + IntersectionLine *first_line = nullptr; + for (; it_line_seed != lines.end(); ++ it_line_seed) + if (it_line_seed->is_seed_candidate()) { + //if (! it_line_seed->skip()) { + first_line = &(*it_line_seed ++); + break; + } + if (first_line == nullptr) + break; + first_line->set_skip(); + Points loop_pts; + loop_pts.emplace_back(first_line->a); + IntersectionLine *last_line = first_line; + + /* + printf("first_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", + first_line->edge_a_id, first_line->edge_b_id, first_line->a_id, first_line->b_id, + first_line->a.x, first_line->a.y, first_line->b.x, first_line->b.y); + */ + + IntersectionLine key; + for (;;) { + // find a line starting where last one finishes + IntersectionLine* next_line = nullptr; + if (last_line->edge_b_id != -1) { + key.edge_a_id = last_line->edge_b_id; + auto it_begin = std::lower_bound(by_edge_a_id.begin(), by_edge_a_id.end(), &key, by_edge_lower); + if (it_begin != by_edge_a_id.end()) { + auto it_end = std::upper_bound(it_begin, by_edge_a_id.end(), &key, by_edge_lower); + for (auto it_line = it_begin; it_line != it_end; ++ it_line) + if (! (*it_line)->skip()) { + next_line = *it_line; + break; + } + } + } + if (next_line == nullptr && last_line->b_id != -1) { + key.a_id = last_line->b_id; + auto it_begin = std::lower_bound(by_a_id.begin(), by_a_id.end(), &key, by_vertex_lower); + if (it_begin != by_a_id.end()) { + auto it_end = std::upper_bound(it_begin, by_a_id.end(), &key, by_vertex_lower); + for (auto it_line = it_begin; it_line != it_end; ++ it_line) + if (! (*it_line)->skip()) { + next_line = *it_line; + break; + } + } + } + if (next_line == nullptr) { + // Check whether we closed this loop. + if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) || + (first_line->a_id != -1 && first_line->a_id == last_line->b_id)) { + // The current loop is complete. Add it to the output. + loops.emplace_back(std::move(loop_pts)); + #ifdef SLIC3R_TRIANGLEMESH_DEBUG + printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size()); + #endif + } else { + // This is an open polyline. Add it to the list of open polylines. These open polylines will processed later. + loop_pts.emplace_back(last_line->b); + open_polylines.emplace_back(OpenPolyline( + IntersectionReference(first_line->a_id, first_line->edge_a_id), + IntersectionReference(last_line->b_id, last_line->edge_b_id), std::move(loop_pts))); + } + break; + } + /* + printf("next_line edge_a_id = %d, edge_b_id = %d, a_id = %d, b_id = %d, a = %d,%d, b = %d,%d\n", + next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id, + next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y); + */ + loop_pts.emplace_back(next_line->a); + last_line = next_line; + next_line->set_skip(); + } + } +} + +std::vector open_polylines_sorted(std::vector &open_polylines, bool update_lengths) +{ + std::vector out; + out.reserve(open_polylines.size()); + for (OpenPolyline &opl : open_polylines) + if (! opl.consumed) { + if (update_lengths) + opl.length = Slic3r::length(opl.points); + out.emplace_back(&opl); + } + std::sort(out.begin(), out.end(), [](const OpenPolyline *lhs, const OpenPolyline *rhs){ return lhs->length > rhs->length; }); + return out; +} + +// called by make_loops() to connect remaining open polylines across shared triangle edges and vertices. +// Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation. +static void chain_open_polylines_exact(std::vector &open_polylines, Polygons &loops, bool try_connect_reversed) +{ + // Store the end points of open_polylines into vectors sorted + struct OpenPolylineEnd { + OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} + OpenPolyline *polyline; + // Is it the start or end point? + bool start; + const IntersectionReference& ipref() const { return start ? polyline->start : polyline->end; } + // Return a unique ID for the intersection point. + // Return a positive id for a point, or a negative id for an edge. + int id() const { const IntersectionReference &r = ipref(); return (r.point_id >= 0) ? r.point_id : - r.edge_id; } + bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } + }; + auto by_id_lower = [](const OpenPolylineEnd &ope1, const OpenPolylineEnd &ope2) { return ope1.id() < ope2.id(); }; + std::vector by_id; + by_id.reserve(2 * open_polylines.size()); + for (OpenPolyline &opl : open_polylines) { + if (opl.start.point_id != -1 || opl.start.edge_id != -1) + by_id.emplace_back(OpenPolylineEnd(&opl, true)); + if (try_connect_reversed && (opl.end.point_id != -1 || opl.end.edge_id != -1)) + by_id.emplace_back(OpenPolylineEnd(&opl, false)); + } + std::sort(by_id.begin(), by_id.end(), by_id_lower); + // Find an iterator to by_id_lower for the particular end of OpenPolyline (by comparing the OpenPolyline pointer and the start attribute). + auto find_polyline_end = [&by_id, by_id_lower](const OpenPolylineEnd &end) -> std::vector::iterator { + for (auto it = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); + it != by_id.end() && it->id() == end.id(); ++ it) + if (*it == end) + return it; + return by_id.end(); + }; + // Try to connect the loops. + std::vector sorted_by_length = open_polylines_sorted(open_polylines, false); + for (OpenPolyline *opl : sorted_by_length) { + if (opl->consumed) + continue; + opl->consumed = true; + OpenPolylineEnd end(opl, false); + for (;;) { + // find a line starting where last one finishes + auto it_next_start = std::lower_bound(by_id.begin(), by_id.end(), end, by_id_lower); + for (; it_next_start != by_id.end() && it_next_start->id() == end.id(); ++ it_next_start) + if (! it_next_start->polyline->consumed) + goto found; + // The current loop could not be closed. Unmark the segment. + opl->consumed = false; + break; + found: + // Attach this polyline to the end of the initial polyline. + if (it_next_start->start) { + auto it = it_next_start->polyline->points.begin(); + std::copy(++ it, it_next_start->polyline->points.end(), back_inserter(opl->points)); + } else { + auto it = it_next_start->polyline->points.rbegin(); + std::copy(++ it, it_next_start->polyline->points.rend(), back_inserter(opl->points)); + } + opl->length += it_next_start->polyline->length; + // Mark the next polyline as consumed. + it_next_start->polyline->points.clear(); + it_next_start->polyline->length = 0.; + it_next_start->polyline->consumed = true; + if (try_connect_reversed) { + // Running in a mode, where the polylines may be connected by mixing their orientations. + // Update the end point lookup structure after the end point of the current polyline was extended. + auto it_end = find_polyline_end(end); + auto it_next_end = find_polyline_end(OpenPolylineEnd(it_next_start->polyline, !it_next_start->start)); + // Swap the end points of the current and next polyline, but keep the polyline ptr and the start flag. + std::swap(opl->end, it_next_end->start ? it_next_end->polyline->start : it_next_end->polyline->end); + // Swap the positions of OpenPolylineEnd structures in the sorted array to match their respective end point positions. + std::swap(*it_end, *it_next_end); + } + // Check whether we closed this loop. + if ((opl->start.edge_id != -1 && opl->start.edge_id == opl->end.edge_id) || + (opl->start.point_id != -1 && opl->start.point_id == opl->end.point_id)) { + // The current loop is complete. Add it to the output. + //assert(opl->points.front().point_id == opl->points.back().point_id); + //assert(opl->points.front().edge_id == opl->points.back().edge_id); + // Remove the duplicate last point. + opl->points.pop_back(); + if (opl->points.size() >= 3) { + if (try_connect_reversed && area(opl->points) < 0) + // The closed polygon is patched from pieces with messed up orientation, therefore + // the orientation of the patched up polygon is not known. + // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. + std::reverse(opl->points.begin(), opl->points.end()); + loops.emplace_back(std::move(opl->points)); + } + opl->points.clear(); + break; + } + // Continue with the current loop. + } + } +} + +// called by make_loops() to connect remaining open polylines across shared triangle edges and vertices, +// possibly closing small gaps. +// Depending on "try_connect_reversed", it may or may not connect segments crossing triangles of opposite orientation. +static void chain_open_polylines_close_gaps(std::vector &open_polylines, Polygons &loops, double max_gap, bool try_connect_reversed) +{ + const coord_t max_gap_scaled = (coord_t)scale_(max_gap); + + // Sort the open polylines by their length, so the new loops will be seeded from longer chains. + // Update the polyline lengths, return only not yet consumed polylines. + std::vector sorted_by_length = open_polylines_sorted(open_polylines, true); + + // Store the end points of open_polylines into ClosestPointInRadiusLookup. + struct OpenPolylineEnd { + OpenPolylineEnd(OpenPolyline *polyline, bool start) : polyline(polyline), start(start) {} + OpenPolyline *polyline; + // Is it the start or end point? + bool start; + const Point& point() const { return start ? polyline->points.front() : polyline->points.back(); } + bool operator==(const OpenPolylineEnd &rhs) const { return this->polyline == rhs.polyline && this->start == rhs.start; } + }; + struct OpenPolylineEndAccessor { + const Point* operator()(const OpenPolylineEnd &pt) const { return pt.polyline->consumed ? nullptr : &pt.point(); } + }; + typedef ClosestPointInRadiusLookup ClosestPointLookupType; + ClosestPointLookupType closest_end_point_lookup(max_gap_scaled); + for (OpenPolyline *opl : sorted_by_length) { + closest_end_point_lookup.insert(OpenPolylineEnd(opl, true)); + if (try_connect_reversed) + closest_end_point_lookup.insert(OpenPolylineEnd(opl, false)); + } + // Try to connect the loops. + for (OpenPolyline *opl : sorted_by_length) { + if (opl->consumed) + continue; + OpenPolylineEnd end(opl, false); + if (try_connect_reversed) + // The end point of this polyline will be modified, thus the following entry will become invalid. Remove it. + closest_end_point_lookup.erase(end); + opl->consumed = true; + size_t n_segments_joined = 1; + for (;;) { + // Find a line starting where last one finishes, only return non-consumed open polylines (OpenPolylineEndAccessor returns null for consumed). + std::pair next_start_and_dist = closest_end_point_lookup.find(end.point()); + const OpenPolylineEnd *next_start = next_start_and_dist.first; + // Check whether we closed this loop. + double current_loop_closing_distance2 = (opl->points.back() - opl->points.front()).cast().squaredNorm(); + bool loop_closed = current_loop_closing_distance2 < coordf_t(max_gap_scaled) * coordf_t(max_gap_scaled); + if (next_start != nullptr && loop_closed && current_loop_closing_distance2 < next_start_and_dist.second) { + // Heuristics to decide, whether to close the loop, or connect another polyline. + // One should avoid closing loops shorter than max_gap_scaled. + loop_closed = sqrt(current_loop_closing_distance2) < 0.3 * length(opl->points); + } + if (loop_closed) { + // Remove the start point of the current polyline from the lookup. + // Mark the current segment as not consumed, otherwise the closest_end_point_lookup.erase() would fail. + opl->consumed = false; + closest_end_point_lookup.erase(OpenPolylineEnd(opl, true)); + if (current_loop_closing_distance2 == 0.) { + // Remove the duplicate last point. + opl->points.pop_back(); + } else { + // The end points are different, keep both of them. + } + if (opl->points.size() >= 3) { + if (try_connect_reversed && n_segments_joined > 1 && area(opl->points) < 0) + // The closed polygon is patched from pieces with messed up orientation, therefore + // the orientation of the patched up polygon is not known. + // Orient the patched up polygons CCW. This heuristic may close some holes and cavities. + std::reverse(opl->points.begin(), opl->points.end()); + loops.emplace_back(std::move(opl->points)); + } + opl->points.clear(); + opl->consumed = true; + break; + } + if (next_start == nullptr) { + // The current loop could not be closed. Unmark the segment. + opl->consumed = false; + if (try_connect_reversed) + // Re-insert the end point. + closest_end_point_lookup.insert(OpenPolylineEnd(opl, false)); + break; + } + // Attach this polyline to the end of the initial polyline. + if (next_start->start) { + auto it = next_start->polyline->points.begin(); + if (*it == opl->points.back()) + ++ it; + std::copy(it, next_start->polyline->points.end(), back_inserter(opl->points)); + } else { + auto it = next_start->polyline->points.rbegin(); + if (*it == opl->points.back()) + ++ it; + std::copy(it, next_start->polyline->points.rend(), back_inserter(opl->points)); + } + ++ n_segments_joined; + // Remove the end points of the consumed polyline segment from the lookup. + OpenPolyline *opl2 = next_start->polyline; + closest_end_point_lookup.erase(OpenPolylineEnd(opl2, true)); + if (try_connect_reversed) + closest_end_point_lookup.erase(OpenPolylineEnd(opl2, false)); + opl2->points.clear(); + opl2->consumed = true; + // Continue with the current loop. + } + } +} + +static Polygons make_loops( + // Lines will have their flags modified. + IntersectionLines &lines) +{ + Polygons loops; +#if 0 +//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. +//#ifdef _DEBUG + for (const Line &l : lines) + assert(l.a != l.b); +#endif /* _DEBUG */ + + // There should be no tangent edges, as the horizontal triangles are ignored and if two triangles touch at a cutting plane, + // only the bottom triangle is considered to be cutting the plane. +// remove_tangent_edges(lines); + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + BoundingBox bbox_svg; + { + static int iRun = 0; + for (const Line &line : lines) { + bbox_svg.merge(line.a); + bbox_svg.merge(line.b); + } + SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-raw_lines-%d.svg", iRun ++).c_str(), bbox_svg); + for (const Line &line : lines) + svg.draw(line); + svg.Close(); + } +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + + std::vector open_polylines; + chain_lines_by_triangle_connectivity(lines, loops, open_polylines); + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRun = 0; + SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-%d.svg", iRun ++).c_str(), bbox_svg); + svg.draw(union_ex(*loops)); + for (const OpenPolyline &pl : open_polylines) + svg.draw(Polyline(pl.points), "red"); + svg.Close(); + } +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + + // Now process the open polylines. + // Do it in two rounds, first try to connect in the same direction only, + // then try to connect the open polylines in reversed order as well. + chain_open_polylines_exact(open_polylines, loops, false); + chain_open_polylines_exact(open_polylines, loops, true); + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRun = 0; + SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines2-%d.svg", iRun++).c_str(), bbox_svg); + svg.draw(union_ex(*loops)); + for (const OpenPolyline &pl : open_polylines) { + if (pl.points.empty()) + continue; + svg.draw(Polyline(pl.points), "red"); + svg.draw(pl.points.front(), "blue"); + svg.draw(pl.points.back(), "blue"); + } + svg.Close(); + } +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + + // Try to close gaps. + // Do it in two rounds, first try to connect in the same direction only, + // then try to connect the open polylines in reversed order as well. +#if 0 + for (double max_gap : { EPSILON, 0.001, 0.1, 1., 2. }) { + chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, false); + chain_open_polylines_close_gaps(open_polylines, *loops, max_gap, true); + } +#else + const double max_gap = 2.; //mm + chain_open_polylines_close_gaps(open_polylines, loops, max_gap, false); + chain_open_polylines_close_gaps(open_polylines, loops, max_gap, true); +#endif + +#ifdef SLIC3R_DEBUG_SLICE_PROCESSING + { + static int iRun = 0; + SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-final-%d.svg", iRun++).c_str(), bbox_svg); + svg.draw(union_ex(*loops)); + for (const OpenPolyline &pl : open_polylines) { + if (pl.points.empty()) + continue; + svg.draw(Polyline(pl.points), "red"); + svg.draw(pl.points.front(), "blue"); + svg.draw(pl.points.back(), "blue"); + } + svg.Close(); + } +#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ + + return loops; +} + +template +static std::vector make_loops( + // Lines will have their flags modified. + std::vector &lines, + const MeshSlicingParams ¶ms, + ThrowOnCancel throw_on_cancel) +{ + std::vector layers; + layers.resize(lines.size()); + tbb::parallel_for( + tbb::blocked_range(0, lines.size()), + [&lines, &layers, ¶ms, throw_on_cancel](const tbb::blocked_range &range) { + for (size_t line_idx = range.begin(); line_idx < range.end(); ++ line_idx) { + if ((line_idx & 0x0ffff) == 0) + throw_on_cancel(); + + Polygons &polygons = layers[line_idx]; + polygons = make_loops(lines[line_idx]); + + auto this_mode = line_idx < params.slicing_mode_normal_below_layer ? params.mode_below : params.mode; + if (! polygons.empty()) { + if (this_mode == MeshSlicingParams::SlicingMode::Positive) { + // Reorient all loops to be CCW. + for (Polygon& p : polygons) + p.make_counter_clockwise(); + } + else if (this_mode == MeshSlicingParams::SlicingMode::PositiveLargestContour) { + // Keep just the largest polygon, make it CCW. + double max_area = 0.; + Polygon* max_area_polygon = nullptr; + for (Polygon& p : polygons) { + double a = p.area(); + if (std::abs(a) > std::abs(max_area)) { + max_area = a; + max_area_polygon = &p; + } + } + assert(max_area_polygon != nullptr); + if (max_area < 0.) + max_area_polygon->reverse(); + Polygon p(std::move(*max_area_polygon)); + polygons.clear(); + polygons.emplace_back(std::move(p)); + } + } + } + } + ); + + return layers; +} + +// Used to cut the mesh into two halves. +static ExPolygons make_expolygons_simple(std::vector &lines) +{ + ExPolygons slices; + Polygons holes; + + for (Polygon &loop : make_loops(lines)) + if (loop.area() >= 0.) + slices.emplace_back(std::move(loop)); + else + holes.emplace_back(std::move(loop)); + + // If there are holes, then there should also be outer contours. + assert(holes.empty() || ! slices.empty()); + if (! slices.empty()) + { + // Assign holes to outer contours. + for (Polygon &hole : holes) { + // Find an outer contour to a hole. + int slice_idx = -1; + double current_contour_area = std::numeric_limits::max(); + for (ExPolygon &slice : slices) + if (slice.contour.contains(hole.points.front())) { + double area = slice.contour.area(); + if (area < current_contour_area) { + slice_idx = &slice - slices.data(); + current_contour_area = area; + } + } + // assert(slice_idx != -1); + if (slice_idx == -1) + // Ignore this hole. + continue; + assert(current_contour_area < std::numeric_limits::max() && current_contour_area >= -hole.area()); + slices[slice_idx].holes.emplace_back(std::move(hole)); + } + +#if 0 + // If the input mesh is not valid, the holes may intersect with the external contour. + // Rather subtract them from the outer contour. + Polygons poly; + for (auto it_slice = slices->begin(); it_slice != slices->end(); ++ it_slice) { + if (it_slice->holes.empty()) { + poly.emplace_back(std::move(it_slice->contour)); + } else { + Polygons contours; + contours.emplace_back(std::move(it_slice->contour)); + for (auto it = it_slice->holes.begin(); it != it_slice->holes.end(); ++ it) + it->reverse(); + polygons_append(poly, diff(contours, it_slice->holes)); + } + } + // If the input mesh is not valid, the input contours may intersect. + *slices = union_ex(poly); +#endif + +#if 0 + // If the input mesh is not valid, the holes may intersect with the external contour. + // Rather subtract them from the outer contour. + ExPolygons poly; + for (auto it_slice = slices->begin(); it_slice != slices->end(); ++ it_slice) { + Polygons contours; + contours.emplace_back(std::move(it_slice->contour)); + for (auto it = it_slice->holes.begin(); it != it_slice->holes.end(); ++ it) + it->reverse(); + expolygons_append(poly, diff_ex(contours, it_slice->holes)); + } + // If the input mesh is not valid, the input contours may intersect. + *slices = std::move(poly); +#endif + } + + return slices; +} + +static void make_expolygons(const Polygons &loops, const float closing_radius, const float extra_offset, ExPolygons* slices) +{ + /* + Input loops are not suitable for evenodd nor nonzero fill types, as we might get + two consecutive concentric loops having the same winding order - and we have to + respect such order. In that case, evenodd would create wrong inversions, and nonzero + would ignore holes inside two concentric contours. + So we're ordering loops and collapse consecutive concentric loops having the same + winding order. + TODO: find a faster algorithm for this, maybe with some sort of binary search. + If we computed a "nesting tree" we could also just remove the consecutive loops + having the same winding order, and remove the extra one(s) so that we could just + supply everything to offset() instead of performing several union/diff calls. + + we sort by area assuming that the outermost loops have larger area; + the previous sorting method, based on $b->contains($a->[0]), failed to nest + loops correctly in some edge cases when original model had overlapping facets + */ + + /* The following lines are commented out because they can generate wrong polygons, + see for example issue #661 */ + + //std::vector area; + //std::vector sorted_area; // vector of indices + //for (Polygons::const_iterator loop = loops.begin(); loop != loops.end(); ++ loop) { + // area.emplace_back(loop->area()); + // sorted_area.emplace_back(loop - loops.begin()); + //} + // + //// outer first + //std::sort(sorted_area.begin(), sorted_area.end(), + // [&area](size_t a, size_t b) { return std::abs(area[a]) > std::abs(area[b]); }); + + //// we don't perform a safety offset now because it might reverse cw loops + //Polygons p_slices; + //for (std::vector::const_iterator loop_idx = sorted_area.begin(); loop_idx != sorted_area.end(); ++ loop_idx) { + // /* we rely on the already computed area to determine the winding order + // of the loops, since the Orientation() function provided by Clipper + // would do the same, thus repeating the calculation */ + // Polygons::const_iterator loop = loops.begin() + *loop_idx; + // if (area[*loop_idx] > +EPSILON) + // p_slices.emplace_back(*loop); + // else if (area[*loop_idx] < -EPSILON) + // //FIXME This is arbitrary and possibly very slow. + // // If the hole is inside a polygon, then there is no need to diff. + // // If the hole intersects a polygon boundary, then diff it, but then + // // there is no guarantee of an ordering of the loops. + // // Maybe we can test for the intersection before running the expensive diff algorithm? + // p_slices = diff(p_slices, *loop); + //} + + // Perform a safety offset to merge very close facets (TODO: find test case for this) + // 0.0499 comes from https://github.com/slic3r/Slic3r/issues/959 +// double safety_offset = scale_(0.0499); + // 0.0001 is set to satisfy GH #520, #1029, #1364 + assert(closing_radius >= 0); + assert(extra_offset >= 0); + double offset_out = + scale_(closing_radius + extra_offset); + double offset_in = - scale_(closing_radius); + + /* The following line is commented out because it can generate wrong polygons, + see for example issue #661 */ + //ExPolygons ex_slices = offset2_ex(p_slices, +safety_offset, -safety_offset); + + #ifdef SLIC3R_TRIANGLEMESH_DEBUG + size_t holes_count = 0; + for (ExPolygons::const_iterator e = ex_slices.begin(); e != ex_slices.end(); ++ e) + holes_count += e->holes.size(); + printf("%zu surface(s) having %zu holes detected from %zu polylines\n", + ex_slices.size(), holes_count, loops.size()); + #endif + + // append to the supplied collection + expolygons_append(*slices, + offset_out > 0 && offset_in < 0 ? offset2_ex(union_ex(loops), offset_out, offset_in) : + offset_out > 0 ? offset_ex(union_ex(loops), offset_out) : + offset_in < 0 ? offset_ex(union_ex(loops), offset_in) : + union_ex(loops)); +} + +std::vector slice_mesh( + const indexed_triangle_set &mesh, + // Unscaled Zs + const std::vector &zs, + const MeshSlicingParams ¶ms, + std::function throw_on_cancel) +{ + BOOST_LOG_TRIVIAL(debug) << "slice_mesh to polygons"; + + std::vector lines; + + { + //FIXME facets_edges is likely not needed and quite costly to calculate. + // Instead of edge identifiers, one shall use a sorted pair of edge vertex indices. + // However facets_edges assigns a single edge ID to two triangles only, thus when factoring facets_edges out, one will have + // to make sure that no code relies on it. + std::vector facets_edges = create_face_neighbors_index(mesh); + const bool identity = params.trafo.matrix() == Transform3d::Identity().matrix(); + static constexpr const double s = 1. / SCALING_FACTOR; + if (zs.size() <= 1) { + // It likely is not worthwile to copy the vertices. Apply the transformation in place. + if (identity) + lines = slice_make_lines( + mesh.vertices, [](const Vec3f &p) { return Vec3f(scaled(p.x()), scaled(p.y()), p.z()); }, + mesh.indices, facets_edges, zs, throw_on_cancel); + else { + // Transform the vertices, scale up in XY, not in Y. + auto t = params.trafo; + t.prescale(Vec3d(s, s, 1.)); + auto tf = t.cast(); + slice_make_lines(mesh.vertices, [tf](const Vec3f &p) { return tf * p; }, mesh.indices, facets_edges, zs, throw_on_cancel); + } + } else { + // Copy and scale vertices in XY, don't scale in Z. + // Possibly apply the transformation. + std::vector vertices(mesh.vertices); + if (identity) { + for (stl_vertex &v : vertices) { + // Scale just XY, leave Z unscaled. + v.x() *= float(s); + v.y() *= float(s); + } + } else { + // Transform the vertices, scale up in XY, not in Y. + auto t = params.trafo; + t.prescale(Vec3d(s, s, 1.)); + auto tf = t.cast(); + for (stl_vertex &v : vertices) + v = tf * v; + } + lines = slice_make_lines(vertices, [](const Vec3f &p) { return p; }, mesh.indices, facets_edges, zs, throw_on_cancel); + } + } + + throw_on_cancel(); + + std::vector layers = make_loops(lines, params, throw_on_cancel); + +#ifdef SLIC3R_DEBUG + { + static int iRun = 0; + for (size_t i = 0; i < z.size(); ++ i) { + Polygons &polygons = (*layers)[i]; + ExPolygons expolygons = union_ex(polygons, true); + SVG::export_expolygons(debug_out_path("slice_%d_%d.svg", iRun, i).c_str(), expolygons); + { + BoundingBox bbox; + for (const IntersectionLine &l : lines[i]) { + bbox.merge(l.a); + bbox.merge(l.b); + } + SVG svg(debug_out_path("slice_loops_%d_%d.svg", iRun, i).c_str(), bbox); + svg.draw(expolygons); + for (const IntersectionLine &l : lines[i]) + svg.draw(l, "red", 0); + svg.draw_outline(expolygons, "black", "blue", 0); + svg.Close(); + } +#if 0 +//FIXME slice_facet() may create zero length edges due to rounding of doubles into coord_t. + for (Polygon &poly : polygons) { + for (size_t i = 1; i < poly.points.size(); ++ i) + assert(poly.points[i-1] != poly.points[i]); + assert(poly.points.front() != poly.points.back()); + } +#endif + } + ++ iRun; + } +#endif + + return layers; +} + +std::vector slice_mesh_ex( + const indexed_triangle_set &mesh, + const std::vector &zs, + const MeshSlicingParamsEx ¶ms, + std::function throw_on_cancel) +{ + std::vector layers_p; + { + MeshSlicingParams slicing_params(params); + if (params.mode == MeshSlicingParams::SlicingMode::PositiveLargestContour) + slicing_params.mode = MeshSlicingParams::SlicingMode::Positive; + if (params.mode_below == MeshSlicingParams::SlicingMode::PositiveLargestContour) + slicing_params.mode_below = MeshSlicingParams::SlicingMode::Positive; + layers_p = slice_mesh(mesh, zs, slicing_params, throw_on_cancel); + } + +// BOOST_LOG_TRIVIAL(debug) << "slice_mesh make_expolygons in parallel - start"; + std::vector layers(layers_p.size(), ExPolygons{}); + tbb::parallel_for( + tbb::blocked_range(0, layers_p.size()), + [&layers_p, ¶ms, &layers, throw_on_cancel] + (const tbb::blocked_range& range) { + for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) { + throw_on_cancel(); + ExPolygons &expolygons = layers[layer_id]; + Slic3r::make_expolygons(layers_p[layer_id], params.closing_radius, params.extra_offset, &expolygons); + //FIXME simplify + const auto this_mode = layer_id < params.slicing_mode_normal_below_layer ? params.mode_below : params.mode; + if (this_mode == MeshSlicingParams::SlicingMode::PositiveLargestContour) + keep_largest_contour_only(expolygons); + if (params.resolution != 0.) + for (ExPolygon &ex : expolygons) + ex.simplify(params.resolution); + } + }); +// BOOST_LOG_TRIVIAL(debug) << "slice_mesh make_expolygons in parallel - end"; + + return layers; +} + +static void triangulate_slice( + indexed_triangle_set &its, + IntersectionLines &lines, + std::vector &slice_vertices, + // Vertices of the original (unsliced) mesh. Newly added vertices are those on the slice. + int num_original_vertices, + // Z height of the slice. + float z) +{ + sort_remove_duplicates(slice_vertices); + + // 1) Create map of the slice vertices from positions to mesh indices. + // As the caller will likely add duplicate points when intersecting triangle edges, there will be duplicates. + std::vector> map_vertex_to_index; + map_vertex_to_index.reserve(slice_vertices.size()); + for (int i : slice_vertices) + map_vertex_to_index.emplace_back(to_2d(its.vertices[i]), i); + std::sort(map_vertex_to_index.begin(), map_vertex_to_index.end(), + [](const std::pair &l, const std::pair &r) { + return l.first.x() < r.first.x() || + (l.first.x() == r.first.x() && (l.first.y() < r.first.y() || + (l.first.y() == r.first.y() && l.second < r.second))); }); + + // 2) Discover duplicate points on the slice. Remap duplicate vertices to a vertex with a lowest index. + { + std::vector map_duplicate_vertex(int(its.vertices.size()) - num_original_vertices, -1); + int i = 0; + int k = 0; + for (; i < int(map_vertex_to_index.size()); ++ i) { + map_vertex_to_index[k ++] = map_vertex_to_index[i]; + const Vec2f &ipos = map_vertex_to_index[i].first; + const int iidx = map_vertex_to_index[i].second; + if (iidx >= num_original_vertices) + // map to itself + map_duplicate_vertex[iidx - num_original_vertices] = iidx; + int j = i; + for (++ j; j < int(map_vertex_to_index.size()) && ipos.x() == map_vertex_to_index[j].first.x() && ipos.y() == map_vertex_to_index[j].first.y(); ++ j) { + const int jidx = map_vertex_to_index[j].second; + assert(jidx >= num_original_vertices); + if (jidx >= num_original_vertices) + // map to the first vertex + map_duplicate_vertex[jidx - num_original_vertices] = iidx; + } + } + map_vertex_to_index.erase(map_vertex_to_index.begin() + k, map_vertex_to_index.end()); + for (stl_triangle_vertex_indices &f : its.indices) + for (i = 0; i < 3; ++ i) + if (f(i) >= num_original_vertices) + f(i) = map_duplicate_vertex[f(i) - num_original_vertices]; + } + + size_t idx_vertex_new_first = its.vertices.size(); + Pointf3s triangles = triangulate_expolygons_3d(make_expolygons_simple(lines), z, true); + for (size_t i = 0; i < triangles.size(); ) { + stl_triangle_vertex_indices facet; + for (size_t j = 0; j < 3; ++ j) { + Vec3f v = triangles[i ++].cast(); + auto it = lower_bound_by_predicate(map_vertex_to_index.begin(), map_vertex_to_index.end(), + [&v](const std::pair &l) { return l.first.x() < v.x() || (l.first.x() == v.x() && l.first.y() < v.y()); }); + int idx = -1; + if (it != map_vertex_to_index.end() && it->first.x() == v.x() && it->first.y() == v.y()) + idx = it->second; + else { + // Try to find the vertex in the list of newly added vertices. Those vertices are not matched on the cut and they shall be rare. + for (size_t k = idx_vertex_new_first; k < its.vertices.size(); ++ k) + if (its.vertices[k] == v) { + idx = int(k); + break; + } + if (idx == -1) { + idx = int(its.vertices.size()); + its.vertices.emplace_back(v); + } + } + facet(j) = idx; + } + if (facet(0) != facet(1) && facet(0) != facet(2) && facet(1) != facet(2)) + its.indices.emplace_back(facet); + } + + // Remove vertices, which are not referenced by any face. + its_compactify_vertices(its); + + // Degenerate faces should not be created. + // its_remove_degenerate_faces(its); +} + +void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *upper, indexed_triangle_set *lower) +{ + assert(upper || lower); + if (upper == nullptr && lower == nullptr) + return; + + BOOST_LOG_TRIVIAL(trace) << "cut_mesh - slicing object"; + + if (upper) { + upper->clear(); + upper->vertices = mesh.vertices; + upper->indices.reserve(mesh.indices.size()); + } + + if (lower) { + lower->clear(); + lower->vertices = mesh.vertices; + lower->indices.reserve(mesh.indices.size()); + } + + // To triangulate the caps after slicing. + IntersectionLines upper_lines, lower_lines; + std::vector upper_slice_vertices, lower_slice_vertices; + std::vector facets_edges = create_face_neighbors_index(mesh); + + for (int facet_idx = 0; facet_idx < int(mesh.indices.size()); ++ facet_idx) { + const stl_triangle_vertex_indices &facet = mesh.indices[facet_idx]; + Vec3f vertices[3] { mesh.vertices[facet(0)], mesh.vertices[facet(1)], mesh.vertices[facet(2)] }; + float min_z = std::min(vertices[0].z(), std::min(vertices[1].z(), vertices[2].z())); + float max_z = std::max(vertices[0].z(), std::max(vertices[1].z(), vertices[2].z())); + + // intersect facet with cutting plane + IntersectionLine line; + int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0); + FacetSliceType slice_type = FacetSliceType::NoSlice; + if (z > min_z - EPSILON && z < max_z + EPSILON) { + Vec3f vertices_scaled[3]; + for (int i = 0; i < 3; ++ i) { + const Vec3f &src = vertices[i]; + Vec3f &dst = vertices_scaled[i]; + dst.x() = scale_(src.x()); + dst.y() = scale_(src.y()); + dst.z() = src.z(); + } + slice_type = slice_facet(z, vertices_scaled, mesh.indices[facet_idx], facets_edges[facet_idx], idx_vertex_lowest, min_z == max_z, line); + } + + if (slice_type != FacetSliceType::NoSlice) { + // Save intersection lines for generating correct triangulations. + if (line.edge_type == IntersectionLine::FacetEdgeType::Top) { + lower_lines.emplace_back(line); + lower_slice_vertices.emplace_back(line.a_id); + lower_slice_vertices.emplace_back(line.b_id); + } else if (line.edge_type == IntersectionLine::FacetEdgeType::Bottom) { + upper_lines.emplace_back(line); + upper_slice_vertices.emplace_back(line.a_id); + upper_slice_vertices.emplace_back(line.b_id); + } else if (line.edge_type == IntersectionLine::FacetEdgeType::General) { + lower_lines.emplace_back(line); + upper_lines.emplace_back(line); + } + } + + if (min_z > z || (min_z == z && max_z > z)) { + // facet is above the cut plane and does not belong to it + if (upper != nullptr) + upper->indices.emplace_back(facet); + } else if (max_z < z || (max_z == z && min_z < z)) { + // facet is below the cut plane and does not belong to it + if (lower != nullptr) + lower->indices.emplace_back(facet); + } else if (min_z < z && max_z > z) { + // Facet is cut by the slicing plane. + assert(slice_type == FacetSliceType::Slicing); + assert(line.edge_type == IntersectionLine::FacetEdgeType::General); + assert(line.edge_a_id != -1); + assert(line.edge_b_id != -1); + + // look for the vertex on whose side of the slicing plane there are no other vertices + int isolated_vertex = + (vertices[0].z() > z) == (vertices[1].z() > z) ? 2 : + (vertices[1].z() > z) == (vertices[2].z() > z) ? 0 : 1; + + // get vertices starting from the isolated one + int iv = isolated_vertex; + stl_vertex v0v1, v2v0; + assert(facets_edges[facet_idx](iv) == line.edge_a_id ||facets_edges[facet_idx](iv) == line.edge_b_id); + if (facets_edges[facet_idx](iv) == line.edge_a_id) { + v0v1 = to_3d(unscaled(line.a), z); + v2v0 = to_3d(unscaled(line.b), z); + } else { + v0v1 = to_3d(unscaled(line.b), z); + v2v0 = to_3d(unscaled(line.a), z); + } + const stl_vertex &v0 = vertices[iv]; + const int iv0 = facet[iv]; + if (++ iv == 3) + iv = 0; + const stl_vertex &v1 = vertices[iv]; + const int iv1 = facet[iv]; + if (++ iv == 3) + iv = 0; + const stl_vertex &v2 = vertices[iv]; + const int iv2 = facet[iv]; + + // intersect v0-v1 and v2-v0 with cutting plane and make new vertices + auto new_vertex = [upper, lower, &upper_slice_vertices, &lower_slice_vertices](const Vec3f &a, const int ia, const Vec3f &b, const int ib, const Vec3f &c) { + int iupper, ilower; + if (c == a) + iupper = ilower = ia; + else if (c == b) + iupper = ilower = ib; + else { + // Insert a new vertex into upper / lower. + if (upper) { + iupper = int(upper->vertices.size()); + upper->vertices.emplace_back(c); + upper_slice_vertices.emplace_back(iupper); + } + if (lower) { + ilower = int(lower->vertices.size()); + lower->vertices.emplace_back(c); + lower_slice_vertices.emplace_back(ilower); + } + } + return std::make_pair(iupper, ilower); + }; + auto [iv0v1_upper, iv0v1_lower] = new_vertex(v1, iv1, v0, iv0, v0v1); + auto [iv2v0_upper, iv2v0_lower] = new_vertex(v2, iv2, v0, iv0, v2v0); + auto new_face = [](indexed_triangle_set *its, int i, int j, int k) { + if (its != nullptr && i != j && i != k && j != k) + its->indices.emplace_back(i, j, k); + }; + + if (v0.z() > z) { + new_face(upper, iv0, iv0v1_upper, iv2v0_upper); + new_face(lower, iv1, iv2, iv0v1_lower); + new_face(lower, iv2, iv2v0_lower, iv0v1_lower); + } else { + new_face(upper, iv1, iv2, iv0v1_upper); + new_face(upper, iv2, iv2v0_upper, iv0v1_upper); + new_face(lower, iv0, iv0v1_lower, iv2v0_lower); + } + } + } + + if (upper != nullptr) + triangulate_slice(*upper, upper_lines, upper_slice_vertices, int(mesh.vertices.size()), z); + + if (lower != nullptr) + triangulate_slice(*lower, lower_lines, lower_slice_vertices, int(mesh.vertices.size()), z); +} + +} diff --git a/src/libslic3r/TriangleMeshSlicer.hpp b/src/libslic3r/TriangleMeshSlicer.hpp new file mode 100644 index 000000000..4ae972d00 --- /dev/null +++ b/src/libslic3r/TriangleMeshSlicer.hpp @@ -0,0 +1,83 @@ +#ifndef slic3r_TriangleMeshSlicer_hpp_ +#define slic3r_TriangleMeshSlicer_hpp_ + +#include +#include +#include "Polygon.hpp" +#include "ExPolygon.hpp" + +namespace Slic3r { + +struct MeshSlicingParams +{ + enum class SlicingMode : uint32_t { + // Regular slicing, maintain all contours and their orientation. + Regular, + // Maintain all contours, orient all contours CCW, therefore all holes are being closed. + Positive, + // Orient all contours CCW and keep only the contour with the largest area. + // This mode is useful for slicing complex objects in vase mode. + PositiveLargestContour, + }; + + SlicingMode mode { SlicingMode::Regular }; + // For vase mode: below this layer a different slicing mode will be used to produce a single contour. + // 0 = ignore. + size_t slicing_mode_normal_below_layer { 0 }; + // Mode to apply below slicing_mode_normal_below_layer. Ignored if slicing_mode_nromal_below_layer == 0. + SlicingMode mode_below { SlicingMode::Regular }; + // Transforming faces during the slicing. + Transform3d trafo { Transform3d::Identity() }; +}; + +struct MeshSlicingParamsEx : public MeshSlicingParams +{ + // Morphological closing operation when creating output expolygons. + float closing_radius { 0 }; + // Positive offset applied when creating output expolygons. + float extra_offset { 0 }; + // Resolution for contour simplification, scaled! + // 0 = don't simplify. + double resolution { 0 }; +}; + +std::vector slice_mesh( + const indexed_triangle_set &mesh, + const std::vector &zs, + const MeshSlicingParams ¶ms, + std::function throw_on_cancel = []{}); + +std::vector slice_mesh_ex( + const indexed_triangle_set &mesh, + const std::vector &zs, + const MeshSlicingParamsEx ¶ms, + std::function throw_on_cancel = []{}); + +inline std::vector slice_mesh_ex( + const indexed_triangle_set &mesh, + const std::vector &zs, + std::function throw_on_cancel = []{}) +{ + return slice_mesh_ex(mesh, zs, MeshSlicingParamsEx{}, throw_on_cancel); +} + +inline std::vector slice_mesh_ex( + const indexed_triangle_set &mesh, + const std::vector &zs, + float closing_radius, + std::function throw_on_cancel = []{}) +{ + MeshSlicingParamsEx params; + params.closing_radius = closing_radius; + return slice_mesh_ex(mesh, zs, params, throw_on_cancel); +} + +void cut_mesh( + const indexed_triangle_set &mesh, + float z, + indexed_triangle_set *upper, + indexed_triangle_set *lower); + +} + +#endif // slic3r_TriangleMeshSlicer_hpp_ diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 7f4b87439..aa3bf01c4 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -189,6 +189,8 @@ set(SLIC3R_GUI_SOURCES GUI/UnsavedChangesDialog.hpp GUI/ExtraRenderers.cpp GUI/ExtraRenderers.hpp + GUI/ProjectDirtyStateManager.hpp + GUI/ProjectDirtyStateManager.cpp GUI/DesktopIntegrationDialog.cpp GUI/DesktopIntegrationDialog.hpp Utils/Http.cpp diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index ba62576f2..462594f0f 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -23,6 +23,9 @@ #include "libslic3r/Format/STL.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/AppConfig.hpp" +#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA +#include "libslic3r/PresetBundle.hpp" +#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA #include #include @@ -345,9 +348,16 @@ void GLVolume::set_render_color(const float* rgba, unsigned int size) void GLVolume::set_render_color() { - if (force_native_color || force_neutral_color) - { +#if ENABLE_ALLOW_NEGATIVE_Z + bool outside = is_outside || is_below_printbed(); +#endif // ENABLE_ALLOW_NEGATIVE_Z + + if (force_native_color || force_neutral_color) { +#if ENABLE_ALLOW_NEGATIVE_Z + if (outside && shader_outside_printer_detection_enabled) +#else if (is_outside && shader_outside_printer_detection_enabled) +#endif // ENABLE_ALLOW_NEGATIVE_Z set_render_color(OUTSIDE_COLOR, 4); else { if (force_native_color) @@ -362,17 +372,24 @@ void GLVolume::set_render_color() else if (hover == HS_Deselect) set_render_color(HOVER_DESELECT_COLOR, 4); else if (selected) +#if ENABLE_ALLOW_NEGATIVE_Z + set_render_color(outside ? SELECTED_OUTSIDE_COLOR : SELECTED_COLOR, 4); +#else set_render_color(is_outside ? SELECTED_OUTSIDE_COLOR : SELECTED_COLOR, 4); +#endif // ENABLE_ALLOW_NEGATIVE_Z else if (disabled) set_render_color(DISABLED_COLOR, 4); +#if ENABLE_ALLOW_NEGATIVE_Z + else if (outside && shader_outside_printer_detection_enabled) +#else else if (is_outside && shader_outside_printer_detection_enabled) +#endif // ENABLE_ALLOW_NEGATIVE_Z set_render_color(OUTSIDE_COLOR, 4); else set_render_color(color, 4); } - if (!printable) - { + if (!printable) { render_color[0] /= 4; render_color[1] /= 4; render_color[2] /= 4; @@ -504,6 +521,25 @@ void GLVolume::render() const bool GLVolume::is_sla_support() const { return this->composite_id.volume_id == -int(slaposSupportTree); } bool GLVolume::is_sla_pad() const { return this->composite_id.volume_id == -int(slaposPad); } +#if ENABLE_ALLOW_NEGATIVE_Z +bool GLVolume::is_sinking() const +{ +#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA + if (is_modifier || GUI::wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) +#else + if (is_modifier) +#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA + return false; + const BoundingBoxf3& box = transformed_convex_hull_bounding_box(); + return box.min(2) < -EPSILON && box.max(2) >= -EPSILON; +} + +bool GLVolume::is_below_printbed() const +{ + return transformed_convex_hull_bounding_box().max(2) < 0.0; +} +#endif // ENABLE_ALLOW_NEGATIVE_Z + std::vector GLVolumeCollection::load_object( const ModelObject *model_object, int obj_idx, @@ -778,6 +814,9 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab shader->set_uniform("print_box.volume_world_matrix", volume.first->world_matrix()); 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())); +#if ENABLE_ALLOW_NEGATIVE_Z + shader->set_uniform("sinking", volume.first->is_sinking()); +#endif // ENABLE_ALLOW_NEGATIVE_Z volume.first->render(); } @@ -1032,28 +1071,6 @@ std::string GLVolumeCollection::log_memory_info() const return " (GLVolumeCollection RAM: " + format_memsize_MB(this->cpu_memory_used()) + " GPU: " + format_memsize_MB(this->gpu_memory_used()) + " Both: " + format_memsize_MB(this->gpu_memory_used()) + ")"; } -bool can_export_to_obj(const GLVolume& volume) -{ - if (!volume.is_active || !volume.is_extrusion_path) - return false; - - bool has_triangles = !volume.indexed_vertex_array.triangle_indices.empty() || (std::min(volume.indexed_vertex_array.triangle_indices_size, volume.tverts_range.second - volume.tverts_range.first) > 0); - bool has_quads = !volume.indexed_vertex_array.quad_indices.empty() || (std::min(volume.indexed_vertex_array.quad_indices_size, volume.qverts_range.second - volume.qverts_range.first) > 0); - - return has_triangles || has_quads; -} - -bool GLVolumeCollection::has_toolpaths_to_export() const -{ - for (const GLVolume* volume : this->volumes) - { - if (can_export_to_obj(*volume)) - return true; - } - - return false; -} - // caller is responsible for supplying NO lines with zero length static void thick_lines_to_indexed_vertex_array( const Lines &lines, diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 25c5443cd..db64bdab5 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -453,6 +453,11 @@ public: bool is_sla_support() const; bool is_sla_pad() const; +#if ENABLE_ALLOW_NEGATIVE_Z + bool is_sinking() const; + bool is_below_printbed() const; +#endif // ENABLE_ALLOW_NEGATIVE_Z + // Return an estimate of the memory consumed by this class. size_t cpu_memory_used() const { //FIXME what to do wih m_convex_hull? @@ -585,8 +590,6 @@ public: // Return CPU, GPU and total memory log line. std::string log_memory_info() const; - bool has_toolpaths_to_export() const; - private: GLVolumeCollection(const GLVolumeCollection &other); GLVolumeCollection& operator=(const GLVolumeCollection &); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index dc24ec0ad..12bf6fe02 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -5,6 +5,8 @@ #include #include +#include + #include #include "libslic3r/PrintBase.hpp" diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index d55758538..c5633b8fa 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -91,6 +91,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK)); DynamicPrintConfig new_conf = *config; auto answer = dialog.ShowModal(); + bool support = true; if (!is_global_config || answer == wxID_YES) { new_conf.set_key_value("perimeters", new ConfigOptionInt(1)); new_conf.set_key_value("top_solid_layers", new ConfigOptionInt(0)); @@ -100,13 +101,17 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con new_conf.set_key_value("ensure_vertical_shell_thickness", new ConfigOptionBool(true)); new_conf.set_key_value("thin_walls", new ConfigOptionBool(false)); fill_density = 0; + support = false; } else { new_conf.set_key_value("spiral_vase", new ConfigOptionBool(false)); } apply(config, &new_conf); - if (cb_value_change) + if (cb_value_change) { cb_value_change("fill_density", fill_density); + if (!support) + cb_value_change("support_material", false); + } } if (config->opt_bool("wipe_tower") && config->opt_bool("support_material") && diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index f4497b997..52df39fd5 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -33,6 +33,7 @@ #include "GUI_App.hpp" #include "GUI_Utils.hpp" #include "GUI_ObjectManipulation.hpp" +#include "Field.hpp" #include "DesktopIntegrationDialog.hpp" #include "slic3r/Config/Snapshot.hpp" #include "slic3r/Utils/PresetUpdater.hpp" @@ -1383,20 +1384,39 @@ void PageBedShape::apply_custom_config(DynamicPrintConfig &config) config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model)); } +static void focus_event(wxFocusEvent& e, wxTextCtrl* ctrl, double def_value) +{ + e.Skip(); + wxString str = ctrl->GetValue(); + // Replace the first occurence of comma in decimal number. + bool was_replace = str.Replace(",", ".", false) > 0; + double val = 0.0; + if (!str.ToCDouble(&val)) { + if (val == 0.0) + val = def_value; + ctrl->SetValue(double_to_string(val)); + show_error(nullptr, _L("Invalid numeric input.")); + ctrl->SetFocus(); + } + else if (was_replace) + ctrl->SetValue(double_to_string(val)); +} + PageDiameters::PageDiameters(ConfigWizard *parent) : ConfigWizardPage(parent, _L("Filament and Nozzle Diameters"), _L("Print Diameters"), 1) - , spin_nozzle(new wxSpinCtrlDouble(this, wxID_ANY)) - , spin_filam(new wxSpinCtrlDouble(this, wxID_ANY)) + , diam_nozzle(new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord))) + , diam_filam (new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(Field::def_width_thinner() * wxGetApp().em_unit(), wxDefaultCoord))) { - spin_nozzle->SetDigits(2); - spin_nozzle->SetIncrement(0.1); auto *default_nozzle = print_config_def.get("nozzle_diameter")->get_default_value(); - spin_nozzle->SetValue(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5); + wxString value = double_to_string(default_nozzle != nullptr && default_nozzle->size() > 0 ? default_nozzle->get_at(0) : 0.5); + diam_nozzle->SetValue(value); - spin_filam->SetDigits(2); - spin_filam->SetIncrement(0.25); auto *default_filam = print_config_def.get("filament_diameter")->get_default_value(); - spin_filam->SetValue(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0); + value = double_to_string(default_filam != nullptr && default_filam->size() > 0 ? default_filam->get_at(0) : 3.0); + diam_filam->SetValue(value); + + diam_nozzle->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_nozzle, 0.5); }, diam_nozzle->GetId()); + diam_filam ->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& e) { focus_event(e, diam_filam , 3.0); }, diam_filam->GetId()); append_text(_L("Enter the diameter of your printer's hot end nozzle.")); @@ -1405,7 +1425,7 @@ PageDiameters::PageDiameters(ConfigWizard *parent) auto *unit_nozzle = new wxStaticText(this, wxID_ANY, _L("mm")); sizer_nozzle->AddGrowableCol(0, 1); sizer_nozzle->Add(text_nozzle, 0, wxALIGN_CENTRE_VERTICAL); - sizer_nozzle->Add(spin_nozzle); + sizer_nozzle->Add(diam_nozzle); sizer_nozzle->Add(unit_nozzle, 0, wxALIGN_CENTRE_VERTICAL); append(sizer_nozzle); @@ -1419,16 +1439,21 @@ PageDiameters::PageDiameters(ConfigWizard *parent) auto *unit_filam = new wxStaticText(this, wxID_ANY, _L("mm")); sizer_filam->AddGrowableCol(0, 1); sizer_filam->Add(text_filam, 0, wxALIGN_CENTRE_VERTICAL); - sizer_filam->Add(spin_filam); + sizer_filam->Add(diam_filam); sizer_filam->Add(unit_filam, 0, wxALIGN_CENTRE_VERTICAL); append(sizer_filam); } void PageDiameters::apply_custom_config(DynamicPrintConfig &config) { - auto *opt_nozzle = new ConfigOptionFloats(1, spin_nozzle->GetValue()); + double val = 0.0; + diam_nozzle->GetValue().ToCDouble(&val); + auto *opt_nozzle = new ConfigOptionFloats(1, val); config.set_key_value("nozzle_diameter", opt_nozzle); - auto *opt_filam = new ConfigOptionFloats(1, spin_filam->GetValue()); + + val = 0.0; + diam_filam->GetValue().ToCDouble(&val); + auto * opt_filam = new ConfigOptionFloats(1, val); config.set_key_value("filament_diameter", opt_filam); auto set_extrusion_width = [&config, opt_nozzle](const char *key, double dmr) { diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index 4e3f1538e..ea39e04ab 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -451,8 +451,8 @@ struct PageBedShape: ConfigWizardPage struct PageDiameters: ConfigWizardPage { - wxSpinCtrlDouble *spin_nozzle; - wxSpinCtrlDouble *spin_filam; + wxTextCtrl *diam_nozzle; + wxTextCtrl *diam_filam; PageDiameters(ConfigWizard *parent); virtual void apply_custom_config(DynamicPrintConfig &config); diff --git a/src/slic3r/GUI/DesktopIntegrationDialog.cpp b/src/slic3r/GUI/DesktopIntegrationDialog.cpp index 8ac134f5f..a2f7c8933 100644 --- a/src/slic3r/GUI/DesktopIntegrationDialog.cpp +++ b/src/slic3r/GUI/DesktopIntegrationDialog.cpp @@ -8,6 +8,12 @@ #include "libslic3r/Utils.hpp" #include "libslic3r/Platform.hpp" +#include +#include + +#include +#include + namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 84a499d6f..6d41e3c82 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -254,7 +254,7 @@ void Control::SetMaxValue(const int max_value) void Control::SetSliderValues(const std::vector& values) { m_values = values; - m_ruler.count = std::count(m_values.begin(), m_values.end(), m_values.front()); + m_ruler.init(m_values); } void Control::draw_scroll_line(wxDC& dc, const int lower_pos, const int higher_pos) @@ -326,10 +326,10 @@ double Control::get_double_value(const SelectedSlider& selection) return m_values[selection == ssLower ? m_lower_value : m_higher_value]; } -int Control::get_tick_from_value(double value) +int Control::get_tick_from_value(double value, bool force_lower_bound/* = false*/) { std::vector::iterator it; - if (m_is_wipe_tower) + if (m_is_wipe_tower && !force_lower_bound) it = std::find_if(m_values.begin(), m_values.end(), [value](const double & val) { return fabs(value - val) <= epsilon(); }); else @@ -575,7 +575,10 @@ void Control::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_ else is_horizontal() ? y_draw = pt_beg.y - m_tick_icon_dim-2 : x_draw = pt_end.x + 3; - dc.DrawBitmap(*icon, x_draw, y_draw); + if (m_draw_mode == dmSequentialFffPrint) + dc.DrawBitmap(create_scaled_bitmap("colorchange_add", nullptr, 16, true), x_draw, y_draw); + else + dc.DrawBitmap(*icon, x_draw, y_draw); //update rect of the tick action icon m_rect_tick_action = wxRect(x_draw, y_draw, m_tick_icon_dim, m_tick_icon_dim); @@ -591,7 +594,7 @@ void Control::draw_info_line_with_icon(wxDC& dc, const wxPoint& pos, const Selec dc.DrawLine(pt_beg, pt_end); //draw action icon - if (m_draw_mode == dmRegular) + if (m_draw_mode == dmRegular || m_draw_mode == dmSequentialFffPrint) draw_action_icon(dc, pt_beg, pt_end); } } @@ -1020,8 +1023,23 @@ void Control::draw_colored_band(wxDC& dc) } } +void Control::Ruler::init(const std::vector& values) +{ + max_values.clear(); + max_values.reserve(std::count(values.begin(), values.end(), values.front())); + + auto it = std::find(values.begin() + 1, values.end(), values.front()); + while (it != values.end()) { + max_values.push_back(*(it - 1)); + it = std::find(it + 1, values.end(), values.front()); + } + max_values.push_back(*(it - 1)); +} + void Control::Ruler::update(wxWindow* win, const std::vector& values, double scroll_step) { + if (values.empty()) + return; int DPI = GUI::get_dpi_for_window(win); int pixels_per_sm = lround((double)(DPI) * 5.0/25.4); @@ -1032,7 +1050,7 @@ void Control::Ruler::update(wxWindow* win, const std::vector& values, do int pow = -2; int step = 0; - auto end_it = count == 1 ? values.end() : values.begin() + lround(values.size() / count); + auto end_it = std::find(values.begin() + 1, values.end(), values.front()); while (pow < 3) { for (int istep : {1, 2, 5}) { @@ -1066,6 +1084,8 @@ void Control::Ruler::update(wxWindow* win, const std::vector& values, do void Control::draw_ruler(wxDC& dc) { + if (m_values.empty()) + return; m_ruler.update(this->GetParent(), m_values, get_scroll_step()); int height, width; @@ -1096,7 +1116,7 @@ void Control::draw_ruler(wxDC& dc) double short_tick = std::nan(""); int tick = 0; double value = 0.0; - int sequence = 0; + size_t sequence = 0; int prev_y_pos = -1; wxCoord label_height = dc.GetMultiLineTextExtent("0").y - 2; @@ -1104,7 +1124,7 @@ void Control::draw_ruler(wxDC& dc) while (tick <= m_max_value) { value += m_ruler.long_step; - if (value > m_values.back() && sequence < m_ruler.count) { + if (value > m_ruler.max_values[sequence] && sequence < m_ruler.count()) { value = m_ruler.long_step; for (; tick < values_size; tick++) if (m_values[tick] < value) @@ -1137,7 +1157,7 @@ void Control::draw_ruler(wxDC& dc) draw_short_ticks(dc, short_tick, tick); - if (value == m_values.back() && sequence < m_ruler.count) { + if (value == m_ruler.max_values[sequence] && sequence < m_ruler.count()) { value = 0.0; sequence++; tick++; @@ -1377,6 +1397,10 @@ wxString Control::get_tooltip(int tick/*=-1*/) if (tick_code_it == m_ticks.ticks.end() && m_focus == fiActionIcon) // tick doesn't exist { + if (m_draw_mode == dmSequentialFffPrint) + return _L("The sequential print is on.\n" + "It's impossible to apply any custom G-code for objects printing sequentually.\n"); + // Show mode as a first string of tooltop tooltip = " " + _L("Print mode") + ": "; tooltip += (m_mode == SingleExtruder ? SingleExtruderMode : @@ -2388,7 +2412,7 @@ void Control::edit_extruder_sequence() extruder = 0; if (m_extruders_sequence.is_mm_intervals) { value += m_extruders_sequence.interval_by_mm; - tick = get_tick_from_value(value); + tick = get_tick_from_value(value, true); if (tick < 0) break; } diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index 439b03a50..c1766f83d 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -322,7 +322,7 @@ private: wxSize get_size() const; void get_size(int* w, int* h) const; double get_double_value(const SelectedSlider& selection); - int get_tick_from_value(double value); + int get_tick_from_value(double value, bool force_lower_bound = false); wxString get_tooltip(int tick = -1); int get_edited_tick_for_position(wxPoint pos, Type type = ColorChange); @@ -424,10 +424,13 @@ private: struct Ruler { double long_step; double short_step; - int count { 1 }; // > 1 for sequential print + std::vector max_values;// max value for each object/instance in sequence print + // > 1 for sequential print + void init(const std::vector& values); void update(wxWindow* win, const std::vector& values, double scroll_step); bool is_ok() { return long_step > 0 && short_step > 0; } + size_t count() { return max_values.size(); } } m_ruler; }; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index ca1617bc2..ce4c18a53 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -17,6 +17,7 @@ #include "GLCanvas3D.hpp" #include "GLToolbar.hpp" #include "GUI_Preview.hpp" +#include "GUI_ObjectManipulation.hpp" #include #include @@ -83,7 +84,6 @@ static float round_to_nearest(float value, unsigned int decimals) return res; } -#if ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::VBuffer::reset() { // release gpu memory @@ -94,38 +94,16 @@ void GCodeViewer::VBuffer::reset() sizes.clear(); count = 0; } -#else -void GCodeViewer::VBuffer::reset() -{ - // release gpu memory - if (id > 0) { - glsafe(::glDeleteBuffers(1, &id)); - id = 0; - } - - count = 0; -} -#endif // ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::IBuffer::reset() { -#if ENABLE_SPLITTED_VERTEX_BUFFER // release gpu memory if (ibo > 0) { glsafe(::glDeleteBuffers(1, &ibo)); ibo = 0; } -#else - // release gpu memory - if (id > 0) { - glsafe(::glDeleteBuffers(1, &id)); - id = 0; - } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER -#if ENABLE_SPLITTED_VERTEX_BUFFER vbo = 0; -#endif // ENABLE_SPLITTED_VERTEX_BUFFER count = 0; } @@ -148,17 +126,10 @@ bool GCodeViewer::Path::matches(const GCodeProcessor::MoveVertex& move) const #endif // ENABLE_SEAMS_VISUALIZATION case EMoveType::Extrude: { // use rounding to reduce the number of generated paths -#if ENABLE_SPLITTED_VERTEX_BUFFER return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && move.position[2] <= sub_paths.front().first.position[2] && feedrate == move.feedrate && fan_speed == move.fan_speed && height == round_to_nearest(move.height, 2) && width == round_to_nearest(move.width, 2) && matches_percent(volumetric_rate, move.volumetric_rate(), 0.05f); -#else - return type == move.type && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id && role == move.extrusion_role && - move.position[2] <= first.position[2] && feedrate == move.feedrate && fan_speed == move.fan_speed && - height == round_to_nearest(move.height, 2) && width == round_to_nearest(move.width, 2) && - matches_percent(volumetric_rate, move.volumetric_rate(), 0.05f); -#endif // ENABLE_SPLITTED_VERTEX_BUFFER } case EMoveType::Travel: { return type == move.type && feedrate == move.feedrate && extruder_id == move.extruder_id && cp_color_id == move.cp_color_id; @@ -185,17 +156,10 @@ void GCodeViewer::TBuffer::add_path(const GCodeProcessor::MoveVertex& move, unsi { Path::Endpoint endpoint = { b_id, i_id, s_id, move.position }; // use rounding to reduce the number of generated paths -#if ENABLE_SPLITTED_VERTEX_BUFFER paths.push_back({ move.type, move.extrusion_role, move.delta_extruder, round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), move.feedrate, move.fan_speed, move.temperature, move.volumetric_rate(), move.extruder_id, move.cp_color_id, { { endpoint, endpoint } } }); -#else - paths.push_back({ move.type, move.extrusion_role, endpoint, endpoint, move.delta_extruder, - round_to_nearest(move.height, 2), round_to_nearest(move.width, 2), - move.feedrate, move.fan_speed, move.temperature, - move.volumetric_rate(), move.extruder_id, move.cp_color_id }); -#endif // ENABLE_SPLITTED_VERTEX_BUFFER } GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) const @@ -221,7 +185,6 @@ GCodeViewer::Color GCodeViewer::Extrusions::Range::get_color_at(float value) con return ret; } -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS GCodeViewer::SequentialRangeCap::~SequentialRangeCap() { if (ibo > 0) glsafe(::glDeleteBuffers(1, &ibo)); @@ -236,7 +199,6 @@ void GCodeViewer::SequentialRangeCap::reset() { vbo = 0; color = { 0.0f, 0.0f, 0.0f }; } -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS void GCodeViewer::SequentialView::Marker::init() { @@ -687,13 +649,13 @@ void GCodeViewer::load(const GCodeProcessor::Result& gcode_result, const Print& wxGetApp().plater()->set_bed_shape(bed_shape, texture, model, gcode_result.bed_shape.empty()); } - m_time_statistics = gcode_result.time_statistics; + m_print_statistics = gcode_result.print_statistics; - if (m_time_estimate_mode != PrintEstimatedTimeStatistics::ETimeMode::Normal) { - float time = m_time_statistics.modes[static_cast(m_time_estimate_mode)].time; + if (m_time_estimate_mode != PrintEstimatedStatistics::ETimeMode::Normal) { + float time = m_print_statistics.modes[static_cast(m_time_estimate_mode)].time; if (time == 0.0f || - short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_time_statistics.modes[static_cast(PrintEstimatedTimeStatistics::ETimeMode::Normal)].time))) - m_time_estimate_mode = PrintEstimatedTimeStatistics::ETimeMode::Normal; + short_time(get_time_dhms(time)) == short_time(get_time_dhms(m_print_statistics.modes[static_cast(PrintEstimatedStatistics::ETimeMode::Normal)].time))) + m_time_estimate_mode = PrintEstimatedStatistics::ETimeMode::Normal; } } @@ -788,7 +750,7 @@ void GCodeViewer::reset() m_layers.reset(); m_layers_z_range = { 0, 0 }; m_roles = std::vector(); - m_time_statistics.reset(); + m_print_statistics.reset(); #if ENABLE_GCODE_WINDOW m_sequential_view.gcode_window.reset(); #endif // ENABLE_GCODE_WINDOW @@ -880,12 +842,10 @@ void GCodeViewer::render() const #endif // ENABLE_GCODE_VIEWER_STATISTICS } -#if ENABLE_SPLITTED_VERTEX_BUFFER bool GCodeViewer::can_export_toolpaths() const { return has_data() && m_buffers[buffer_id(EMoveType::Extrude)].render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle; } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::update_sequential_view_current(unsigned int first, unsigned int last) { @@ -893,12 +853,8 @@ void GCodeViewer::update_sequential_view_current(unsigned int first, unsigned in for (const TBuffer& buffer : m_buffers) { if (buffer.visible) { for (const Path& path : buffer.paths) { -#if ENABLE_SPLITTED_VERTEX_BUFFER if (path.sub_paths.front().first.s_id <= id && id <= path.sub_paths.back().last.s_id) -#else - if (path.first.s_id <= id && id <= path.last.s_id) -#endif // ENABLE_SPLITTED_VERTEX_BUFFER - return true; + return true; } } } @@ -1020,20 +976,16 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const if (!t_buffer.has_data()) return; -#if ENABLE_SPLITTED_VERTEX_BUFFER if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Triangle) return; -#endif // ENABLE_SPLITTED_VERTEX_BUFFER // collect color information to generate materials std::vector colors; for (const RenderPath& path : t_buffer.render_paths) { colors.push_back(path.color); } -#if ENABLE_SPLITTED_VERTEX_BUFFER std::sort(colors.begin(), colors.end()); colors.erase(std::unique(colors.begin(), colors.end()), colors.end()); -#endif // ENABLE_SPLITTED_VERTEX_BUFFER // save materials file boost::filesystem::path mat_filename(filename); @@ -1068,7 +1020,6 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const fprintf(fp, "# Generated by %s-%s based on Slic3r\n", SLIC3R_APP_NAME, SLIC3R_VERSION); fprintf(fp, "\nmtllib ./%s\n", mat_filename.filename().string().c_str()); -#if ENABLE_SPLITTED_VERTEX_BUFFER const size_t floats_per_vertex = t_buffer.vertices.vertex_size_floats(); std::vector out_vertices; @@ -1154,253 +1105,10 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const } ++i; } -#else - // get vertices data from vertex buffer on gpu - size_t floats_per_vertex = t_buffer.vertices.vertex_size_floats(); - VertexBuffer vertices = VertexBuffer(t_buffer.vertices.count * floats_per_vertex); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, t_buffer.vertices.id)); - glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, 0, t_buffer.vertices.data_size_bytes(), vertices.data())); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - - // get indices data from index buffer on gpu - MultiIndexBuffer indices; - for (size_t i = 0; i < t_buffer.indices.size(); ++i) { - indices.push_back(IndexBuffer(t_buffer.indices[i].count)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, t_buffer.indices[i].id)); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, static_cast(indices.back().size() * sizeof(unsigned int)), indices.back().data())); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - - auto get_vertex = [&vertices, floats_per_vertex](unsigned int id) { - // extract vertex from vector of floats - unsigned int base_id = id * floats_per_vertex; - return Vec3f(vertices[base_id + 0], vertices[base_id + 1], vertices[base_id + 2]); - }; - - struct Segment - { - Vec3f v1; - Vec3f v2; - Vec3f dir; - Vec3f right; - Vec3f up; - Vec3f rl_displacement; - Vec3f tb_displacement; - float length; - }; - - auto generate_segment = [get_vertex](unsigned int start_id, unsigned int end_id, float half_width, float half_height) { - auto local_basis = [](const Vec3f& dir) { - // calculate local basis (dir, right, up) on given segment - std::array ret; - ret[0] = dir.normalized(); - if (std::abs(ret[0][2]) < EPSILON) { - // segment parallel to XY plane - ret[1] = { ret[0][1], -ret[0][0], 0.0f }; - ret[2] = Vec3f::UnitZ(); - } - else if (std::abs(std::abs(ret[0].dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) { - // segment parallel to Z axis - ret[1] = Vec3f::UnitX(); - ret[2] = Vec3f::UnitY(); - } - else { - ret[0] = dir.normalized(); - ret[1] = ret[0].cross(Vec3f::UnitZ()).normalized(); - ret[2] = ret[1].cross(ret[0]); - } - return ret; - }; - - Vec3f v1 = get_vertex(start_id) - half_height * Vec3f::UnitZ(); - Vec3f v2 = get_vertex(end_id) - half_height * Vec3f::UnitZ(); - float length = (v2 - v1).norm(); - const auto&& [dir, right, up] = local_basis(v2 - v1); - return Segment({ v1, v2, dir, right, up, half_width * right, half_height * up, length }); - }; - - size_t out_vertices_count = 0; - unsigned int indices_per_segment = t_buffer.indices_per_segment(); - unsigned int start_vertex_offset = t_buffer.start_segment_vertex_offset(); - unsigned int end_vertex_offset = t_buffer.end_segment_vertex_offset(); - - size_t i = 0; - for (const RenderPath& render_path : t_buffer.render_paths) { - // get paths segments from buffer paths - const IndexBuffer& ibuffer = indices[render_path.ibuffer_id]; - const Path& path = t_buffer.paths[render_path.path_id]; - - float half_width = 0.5f * path.width; - // clamp height to avoid artifacts due to z-fighting when importing the obj file into blender and similar - float half_height = std::max(0.5f * path.height, 0.005f); - - // generates vertices/normals/triangles - std::vector out_vertices; - std::vector out_normals; - using Triangle = std::array; - std::vector out_triangles; - for (size_t j = 0; j < render_path.offsets.size(); ++j) { - unsigned int start = static_cast(render_path.offsets[j] / sizeof(unsigned int)); - unsigned int end = start + render_path.sizes[j]; - - for (size_t k = start; k < end; k += static_cast(indices_per_segment)) { - Segment curr = generate_segment(ibuffer[k + start_vertex_offset], ibuffer[k + end_vertex_offset], half_width, half_height); - if (k == start) { - // starting endpoint vertices/normals - out_vertices.push_back(curr.v1 + curr.rl_displacement); out_normals.push_back(curr.right); // right - out_vertices.push_back(curr.v1 + curr.tb_displacement); out_normals.push_back(curr.up); // top - out_vertices.push_back(curr.v1 - curr.rl_displacement); out_normals.push_back(-curr.right); // left - out_vertices.push_back(curr.v1 - curr.tb_displacement); out_normals.push_back(-curr.up); // bottom - out_vertices_count += 4; - - // starting cap triangles - size_t base_id = out_vertices_count - 4 + 1; - out_triangles.push_back({ base_id + 0, base_id + 1, base_id + 2 }); - out_triangles.push_back({ base_id + 0, base_id + 2, base_id + 3 }); - } - else { - // for the endpoint shared by the current and the previous segments - // we keep the top and bottom vertices of the previous vertices - // and add new left/right vertices for the current segment - out_vertices.push_back(curr.v1 + curr.rl_displacement); out_normals.push_back(curr.right); // right - out_vertices.push_back(curr.v1 - curr.rl_displacement); out_normals.push_back(-curr.right); // left - out_vertices_count += 2; - - size_t first_vertex_id = k - static_cast(indices_per_segment); - Segment prev = generate_segment(ibuffer[first_vertex_id + start_vertex_offset], ibuffer[first_vertex_id + end_vertex_offset], half_width, half_height); - float disp = 0.0f; - float cos_dir = prev.dir.dot(curr.dir); - if (cos_dir > -0.9998477f) { - // if the angle between adjacent segments is smaller than 179 degrees - Vec3f med_dir = (prev.dir + curr.dir).normalized(); - disp = half_width * ::tan(::acos(std::clamp(curr.dir.dot(med_dir), -1.0f, 1.0f))); - } - - Vec3f disp_vec = disp * prev.dir; - - bool is_right_turn = prev.up.dot(prev.dir.cross(curr.dir)) <= 0.0f; - if (cos_dir < 0.7071068f) { - // if the angle between two consecutive segments is greater than 45 degrees - // we add a cap in the outside corner - // and displace the vertices in the inside corner to the same position, if possible - if (is_right_turn) { - // corner cap triangles (left) - size_t base_id = out_vertices_count - 6 + 1; - out_triangles.push_back({ base_id + 5, base_id + 2, base_id + 1 }); - out_triangles.push_back({ base_id + 5, base_id + 3, base_id + 2 }); - - // update right vertices - if (disp > 0.0f && disp < prev.length && disp < curr.length) { - base_id = out_vertices.size() - 6; - out_vertices[base_id + 0] -= disp_vec; - out_vertices[base_id + 4] = out_vertices[base_id + 0]; - } - } - else { - // corner cap triangles (right) - size_t base_id = out_vertices_count - 6 + 1; - out_triangles.push_back({ base_id + 0, base_id + 4, base_id + 1 }); - out_triangles.push_back({ base_id + 0, base_id + 3, base_id + 4 }); - - // update left vertices - if (disp > 0.0f && disp < prev.length && disp < curr.length) { - base_id = out_vertices.size() - 6; - out_vertices[base_id + 2] -= disp_vec; - out_vertices[base_id + 5] = out_vertices[base_id + 2]; - } - } - } - else { - // if the angle between two consecutive segments is lesser than 45 degrees - // displace the vertices to the same position - if (is_right_turn) { - size_t base_id = out_vertices.size() - 6; - // right - out_vertices[base_id + 0] -= disp_vec; - out_vertices[base_id + 4] = out_vertices[base_id + 0]; - // left - out_vertices[base_id + 2] += disp_vec; - out_vertices[base_id + 5] = out_vertices[base_id + 2]; - } - else { - size_t base_id = out_vertices.size() - 6; - // right - out_vertices[base_id + 0] += disp_vec; - out_vertices[base_id + 4] = out_vertices[base_id + 0]; - // left - out_vertices[base_id + 2] -= disp_vec; - out_vertices[base_id + 5] = out_vertices[base_id + 2]; - } - } - } - - // current second endpoint vertices/normals - out_vertices.push_back(curr.v2 + curr.rl_displacement); out_normals.push_back(curr.right); // right - out_vertices.push_back(curr.v2 + curr.tb_displacement); out_normals.push_back(curr.up); // top - out_vertices.push_back(curr.v2 - curr.rl_displacement); out_normals.push_back(-curr.right); // left - out_vertices.push_back(curr.v2 - curr.tb_displacement); out_normals.push_back(-curr.up); // bottom - out_vertices_count += 4; - - // sides triangles - if (k == start) { - size_t base_id = out_vertices_count - 8 + 1; - out_triangles.push_back({ base_id + 0, base_id + 4, base_id + 5 }); - out_triangles.push_back({ base_id + 0, base_id + 5, base_id + 1 }); - out_triangles.push_back({ base_id + 1, base_id + 5, base_id + 6 }); - out_triangles.push_back({ base_id + 1, base_id + 6, base_id + 2 }); - out_triangles.push_back({ base_id + 2, base_id + 6, base_id + 7 }); - out_triangles.push_back({ base_id + 2, base_id + 7, base_id + 3 }); - out_triangles.push_back({ base_id + 3, base_id + 7, base_id + 4 }); - out_triangles.push_back({ base_id + 3, base_id + 4, base_id + 0 }); - } - else { - size_t base_id = out_vertices_count - 10 + 1; - out_triangles.push_back({ base_id + 4, base_id + 6, base_id + 7 }); - out_triangles.push_back({ base_id + 4, base_id + 7, base_id + 1 }); - out_triangles.push_back({ base_id + 1, base_id + 7, base_id + 8 }); - out_triangles.push_back({ base_id + 1, base_id + 8, base_id + 5 }); - out_triangles.push_back({ base_id + 5, base_id + 8, base_id + 9 }); - out_triangles.push_back({ base_id + 5, base_id + 9, base_id + 3 }); - out_triangles.push_back({ base_id + 3, base_id + 9, base_id + 6 }); - out_triangles.push_back({ base_id + 3, base_id + 6, base_id + 4 }); - } - - if (k + 2 == end) { - // ending cap triangles - size_t base_id = out_vertices_count - 4 + 1; - out_triangles.push_back({ base_id + 0, base_id + 2, base_id + 1 }); - out_triangles.push_back({ base_id + 0, base_id + 3, base_id + 2 }); - } - } - } - - // save to file - fprintf(fp, "\n# vertices path %zu\n", i + 1); - for (const Vec3f& v : out_vertices) { - fprintf(fp, "v %g %g %g\n", v[0], v[1], v[2]); - } - - fprintf(fp, "\n# normals path %zu\n", i + 1); - for (const Vec3f& n : out_normals) { - fprintf(fp, "vn %g %g %g\n", n[0], n[1], n[2]); - } - - fprintf(fp, "\n# material path %zu\n", i + 1); - fprintf(fp, "usemtl material_%zu\n", i + 1); - - fprintf(fp, "\n# triangles path %zu\n", i + 1); - for (const Triangle& t : out_triangles) { - fprintf(fp, "f %zu//%zu %zu//%zu %zu//%zu\n", t[0], t[0], t[1], t[1], t[2], t[2]); - } - - ++ i; - } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER fclose(fp); } -#if ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) { // max index buffer size, in bytes @@ -1531,13 +1239,8 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) last_path.sub_paths.back().last = { vbuffer_id, vertices.size(), move_id, curr.position }; }; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS auto add_indices_as_solid = [&](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, const GCodeProcessor::MoveVertex* next, TBuffer& buffer, size_t& vbuffer_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { -#else - auto add_indices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - size_t& vbuffer_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS static Vec3f prev_dir; static Vec3f prev_up; static float sq_prev_length; @@ -1550,7 +1253,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) store_triangle(indices, id, id, id); store_triangle(indices, id, id, id); }; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS auto convert_vertices_offset = [](size_t vbuffer_size, const std::array& v_offsets) { std::array ret = { static_cast(static_cast(vbuffer_size) + v_offsets[0]), @@ -1582,32 +1284,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) store_triangle(indices, v_offsets[4], v_offsets[6], v_offsets[7]); store_triangle(indices, v_offsets[4], v_offsets[5], v_offsets[6]); }; -#else - auto append_stem_triangles = [&](IndexBuffer& indices, size_t vbuffer_size, const std::array& v_offsets) { - std::array v_ids; - for (size_t i = 0; i < v_ids.size(); ++i) { - v_ids[i] = static_cast(static_cast(vbuffer_size) + v_offsets[i]); - } - - // triangles starting cap - store_triangle(indices, v_ids[0], v_ids[2], v_ids[1]); - store_triangle(indices, v_ids[0], v_ids[3], v_ids[2]); - - // triangles sides - store_triangle(indices, v_ids[0], v_ids[1], v_ids[4]); - store_triangle(indices, v_ids[1], v_ids[5], v_ids[4]); - store_triangle(indices, v_ids[1], v_ids[2], v_ids[5]); - store_triangle(indices, v_ids[2], v_ids[6], v_ids[5]); - store_triangle(indices, v_ids[2], v_ids[3], v_ids[6]); - store_triangle(indices, v_ids[3], v_ids[7], v_ids[6]); - store_triangle(indices, v_ids[3], v_ids[0], v_ids[7]); - store_triangle(indices, v_ids[0], v_ids[4], v_ids[7]); - - // triangles ending cap - store_triangle(indices, v_ids[4], v_ids[6], v_ids[7]); - store_triangle(indices, v_ids[4], v_ids[5], v_ids[6]); - }; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1); @@ -1621,30 +1297,20 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) Vec3f up = right.cross(dir); float sq_length = (curr.position - prev.position).squaredNorm(); -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS const std::array first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { 0, 1, 2, 3, 4, 5, 6, 7 }); const std::array non_first_seg_v_offsets = convert_vertices_offset(vbuffer_size, { -4, 0, -2, 1, 2, 3, 4, 5 }); bool is_first_segment = (last_path.vertices_count() == 1); if (is_first_segment || vbuffer_size == 0) { -#else - if (last_path.vertices_count() == 1 || vbuffer_size == 0) { -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS // 1st segment or restart into a new vertex buffer // =============================================== -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS if (is_first_segment) // starting cap triangles append_starting_cap_triangles(indices, first_seg_v_offsets); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS // dummy triangles outer corner cap append_dummy_cap(indices, vbuffer_size); // stem triangles -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS append_stem_triangles(indices, first_seg_v_offsets); -#else - append_stem_triangles(indices, vbuffer_size, { 0, 1, 2, 3, 4, 5, 6, 7 }); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS vbuffer_size += 8; } @@ -1698,20 +1364,14 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) } // stem triangles -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS append_stem_triangles(indices, non_first_seg_v_offsets); -#else - append_stem_triangles(indices, vbuffer_size, { -4, 0, -2, 1, 2, 3, 4, 5 }); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS vbuffer_size += 6; } -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS if (next != nullptr && (curr.type != next->type || !last_path.matches(*next))) // ending cap triangles append_ending_cap_triangles(indices, is_first_segment ? first_seg_v_offsets : non_first_seg_v_offsets); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS last_path.sub_paths.back().last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; prev_dir = dir; @@ -2051,11 +1711,9 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) continue; const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS const GCodeProcessor::MoveVertex* next = nullptr; if (i < m_moves_count - 1) next = &gcode_result.moves[i + 1]; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS ++progress_count; if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { @@ -2079,11 +1737,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) // if adding the indices for the current segment exceeds the threshold size of the current index buffer // create another index buffer -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS if (i_multibuffer.back().size() * sizeof(IBufferType) >= IBUFFER_THRESHOLD_BYTES - t_buffer.max_indices_per_segment_size_bytes()) { -#else - if (i_multibuffer.back().size() * sizeof(IBufferType) >= IBUFFER_THRESHOLD_BYTES - t_buffer.indices_per_segment_size_bytes()) { -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS i_multibuffer.push_back(IndexBuffer()); vbo_index_list.push_back(t_buffer.vertices.vbos[curr_vertex_buffer.first]); if (t_buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point) { @@ -2122,11 +1776,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) break; } case TBuffer::ERenderPrimitiveType::Triangle: { -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS add_indices_as_solid(prev, curr, next, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, i); -#else - add_indices_as_solid(prev, curr, t_buffer, curr_vertex_buffer.second, static_cast(i_multibuffer.size()) - 1, i_buffer, i); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS break; } } @@ -2178,7 +1828,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) auto update_segments_count = [&](EMoveType type, int64_t& count) { unsigned int id = buffer_id(type); const MultiIndexBuffer& buffers = indices[id]; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS int64_t indices_count = 0; for (const IndexBuffer& buffer : buffers) { indices_count += buffer.size(); @@ -2188,11 +1837,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) indices_count -= static_cast(12 * t_buffer.paths.size()); // remove the starting + ending caps = 4 triangles count += indices_count / t_buffer.indices_per_segment(); -#else - for (const IndexBuffer& buffer : buffers) { - count += buffer.size() / m_buffers[id].indices_per_segment(); - } -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS }; update_segments_count(EMoveType::Travel, m_statistics.travel_segments_count); @@ -2264,667 +1908,6 @@ void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) if (progress_dialog != nullptr) progress_dialog->Destroy(); } -#else -void GCodeViewer::load_toolpaths(const GCodeProcessor::Result& gcode_result) -{ -#if ENABLE_GCODE_VIEWER_STATISTICS - auto start_time = std::chrono::high_resolution_clock::now(); - m_statistics.results_size = SLIC3R_STDVEC_MEMSIZE(gcode_result.moves, GCodeProcessor::MoveVertex); - m_statistics.results_time = gcode_result.time; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - // vertices data - m_moves_count = gcode_result.moves.size(); - if (m_moves_count == 0) - return; - - unsigned int progress_count = 0; - static const unsigned int progress_threshold = 1000; - wxProgressDialog* progress_dialog = wxGetApp().is_gcode_viewer() ? - new wxProgressDialog(_L("Generating toolpaths"), "...", - 100, wxGetApp().plater(), wxPD_AUTO_HIDE | wxPD_APP_MODAL) : nullptr; - - m_extruders_count = gcode_result.extruders_count; - - for (size_t i = 0; i < m_moves_count; ++i) { - const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; - if (wxGetApp().is_gcode_viewer()) - // for the gcode viewer we need all moves to correctly size the printbed - m_paths_bounding_box.merge(move.position.cast()); - else { - if (move.type == EMoveType::Extrude && move.width != 0.0f && move.height != 0.0f) - m_paths_bounding_box.merge(move.position.cast()); - } - } - - // max bounding box (account for 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()); - - auto log_memory_usage = [this](const std::string& label, const std::vector& vertices, const std::vector& indices) { - int64_t vertices_size = 0; - for (size_t i = 0; i < vertices.size(); ++i) { - vertices_size += SLIC3R_STDVEC_MEMSIZE(vertices[i], float); - } - int64_t indices_size = 0; - for (size_t i = 0; i < indices.size(); ++i) { - for (size_t j = 0; j < indices[i].size(); ++j) { - indices_size += SLIC3R_STDVEC_MEMSIZE(indices[i][j], unsigned int); - } - } - log_memory_used(label, vertices_size + indices_size); - }; - - // format data into the buffers to be rendered as points - auto add_vertices_as_point = [](const GCodeProcessor::MoveVertex& curr, VertexBuffer& vertices) { - vertices.push_back(curr.position[0]); - vertices.push_back(curr.position[1]); - vertices.push_back(curr.position[2]); - }; - auto add_indices_as_point = [](const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { - buffer.add_path(curr, ibuffer_id, indices.size(), move_id); - indices.push_back(static_cast(indices.size())); - }; - - // format data into the buffers to be rendered as lines - auto add_vertices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, - VertexBuffer& vertices) { - // x component of the normal to the current segment (the normal is parallel to the XY plane) - float normal_x = (curr.position - prev.position).normalized()[1]; - - auto add_vertex = [&vertices, normal_x](const GCodeProcessor::MoveVertex& vertex) { - // add position - vertices.push_back(vertex.position[0]); - vertices.push_back(vertex.position[1]); - vertices.push_back(vertex.position[2]); - // add normal x component - vertices.push_back(normal_x); - }; - - // add previous vertex - add_vertex(prev); - // add current vertex - add_vertex(curr); - }; - auto add_indices_as_line = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { - if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - // add starting index - indices.push_back(static_cast(indices.size())); - buffer.add_path(curr, ibuffer_id, indices.size() - 1, move_id - 1); - buffer.paths.back().first.position = prev.position; - } - - Path& last_path = buffer.paths.back(); - if (last_path.first.i_id != last_path.last.i_id) { - // add previous index - indices.push_back(static_cast(indices.size())); - } - - // add current index - indices.push_back(static_cast(indices.size())); - last_path.last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; - }; - - // format data into the buffers to be rendered as solid - auto add_vertices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - VertexBuffer& vertices, size_t move_id) { - static Vec3f prev_dir; - static Vec3f prev_up; - static float prev_length; - auto store_vertex = [](VertexBuffer& vertices, const Vec3f& position, const Vec3f& normal) { - // append position - vertices.push_back(position[0]); - vertices.push_back(position[1]); - vertices.push_back(position[2]); - // append normal - vertices.push_back(normal[0]); - vertices.push_back(normal[1]); - vertices.push_back(normal[2]); - }; - auto extract_position_at = [](const VertexBuffer& vertices, size_t id) { - return Vec3f(vertices[id + 0], vertices[id + 1], vertices[id + 2]); - }; - auto update_position_at = [](VertexBuffer& vertices, size_t id, const Vec3f& position) { - vertices[id + 0] = position[0]; - vertices[id + 1] = position[1]; - vertices[id + 2] = position[2]; - }; - - if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - buffer.add_path(curr, 0, 0, move_id - 1); - buffer.paths.back().first.position = prev.position; - } - - unsigned int starting_vertices_size = static_cast(vertices.size() / buffer.vertices.vertex_size_floats()); - - Vec3f dir = (curr.position - prev.position).normalized(); - Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized(); - Vec3f left = -right; - Vec3f up = right.cross(dir); - Vec3f down = -up; - - Path& last_path = buffer.paths.back(); - - float half_width = 0.5f * last_path.width; - float half_height = 0.5f * last_path.height; - - Vec3f prev_pos = prev.position - half_height * up; - Vec3f curr_pos = curr.position - half_height * up; - - float length = (curr_pos - prev_pos).norm(); - if (last_path.vertices_count() == 1) { - // 1st segment - - // vertices 1st endpoint - store_vertex(vertices, prev_pos + half_height * up, up); - store_vertex(vertices, prev_pos + half_width * right, right); - store_vertex(vertices, prev_pos + half_height * down, down); - store_vertex(vertices, prev_pos + half_width * left, left); - - // vertices 2nd endpoint - store_vertex(vertices, curr_pos + half_height * up, up); - store_vertex(vertices, curr_pos + half_width * right, right); - store_vertex(vertices, curr_pos + half_height * down, down); - store_vertex(vertices, curr_pos + half_width * left, left); - } - else { - // any other segment - float displacement = 0.0f; - float cos_dir = prev_dir.dot(dir); - if (cos_dir > -0.9998477f) { - // if the angle between adjacent segments is smaller than 179 degrees - Vec3f med_dir = (prev_dir + dir).normalized(); - displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); - } - - Vec3f displacement_vec = displacement * prev_dir; - bool can_displace = displacement > 0.0f && displacement < prev_length&& displacement < length; - - size_t prev_right_id = (starting_vertices_size - 3) * buffer.vertices.vertex_size_floats(); - size_t prev_left_id = (starting_vertices_size - 1) * buffer.vertices.vertex_size_floats(); - Vec3f prev_right_pos = extract_position_at(vertices, prev_right_id); - Vec3f prev_left_pos = extract_position_at(vertices, prev_left_id); - - bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; - // whether the angle between adjacent segments is greater than 45 degrees - bool is_sharp = cos_dir < 0.7071068f; - - bool right_displaced = false; - bool left_displaced = false; - - // displace the vertex (inner with respect to the corner) of the previous segment 2nd enpoint, if possible - if (can_displace) { - if (is_right_turn) { - prev_right_pos -= displacement_vec; - update_position_at(vertices, prev_right_id, prev_right_pos); - right_displaced = true; - } - else { - prev_left_pos -= displacement_vec; - update_position_at(vertices, prev_left_id, prev_left_pos); - left_displaced = true; - } - } - - if (!is_sharp) { - // displace the vertex (outer with respect to the corner) of the previous segment 2nd enpoint, if possible - if (can_displace) { - if (is_right_turn) { - prev_left_pos += displacement_vec; - update_position_at(vertices, prev_left_id, prev_left_pos); - left_displaced = true; - } - else { - prev_right_pos += displacement_vec; - update_position_at(vertices, prev_right_id, prev_right_pos); - right_displaced = true; - } - } - - // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint) - // vertices position matches that of the previous segment 2nd endpoint, if displaced - store_vertex(vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right); - store_vertex(vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left); - } - else { - // vertices 1st endpoint (top and bottom are from previous segment 2nd endpoint) - // the inner corner vertex position matches that of the previous segment 2nd endpoint, if displaced - if (is_right_turn) { - store_vertex(vertices, right_displaced ? prev_right_pos : prev_pos + half_width * right, right); - store_vertex(vertices, prev_pos + half_width * left, left); - } - else { - store_vertex(vertices, prev_pos + half_width * right, right); - store_vertex(vertices, left_displaced ? prev_left_pos : prev_pos + half_width * left, left); - } - } - - // vertices 2nd endpoint - store_vertex(vertices, curr_pos + half_height * up, up); - store_vertex(vertices, curr_pos + half_width * right, right); - store_vertex(vertices, curr_pos + half_height * down, down); - store_vertex(vertices, curr_pos + half_width * left, left); - } - - last_path.last = { 0, 0, move_id, curr.position }; - prev_dir = dir; - prev_up = up; - prev_length = length; - }; - auto add_indices_as_solid = [](const GCodeProcessor::MoveVertex& prev, const GCodeProcessor::MoveVertex& curr, TBuffer& buffer, - size_t& buffer_vertices_size, unsigned int ibuffer_id, IndexBuffer& indices, size_t move_id) { - static Vec3f prev_dir; - static Vec3f prev_up; - static float prev_length; - auto store_triangle = [](IndexBuffer& indices, unsigned int i1, unsigned int i2, unsigned int i3) { - indices.push_back(i1); - indices.push_back(i2); - indices.push_back(i3); - }; - auto append_dummy_cap = [store_triangle](IndexBuffer& indices, unsigned int id) { - store_triangle(indices, id, id, id); - store_triangle(indices, id, id, id); - }; - - if (prev.type != curr.type || !buffer.paths.back().matches(curr)) { - buffer.add_path(curr, ibuffer_id, indices.size(), move_id - 1); - buffer.paths.back().first.position = prev.position; - } - - Vec3f dir = (curr.position - prev.position).normalized(); - Vec3f right = (std::abs(std::abs(dir.dot(Vec3f::UnitZ())) - 1.0f) < EPSILON) ? -Vec3f::UnitY() : Vec3f(dir[1], -dir[0], 0.0f).normalized(); - Vec3f up = right.cross(dir); - - Path& last_path = buffer.paths.back(); - - float half_width = 0.5f * last_path.width; - float half_height = 0.5f * last_path.height; - - Vec3f prev_pos = prev.position - half_height * up; - Vec3f curr_pos = curr.position - half_height * up; - - float length = (curr_pos - prev_pos).norm(); - if (last_path.vertices_count() == 1) { - // 1st segment - - // triangles starting cap - store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 2, buffer_vertices_size + 1); - store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 3, buffer_vertices_size + 2); - - // dummy triangles outer corner cap - append_dummy_cap(indices, buffer_vertices_size); - - // triangles sides - store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 1, buffer_vertices_size + 4); - store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size + 5, buffer_vertices_size + 4); - store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size + 2, buffer_vertices_size + 5); - store_triangle(indices, buffer_vertices_size + 2, buffer_vertices_size + 6, buffer_vertices_size + 5); - store_triangle(indices, buffer_vertices_size + 2, buffer_vertices_size + 3, buffer_vertices_size + 6); - store_triangle(indices, buffer_vertices_size + 3, buffer_vertices_size + 7, buffer_vertices_size + 6); - store_triangle(indices, buffer_vertices_size + 3, buffer_vertices_size + 0, buffer_vertices_size + 7); - store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 4, buffer_vertices_size + 7); - - // triangles ending cap - store_triangle(indices, buffer_vertices_size + 4, buffer_vertices_size + 6, buffer_vertices_size + 7); - store_triangle(indices, buffer_vertices_size + 4, buffer_vertices_size + 5, buffer_vertices_size + 6); - - buffer_vertices_size += 8; - } - else { - // any other segment - float displacement = 0.0f; - float cos_dir = prev_dir.dot(dir); - if (cos_dir > -0.9998477f) { - // if the angle between adjacent segments is smaller than 179 degrees - Vec3f med_dir = (prev_dir + dir).normalized(); - displacement = half_width * ::tan(::acos(std::clamp(dir.dot(med_dir), -1.0f, 1.0f))); - } - - Vec3f displacement_vec = displacement * prev_dir; - bool can_displace = displacement > 0.0f && displacement < prev_length && displacement < length; - - bool is_right_turn = prev_up.dot(prev_dir.cross(dir)) <= 0.0f; - // whether the angle between adjacent segments is greater than 45 degrees - bool is_sharp = cos_dir < 0.7071068f; - - bool right_displaced = false; - bool left_displaced = false; - - if (!is_sharp) { - if (can_displace) { - if (is_right_turn) - left_displaced = true; - else - right_displaced = true; - } - } - - // triangles starting cap - store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size - 2, buffer_vertices_size + 0); - store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size + 1, buffer_vertices_size - 2); - - // triangles outer corner cap - if (is_right_turn) { - if (left_displaced) - // dummy triangles - append_dummy_cap(indices, buffer_vertices_size); - else { - store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size + 1, buffer_vertices_size - 1); - store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size - 2, buffer_vertices_size - 1); - } - } - else { - if (right_displaced) - // dummy triangles - append_dummy_cap(indices, buffer_vertices_size); - else { - store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size - 3, buffer_vertices_size + 0); - store_triangle(indices, buffer_vertices_size - 3, buffer_vertices_size - 2, buffer_vertices_size + 0); - } - } - - // triangles sides - store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size + 0, buffer_vertices_size + 2); - store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size + 3, buffer_vertices_size + 2); - store_triangle(indices, buffer_vertices_size + 0, buffer_vertices_size - 2, buffer_vertices_size + 3); - store_triangle(indices, buffer_vertices_size - 2, buffer_vertices_size + 4, buffer_vertices_size + 3); - store_triangle(indices, buffer_vertices_size - 2, buffer_vertices_size + 1, buffer_vertices_size + 4); - store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size + 5, buffer_vertices_size + 4); - store_triangle(indices, buffer_vertices_size + 1, buffer_vertices_size - 4, buffer_vertices_size + 5); - store_triangle(indices, buffer_vertices_size - 4, buffer_vertices_size + 2, buffer_vertices_size + 5); - - // triangles ending cap - store_triangle(indices, buffer_vertices_size + 2, buffer_vertices_size + 4, buffer_vertices_size + 5); - store_triangle(indices, buffer_vertices_size + 2, buffer_vertices_size + 3, buffer_vertices_size + 4); - - buffer_vertices_size += 6; - } - - last_path.last = { ibuffer_id, indices.size() - 1, move_id, curr.position }; - prev_dir = dir; - prev_up = up; - prev_length = length; - }; - - wxBusyCursor busy; - - // to reduce the peak in memory usage, we split the generation of the vertex and index buffers in two steps. - // the data are deleted as soon as they are sent to the gpu. - std::vector vertices(m_buffers.size()); - std::vector indices(m_buffers.size()); - std::vector options_zs; - - // toolpaths data -> extract vertices from result - for (size_t i = 0; i < m_moves_count; ++i) { - // skip first vertex - if (i == 0) - continue; - - ++progress_count; - if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { - progress_dialog->Update(int(100.0f * float(i) / (2.0f * float(m_moves_count))), - _L("Generating vertex buffer") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); - progress_dialog->Fit(); - progress_count = 0; - } - - const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; - const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; - - unsigned char id = buffer_id(curr.type); - TBuffer& buffer = m_buffers[id]; - VertexBuffer& buffer_vertices = vertices[id]; - - switch (buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Point: { - add_vertices_as_point(curr, buffer_vertices); - break; - } - case TBuffer::ERenderPrimitiveType::Line: { - add_vertices_as_line(prev, curr, buffer_vertices); - break; - } - case TBuffer::ERenderPrimitiveType::Triangle: { - add_vertices_as_solid(prev, curr, buffer, buffer_vertices, i); - break; - } - } - - if (curr.type == EMoveType::Pause_Print || curr.type == EMoveType::Custom_GCode) { - const float* const last_z = options_zs.empty() ? nullptr : &options_zs.back(); - if (last_z == nullptr || curr.position[2] < *last_z - EPSILON || *last_z + EPSILON < curr.position[2]) - options_zs.emplace_back(curr.position[2]); - } - } - - // move the wipe toolpaths half height up to render them on proper position - VertexBuffer& wipe_vertices = vertices[buffer_id(EMoveType::Wipe)]; - for (size_t i = 2; i < wipe_vertices.size(); i += 3) { - wipe_vertices[i] += 0.5f * GCodeProcessor::Wipe_Height; - } - - log_memory_usage("Loaded G-code generated vertex buffers, ", vertices, indices); - - // toolpaths data -> send vertices data to gpu - for (size_t i = 0; i < m_buffers.size(); ++i) { - TBuffer& buffer = m_buffers[i]; - - const VertexBuffer& buffer_vertices = vertices[i]; - buffer.vertices.count = buffer_vertices.size() / buffer.vertices.vertex_size_floats(); -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.total_vertices_gpu_size += buffer_vertices.size() * sizeof(float); - m_statistics.max_vbuffer_gpu_size = std::max(m_statistics.max_vbuffer_gpu_size, static_cast(buffer_vertices.size() * sizeof(float))); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - if (buffer.vertices.count > 0) { -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.vbuffers_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - glsafe(::glGenBuffers(1, &buffer.vertices.id)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); - glsafe(::glBufferData(GL_ARRAY_BUFFER, buffer_vertices.size() * sizeof(float), buffer_vertices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - } - } - - // dismiss vertices data, no more needed - std::vector().swap(vertices); - - // toolpaths data -> extract indices from result - // paths may have been filled while extracting vertices, - // so reset them, they will be filled again while extracting indices - for (TBuffer& buffer : m_buffers) { - buffer.paths.clear(); - } - - // max index buffer size - const size_t IBUFFER_THRESHOLD = 1024 * 1024 * 32; - - // variable used to keep track of the current size (in vertices) of the vertex buffer - std::vector curr_buffer_vertices_size(m_buffers.size(), 0); - for (size_t i = 0; i < m_moves_count; ++i) { - // skip first vertex - if (i == 0) - continue; - - ++progress_count; - if (progress_dialog != nullptr && progress_count % progress_threshold == 0) { - progress_dialog->Update(int(100.0f * float(m_moves_count + i) / (2.0f * float(m_moves_count))), - _L("Generating index buffers") + ": " + wxNumberFormatter::ToString(100.0 * double(i) / double(m_moves_count), 0, wxNumberFormatter::Style_None) + "%"); - progress_dialog->Fit(); - progress_count = 0; - } - - const GCodeProcessor::MoveVertex& prev = gcode_result.moves[i - 1]; - const GCodeProcessor::MoveVertex& curr = gcode_result.moves[i]; - - unsigned char id = buffer_id(curr.type); - TBuffer& buffer = m_buffers[id]; - MultiIndexBuffer& buffer_indices = indices[id]; - if (buffer_indices.empty()) - buffer_indices.push_back(IndexBuffer()); - - // if adding the indices for the current segment exceeds the threshold size of the current index buffer - // create another index buffer, and move the current path indices into it - if (buffer_indices.back().size() >= IBUFFER_THRESHOLD - static_cast(buffer.indices_per_segment())) { - buffer_indices.push_back(IndexBuffer()); - if (buffer.render_primitive_type != TBuffer::ERenderPrimitiveType::Point) { - if (!(prev.type != curr.type || !buffer.paths.back().matches(curr))) { - Path& last_path = buffer.paths.back(); - size_t delta_id = last_path.last.i_id - last_path.first.i_id; - - // move indices of the last path from the previous into the new index buffer - IndexBuffer& src_buffer = buffer_indices[buffer_indices.size() - 2]; - IndexBuffer& dst_buffer = buffer_indices[buffer_indices.size() - 1]; - std::move(src_buffer.begin() + last_path.first.i_id, src_buffer.end(), std::back_inserter(dst_buffer)); - src_buffer.erase(src_buffer.begin() + last_path.first.i_id, src_buffer.end()); - - // updates path indices - last_path.first.b_id = buffer_indices.size() - 1; - last_path.first.i_id = 0; - last_path.last.b_id = buffer_indices.size() - 1; - last_path.last.i_id = delta_id; - } - } - } - - switch (buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Point: { - add_indices_as_point(curr, buffer, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); - break; - } - case TBuffer::ERenderPrimitiveType::Line: { - add_indices_as_line(prev, curr, buffer, static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); - break; - } - case TBuffer::ERenderPrimitiveType::Triangle: { - add_indices_as_solid(prev, curr, buffer, curr_buffer_vertices_size[id], static_cast(buffer_indices.size()) - 1, buffer_indices.back(), i); - break; - } - } - } - - if (progress_dialog != nullptr) { - progress_dialog->Update(100, ""); - progress_dialog->Fit(); - } - - log_memory_usage("Loaded G-code generated indices buffers, ", vertices, indices); - - // toolpaths data -> send indices data to gpu - for (size_t i = 0; i < m_buffers.size(); ++i) { - TBuffer& buffer = m_buffers[i]; - - for (size_t j = 0; j < indices[i].size(); ++j) { - const IndexBuffer& buffer_indices = indices[i][j]; - buffer.indices.push_back(IBuffer()); - IBuffer& ibuffer = buffer.indices.back(); - ibuffer.count = buffer_indices.size(); -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.total_indices_gpu_size += ibuffer.count * sizeof(unsigned int); - m_statistics.max_ibuffer_gpu_size = std::max(m_statistics.max_ibuffer_gpu_size, static_cast(ibuffer.count * sizeof(unsigned int))); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - if (ibuffer.count > 0) { -#if ENABLE_GCODE_VIEWER_STATISTICS - ++m_statistics.ibuffers_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - glsafe(::glGenBuffers(1, &ibuffer.id)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibuffer.id)); - glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, buffer_indices.size() * sizeof(unsigned int), buffer_indices.data(), GL_STATIC_DRAW)); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - } - } - -#if ENABLE_GCODE_VIEWER_STATISTICS - for (const TBuffer& buffer : m_buffers) { - m_statistics.paths_size += SLIC3R_STDVEC_MEMSIZE(buffer.paths, Path); - } - unsigned int travel_buffer_id = buffer_id(EMoveType::Travel); - const MultiIndexBuffer& travel_buffer_indices = indices[travel_buffer_id]; - for (size_t i = 0; i < travel_buffer_indices.size(); ++i) { - m_statistics.travel_segments_count += travel_buffer_indices[i].size() / m_buffers[travel_buffer_id].indices_per_segment(); - } - unsigned int wipe_buffer_id = buffer_id(EMoveType::Wipe); - const MultiIndexBuffer& wipe_buffer_indices = indices[wipe_buffer_id]; - for (size_t i = 0; i < wipe_buffer_indices.size(); ++i) { - m_statistics.wipe_segments_count += wipe_buffer_indices[i].size() / m_buffers[wipe_buffer_id].indices_per_segment(); - } - unsigned int extrude_buffer_id = buffer_id(EMoveType::Extrude); - const MultiIndexBuffer& extrude_buffer_indices = indices[extrude_buffer_id]; - for (size_t i = 0; i < extrude_buffer_indices.size(); ++i) { - m_statistics.extrude_segments_count += extrude_buffer_indices[i].size() / m_buffers[extrude_buffer_id].indices_per_segment(); - } -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - // dismiss indices data, no more needed - std::vector().swap(indices); - - // layers zs / roles / extruder ids / cp color ids -> extract from result - size_t last_travel_s_id = 0; - for (size_t i = 0; i < m_moves_count; ++i) { - const GCodeProcessor::MoveVertex& move = gcode_result.moves[i]; - if (move.type == EMoveType::Extrude) { - // layers zs - const double* const last_z = m_layers.empty() ? nullptr : &m_layers.get_zs().back(); - double z = static_cast(move.position[2]); - if (last_z == nullptr || z < *last_z - EPSILON || *last_z + EPSILON < z) - m_layers.append(z, { last_travel_s_id, i }); - else - m_layers.get_endpoints().back().last = i; - // extruder ids - m_extruder_ids.emplace_back(move.extruder_id); - // roles - if (i > 0) - m_roles.emplace_back(move.extrusion_role); - } - else if (move.type == EMoveType::Travel) { - if (i - last_travel_s_id > 1 && !m_layers.empty()) - m_layers.get_endpoints().back().last = i; - - last_travel_s_id = i; - } - } - - // set layers z range - if (!m_layers.empty()) - m_layers_z_range = { 0, static_cast(m_layers.size() - 1) }; - - // change color of paths whose layer contains option points - if (!options_zs.empty()) { - TBuffer& extrude_buffer = m_buffers[buffer_id(EMoveType::Extrude)]; - for (Path& path : extrude_buffer.paths) { - float z = path.first.position[2]; - if (std::find_if(options_zs.begin(), options_zs.end(), [z](float f) { return f - EPSILON <= z && z <= f + EPSILON; }) != options_zs.end()) - path.cp_color_id = 255 - path.cp_color_id; - } - } - - // roles -> remove duplicates - std::sort(m_roles.begin(), m_roles.end()); - m_roles.erase(std::unique(m_roles.begin(), m_roles.end()), m_roles.end()); - m_roles.shrink_to_fit(); - - // extruder ids -> remove duplicates - std::sort(m_extruder_ids.begin(), m_extruder_ids.end()); - m_extruder_ids.erase(std::unique(m_extruder_ids.begin(), m_extruder_ids.end()), m_extruder_ids.end()); - m_extruder_ids.shrink_to_fit(); - - log_memory_usage("Loaded G-code generated extrusion paths, ", vertices, indices); - -#if ENABLE_GCODE_VIEWER_STATISTICS - m_statistics.load_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - if (progress_dialog != nullptr) - progress_dialog->Destroy(); -} -#endif // ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::load_shells(const Print& print, bool initialized) { @@ -2980,7 +1963,6 @@ void GCodeViewer::load_shells(const Print& print, bool initialized) } } -#if ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const { #if ENABLE_GCODE_VIEWER_STATISTICS @@ -3071,11 +2053,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool if (!keep_sequential_current_last) sequential_view->current.last = m_moves_count; // first pass: collect visible paths and update sequential view data -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS std::vector> paths; -#else - std::vector> paths; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS for (size_t b = 0; b < m_buffers.size(); ++b) { TBuffer& buffer = const_cast(m_buffers[b]); // reset render paths @@ -3098,11 +2076,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool // store valid path for (size_t j = 0; j < path.sub_paths.size(); ++j) { -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS paths.push_back({ static_cast(b), path.sub_paths[j].first.b_id, static_cast(i), static_cast(j) }); -#else - paths.push_back({ &buffer, path.sub_paths[j].first.b_id, static_cast(i), static_cast(j) }); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS } global_endpoints.first = std::min(global_endpoints.first, path.sub_paths.front().first.s_id); @@ -3142,13 +2116,9 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool offset = 2 * offset - 1; else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { unsigned int indices_count = buffer.indices_per_segment(); -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS offset = indices_count * (offset - 1) + (indices_count - 2); if (sub_path_id == 0) offset += 6; // add 2 triangles for starting cap -#else - offset = indices_count * (offset - 1) + (indices_count - 6); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS } } offset += static_cast(sub_path.first.i_id); @@ -3177,14 +2147,9 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool // second pass: filter paths by sequential data and collect them by color RenderPath* render_path = nullptr; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS for (const auto& [tbuffer_id, ibuffer_id, path_id, sub_path_id] : paths) { TBuffer& buffer = const_cast(m_buffers[tbuffer_id]); const Path& path = buffer.paths[path_id]; -#else - for (const auto& [buffer, ibuffer_id, path_id, sub_path_id] : paths) { - const Path& path = buffer->paths[path_id]; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS const Path::Sub_Path& sub_path = path.sub_paths[sub_path_id]; if (m_sequential_view.current.last < sub_path.first.s_id || sub_path.last.s_id < m_sequential_view.current.first) continue; @@ -3223,7 +2188,6 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool default: { color = { 0.0f, 0.0f, 0.0f }; break; } } -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS RenderPath key{ tbuffer_id, color, static_cast(ibuffer_id), path_id }; if (render_path == nullptr || !RenderPathPropertyEqual()(*render_path, key)) render_path = const_cast(&(*buffer.render_paths.emplace(key).first)); @@ -3231,40 +2195,22 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool unsigned int delta_1st = 0; if (sub_path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= sub_path.last.s_id) delta_1st = static_cast(m_sequential_view.current.first - sub_path.first.s_id); -#else - RenderPath key{ color, static_cast(ibuffer_id), path_id }; - if (render_path == nullptr || !RenderPathPropertyEqual()(*render_path, key)) - render_path = const_cast(&(*buffer->render_paths.emplace(key).first)); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS unsigned int size_in_indices = 0; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS switch (buffer.render_primitive_type) -#else - switch (buffer->render_primitive_type) -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS { case TBuffer::ERenderPrimitiveType::Point: { -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS size_in_indices = buffer.indices_per_segment(); -#else - size_in_indices = buffer->indices_per_segment(); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS break; } case TBuffer::ERenderPrimitiveType::Line: case TBuffer::ERenderPrimitiveType::Triangle: { unsigned int segments_count = std::min(m_sequential_view.current.last, sub_path.last.s_id) - std::max(m_sequential_view.current.first, sub_path.first.s_id); -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS size_in_indices = buffer.indices_per_segment() * segments_count; -#else - size_in_indices = buffer->indices_per_segment() * segments_count; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS break; } } -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS if (size_in_indices == 0) continue; @@ -3276,17 +2222,9 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool if (delta_1st > 0) size_in_indices -= 6; // remove 2 triangles for corner cap } -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS render_path->sizes.push_back(size_in_indices); -#if !ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS - unsigned int delta_1st = 0; - if (sub_path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= sub_path.last.s_id) - delta_1st = m_sequential_view.current.first - sub_path.first.s_id; -#endif // !ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS - -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { delta_1st *= buffer.indices_per_segment(); if (delta_1st > 0) { @@ -3295,10 +2233,6 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool delta_1st += 6; // skip 2 triangles for starting cap } } -#else - if (buffer->render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) - delta_1st *= buffer->indices_per_segment(); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS render_path->offsets.push_back(static_cast((sub_path.first.i_id + delta_1st) * sizeof(IBufferType))); @@ -3317,7 +2251,6 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool sequential_view->endpoints = top_layer_only ? top_layer_endpoints : global_endpoints; sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, sequential_view->endpoints.first, sequential_view->endpoints.last) : sequential_view->endpoints.first; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS // updates sequential range caps std::array* sequential_range_caps = const_cast*>(&m_sequential_range_caps); (*sequential_range_caps)[0].reset(); @@ -3428,7 +2361,6 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool break; } } -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS wxGetApp().plater()->enable_preview_moves_slider(!paths.empty()); @@ -3443,261 +2375,7 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool statistics->refresh_paths_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); #endif // ENABLE_GCODE_VIEWER_STATISTICS } -#else -void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool keep_sequential_current_last) const -{ -#if ENABLE_GCODE_VIEWER_STATISTICS - auto start_time = std::chrono::high_resolution_clock::now(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS - auto extrusion_color = [this](const Path& path) { - Color color; - switch (m_view_type) - { - case EViewType::FeatureType: { color = Extrusion_Role_Colors[static_cast(path.role)]; break; } - case EViewType::Height: { color = m_extrusions.ranges.height.get_color_at(path.height); break; } - case EViewType::Width: { color = m_extrusions.ranges.width.get_color_at(path.width); break; } - case EViewType::Feedrate: { color = m_extrusions.ranges.feedrate.get_color_at(path.feedrate); break; } - case EViewType::FanSpeed: { color = m_extrusions.ranges.fan_speed.get_color_at(path.fan_speed); break; } - case EViewType::Temperature: { color = m_extrusions.ranges.temperature.get_color_at(path.temperature); break; } - case EViewType::VolumetricRate: { color = m_extrusions.ranges.volumetric_rate.get_color_at(path.volumetric_rate); break; } - case EViewType::Tool: { color = m_tool_colors[path.extruder_id]; break; } - case EViewType::ColorPrint: { - if (path.cp_color_id >= static_cast(m_tool_colors.size())) { - color = { 0.5f, 0.5f, 0.5f }; -// // complementary color -// color = m_tool_colors[255 - path.cp_color_id]; -// color = { 1.0f - color[0], 1.0f - color[1], 1.0f - color[2] }; - } - else - color = m_tool_colors[path.cp_color_id]; - - break; - } - default: { color = { 1.0f, 1.0f, 1.0f }; break; } - } - - return color; - }; - - auto travel_color = [this](const Path& path) { - return (path.delta_extruder < 0.0f) ? Travel_Colors[2] /* Retract */ : - ((path.delta_extruder > 0.0f) ? Travel_Colors[1] /* Extrude */ : - Travel_Colors[0] /* Move */); - }; - - auto is_in_layers_range = [this](const Path& path, size_t min_id, size_t max_id) { - auto in_layers_range = [this, min_id, max_id](size_t id) { - return m_layers.get_endpoints_at(min_id).first <= id && id <= m_layers.get_endpoints_at(max_id).last; - }; - - return in_layers_range(path.first.s_id) || in_layers_range(path.last.s_id); - }; - - auto is_travel_in_layers_range = [this](size_t path_id, size_t min_id, size_t max_id) { - auto is_in_z_range = [](const Path& path, double min_z, double max_z) { - auto in_z_range = [min_z, max_z](double z) { - return min_z - EPSILON < z&& z < max_z + EPSILON; - }; - - return in_z_range(path.first.position[2]) || in_z_range(path.last.position[2]); - }; - - const TBuffer& buffer = m_buffers[buffer_id(EMoveType::Travel)]; - if (path_id >= buffer.paths.size()) - return false; - - Path path = buffer.paths[path_id]; - size_t first = path_id; - size_t last = path_id; - - // check adjacent paths - while (first > 0 && path.first.position.isApprox(buffer.paths[first - 1].last.position)) { - --first; - path.first = buffer.paths[first].first; - } - while (last < buffer.paths.size() - 1 && path.last.position.isApprox(buffer.paths[last + 1].first.position)) { - ++last; - path.last = buffer.paths[last].last; - } - - size_t min_s_id = m_layers.get_endpoints_at(min_id).first; - size_t max_s_id = m_layers.get_endpoints_at(max_id).last; - - return (min_s_id <= path.first.s_id && path.first.s_id <= max_s_id) || - (min_s_id <= path.last.s_id && path.last.s_id <= max_s_id); - }; - -#if ENABLE_GCODE_VIEWER_STATISTICS - Statistics* statistics = const_cast(&m_statistics); - statistics->render_paths_size = 0; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - - bool top_layer_only = get_app_config()->get("seq_top_layer_only") == "1"; - - SequentialView::Endpoints global_endpoints = { m_moves_count , 0 }; - SequentialView::Endpoints top_layer_endpoints = global_endpoints; - SequentialView* sequential_view = const_cast(&m_sequential_view); - if (top_layer_only || !keep_sequential_current_first) sequential_view->current.first = 0; - if (!keep_sequential_current_last) sequential_view->current.last = m_moves_count; - - // first pass: collect visible paths and update sequential view data - std::vector> paths; - for (size_t b = 0; b < m_buffers.size(); ++b) { - TBuffer& buffer = const_cast(m_buffers[b]); - // reset render paths - buffer.render_paths.clear(); - - if (!buffer.visible) - continue; - - for (size_t i = 0; i < buffer.paths.size(); ++i) { - const Path& path = buffer.paths[i]; - if (path.type == EMoveType::Travel) { - if (!is_travel_in_layers_range(i, m_layers_z_range[0], m_layers_z_range[1])) - continue; - } - else if (!is_in_layers_range(path, m_layers_z_range[0], m_layers_z_range[1])) - continue; - - if (path.type == EMoveType::Extrude && !is_visible(path)) - continue; - - // store valid path - paths.push_back({ &buffer, path.first.b_id, static_cast(i) }); - - global_endpoints.first = std::min(global_endpoints.first, path.first.s_id); - global_endpoints.last = std::max(global_endpoints.last, path.last.s_id); - - if (top_layer_only) { - if (path.type == EMoveType::Travel) { - if (is_travel_in_layers_range(i, m_layers_z_range[1], m_layers_z_range[1])) { - top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.first.s_id); - top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.last.s_id); - } - } - else if (is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) { - top_layer_endpoints.first = std::min(top_layer_endpoints.first, path.first.s_id); - top_layer_endpoints.last = std::max(top_layer_endpoints.last, path.last.s_id); - } - } - } - } - - // update current sequential position - sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, global_endpoints.first, global_endpoints.last) : global_endpoints.first; - sequential_view->current.last = keep_sequential_current_last ? std::clamp(sequential_view->current.last, global_endpoints.first, global_endpoints.last) : global_endpoints.last; - - // get the world position from gpu - bool found = false; - for (const TBuffer& buffer : m_buffers) { - // searches the path containing the current position - for (const Path& path : buffer.paths) { - if (path.contains(m_sequential_view.current.last)) { - unsigned int offset = static_cast(m_sequential_view.current.last - path.first.s_id); - if (offset > 0) { - if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Line) - offset = 2 * offset - 1; - else if (buffer.render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) { - unsigned int indices_count = buffer.indices_per_segment(); - offset = indices_count * (offset - 1) + (indices_count - 6); - } - } - offset += static_cast(path.first.i_id); - - // gets the index from the index buffer on gpu - unsigned int index = 0; - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices[path.first.b_id].id)); - glsafe(::glGetBufferSubData(GL_ELEMENT_ARRAY_BUFFER, static_cast(offset * sizeof(unsigned int)), static_cast(sizeof(unsigned int)), static_cast(&index))); - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - - // gets the position from the vertices buffer on gpu - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); - glsafe(::glGetBufferSubData(GL_ARRAY_BUFFER, static_cast(index * buffer.vertices.vertex_size_bytes()), static_cast(3 * sizeof(float)), static_cast(sequential_view->current_position.data()))); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - found = true; - break; - } - } - if (found) - break; - } - - // second pass: filter paths by sequential data and collect them by color - RenderPath *render_path = nullptr; - for (const auto& [buffer, ibuffer_id, path_id] : paths) { - const Path& path = buffer->paths[path_id]; - if (m_sequential_view.current.last <= path.first.s_id || path.last.s_id <= m_sequential_view.current.first) - continue; - - Color color; - switch (path.type) - { - case EMoveType::Extrude: { - if (!top_layer_only || - m_sequential_view.current.last == global_endpoints.last || - is_in_layers_range(path, m_layers_z_range[1], m_layers_z_range[1])) - color = extrusion_color(path); - else - color = { 0.25f, 0.25f, 0.25f }; - - break; - } - case EMoveType::Travel: { - if (!top_layer_only || m_sequential_view.current.last == global_endpoints.last || is_travel_in_layers_range(path_id, m_layers_z_range[1], m_layers_z_range[1])) - color = (m_view_type == EViewType::Feedrate || m_view_type == EViewType::Tool || m_view_type == EViewType::ColorPrint) ? extrusion_color(path) : travel_color(path); - else - color = { 0.25f, 0.25f, 0.25f }; - - break; - } - case EMoveType::Wipe: { color = Wipe_Color; break; } - default: { color = { 0.0f, 0.0f, 0.0f }; break; } - } - - RenderPath key{ color, static_cast(ibuffer_id), path_id }; - if (render_path == nullptr || ! RenderPathPropertyEqual()(*render_path, key)) - render_path = const_cast(&(*buffer->render_paths.emplace(key).first)); - unsigned int segments_count = std::min(m_sequential_view.current.last, path.last.s_id) - std::max(m_sequential_view.current.first, path.first.s_id) + 1; - unsigned int size_in_indices = 0; - switch (buffer->render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Point: { size_in_indices = segments_count; break; } - case TBuffer::ERenderPrimitiveType::Line: - case TBuffer::ERenderPrimitiveType::Triangle: { size_in_indices = buffer->indices_per_segment() * (segments_count - 1); break; } - } - render_path->sizes.push_back(size_in_indices); - - unsigned int delta_1st = 0; - if (path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= path.last.s_id) - delta_1st = m_sequential_view.current.first - path.first.s_id; - - if (buffer->render_primitive_type == TBuffer::ERenderPrimitiveType::Triangle) - delta_1st *= buffer->indices_per_segment(); - - render_path->offsets.push_back(static_cast((path.first.i_id + delta_1st) * sizeof(unsigned int))); - } - - // set sequential data to their final value - sequential_view->endpoints = top_layer_only ? top_layer_endpoints : global_endpoints; - sequential_view->current.first = !top_layer_only && keep_sequential_current_first ? std::clamp(sequential_view->current.first, sequential_view->endpoints.first, sequential_view->endpoints.last) : sequential_view->endpoints.first; - - wxGetApp().plater()->enable_preview_moves_slider(!paths.empty()); - -#if ENABLE_GCODE_VIEWER_STATISTICS - for (const TBuffer& buffer : m_buffers) { - statistics->render_paths_size += SLIC3R_STDUNORDEREDSET_MEMSIZE(buffer.render_paths, RenderPath); - for (const RenderPath& path : buffer.render_paths) { - statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.sizes, unsigned int); - statistics->render_paths_size += SLIC3R_STDVEC_MEMSIZE(path.offsets, size_t); - } - } - statistics->refresh_paths_time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - start_time).count(); -#endif // ENABLE_GCODE_VIEWER_STATISTICS -} -#endif // ENABLE_SPLITTED_VERTEX_BUFFER - -#if ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::render_toolpaths() const { #if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS @@ -3833,7 +2511,6 @@ void GCodeViewer::render_toolpaths() const } } -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS auto render_sequential_range_cap = [set_uniform_color](const SequentialRangeCap& cap) { GLShaderProgram* shader = wxGetApp().get_shader(cap.buffer->shader.c_str()); if (shader != nullptr) { @@ -3872,157 +2549,7 @@ void GCodeViewer::render_toolpaths() const if (m_sequential_range_caps[i].is_renderable()) render_sequential_range_cap(m_sequential_range_caps[i]); } -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS } -#else -void GCodeViewer::render_toolpaths() const -{ -#if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS - float point_size = 20.0f; -#else - float point_size = 0.8f; -#endif // ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS - std::array light_intensity = { 0.25f, 0.70f, 0.75f, 0.75f }; - const Camera& camera = wxGetApp().plater()->get_camera(); - double zoom = camera.get_zoom(); - const std::array& viewport = camera.get_viewport(); - float near_plane_height = camera.get_type() == Camera::Perspective ? static_cast(viewport[3]) / (2.0f * static_cast(2.0 * std::tan(0.5 * Geometry::deg2rad(camera.get_fov())))) : - static_cast(viewport[3]) * 0.0005; - - auto set_uniform_color = [](const std::array& color, GLShaderProgram& shader) { - std::array color4 = { color[0], color[1], color[2], 1.0f }; - shader.set_uniform("uniform_color", color4); - }; - - auto render_as_points = [this, zoom, point_size, near_plane_height, set_uniform_color] - (const TBuffer& buffer, unsigned int ibuffer_id, EOptionsColors color_id, GLShaderProgram& shader) { - set_uniform_color(Options_Colors[static_cast(color_id)], shader); -#if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS - shader.set_uniform("use_fixed_screen_size", 1); -#else - shader.set_uniform("use_fixed_screen_size", 0); -#endif // ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS - shader.set_uniform("zoom", zoom); - shader.set_uniform("percent_outline_radius", 0.0f); - shader.set_uniform("percent_center_radius", 0.33f); - shader.set_uniform("point_size", point_size); - shader.set_uniform("near_plane_height", near_plane_height); - - glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE)); - glsafe(::glEnable(GL_POINT_SPRITE)); - - for (const RenderPath& path : buffer.render_paths) { - if (path.ibuffer_id == ibuffer_id) { - glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++const_cast(&m_statistics)->gl_multi_points_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - } - - glsafe(::glDisable(GL_POINT_SPRITE)); - glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE)); - }; - - auto render_as_lines = [this, light_intensity, set_uniform_color](const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { - shader.set_uniform("light_intensity", light_intensity); - for (const RenderPath& path : buffer.render_paths) { - if (path.ibuffer_id == ibuffer_id) { - set_uniform_color(path.color, shader); - glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++const_cast(&m_statistics)->gl_multi_lines_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - } - }; - - auto render_as_triangles = [this, set_uniform_color](const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) { - for (const RenderPath& path : buffer.render_paths) { - if (path.ibuffer_id == ibuffer_id) { - set_uniform_color(path.color, shader); - glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_INT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size())); -#if ENABLE_GCODE_VIEWER_STATISTICS - ++const_cast(&m_statistics)->gl_multi_triangles_calls_count; -#endif // ENABLE_GCODE_VIEWER_STATISTICS - } - } - }; - - auto line_width = [](double zoom) { - return (zoom < 5.0) ? 1.0 : (1.0 + 5.0 * (zoom - 5.0) / (100.0 - 5.0)); - }; - - glsafe(::glLineWidth(static_cast(line_width(zoom)))); - - unsigned char begin_id = buffer_id(EMoveType::Retract); - unsigned char end_id = buffer_id(EMoveType::Count); - - for (unsigned char i = begin_id; i < end_id; ++i) { - const TBuffer& buffer = m_buffers[i]; - if (!buffer.visible || !buffer.has_data()) - continue; - - GLShaderProgram* shader = wxGetApp().get_shader(buffer.shader.c_str()); - if (shader != nullptr) { - shader->start_using(); - - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, buffer.vertices.id)); - glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_size())); - glsafe(::glEnableClientState(GL_VERTEX_ARRAY)); - bool has_normals = buffer.vertices.normal_size_floats() > 0; - if (has_normals) { - glsafe(::glNormalPointer(GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.normal_offset_size())); - glsafe(::glEnableClientState(GL_NORMAL_ARRAY)); - } - - for (size_t j = 0; j < buffer.indices.size(); ++j) { - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.indices[j].id)); - - switch (buffer.render_primitive_type) - { - case TBuffer::ERenderPrimitiveType::Point: - { - EOptionsColors color = EOptionsColors(0); - switch (buffer_type(i)) - { - case EMoveType::Tool_change: { color = EOptionsColors::ToolChanges; break; } - case EMoveType::Color_change: { color = EOptionsColors::ColorChanges; break; } - case EMoveType::Pause_Print: { color = EOptionsColors::PausePrints; break; } - case EMoveType::Custom_GCode: { color = EOptionsColors::CustomGCodes; break; } - case EMoveType::Retract: { color = EOptionsColors::Retractions; break; } - case EMoveType::Unretract: { color = EOptionsColors::Unretractions; break; } - default: { assert(false); break; } - } - render_as_points(buffer, static_cast(j), color, *shader); - break; - } - case TBuffer::ERenderPrimitiveType::Line: - { - render_as_lines(buffer, static_cast(j), *shader); - break; - } - case TBuffer::ERenderPrimitiveType::Triangle: - { - render_as_triangles(buffer, static_cast(j), *shader); - break; - } - } - - glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); - } - - if (has_normals) - glsafe(::glDisableClientState(GL_NORMAL_ARRAY)); - - glsafe(::glDisableClientState(GL_VERTEX_ARRAY)); - glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); - - shader->stop_using(); - } - } -} -#endif // ENABLE_SPLITTED_VERTEX_BUFFER void GCodeViewer::render_shells() const { @@ -4051,14 +2578,25 @@ void GCodeViewer::render_legend() const if (!m_legend_enabled) return; +#if ENABLE_SCROLLABLE_LEGEND + const Size cnv_size = wxGetApp().plater()->get_current_canvas3D()->get_canvas_size(); +#endif // ENABLE_SCROLLABLE_LEGEND + ImGuiWrapper& imgui = *wxGetApp().imgui(); imgui.set_next_window_pos(0.0f, 0.0f, ImGuiCond_Always); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::SetNextWindowBgAlpha(0.6f); +#if ENABLE_SCROLLABLE_LEGEND + const float max_height = 0.75f * static_cast(cnv_size.get_height()); + const float child_height = 0.3333f * max_height; + ImGui::SetNextWindowSizeConstraints({ 0.0f, 0.0f }, { -1.0f, max_height }); + imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove); +#else imgui.begin(std::string("Legend"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove); ImDrawList* draw_list = ImGui::GetWindowDrawList(); +#endif // ENABLE_SCROLLABLE_LEGEND enum class EItemType : unsigned char { @@ -4068,95 +2606,126 @@ void GCodeViewer::render_legend() const Line }; - const PrintEstimatedTimeStatistics::Mode& time_mode = m_time_statistics.modes[static_cast(m_time_estimate_mode)]; + const PrintEstimatedStatistics::Mode& time_mode = m_print_statistics.modes[static_cast(m_time_estimate_mode)]; +#if ENABLE_SCROLLABLE_LEGEND + bool show_estimated_time = time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || + (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty())); +#endif // ENABLE_SCROLLABLE_LEGEND - float icon_size = ImGui::GetTextLineHeight(); - float percent_bar_size = 2.0f * ImGui::GetTextLineHeight(); + const float icon_size = ImGui::GetTextLineHeight(); + const float percent_bar_size = 2.0f * ImGui::GetTextLineHeight(); - auto append_item = [this, draw_list, icon_size, percent_bar_size, &imgui](EItemType type, const Color& color, const std::string& label, - bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array& offsets = { 0.0f, 0.0f }, + bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + +#if ENABLE_SCROLLABLE_LEGEND + auto append_item = [this, icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const Color& color, const std::string& label, +#else + auto append_item = [this, draw_list, icon_size, percent_bar_size, &imgui, imperial_units](EItemType type, const Color& color, const std::string& label, +#endif // ENABLE_SCROLLABLE_LEGEND + bool visible = true, const std::string& time = "", float percent = 0.0f, float max_percent = 0.0f, const std::array& offsets = { 0.0f, 0.0f, 0.0f, 0.0f }, + double used_filament_m = 0.0, double used_filament_g = 0.0, std::function callback = nullptr) { - if (!visible) - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - ImVec2 pos = ImGui::GetCursorScreenPos(); - switch (type) { - default: - case EItemType::Rect: { - draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, - ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); - break; - } - case EItemType::Circle: { - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") { - draw_list->AddCircleFilled(center, 0.5f * icon_size, - ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); - float radius = 0.5f * icon_size; - draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - radius = 0.5f * icon_size * 0.01f * 33.0f; - draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); - } - else - draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + if (!visible) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - break; - } - case EItemType::Hexagon: { - ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); - draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); - break; - } - case EItemType::Line: { - draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); - break; - } - } - - // draw text - ImGui::Dummy({ icon_size, icon_size }); - ImGui::SameLine(); - if (callback != nullptr) { - if (ImGui::MenuItem(label.c_str())) - callback(); - else { - // show tooltip - if (ImGui::IsItemHovered()) { - if (!visible) - ImGui::PopStyleVar(); - ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); - ImGui::BeginTooltip(); - imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show")); - ImGui::EndTooltip(); - ImGui::PopStyleColor(); - if (!visible) - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); - - // to avoid the tooltip to change size when moving the mouse - wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); - wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); - } - } - - if (!time.empty()) { - ImGui::SameLine(offsets[0]); - imgui.text(time); - ImGui::SameLine(offsets[1]); - pos = ImGui::GetCursorScreenPos(); - float width = std::max(1.0f, percent_bar_size * percent / max_percent); - draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, - ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); - ImGui::Dummy({ percent_bar_size, icon_size }); - ImGui::SameLine(); - char buf[64]; - ::sprintf(buf, "%.1f%%", 100.0f * percent); - ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); - } +#if ENABLE_SCROLLABLE_LEGEND + ImDrawList* draw_list = ImGui::GetWindowDrawList(); +#endif // ENABLE_SCROLLABLE_LEGEND + ImVec2 pos = ImGui::GetCursorScreenPos(); + switch (type) { + default: + case EItemType::Rect: { + draw_list->AddRectFilled({ pos.x + 1.0f, pos.y + 1.0f }, { pos.x + icon_size - 1.0f, pos.y + icon_size - 1.0f }, + ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f })); + break; + } + case EItemType::Circle: { + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + if (m_buffers[buffer_id(EMoveType::Retract)].shader == "options_120") { + draw_list->AddCircleFilled(center, 0.5f * icon_size, + ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); + float radius = 0.5f * icon_size; + draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); + radius = 0.5f * icon_size * 0.01f * 33.0f; + draw_list->AddCircleFilled(center, radius, ImGui::GetColorU32({ 0.5f * color[0], 0.5f * color[1], 0.5f * color[2], 1.0f }), 16); } else - imgui.text(label); + draw_list->AddCircleFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 16); - if (!visible) - ImGui::PopStyleVar(); + break; + } + case EItemType::Hexagon: { + ImVec2 center(0.5f * (pos.x + pos.x + icon_size), 0.5f * (pos.y + pos.y + icon_size)); + draw_list->AddNgonFilled(center, 0.5f * icon_size, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 6); + break; + } + case EItemType::Line: { + draw_list->AddLine({ pos.x + 1, pos.y + icon_size - 1 }, { pos.x + icon_size - 1, pos.y + 1 }, ImGui::GetColorU32({ color[0], color[1], color[2], 1.0f }), 3.0f); + break; + } + } + + // draw text + ImGui::Dummy({ icon_size, icon_size }); + ImGui::SameLine(); + if (callback != nullptr) { + if (ImGui::MenuItem(label.c_str())) + callback(); + else { + // show tooltip + if (ImGui::IsItemHovered()) { + if (!visible) + ImGui::PopStyleVar(); + ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND); + ImGui::BeginTooltip(); + imgui.text(visible ? _u8L("Click to hide") : _u8L("Click to show")); + ImGui::EndTooltip(); + ImGui::PopStyleColor(); + if (!visible) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.3333f); + + // to avoid the tooltip to change size when moving the mouse + wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); + wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); + } + } + + if (!time.empty()) { + ImGui::SameLine(offsets[0]); + imgui.text(time); + ImGui::SameLine(offsets[1]); + pos = ImGui::GetCursorScreenPos(); + const float width = std::max(1.0f, percent_bar_size * percent / max_percent); + draw_list->AddRectFilled({ pos.x, pos.y + 2.0f }, { pos.x + width, pos.y + icon_size - 2.0f }, + ImGui::GetColorU32(ImGuiWrapper::COL_ORANGE_LIGHT)); + ImGui::Dummy({ percent_bar_size, icon_size }); + ImGui::SameLine(); + char buf[64]; + ::sprintf(buf, "%.1f%%", 100.0f * percent); + ImGui::TextUnformatted((percent > 0.0f) ? buf : ""); + ImGui::SameLine(offsets[2]); + ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m); + imgui.text(buf); + ImGui::SameLine(offsets[3]); + ::sprintf(buf, "%.2f g", used_filament_g); + imgui.text(buf); + } + } + else { + imgui.text(label); + if (used_filament_m > 0.0) { + char buf[64]; + ImGui::SameLine(offsets[0]); + ::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m); + imgui.text(buf); + ImGui::SameLine(offsets[1]); + ::sprintf(buf, "%.2f g", used_filament_g); + imgui.text(buf); + } + } + + if (!visible) + ImGui::PopStyleVar(); }; auto append_range = [append_item](const Extrusions::Range& range, unsigned int decimals) { @@ -4174,19 +2743,20 @@ void GCodeViewer::render_legend() const append_range_item(0, range.min, decimals); } else { - float step_size = range.step_size(); + const float step_size = range.step_size(); for (int i = static_cast(Range_Colors.size()) - 1; i >= 0; --i) { append_range_item(i, range.min + static_cast(i) * step_size, decimals); } } }; - auto append_headers = [&imgui](const std::array& texts, const std::array& offsets) { - imgui.text(texts[0]); - ImGui::SameLine(offsets[0]); - imgui.text(texts[1]); - ImGui::SameLine(offsets[1]); - imgui.text(texts[2]); + auto append_headers = [&imgui](const std::array& texts, const std::array& offsets) { + size_t i = 0; + for (; i < offsets.size(); i++) { + imgui.text(texts[i]); + ImGui::SameLine(offsets[i]); + } + imgui.text(texts[i]); ImGui::Separator(); }; @@ -4199,11 +2769,12 @@ void GCodeViewer::render_legend() const }; auto calculate_offsets = [max_width](const std::vector& labels, const std::vector& times, - const std::array& titles, float extra_size = 0.0f) { + const std::array& titles, float extra_size = 0.0f) { const ImGuiStyle& style = ImGui::GetStyle(); - std::array ret = { 0.0f, 0.0f }; + std::array ret = { 0.0f, 0.0f, 0.0f, 0.0f }; ret[0] = max_width(labels, titles[0], extra_size) + 3.0f * style.ItemSpacing.x; - ret[1] = ret[0] + max_width(times, titles[1]) + style.ItemSpacing.x; + for (size_t i = 1; i < titles.size(); i++) + ret[i] = ret[i-1] + max_width(times, titles[i]) + style.ItemSpacing.x; return ret; }; @@ -4223,8 +2794,8 @@ void GCodeViewer::render_legend() const if (lower_b == zs.end()) continue; - double current_z = *lower_b; - double previous_z = (lower_b == zs.begin()) ? 0.0 : *(--lower_b); + const double current_z = *lower_b; + const double previous_z = (lower_b == zs.begin()) ? 0.0 : *(--lower_b); // to avoid duplicate values, check adding values if (ret.empty() || !(ret.back().second.first == previous_z && ret.back().second.second == current_z)) @@ -4254,16 +2825,27 @@ void GCodeViewer::render_legend() const return _u8L("from") + " " + std::string(buf1) + " " + _u8L("to") + " " + std::string(buf2) + " " + _u8L("mm"); }; - auto role_time_and_percent = [ time_mode](ExtrusionRole role) { + auto role_time_and_percent = [time_mode](ExtrusionRole role) { auto it = std::find_if(time_mode.roles_times.begin(), time_mode.roles_times.end(), [role](const std::pair& item) { return role == item.first; }); return (it != time_mode.roles_times.end()) ? std::make_pair(it->second, it->second / time_mode.time) : std::make_pair(0.0f, 0.0f); }; + auto used_filament_per_role = [this, imperial_units](ExtrusionRole role) { + auto it = m_print_statistics.used_filaments_per_role.find(role); + if (it == m_print_statistics.used_filaments_per_role.end()) + return std::make_pair(0.0, 0.0); + + double koef = imperial_units ? 1000.0 / ObjectManipulation::in_to_mm : 1.0; + return std::make_pair(it->second.first * koef, it->second.second); + }; + // data used to properly align items in columns when showing time - std::array offsets = { 0.0f, 0.0f }; + std::array offsets = { 0.0f, 0.0f, 0.0f, 0.0f }; std::vector labels; std::vector times; std::vector percents; + std::vector used_filaments_m; + std::vector used_filaments_g; float max_percent = 0.0f; if (m_view_type == EViewType::FeatureType) { @@ -4276,10 +2858,73 @@ void GCodeViewer::render_legend() const times.push_back((time > 0.0f) ? short_time(get_time_dhms(time)) : ""); percents.push_back(percent); max_percent = std::max(max_percent, percent); + auto [used_filament_m, used_filament_g] = used_filament_per_role(role); + used_filaments_m.push_back(used_filament_m); + used_filaments_g.push_back(used_filament_g); } } - offsets = calculate_offsets(labels, times, { _u8L("Feature type"), _u8L("Time") }, icon_size); + std::string longest_percentage_string; + for (double item : percents) { + char buffer[64]; + ::sprintf(buffer, "%.2f %%", item); + if (::strlen(buffer) > longest_percentage_string.length()) + longest_percentage_string = buffer; + } + longest_percentage_string += " "; + if (_u8L("Percentage").length() > longest_percentage_string.length()) + longest_percentage_string = _u8L("Percentage"); + + std::string longest_used_filament_string; + for (double item : used_filaments_m) { + char buffer[64]; + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item); + if (::strlen(buffer) > longest_used_filament_string.length()) + longest_used_filament_string = buffer; + } + + offsets = calculate_offsets(labels, times, { _u8L("Feature type"), _u8L("Time"), longest_percentage_string, longest_used_filament_string }, icon_size); + } + + // get used filament (meters and grams) from used volume in respect to the active extruder + auto get_used_filament_from_volume = [imperial_units](double volume, int extruder_id) { + const std::vector& filament_presets = wxGetApp().preset_bundle->filament_presets; + const PresetCollection& filaments = wxGetApp().preset_bundle->filaments; + + double koef = imperial_units ? 1.0/ObjectManipulation::in_to_mm : 0.001; + + std::pair ret = { 0.0, 0.0 }; + if (const Preset* filament_preset = filaments.find_preset(filament_presets[extruder_id], false)) { + double filament_radius = 0.5 * filament_preset->config.opt_float("filament_diameter", 0); + double s = PI * sqr(filament_radius); + ret.first = volume / s * koef; + double filament_density = filament_preset->config.opt_float("filament_density", 0); + ret.second = volume * filament_density * 0.001; + } + return ret; + }; + + if (m_view_type == EViewType::Tool) { + // calculate used filaments data + for (size_t extruder_id : m_extruder_ids) { + if (m_print_statistics.volumes_per_extruder.find(extruder_id) == m_print_statistics.volumes_per_extruder.end()) + continue; + double volume = m_print_statistics.volumes_per_extruder.at(extruder_id); + + auto [used_filament_m, used_filament_g] = get_used_filament_from_volume(volume, extruder_id); + used_filaments_m.push_back(used_filament_m); + used_filaments_g.push_back(used_filament_g); + } + + std::string longest_used_filament_string; + for (double item : used_filaments_m) { + char buffer[64]; + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item); + if (::strlen(buffer) > longest_used_filament_string.length()) + longest_used_filament_string = buffer; + } + + offsets = calculate_offsets(labels, times, { "Extruder NNN", longest_used_filament_string }, icon_size); } // extrusion paths section -> title @@ -4287,7 +2932,7 @@ void GCodeViewer::render_legend() const { case EViewType::FeatureType: { - append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage") }, offsets); + append_headers({ _u8L("Feature type"), _u8L("Time"), _u8L("Percentage"), _u8L("Used filament") }, offsets); break; } case EViewType::Height: { imgui.title(_u8L("Height (mm)")); break; } @@ -4296,7 +2941,11 @@ void GCodeViewer::render_legend() const case EViewType::FanSpeed: { imgui.title(_u8L("Fan Speed (%)")); break; } case EViewType::Temperature: { imgui.title(_u8L("Temperature (°C)")); break; } case EViewType::VolumetricRate: { imgui.title(_u8L("Volumetric flow rate (mm³/s)")); break; } - case EViewType::Tool: { imgui.title(_u8L("Tool")); break; } + case EViewType::Tool: + { + append_headers({ _u8L("Tool"), _u8L("Used filament") }, offsets); + break; + } case EViewType::ColorPrint: { imgui.title(_u8L("Color Print")); break; } default: { break; } } @@ -4310,9 +2959,9 @@ void GCodeViewer::render_legend() const ExtrusionRole role = m_roles[i]; if (role >= erCount) continue; - bool visible = is_visible(role); + const bool visible = is_visible(role); append_item(EItemType::Rect, Extrusion_Role_Colors[static_cast(role)], labels[i], - visible, times[i], percents[i], max_percent, offsets, [this, role, visible]() { + visible, times[i], percents[i], max_percent, offsets, used_filaments_m[i], used_filaments_g[i], [this, role, visible]() { Extrusions* extrusions = const_cast(&m_extrusions); extrusions->role_visibility_flags = visible ? extrusions->role_visibility_flags & ~(1 << role) : extrusions->role_visibility_flags | (1 << role); // update buffers' render paths @@ -4334,16 +2983,33 @@ void GCodeViewer::render_legend() const case EViewType::Tool: { // shows only extruders actually used - for (unsigned char i : m_extruder_ids) { - append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1)); + size_t i = 0; + for (unsigned char extruder_id : m_extruder_ids) { + append_item(EItemType::Rect, m_tool_colors[extruder_id], _u8L("Extruder") + " " + std::to_string(extruder_id + 1), + true, "", 0.0f, 0.0f, offsets, used_filaments_m[i], used_filaments_g[i]); + i++; } break; } case EViewType::ColorPrint: { +#if ENABLE_SCROLLABLE_LEGEND const std::vector& custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; + size_t total_items = 1; + for (unsigned char i : m_extruder_ids) { + total_items += color_print_ranges(i, custom_gcode_per_print_z).size(); + } + + const bool need_scrollable = static_cast(total_items) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; + + // add scrollable region, if needed + if (need_scrollable) + ImGui::BeginChild("color_prints", { -1.0f, child_height }, false); +#else + const std::vector& custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; +#endif // ENABLE_SCROLLABLE_LEGEND if (m_extruders_count == 1) { // single extruder use case - std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); + const std::vector>> cp_values = color_print_ranges(0, custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode append_item(EItemType::Rect, m_tool_colors.front(), _u8L("Default color")); @@ -4366,7 +3032,7 @@ void GCodeViewer::render_legend() const else { // multi extruder use case // shows only extruders actually used for (unsigned char i : m_extruder_ids) { - std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); + const std::vector>> cp_values = color_print_ranges(i, custom_gcode_per_print_z); const int items_cnt = static_cast(cp_values.size()); if (items_cnt == 0) { // There are no color changes, but there are some pause print or custom Gcode append_item(EItemType::Rect, m_tool_colors[i], _u8L("Extruder") + " " + std::to_string(i + 1) + " " + _u8L("default color")); @@ -4392,6 +3058,10 @@ void GCodeViewer::render_legend() const } } } +#if ENABLE_SCROLLABLE_LEGEND + if (need_scrollable) + ImGui::EndChild(); +#endif // ENABLE_SCROLLABLE_LEGEND break; } @@ -4417,10 +3087,11 @@ void GCodeViewer::render_legend() const Color color1; Color color2; Times times; + std::pair used_filament {0.0f, 0.0f}; }; using PartialTimes = std::vector; - auto generate_partial_times = [this](const TimesList& times) { + auto generate_partial_times = [this, get_used_filament_from_volume](const TimesList& times, const std::vector& used_filaments) { PartialTimes items; std::vector custom_gcode_per_print_z = wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes; @@ -4430,6 +3101,7 @@ void GCodeViewer::render_legend() const last_color[i] = m_tool_colors[i]; } int last_extruder_id = 1; + int color_change_idx = 0; for (const auto& time_rec : times) { switch (time_rec.first) { @@ -4445,14 +3117,14 @@ void GCodeViewer::render_legend() const case CustomGCode::ColorChange: { auto it = std::find_if(custom_gcode_per_print_z.begin(), custom_gcode_per_print_z.end(), [time_rec](const CustomGCode::Item& item) { return item.type == time_rec.first; }); if (it != custom_gcode_per_print_z.end()) { - items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second }); + items.push_back({ PartialTime::EType::Print, it->extruder, last_color[it->extruder - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], it->extruder-1) }); items.push_back({ PartialTime::EType::ColorChange, it->extruder, last_color[it->extruder - 1], decode_color(it->color), time_rec.second }); last_color[it->extruder - 1] = decode_color(it->color); last_extruder_id = it->extruder; custom_gcode_per_print_z.erase(it); } else - items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], Color(), time_rec.second }); + items.push_back({ PartialTime::EType::Print, last_extruder_id, last_color[last_extruder_id - 1], Color(), time_rec.second, get_used_filament_from_volume(used_filaments[color_change_idx++], last_extruder_id -1) }); break; } @@ -4463,7 +3135,7 @@ void GCodeViewer::render_legend() const return items; }; - auto append_color_change = [&imgui](const Color& color1, const Color& color2, const std::array& offsets, const Times& times) { + auto append_color_change = [&imgui](const Color& color1, const Color& color2, const std::array& offsets, const Times& times) { imgui.text(_u8L("Color change")); ImGui::SameLine(); @@ -4482,7 +3154,7 @@ void GCodeViewer::render_legend() const imgui.text(short_time(get_time_dhms(times.second - times.first))); }; - auto append_print = [&imgui](const Color& color, const std::array& offsets, const Times& times) { + auto append_print = [&imgui, imperial_units](const Color& color, const std::array& offsets, const Times& times, std::pair used_filament) { imgui.text(_u8L("Print")); ImGui::SameLine(); @@ -4498,9 +3170,19 @@ void GCodeViewer::render_legend() const imgui.text(short_time(get_time_dhms(times.second))); ImGui::SameLine(offsets[1]); imgui.text(short_time(get_time_dhms(times.first))); + if (used_filament.first > 0.0f) { + char buffer[64]; + ImGui::SameLine(offsets[2]); + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", used_filament.first); + imgui.text(buffer); + + ImGui::SameLine(offsets[3]); + ::sprintf(buffer, "%.2f g", used_filament.second); + imgui.text(buffer); + } }; - PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times); + PartialTimes partial_times = generate_partial_times(time_mode.custom_gcode_times, m_print_statistics.volumes_per_color_change); if (!partial_times.empty()) { labels.clear(); times.clear(); @@ -4514,15 +3196,34 @@ void GCodeViewer::render_legend() const } times.push_back(short_time(get_time_dhms(item.times.second))); } - offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time") }, 2.0f * icon_size); + + + std::string longest_used_filament_string; + for (const PartialTime& item : partial_times) { + if (item.used_filament.first > 0.0f) { + char buffer[64]; + ::sprintf(buffer, imperial_units ? "%.2f in" : "%.2f m", item.used_filament.first); + if (::strlen(buffer) > longest_used_filament_string.length()) + longest_used_filament_string = buffer; + } + } + + offsets = calculate_offsets(labels, times, { _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), longest_used_filament_string }, 2.0f * icon_size); ImGui::Spacing(); - append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration") }, offsets); + append_headers({ _u8L("Event"), _u8L("Remaining time"), _u8L("Duration"), _u8L("Used filament") }, offsets); +#if ENABLE_SCROLLABLE_LEGEND + const bool need_scrollable = static_cast(partial_times.size()) * (icon_size + ImGui::GetStyle().ItemSpacing.y) > child_height; + if (need_scrollable) + // add scrollable region + ImGui::BeginChild("events", { -1.0f, child_height }, false); +#endif // ENABLE_SCROLLABLE_LEGEND + for (const PartialTime& item : partial_times) { switch (item.type) { case PartialTime::EType::Print: { - append_print(item.color1, offsets, item.times); + append_print(item.color1, offsets, item.times, item.used_filament); break; } case PartialTime::EType::Pause: { @@ -4537,6 +3238,11 @@ void GCodeViewer::render_legend() const } } } + +#if ENABLE_SCROLLABLE_LEGEND + if (need_scrollable) + ImGui::EndChild(); +#endif // ENABLE_SCROLLABLE_LEGEND } } @@ -4680,46 +3386,50 @@ void GCodeViewer::render_legend() const } // total estimated printing time section +#if ENABLE_SCROLLABLE_LEGEND + if (show_estimated_time) { +#else if (time_mode.time > 0.0f && (m_view_type == EViewType::FeatureType || (m_view_type == EViewType::ColorPrint && !time_mode.custom_gcode_times.empty()))) { ImGui::Spacing(); +#endif // ENABLE_SCROLLABLE_LEGEND ImGui::Spacing(); - ImGui::PushStyleColor(ImGuiCol_Separator, { 1.0f, 1.0f, 1.0f, 1.0f }); - ImGui::Separator(); - ImGui::PopStyleColor(); - ImGui::Spacing(); - - ImGui::AlignTextToFramePadding(); + std::string time_title = _u8L("Estimated printing times"); switch (m_time_estimate_mode) { - case PrintEstimatedTimeStatistics::ETimeMode::Normal: - { - imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Normal mode") + "]:"); - break; + case PrintEstimatedStatistics::ETimeMode::Normal: { time_title += " [" + _u8L("Normal mode") + "]:"; break; } + case PrintEstimatedStatistics::ETimeMode::Stealth: { time_title += " [" + _u8L("Stealth mode") + "]:"; break; } + default: { assert(false); break; } } - case PrintEstimatedTimeStatistics::ETimeMode::Stealth: - { - imgui.text(_u8L("Estimated printing time") + " [" + _u8L("Stealth mode") + "]:"); - break; - } - default : { assert(false); break; } - } - ImGui::SameLine(); + + imgui.title(time_title); + + std::string first_str = _u8L("First layer"); + std::string total_str = _u8L("Total"); + + float max_len = 10.0f + ImGui::GetStyle().ItemSpacing.x + std::max(ImGui::CalcTextSize(first_str.c_str()).x, ImGui::CalcTextSize(total_str.c_str()).x); + + imgui.text(first_str + ":"); + ImGui::SameLine(max_len); + imgui.text(short_time(get_time_dhms(time_mode.layers_times.front()))); + + imgui.text(total_str + ":"); + ImGui::SameLine(max_len); imgui.text(short_time(get_time_dhms(time_mode.time))); - auto show_mode_button = [this, &imgui](const wxString& label, PrintEstimatedTimeStatistics::ETimeMode mode) { + auto show_mode_button = [this, &imgui](const wxString& label, PrintEstimatedStatistics::ETimeMode mode) { bool show = false; - for (size_t i = 0; i < m_time_statistics.modes.size(); ++i) { + for (size_t i = 0; i < m_print_statistics.modes.size(); ++i) { if (i != static_cast(mode) && - short_time(get_time_dhms(m_time_statistics.modes[static_cast(mode)].time)) != short_time(get_time_dhms(m_time_statistics.modes[i].time))) { + short_time(get_time_dhms(m_print_statistics.modes[static_cast(mode)].time)) != short_time(get_time_dhms(m_print_statistics.modes[i].time))) { show = true; break; } } - if (show && m_time_statistics.modes[static_cast(mode)].roles_times.size() > 0) { + if (show && m_print_statistics.modes[static_cast(mode)].roles_times.size() > 0) { if (imgui.button(label)) { - *const_cast(&m_time_estimate_mode) = mode; + *const_cast(&m_time_estimate_mode) = mode; wxGetApp().plater()->get_current_canvas3D()->set_as_dirty(); wxGetApp().plater()->get_current_canvas3D()->request_extra_frame(); } @@ -4727,12 +3437,12 @@ void GCodeViewer::render_legend() const }; switch (m_time_estimate_mode) { - case PrintEstimatedTimeStatistics::ETimeMode::Normal: { - show_mode_button(_L("Show stealth mode"), PrintEstimatedTimeStatistics::ETimeMode::Stealth); + case PrintEstimatedStatistics::ETimeMode::Normal: { + show_mode_button(_L("Show stealth mode"), PrintEstimatedStatistics::ETimeMode::Stealth); break; } - case PrintEstimatedTimeStatistics::ETimeMode::Stealth: { - show_mode_button(_L("Show normal mode"), PrintEstimatedTimeStatistics::ETimeMode::Normal); + case PrintEstimatedStatistics::ETimeMode::Stealth: { + show_mode_button(_L("Show normal mode"), PrintEstimatedStatistics::ETimeMode::Normal); break; } default : { assert(false); break; } @@ -4814,9 +3524,7 @@ void GCodeViewer::render_statistics() const add_counter(std::string("Multi GL_POINTS:"), m_statistics.gl_multi_points_calls_count); add_counter(std::string("Multi GL_LINES:"), m_statistics.gl_multi_lines_calls_count); add_counter(std::string("Multi GL_TRIANGLES:"), m_statistics.gl_multi_triangles_calls_count); -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS add_counter(std::string("GL_TRIANGLES:"), m_statistics.gl_triangles_calls_count); -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS } if (ImGui::CollapsingHeader("CPU memory")) { @@ -4863,15 +3571,9 @@ void GCodeViewer::log_memory_used(const std::string& label, int64_t additional) } int64_t layers_size = SLIC3R_STDVEC_MEMSIZE(m_layers.get_zs(), double); layers_size += SLIC3R_STDVEC_MEMSIZE(m_layers.get_endpoints(), Layers::Endpoints); -#if ENABLE_SPLITTED_VERTEX_BUFFER BOOST_LOG_TRIVIAL(trace) << label << "(" << format_memsize_MB(additional + paths_size + render_paths_size + layers_size) << ");" << log_memory_info(); -#else - BOOST_LOG_TRIVIAL(trace) << label - << format_memsize_MB(additional + paths_size + render_paths_size + layers_size) - << log_memory_info(); -#endif // ENABLE_SPLITTED_VERTEX_BUFFER } } diff --git a/src/slic3r/GUI/GCodeViewer.hpp b/src/slic3r/GUI/GCodeViewer.hpp index 2ccda6f5d..931f01b68 100644 --- a/src/slic3r/GUI/GCodeViewer.hpp +++ b/src/slic3r/GUI/GCodeViewer.hpp @@ -23,17 +23,11 @@ namespace GUI { class GCodeViewer { -#if ENABLE_SPLITTED_VERTEX_BUFFER using IBufferType = unsigned short; -#endif // ENABLE_SPLITTED_VERTEX_BUFFER using Color = std::array; using VertexBuffer = std::vector; -#if ENABLE_SPLITTED_VERTEX_BUFFER using MultiVertexBuffer = std::vector; using IndexBuffer = std::vector; -#else - using IndexBuffer = std::vector; -#endif // ENABLE_SPLITTED_VERTEX_BUFFER using MultiIndexBuffer = std::vector; static const std::vector Extrusion_Role_Colors; @@ -69,24 +63,17 @@ class GCodeViewer }; EFormat format{ EFormat::Position }; -#if ENABLE_SPLITTED_VERTEX_BUFFER // vbos id std::vector vbos; // sizes of the buffers, in bytes, used in export to obj std::vector sizes; -#else - // vbo id - unsigned int id{ 0 }; -#endif // ENABLE_SPLITTED_VERTEX_BUFFER // count of vertices, updated after data are sent to gpu size_t count{ 0 }; size_t data_size_bytes() const { return count * vertex_size_bytes(); } -#if ENABLE_SPLITTED_VERTEX_BUFFER // We set 65536 as max count of vertices inside a vertex buffer to allow // to use unsigned short in place of unsigned int for indices in the index buffer, to save memory size_t max_size_bytes() const { return 65536 * vertex_size_bytes(); } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER size_t vertex_size_floats() const { return position_size_floats() + normal_size_floats(); } size_t vertex_size_bytes() const { return vertex_size_floats() * sizeof(float); } @@ -116,15 +103,10 @@ class GCodeViewer // ibo buffer containing indices data (for lines/triangles) used to render a specific toolpath type struct IBuffer { -#if ENABLE_SPLITTED_VERTEX_BUFFER // id of the associated vertex buffer unsigned int vbo{ 0 }; // ibo id unsigned int ibo{ 0 }; -#else - // ibo id - unsigned int id{ 0 }; -#endif // ENABLE_SPLITTED_VERTEX_BUFFER // count of indices, updated after data are sent to gpu size_t count{ 0 }; @@ -148,7 +130,6 @@ class GCodeViewer Vec3f position{ Vec3f::Zero() }; }; -#if ENABLE_SPLITTED_VERTEX_BUFFER struct Sub_Path { Endpoint first; @@ -158,14 +139,9 @@ class GCodeViewer return first.s_id <= s_id && s_id <= last.s_id; } }; -#endif // ENABLE_SPLITTED_VERTEX_BUFFER EMoveType type{ EMoveType::Noop }; ExtrusionRole role{ erNone }; -#if !ENABLE_SPLITTED_VERTEX_BUFFER - Endpoint first; - Endpoint last; -#endif // !ENABLE_SPLITTED_VERTEX_BUFFER float delta_extruder{ 0.0f }; float height{ 0.0f }; float width{ 0.0f }; @@ -175,12 +151,9 @@ class GCodeViewer float volumetric_rate{ 0.0f }; unsigned char extruder_id{ 0 }; unsigned char cp_color_id{ 0 }; -#if ENABLE_SPLITTED_VERTEX_BUFFER std::vector sub_paths; -#endif // ENABLE_SPLITTED_VERTEX_BUFFER bool matches(const GCodeProcessor::MoveVertex& move) const; -#if ENABLE_SPLITTED_VERTEX_BUFFER size_t vertices_count() const { return sub_paths.empty() ? 0 : sub_paths.back().last.s_id - sub_paths.front().first.s_id + 1; } @@ -202,19 +175,13 @@ class GCodeViewer Endpoint endpoint = { b_id, i_id, s_id, move.position }; sub_paths.push_back({ endpoint , endpoint }); } -#else - size_t vertices_count() const { return last.s_id - first.s_id + 1; } - bool contains(size_t id) const { return first.s_id <= id && id <= last.s_id; } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER }; // Used to batch the indices needed to render the paths struct RenderPath { -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS // Index of the parent tbuffer unsigned char tbuffer_id; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS // Render path property Color color; // Index of the buffer in TBuffer::indices @@ -224,7 +191,6 @@ class GCodeViewer unsigned int path_id; std::vector sizes; std::vector offsets; // use size_t because we need an unsigned integer whose size matches pointer's size (used in the call glMultiDrawElements()) -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS bool contains(size_t offset) const { for (size_t i = 0; i < offsets.size(); ++i) { if (offsets[i] <= offset && offset <= offsets[i] + static_cast(sizes[i] * sizeof(IBufferType))) @@ -232,7 +198,6 @@ class GCodeViewer } return false; } -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS }; // // for unordered_set implementation of render_paths // struct RenderPathPropertyHash { @@ -244,10 +209,8 @@ class GCodeViewer // }; struct RenderPathPropertyLower { bool operator() (const RenderPath &l, const RenderPath &r) const { -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS if (l.tbuffer_id < r.tbuffer_id) return true; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS for (int i = 0; i < 3; ++i) { if (l.color[i] < r.color[i]) return true; @@ -259,11 +222,7 @@ class GCodeViewer }; struct RenderPathPropertyEqual { bool operator() (const RenderPath &l, const RenderPath &r) const { -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS return l.tbuffer_id == r.tbuffer_id && l.ibuffer_id == r.ibuffer_id && l.color == r.color; -#else - return l.color == r.color && l.ibuffer_id == r.ibuffer_id; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS } }; @@ -295,7 +254,6 @@ class GCodeViewer // s_id index of first vertex contained in this->vertices void add_path(const GCodeProcessor::MoveVertex& move, unsigned int b_id, size_t i_id, size_t s_id); -#if ENABLE_SPLITTED_VERTEX_BUFFER unsigned int max_vertices_per_segment() const { switch (render_primitive_type) { @@ -308,24 +266,16 @@ class GCodeViewer size_t max_vertices_per_segment_size_floats() const { return vertices.vertex_size_floats() * static_cast(max_vertices_per_segment()); } size_t max_vertices_per_segment_size_bytes() const { return max_vertices_per_segment_size_floats() * sizeof(float); } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER unsigned int indices_per_segment() const { switch (render_primitive_type) { case ERenderPrimitiveType::Point: { return 1; } case ERenderPrimitiveType::Line: { return 2; } -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS case ERenderPrimitiveType::Triangle: { return 30; } // 3 indices x 10 triangles -#else - case ERenderPrimitiveType::Triangle: { return 42; } // 3 indices x 14 triangles -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS default: { return 0; } } } -#if ENABLE_SPLITTED_VERTEX_BUFFER size_t indices_per_segment_size_bytes() const { return static_cast(indices_per_segment() * sizeof(IBufferType)); } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS unsigned int max_indices_per_segment() const { switch (render_primitive_type) { @@ -336,26 +286,10 @@ class GCodeViewer } } size_t max_indices_per_segment_size_bytes() const { return max_indices_per_segment() * sizeof(IBufferType); } -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS -#if ENABLE_SPLITTED_VERTEX_BUFFER bool has_data() const { return !vertices.vbos.empty() && vertices.vbos.front() != 0 && !indices.empty() && indices.front().ibo != 0; } -#else - unsigned int start_segment_vertex_offset() const { return 0; } - unsigned int end_segment_vertex_offset() const { - switch (render_primitive_type) - { - case ERenderPrimitiveType::Point: { return 0; } - case ERenderPrimitiveType::Line: { return 1; } - case ERenderPrimitiveType::Triangle: { return 36; } // 1st vertex of 13th triangle - default: { return 0; } - } - } - - bool has_data() const { return vertices.id != 0 && !indices.empty() && indices.front().id != 0; } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER }; // helper to render shells @@ -434,11 +368,9 @@ class GCodeViewer size_t first{ 0 }; size_t last{ 0 }; -#if ENABLE_SPLITTED_VERTEX_BUFFER bool operator == (const Endpoints& other) const { return first == other.first && last == other.last; } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER }; private: @@ -464,7 +396,6 @@ class GCodeViewer double get_z_at(unsigned int id) const { return (id < m_zs.size()) ? m_zs[id] : 0.0; } Endpoints get_endpoints_at(unsigned int id) const { return (id < m_endpoints.size()) ? m_endpoints[id] : Endpoints(); } -#if ENABLE_SPLITTED_VERTEX_BUFFER bool operator != (const Layers& other) const { if (m_zs != other.m_zs) return true; @@ -473,10 +404,8 @@ class GCodeViewer return false; } -#endif // ENABLE_SPLITTED_VERTEX_BUFFER }; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS // used to render the toolpath caps of the current sequential range // (i.e. when sliding on the horizontal slider) struct SequentialRangeCap @@ -491,7 +420,6 @@ class GCodeViewer void reset(); size_t indices_count() const { return 6; } }; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS #if ENABLE_GCODE_VIEWER_STATISTICS struct Statistics @@ -508,9 +436,7 @@ class GCodeViewer int64_t gl_multi_points_calls_count{ 0 }; int64_t gl_multi_lines_calls_count{ 0 }; int64_t gl_multi_triangles_calls_count{ 0 }; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS int64_t gl_triangles_calls_count{ 0 }; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS // memory int64_t results_size{ 0 }; int64_t total_vertices_gpu_size{ 0 }; @@ -547,9 +473,7 @@ class GCodeViewer gl_multi_points_calls_count = 0; gl_multi_lines_calls_count = 0; gl_multi_triangles_calls_count = 0; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS gl_triangles_calls_count = 0; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS } void reset_sizes() { @@ -696,16 +620,14 @@ private: Shells m_shells; EViewType m_view_type{ EViewType::FeatureType }; bool m_legend_enabled{ true }; - PrintEstimatedTimeStatistics m_time_statistics; - PrintEstimatedTimeStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedTimeStatistics::ETimeMode::Normal }; + PrintEstimatedStatistics m_print_statistics; + PrintEstimatedStatistics::ETimeMode m_time_estimate_mode{ PrintEstimatedStatistics::ETimeMode::Normal }; #if ENABLE_GCODE_VIEWER_STATISTICS Statistics m_statistics; #endif // ENABLE_GCODE_VIEWER_STATISTICS std::array m_detected_point_sizes = { 0.0f, 0.0f }; GCodeProcessor::Result::SettingsIds m_settings_ids; -#if ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS std::array m_sequential_range_caps; -#endif // ENABLE_REDUCED_TOOLPATHS_SEGMENT_CAPS public: GCodeViewer(); @@ -722,9 +644,7 @@ public: void render() const; bool has_data() const { return !m_roles.empty(); } -#if ENABLE_SPLITTED_VERTEX_BUFFER bool can_export_toolpaths() const; -#endif // ENABLE_SPLITTED_VERTEX_BUFFER const BoundingBoxf3& get_paths_bounding_box() const { return m_paths_bounding_box; } const BoundingBoxf3& get_max_bounding_box() const { return m_max_bounding_box; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 26c4a314c..ea3bec131 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -134,27 +134,9 @@ void Size::set_scale_factor(int scale_factor) m_scale_factor = scale_factor; } -GLCanvas3D::LayersEditing::LayersEditing() - : m_enabled(false) - , m_z_texture_id(0) - , m_model_object(nullptr) - , m_object_max_z(0.f) - , m_slicing_parameters(nullptr) - , m_layer_height_profile_modified(false) - , m_adaptive_quality(0.5f) - , state(Unknown) - , band_width(2.0f) - , strength(0.005f) - , last_object_id(-1) - , last_z(0.0f) - , last_action(LAYER_HEIGHT_EDIT_ACTION_INCREASE) -{ -} - GLCanvas3D::LayersEditing::~LayersEditing() { - if (m_z_texture_id != 0) - { + if (m_z_texture_id != 0) { glsafe(::glDeleteTextures(1, &m_z_texture_id)); m_z_texture_id = 0; } @@ -186,11 +168,18 @@ void GLCanvas3D::LayersEditing::set_config(const DynamicPrintConfig* config) void GLCanvas3D::LayersEditing::select_object(const Model &model, int object_id) { const ModelObject *model_object_new = (object_id >= 0) ? model.objects[object_id] : nullptr; +#if ENABLE_ALLOW_NEGATIVE_Z + // Maximum height of an object changes when the object gets rotated or scaled. + // Changing maximum height of an object will invalidate the layer heigth editing profile. + // m_model_object->bounding_box() is cached, therefore it is cheap even if this method is called frequently. + const float new_max_z = (model_object_new == nullptr) ? 0.0f : static_cast(model_object_new->bounding_box().max.z()); +#else // Maximum height of an object changes when the object gets rotated or scaled. // Changing maximum height of an object will invalidate the layer heigth editing profile. // m_model_object->raw_bounding_box() is cached, therefore it is cheap even if this method is called frequently. float new_max_z = (model_object_new == nullptr) ? 0.f : model_object_new->raw_bounding_box().size().z(); - if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z || +#endif // ENABLE_ALLOW_NEGATIVE_Z + if (m_model_object != model_object_new || this->last_object_id != object_id || m_object_max_z != new_max_z || (model_object_new != nullptr && m_model_object->id() != model_object_new->id())) { m_layer_height_profile.clear(); m_layer_height_profile_modified = false; @@ -218,7 +207,7 @@ void GLCanvas3D::LayersEditing::set_enabled(bool enabled) m_enabled = is_allowed() && enabled; } -float GLCanvas3D::LayersEditing::s_overelay_window_width; +float GLCanvas3D::LayersEditing::s_overlay_window_width; void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const { @@ -302,7 +291,7 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const if (imgui.button(_L("Reset"))) wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE)); - GLCanvas3D::LayersEditing::s_overelay_window_width = ImGui::GetWindowSize().x /*+ (float)m_layers_texture.width/4*/; + GLCanvas3D::LayersEditing::s_overlay_window_width = ImGui::GetWindowSize().x /*+ (float)m_layers_texture.width/4*/; imgui.end(); const Rect& bar_rect = get_bar_rect_viewport(canvas); @@ -319,7 +308,7 @@ float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas) float t = rect.get_top(); float b = rect.get_bottom(); - return ((rect.get_left() <= x) && (x <= rect.get_right()) && (t <= y) && (y <= b)) ? + return (rect.get_left() <= x && x <= rect.get_right() && t <= y && y <= b) ? // Inside the bar. (b - y - 1.0f) / (b - t - 1.0f) : // Outside the bar. @@ -329,7 +318,7 @@ float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas) bool GLCanvas3D::LayersEditing::bar_rect_contains(const GLCanvas3D& canvas, float x, float y) { const Rect& rect = get_bar_rect_screen(canvas); - return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom()); + return rect.get_left() <= x && x <= rect.get_right() && rect.get_top() <= y && y <= rect.get_bottom(); } Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas) @@ -338,7 +327,7 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas) float w = (float)cnv_size.get_width(); float h = (float)cnv_size.get_height(); - return Rect(w - thickness_bar_width(canvas), 0.0f, w, h); + return { w - thickness_bar_width(canvas), 0.0f, w, h }; } Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) @@ -347,7 +336,7 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas) float half_w = 0.5f * (float)cnv_size.get_width(); float half_h = 0.5f * (float)cnv_size.get_height(); float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom); + return { (half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom }; } bool GLCanvas3D::LayersEditing::is_initialized() const @@ -365,11 +354,12 @@ std::string GLCanvas3D::LayersEditing::get_tooltip(const GLCanvas3D& canvas) con float h = 0.0f; for (size_t i = m_layer_height_profile.size() - 2; i >= 2; i -= 2) { - float zi = m_layer_height_profile[i]; - float zi_1 = m_layer_height_profile[i - 2]; + const float zi = static_cast(m_layer_height_profile[i]); + const float zi_1 = static_cast(m_layer_height_profile[i - 2]); if (zi_1 <= z && z <= zi) { float dz = zi - zi_1; - h = (dz != 0.0f) ? lerp(m_layer_height_profile[i - 1], m_layer_height_profile[i + 1], (z - zi_1) / dz) : m_layer_height_profile[i + 1]; + h = (dz != 0.0f) ? static_cast(lerp(m_layer_height_profile[i - 1], m_layer_height_profile[i + 1], (z - zi_1) / dz)) : + static_cast(m_layer_height_profile[i + 1]); break; } } @@ -398,10 +388,10 @@ void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3 glsafe(::glBindTexture(GL_TEXTURE_2D, m_z_texture_id)); // Render the color bar - float l = bar_rect.get_left(); - float r = bar_rect.get_right(); - float t = bar_rect.get_top(); - float b = bar_rect.get_bottom(); + const float l = bar_rect.get_left(); + const float r = bar_rect.get_right(); + const float t = bar_rect.get_top(); + const float b = bar_rect.get_bottom(); ::glBegin(GL_QUADS); ::glNormal3f(0.0f, 0.0f, 1.0f); @@ -564,7 +554,7 @@ void GLCanvas3D::LayersEditing::accept_changes(GLCanvas3D& canvas) { if (last_object_id >= 0) { if (m_layer_height_profile_modified) { - wxGetApp().plater()->take_snapshot(_(L("Variable layer height - Manual edit"))); + wxGetApp().plater()->take_snapshot(_L("Variable layer height - Manual edit")); const_cast(m_model_object)->layer_height_profile.set(m_layer_height_profile); canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); wxGetApp().obj_list()->update_info_items(last_object_id); @@ -577,7 +567,7 @@ void GLCanvas3D::LayersEditing::update_slicing_parameters() { if (m_slicing_parameters == nullptr) { m_slicing_parameters = new SlicingParameters(); - *m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z); + *m_slicing_parameters = PrintObject::slicing_parameters(*m_config, *m_model_object, m_object_max_z); } } @@ -614,278 +604,6 @@ GLCanvas3D::Mouse::Mouse() { } -#if !ENABLE_WARNING_TEXTURE_REMOVAL -const unsigned char GLCanvas3D::WarningTexture::Background_Color[3] = { 120, 120, 120 };//{ 9, 91, 134 }; -const unsigned char GLCanvas3D::WarningTexture::Opacity = 255; - -GLCanvas3D::WarningTexture::WarningTexture() - : GUI::GLTexture() - , m_original_width(0) - , m_original_height(0) -{ -} - -void GLCanvas3D::WarningTexture::activate(WarningTexture::Warning warning, bool state, const GLCanvas3D& canvas) -{ - // Since we have NotificationsManager.hpp the warning textures are no loger needed. - // However i have left the infrastructure here and only commented the rendering. - // The plater warning / error notifications are added and closed from here. - - std::string text; - bool error = false; - switch (warning) { - case ObjectOutside: text = _u8L("An object outside the print area was detected."); break; - case ToolpathOutside: text = _u8L("A toolpath outside the print area was detected."); error = true; break; - case SlaSupportsOutside: text = _u8L("SLA supports outside the print area were detected."); error = true; break; - case SomethingNotShown: text = _u8L("Some objects are not visible."); break; - case ObjectClashed: - text = _u8L( "An object outside the print area was detected.\n" - "Resolve the current problem to continue slicing."); - error = true; - break; - } - BOOST_LOG_TRIVIAL(error) << state << " : " << text ; - auto ¬ification_manager = *wxGetApp().plater()->get_notification_manager(); - if (state) { - if(error) - notification_manager.push_plater_error_notification(text); - else - notification_manager.push_plater_warning_notification(text); - } else { - if (error) - notification_manager.close_plater_error_notification(text); - else - notification_manager.close_plater_warning_notification(text); - } - - /* - auto it = std::find(m_warnings.begin(), m_warnings.end(), warning); - - if (state) { - if (it != m_warnings.end()) // this warning is already set to be shown - return; - - m_warnings.push_back(warning); - std::sort(m_warnings.begin(), m_warnings.end()); - } - else { - if (it == m_warnings.end()) // deactivating something that is not active is an easy task - return; - - m_warnings.erase(it); - if (m_warnings.empty()) { // nothing remains to be shown - reset(); - m_msg_text = "";// save information for rescaling - return; - } - } - - // Look at the end of our vector and generate proper texture. - std::string text; - bool red_colored = false; - switch (m_warnings.back()) { - case ObjectOutside : text = L("An object outside the print area was detected"); break; - case ToolpathOutside : text = L("A toolpath outside the print area was detected"); break; - case SlaSupportsOutside : text = L("SLA supports outside the print area were detected"); break; - case SomethingNotShown : text = L("Some objects are not visible when editing supports"); break; - case ObjectClashed: { - text = L("An object outside the print area was detected\n" - "Resolve the current problem to continue slicing"); - red_colored = true; - break; - } - } - - generate(text, canvas, true, red_colored); // GUI::GLTexture::reset() is called at the beginning of generate(...) - - // save information for rescaling - m_msg_text = text; - m_is_colored_red = red_colored; - */ -} - - -#ifdef __WXMSW__ -static bool is_font_cleartype(const wxFont &font) -{ - // Native font description: on MSW, it is a version number plus the content of LOGFONT, separated by semicolon. - wxString font_desc = font.GetNativeFontInfoDesc(); - // Find the quality field. - wxString sep(";"); - size_t startpos = 0; - for (size_t i = 0; i < 12; ++ i) - startpos = font_desc.find(sep, startpos + 1); - ++ startpos; - size_t endpos = font_desc.find(sep, startpos); - int quality = wxAtoi(font_desc(startpos, endpos - startpos)); - return quality == CLEARTYPE_QUALITY; -} - -// ClearType produces renders, which are difficult to convert into an alpha blended OpenGL texture. -// Therefore it is better to disable it, though Vojtech found out, that the font returned with ClearType -// disabled is signifcantly thicker than the default ClearType font. -// This function modifies the font provided. -static void msw_disable_cleartype(wxFont &font) -{ - // Native font description: on MSW, it is a version number plus the content of LOGFONT, separated by semicolon. - wxString font_desc = font.GetNativeFontInfoDesc(); - // Find the quality field. - wxString sep(";"); - size_t startpos_weight = 0; - for (size_t i = 0; i < 5; ++ i) - startpos_weight = font_desc.find(sep, startpos_weight + 1); - ++ startpos_weight; - size_t endpos_weight = font_desc.find(sep, startpos_weight); - // Parse the weight field. - unsigned int weight = wxAtoi(font_desc(startpos_weight, endpos_weight - startpos_weight)); - size_t startpos = endpos_weight; - for (size_t i = 0; i < 6; ++ i) - startpos = font_desc.find(sep, startpos + 1); - ++ startpos; - size_t endpos = font_desc.find(sep, startpos); - int quality = wxAtoi(font_desc(startpos, endpos - startpos)); - if (quality == CLEARTYPE_QUALITY) { - // Replace the weight with a smaller value to compensate the weight of non ClearType font. - wxString sweight = std::to_string(weight * 2 / 4); - size_t len_weight = endpos_weight - startpos_weight; - wxString squality = std::to_string(ANTIALIASED_QUALITY); - font_desc.replace(startpos_weight, len_weight, sweight); - font_desc.replace(startpos + sweight.size() - len_weight, endpos - startpos, squality); - font.SetNativeFontInfo(font_desc); - wxString font_desc2 = font.GetNativeFontInfoDesc(); - } - wxString font_desc2 = font.GetNativeFontInfoDesc(); -} -#endif /* __WXMSW__ */ - -bool GLCanvas3D::WarningTexture::generate(const std::string& msg_utf8, const GLCanvas3D& canvas, bool compress, bool red_colored/* = false*/) -{ - reset(); - - if (msg_utf8.empty()) - return false; - - wxString msg = _(msg_utf8); - - wxMemoryDC memDC; - -#ifdef __WXMSW__ - // set scaled application normal font as default font - wxFont font = wxGetApp().normal_font(); -#else - // select default font - const float scale = canvas.get_canvas_size().get_scale_factor(); -#if ENABLE_RETINA_GL - // For non-visible or non-created window getBackingScaleFactor function return 0.0 value. - // And using of the zero scale causes a crash, when we trying to draw text to the (0,0) rectangle - // https://github.com/prusa3d/PrusaSlicer/issues/3916 - if (scale <= 0.0f) - return false; -#endif - wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT).Scale(scale); -#endif - - font.MakeLarger(); - font.MakeBold(); - memDC.SetFont(font); - - // calculates texture size - wxCoord w, h; - memDC.GetMultiLineTextExtent(msg, &w, &h); - - m_original_width = (int)w; - m_original_height = (int)h; - m_width = (int)next_highest_power_of_2((uint32_t)w); - m_height = (int)next_highest_power_of_2((uint32_t)h); - - // generates bitmap - wxBitmap bitmap(m_width, m_height); - - memDC.SelectObject(bitmap); - memDC.SetBackground(wxBrush(*wxBLACK)); - memDC.Clear(); - - // draw message - memDC.SetTextForeground(*wxRED); - memDC.DrawLabel(msg, wxRect(0,0, m_original_width, m_original_height), wxALIGN_CENTER); - - memDC.SelectObject(wxNullBitmap); - - // Convert the bitmap into a linear data ready to be loaded into the GPU. - wxImage image = bitmap.ConvertToImage(); - - // prepare buffer - std::vector data(4 * m_width * m_height, 0); - const unsigned char *src = image.GetData(); - for (int h = 0; h < m_height; ++h) { - unsigned char* dst = data.data() + 4 * h * m_width; - for (int w = 0; w < m_width; ++w) { - *dst++ = 255; - if (red_colored) { - *dst++ = 72; // 204 - *dst++ = 65; // 204 - } else { - *dst++ = 255; - *dst++ = 255; - } - *dst++ = (unsigned char)std::min(255, *src); - src += 3; - } - } - - // sends buffer to gpu - glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); - glsafe(::glGenTextures(1, &m_id)); - glsafe(::glBindTexture(GL_TEXTURE_2D, (GLuint)m_id)); - if (compress && GLEW_EXT_texture_compression_s3tc) - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); - else - glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); - glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); - - return true; -} - -void GLCanvas3D::WarningTexture::render(const GLCanvas3D& canvas) const -{ - if (m_warnings.empty()) - return; - - if (m_id > 0 && m_original_width > 0 && m_original_height > 0 && m_width > 0 && m_height > 0) { - const Size& cnv_size = canvas.get_canvas_size(); - float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); - float left = (-0.5f * (float)m_original_width) * inv_zoom; - float top = (-0.5f * (float)cnv_size.get_height() + (float)m_original_height + 2.0f) * inv_zoom; - float right = left + (float)m_original_width * inv_zoom; - float bottom = top - (float)m_original_height * inv_zoom; - - float uv_left = 0.0f; - float uv_top = 0.0f; - float uv_right = (float)m_original_width / (float)m_width; - float uv_bottom = (float)m_original_height / (float)m_height; - - GLTexture::Quad_UVs uvs; - uvs.left_top = { uv_left, uv_top }; - uvs.left_bottom = { uv_left, uv_bottom }; - uvs.right_bottom = { uv_right, uv_bottom }; - uvs.right_top = { uv_right, uv_top }; - - GLTexture::render_sub_texture(m_id, left, right, bottom, top, uvs); - } -} - -void GLCanvas3D::WarningTexture::msw_rescale(const GLCanvas3D& canvas) -{ - if (m_msg_text.empty()) - return; - - generate(m_msg_text, canvas, true, m_is_colored_red); -} -#endif // !ENABLE_WARNING_TEXTURE_REMOVAL - void GLCanvas3D::Labels::render(const std::vector& sorted_instances) const { if (!m_enabled || !is_shown()) @@ -1069,8 +787,6 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas ImGui::PopStyleVar(2); } -float GLCanvas3D::Slope::s_window_width; - wxDEFINE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_OBJECT_SELECT, SimpleEvent); wxDEFINE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent); @@ -1304,11 +1020,7 @@ void GLCanvas3D::reset_volumes() m_volumes.clear(); m_dirty = true; -#if ENABLE_WARNING_TEXTURE_REMOVAL _set_warning_notification(EWarning::ObjectOutside, false); -#else - _set_warning_texture(WarningTexture::ObjectOutside, false); -#endif // ENABLE_WARNING_TEXTURE_REMOVAL } int GLCanvas3D::check_volumes_outside_state() const @@ -1362,19 +1074,11 @@ void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject if (visible && !mo) toggle_sla_auxiliaries_visibility(true, mo, instance_idx); -#if ENABLE_WARNING_TEXTURE_REMOVAL if (!mo && !visible && !m_model->objects.empty() && (m_model->objects.size() > 1 || m_model->objects.front()->instances.size() > 1)) _set_warning_notification(EWarning::SomethingNotShown, true); if (!mo && visible) _set_warning_notification(EWarning::SomethingNotShown, false); -#else - if (!mo && !visible && !m_model->objects.empty() && (m_model->objects.size() > 1 || m_model->objects.front()->instances.size() > 1)) - _set_warning_texture(WarningTexture::SomethingNotShown, true); - - if (!mo && visible) - _set_warning_texture(WarningTexture::SomethingNotShown, false); -#endif // ENABLE_WARNING_TEXTURE_REMOVAL } void GLCanvas3D::update_instance_printable_state_for_object(const size_t obj_idx) @@ -1723,6 +1427,11 @@ void GLCanvas3D::render() } #endif // ENABLE_RENDER_STATISTICS +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + if (wxGetApp().is_editor() && wxGetApp().plater()->is_view3D_shown()) + wxGetApp().plater()->render_project_state_debug_window(); +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + #if ENABLE_CAMERA_STATISTICS camera.debug_render(); #endif // ENABLE_CAMERA_STATISTICS @@ -1956,7 +1665,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re m_reload_delayed = !m_canvas->IsShown() && !refresh_immediately && !force_full_scene_refresh; - PrinterTechnology printer_technology = m_process->current_printer_technology(); + PrinterTechnology printer_technology = current_printer_technology(); int volume_idx_wipe_tower_old = -1; // Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed). @@ -2246,31 +1955,18 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re bool fullyOut = false; const bool contained_min_one = m_volumes.check_outside_state(m_config, partlyOut, fullyOut); -#if ENABLE_WARNING_TEXTURE_REMOVAL _set_warning_notification(EWarning::ObjectClashed, partlyOut); _set_warning_notification(EWarning::ObjectOutside, fullyOut); if (printer_technology != ptSLA || !contained_min_one) _set_warning_notification(EWarning::SlaSupportsOutside, false); -#else - _set_warning_texture(WarningTexture::ObjectClashed, partlyOut); - _set_warning_texture(WarningTexture::ObjectOutside, fullyOut); - if(printer_technology != ptSLA || !contained_min_one) - _set_warning_texture(WarningTexture::SlaSupportsOutside, false); -#endif // ENABLE_WARNING_TEXTURE_REMOVAL post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, contained_min_one && !m_model->objects.empty() && !partlyOut)); } else { -#if ENABLE_WARNING_TEXTURE_REMOVAL _set_warning_notification(EWarning::ObjectOutside, false); _set_warning_notification(EWarning::ObjectClashed, false); _set_warning_notification(EWarning::SlaSupportsOutside, false); -#else - _set_warning_texture(WarningTexture::ObjectOutside, false); - _set_warning_texture(WarningTexture::ObjectClashed, false); - _set_warning_texture(WarningTexture::SlaSupportsOutside, false); -#endif // ENABLE_WARNING_TEXTURE_REMOVAL post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false)); } @@ -2313,11 +2009,7 @@ void GLCanvas3D::load_gcode_preview(const GCodeProcessor::Result& gcode_result) if (wxGetApp().is_editor()) { m_gcode_viewer.update_shells_color_by_extruder(m_config); -#if ENABLE_WARNING_TEXTURE_REMOVAL _set_warning_notification_if_needed(EWarning::ToolpathOutside); -#else - _show_warning_texture_if_needed(WarningTexture::ToolpathOutside); -#endif // ENABLE_WARNING_TEXTURE_REMOVAL } } @@ -2344,11 +2036,7 @@ void GLCanvas3D::load_sla_preview() this->reset_volumes(); _load_sla_shells(); _update_sla_shells_outside_state(); -#if ENABLE_WARNING_TEXTURE_REMOVAL _set_warning_notification_if_needed(EWarning::SlaSupportsOutside); -#else - _show_warning_texture_if_needed(WarningTexture::SlaSupportsOutside); -#endif // ENABLE_WARNING_TEXTURE_REMOVAL } } @@ -2369,11 +2057,7 @@ void GLCanvas3D::load_preview(const std::vector& str_tool_colors, c _load_print_object_toolpaths(*object, str_tool_colors, color_print_values); _update_toolpath_volumes_outside_state(); -#if ENABLE_WARNING_TEXTURE_REMOVAL _set_warning_notification_if_needed(EWarning::ToolpathOutside); -#else - _show_warning_texture_if_needed(WarningTexture::ToolpathOutside); -#endif // ENABLE_WARNING_TEXTURE_REMOVAL } void GLCanvas3D::bind_event_handlers() @@ -2519,9 +2203,19 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) #ifdef _WIN32 if (wxGetApp().app_config->get("use_legacy_3DConnexion") == "1") { #endif //_WIN32 +#ifdef __APPLE__ + // On OSX use Cmd+Shift+M to "Show/Hide 3Dconnexion devices settings dialog" + if ((evt.GetModifiers() & shiftMask) != 0) { +#endif // __APPLE__ Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller(); controller.show_settings_dialog(!controller.is_settings_dialog_shown()); m_dirty = true; +#ifdef __APPLE__ + } + else + // and Cmd+M to minimize application + wxGetApp().mainframe->Iconize(); +#endif // __APPLE__ #ifdef _WIN32 } #endif //_WIN32 @@ -3573,14 +3267,38 @@ void GLCanvas3D::do_move(const std::string& snapshot_type) wipe_tower_origin = v->get_volume_offset(); } +#if ENABLE_ALLOW_NEGATIVE_Z + // Fixes flying instances +#else // Fixes sinking/flying instances +#endif // ENABLE_ALLOW_NEGATIVE_Z for (const std::pair& i : done) { ModelObject* m = m_model->objects[i.first]; +#if ENABLE_ALLOW_NEGATIVE_Z + double shift_z = m->get_instance_min_z(i.second); +#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA + if (current_printer_technology() == ptSLA || shift_z > 0.0) { +#else + if (shift_z > 0.0) { +#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA + Vec3d shift(0.0, 0.0, -shift_z); +#else Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second)); +#endif // ENABLE_ALLOW_NEGATIVE_Z m_selection.translate(i.first, i.second, shift); m->translate_instance(i.second, shift); +#if ENABLE_ALLOW_NEGATIVE_Z + } +#endif // ENABLE_ALLOW_NEGATIVE_Z } +#if ENABLE_ALLOW_NEGATIVE_Z + // if the selection is not valid to allow for layer editing after the move, we need to turn off the tool if it is running + // similar to void Plater::priv::selection_changed() + if (!wxGetApp().plater()->can_layers_editing() && is_layers_editing_enabled()) + post_event(SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); +#endif // ENABLE_ALLOW_NEGATIVE_Z + if (object_moved) post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_MOVED)); @@ -3598,18 +3316,30 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) if (!snapshot_type.empty()) wxGetApp().plater()->take_snapshot(_(snapshot_type)); +#if ENABLE_ALLOW_NEGATIVE_Z + // stores current min_z of instances + std::map, double> min_zs; + if (!snapshot_type.empty()) { + for (int i = 0; i < static_cast(m_model->objects.size()); ++i) { + const ModelObject* obj = m_model->objects[i]; + for (int j = 0; j < static_cast(obj->instances.size()); ++j) { + min_zs[{ i, j }] = obj->instance_bounding_box(j).min(2); + } + } + } +#endif // ENABLE_ALLOW_NEGATIVE_Z + std::set> done; // keeps track of modified instances Selection::EMode selection_mode = m_selection.get_mode(); - for (const GLVolume* v : m_volumes.volumes) - { + for (const GLVolume* v : m_volumes.volumes) { int object_idx = v->object_idx(); if (object_idx == 1000) { // the wipe tower Vec3d offset = v->get_volume_offset(); post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset(0), offset(1), v->get_volume_rotation()(2)))); } - if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx)) + if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) continue; int instance_idx = v->instance_idx(); @@ -3619,15 +3349,12 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) // Rotate instances/volumes. ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) - { - if (selection_mode == Selection::Instance) - { + if (model_object != nullptr) { + if (selection_mode == Selection::Instance) { model_object->instances[instance_idx]->set_rotation(v->get_instance_rotation()); model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); } - else if (selection_mode == Selection::Volume) - { + else if (selection_mode == Selection::Volume) { model_object->volumes[volume_idx]->set_rotation(v->get_volume_rotation()); model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); } @@ -3636,12 +3363,21 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type) } // Fixes sinking/flying instances - for (const std::pair& i : done) - { + for (const std::pair& i : done) { ModelObject* m = m_model->objects[i.first]; +#if ENABLE_ALLOW_NEGATIVE_Z + double shift_z = m->get_instance_min_z(i.second); + // leave sinking instances as sinking + if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= 0.0 || shift_z > 0.0) { + Vec3d shift(0.0, 0.0, -shift_z); +#else Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second)); - m_selection.translate(i.first, i.second, shift); - m->translate_instance(i.second, shift); +#endif // ENABLE_ALLOW_NEGATIVE_Z + m_selection.translate(i.first, i.second, shift); + m->translate_instance(i.second, shift); +#if ENABLE_ALLOW_NEGATIVE_Z + } +#endif // ENABLE_ALLOW_NEGATIVE_Z } if (!done.empty()) @@ -3658,14 +3394,26 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) if (!snapshot_type.empty()) wxGetApp().plater()->take_snapshot(_(snapshot_type)); +#if ENABLE_ALLOW_NEGATIVE_Z + // stores current min_z of instances + std::map, double> min_zs; + if (!snapshot_type.empty()) { + for (int i = 0; i < static_cast(m_model->objects.size()); ++i) { + const ModelObject* obj = m_model->objects[i]; + for (int j = 0; j < static_cast(obj->instances.size()); ++j) { + min_zs[{ i, j }] = obj->instance_bounding_box(j).min(2); + } + } + } +#endif // ENABLE_ALLOW_NEGATIVE_Z + std::set> done; // keeps track of modified instances Selection::EMode selection_mode = m_selection.get_mode(); - for (const GLVolume* v : m_volumes.volumes) - { + for (const GLVolume* v : m_volumes.volumes) { int object_idx = v->object_idx(); - if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx)) + if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) continue; int instance_idx = v->instance_idx(); @@ -3675,15 +3423,12 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) // Rotate instances/volumes ModelObject* model_object = m_model->objects[object_idx]; - if (model_object != nullptr) - { - if (selection_mode == Selection::Instance) - { + if (model_object != nullptr) { + if (selection_mode == Selection::Instance) { model_object->instances[instance_idx]->set_scaling_factor(v->get_instance_scaling_factor()); model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); } - else if (selection_mode == Selection::Volume) - { + else if (selection_mode == Selection::Volume) { model_object->instances[instance_idx]->set_offset(v->get_instance_offset()); model_object->volumes[volume_idx]->set_scaling_factor(v->get_volume_scaling_factor()); model_object->volumes[volume_idx]->set_offset(v->get_volume_offset()); @@ -3693,16 +3438,25 @@ void GLCanvas3D::do_scale(const std::string& snapshot_type) } // Fixes sinking/flying instances - for (const std::pair& i : done) - { + for (const std::pair& i : done) { ModelObject* m = m_model->objects[i.first]; +#if ENABLE_ALLOW_NEGATIVE_Z + double shift_z = m->get_instance_min_z(i.second); + // leave sinking instances as sinking + if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= 0.0 || shift_z > 0.0) { + Vec3d shift(0.0, 0.0, -shift_z); +#else Vec3d shift(0.0, 0.0, -m->get_instance_min_z(i.second)); +#endif // ENABLE_ALLOW_NEGATIVE_Z m_selection.translate(i.first, i.second, shift); m->translate_instance(i.second, shift); +#if ENABLE_ALLOW_NEGATIVE_Z + } +#endif // ENABLE_ALLOW_NEGATIVE_Z } if (!done.empty()) - post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_ROTATED)); + post_event(SimpleEvent(EVT_GLCANVAS_INSTANCE_SCALED)); m_dirty = true; } @@ -3861,16 +3615,13 @@ void GLCanvas3D::set_cursor(ECursorType type) void GLCanvas3D::msw_rescale() { -#if !ENABLE_WARNING_TEXTURE_REMOVAL - m_warning_texture.msw_rescale(*this); -#endif // !ENABLE_WARNING_TEXTURE_REMOVAL } void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar() { std::string new_tooltip = _u8L("Switch to Settings") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") + - "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (m_process->current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ; m_main_toolbar.set_tooltip(get_main_toolbar_item_id("settings"), new_tooltip); @@ -3878,11 +3629,7 @@ void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar() bool GLCanvas3D::has_toolpaths_to_export() const { -#if ENABLE_SPLITTED_VERTEX_BUFFER return m_gcode_viewer.can_export_toolpaths(); -#else - return m_gcode_viewer.has_data(); -#endif // ENABLE_SPLITTED_VERTEX_BUFFER } void GLCanvas3D::export_toolpaths_to_obj(const char* filename) const @@ -4024,7 +3771,7 @@ bool GLCanvas3D::_render_arrange_menu(float pos_x) ArrangeSettings &settings_out = get_arrange_settings(); auto &appcfg = wxGetApp().app_config; - PrinterTechnology ptech = m_process->current_printer_technology(); + PrinterTechnology ptech = current_printer_technology(); bool settings_changed = false; float dist_min = 0.f; @@ -4591,7 +4338,7 @@ bool GLCanvas3D::_init_main_toolbar() item.name = "settings"; item.icon_filename = "settings.svg"; item.tooltip = _u8L("Switch to Settings") + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") + - "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (m_process->current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ; item.sprite_id = 10; item.enabling_callback = GLToolbarItem::Default_Enabling_Callback; @@ -4633,7 +4380,7 @@ bool GLCanvas3D::_init_main_toolbar() item.sprite_id = 12; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; item.visibility_callback = [this]()->bool { - bool res = m_process->current_printer_technology() == ptFFF; + bool res = current_printer_technology() == ptFFF; // turns off if changing printer technology if (!res && m_main_toolbar.is_item_visible("layersediting") && m_main_toolbar.is_item_pressed("layersediting")) force_main_toolbar_left_action(get_main_toolbar_item_id("layersediting")); @@ -4781,7 +4528,7 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) return; auto *imgui = wxGetApp().imgui(); - imgui->set_display_size((float)w, (float)h); + imgui->set_display_size(static_cast(w), static_cast(h)); const float font_size = 1.5f * wxGetApp().em_unit(); #if ENABLE_RETINA_GL imgui->set_scaling(font_size, 1.0f, m_retina_helper->get_scale_factor()); @@ -4789,6 +4536,10 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h) imgui->set_scaling(font_size, m_canvas->GetContentScaleFactor(), 1.0f); #endif +#if ENABLE_SCROLLABLE_LEGEND + this->request_extra_frame(); +#endif // ENABLE_SCROLLABLE_LEGEND + // ensures that this canvas is current _set_current(); } @@ -4829,8 +4580,7 @@ void GLCanvas3D::_update_camera_zoom(double zoom) void GLCanvas3D::_refresh_if_shown_on_screen() { - if (_is_shown_on_screen()) - { + if (_is_shown_on_screen()) { const Size& cnv_size = get_canvas_size(); _resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height()); @@ -5208,9 +4958,6 @@ void GLCanvas3D::_render_overlays() const _check_and_update_toolbar_icon_scale(); _render_gizmos_overlay(); -#if !ENABLE_WARNING_TEXTURE_REMOVAL - _render_warning_texture(); -#endif // !ENABLE_WARNING_TEXTURE_REMOVAL // main toolbar and undoredo toolbar need to be both updated before rendering because both their sizes are needed // to correctly place them @@ -5248,13 +4995,6 @@ void GLCanvas3D::_render_overlays() const glsafe(::glPopMatrix()); } -#if !ENABLE_WARNING_TEXTURE_REMOVAL -void GLCanvas3D::_render_warning_texture() const -{ - m_warning_texture.render(*this); -} -#endif // !ENABLE_WARNING_TEXTURE_REMOVAL - void GLCanvas3D::_render_volumes_for_picking() const { static const GLfloat INV_255 = 1.0f / 255.0f; @@ -6251,7 +5991,6 @@ void GLCanvas3D::_update_sla_shells_outside_state() } } -#if ENABLE_WARNING_TEXTURE_REMOVAL void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) { _set_current(); @@ -6268,24 +6007,6 @@ void GLCanvas3D::_set_warning_notification_if_needed(EWarning warning) } _set_warning_notification(warning, show); } -#else -void GLCanvas3D::_show_warning_texture_if_needed(WarningTexture::Warning warning) -{ - _set_current(); - bool show = false; - if (!m_volumes.empty()) - show = _is_any_volume_outside(); - else { - if (wxGetApp().is_editor()) { - 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); - } - } - _set_warning_texture(warning, show); -} -#endif // ENABLE_WARNING_TEXTURE_REMOVAL std::vector GLCanvas3D::_parse_colors(const std::vector& colors) { @@ -6309,7 +6030,6 @@ std::vector GLCanvas3D::_parse_colors(const std::vector& col return output; } -#if ENABLE_WARNING_TEXTURE_REMOVAL void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) { enum ErrorType{ @@ -6355,12 +6075,6 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state) break; } } -#else -void GLCanvas3D::_set_warning_texture(WarningTexture::Warning warning, bool state) -{ - m_warning_texture.activate(warning, state, *this); -} -#endif // !ENABLE_WARNING_TEXTURE_REMOVAL bool GLCanvas3D::_is_any_volume_outside() const { diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 9e9a2501e..a5c010d95 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -154,53 +154,50 @@ class GLCanvas3D static const float THICKNESS_BAR_WIDTH; private: - bool m_enabled; - unsigned int m_z_texture_id; + bool m_enabled{ false }; + unsigned int m_z_texture_id{ 0 }; // Not owned by LayersEditing. - const DynamicPrintConfig *m_config; + const DynamicPrintConfig *m_config{ nullptr }; // ModelObject for the currently selected object (Model::objects[last_object_id]). - const ModelObject *m_model_object; + const ModelObject *m_model_object{ nullptr }; // Maximum z of the currently selected object (Model::objects[last_object_id]). - float m_object_max_z; + float m_object_max_z{ 0.0f }; // Owned by LayersEditing. - SlicingParameters *m_slicing_parameters; + SlicingParameters *m_slicing_parameters{ nullptr }; std::vector m_layer_height_profile; - bool m_layer_height_profile_modified; + bool m_layer_height_profile_modified{ false }; - mutable float m_adaptive_quality; + mutable float m_adaptive_quality{ 0.5f }; mutable HeightProfileSmoothingParams m_smooth_params; - static float s_overelay_window_width; + static float s_overlay_window_width; - class LayersTexture + struct LayersTexture { - public: - LayersTexture() : width(0), height(0), levels(0), cells(0), valid(false) {} - // Texture data std::vector data; // Width of the texture, top level. - size_t width; + size_t width{ 0 }; // Height of the texture, top level. - size_t height; + size_t height{ 0 }; // For how many levels of detail is the data allocated? - size_t levels; + size_t levels{ 0 }; // Number of texture cells allocated for the height texture. - size_t cells; + size_t cells{ 0 }; // Does it need to be refreshed? - bool valid; + bool valid{ false }; }; LayersTexture m_layers_texture; public: - EState state; - float band_width; - float strength; - int last_object_id; - float last_z; - LayerHeightEditActionType last_action; + EState state{ Unknown }; + float band_width{ 2.0f }; + float strength{ 0.005f }; + int last_object_id{ -1 }; + float last_z{ 0.0f }; + LayerHeightEditActionType last_action{ LAYER_HEIGHT_EDIT_ACTION_INCREASE }; - LayersEditing(); + LayersEditing() = default; ~LayersEditing(); void init(); @@ -226,7 +223,7 @@ class GLCanvas3D static bool bar_rect_contains(const GLCanvas3D& canvas, float x, float y); static Rect get_bar_rect_screen(const GLCanvas3D& canvas); static Rect get_bar_rect_viewport(const GLCanvas3D& canvas); - static float get_overlay_window_width() { return LayersEditing::s_overelay_window_width; } + static float get_overlay_window_width() { return LayersEditing::s_overlay_window_width; } float object_max_z() const { return m_object_max_z; } @@ -298,7 +295,6 @@ class GLCanvas3D bool matches(double z) const { return this->z == z; } }; -#if ENABLE_WARNING_TEXTURE_REMOVAL enum class EWarning { ObjectOutside, ToolpathOutside, @@ -306,46 +302,6 @@ class GLCanvas3D SomethingNotShown, ObjectClashed }; -#else - class WarningTexture : public GUI::GLTexture - { - public: - WarningTexture(); - - enum Warning { - ObjectOutside, - ToolpathOutside, - SlaSupportsOutside, - SomethingNotShown, - ObjectClashed - }; - - // Sets a warning of the given type to be active/inactive. If several warnings are active simultaneously, - // only the last one is shown (decided by the order in the enum above). - void activate(WarningTexture::Warning warning, bool state, const GLCanvas3D& canvas); - void render(const GLCanvas3D& canvas) const; - - // function used to get an information for rescaling of the warning - void msw_rescale(const GLCanvas3D& canvas); - - private: - static const unsigned char Background_Color[3]; - static const unsigned char Opacity; - - int m_original_width; - int m_original_height; - - // information for rescaling of the warning legend - std::string m_msg_text = ""; - bool m_is_colored_red{false}; - - // Information about which warnings are currently active. - std::vector m_warnings; - - // Generates the texture with given text. - bool generate(const std::string& msg, const GLCanvas3D& canvas, bool compress, bool red_colored = false); - }; -#endif // ENABLE_WARNING_TEXTURE_REMOVAL #if ENABLE_RENDER_STATISTICS class RenderStats @@ -401,7 +357,6 @@ class GLCanvas3D { bool m_enabled{ false }; GLVolumeCollection& m_volumes; - static float s_window_width; public: Slope(GLVolumeCollection& volumes) : m_volumes(volumes) {} @@ -412,7 +367,6 @@ class GLCanvas3D void set_normal_angle(float angle_in_deg) const { m_volumes.set_slope_normal_z(-::cos(Geometry::deg2rad(90.0f - angle_in_deg))); } - static float get_window_width() { return s_window_width; }; }; class RenderTimer : public wxTimer { @@ -443,9 +397,6 @@ private: std::unique_ptr m_retina_helper; #endif bool m_in_render; -#if !ENABLE_WARNING_TEXTURE_REMOVAL - WarningTexture m_warning_texture; -#endif // !ENABLE_WARNING_TEXTURE_REMOVAL wxTimer m_timer; LayersEditing m_layers_editing; Mouse m_mouse; @@ -813,9 +764,6 @@ private: #endif // ENABLE_RENDER_SELECTION_CENTER void _check_and_update_toolbar_icon_scale() const; void _render_overlays() const; -#if !ENABLE_WARNING_TEXTURE_REMOVAL - void _render_warning_texture() const; -#endif // !ENABLE_WARNING_TEXTURE_REMOVAL void _render_volumes_for_picking() const; void _render_current_gizmo() const; void _render_gizmos_overlay() const; @@ -868,17 +816,10 @@ private: void _load_sla_shells(); void _update_toolpath_volumes_outside_state(); void _update_sla_shells_outside_state(); -#if ENABLE_WARNING_TEXTURE_REMOVAL void _set_warning_notification_if_needed(EWarning warning); // generates a warning notification containing the given message void _set_warning_notification(EWarning warning, bool state); -#else - void _show_warning_texture_if_needed(WarningTexture::Warning warning); - - // generates a warning texture containing the given message - void _set_warning_texture(WarningTexture::Warning warning, bool state); -#endif // ENABLE_WARNING_TEXTURE_REMOVAL bool _is_any_volume_outside() const; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index cb7c2d24e..803ab5a14 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -916,6 +916,14 @@ bool GUI_App::on_init_inner() } else load_current_presets(); + +#if ENABLE_PROJECT_DIRTY_STATE + if (plater_ != nullptr) { + plater_->reset_project_dirty_initial_presets(); + plater_->update_project_dirty_from_presets(); + } +#endif // ENABLE_PROJECT_DIRTY_STATE + mainframe->Show(true); obj_list()->set_min_height(); @@ -1686,7 +1694,11 @@ void GUI_App::add_config_menu(wxMenuBar *menu) #endif case ConfigMenuTakeSnapshot: // Take a configuration snapshot. +#if ENABLE_PROJECT_DIRTY_STATE + if (check_and_save_current_preset_changes()) { +#else if (check_unsaved_changes()) { +#endif // ENABLE_PROJECT_DIRTY_STATE wxTextEntryDialog dlg(nullptr, _L("Taking configuration snapshot"), _L("Snapshot name")); // set current normal font for dialog children, @@ -1701,7 +1713,11 @@ void GUI_App::add_config_menu(wxMenuBar *menu) } break; case ConfigMenuSnapshots: +#if ENABLE_PROJECT_DIRTY_STATE + if (check_and_save_current_preset_changes()) { +#else if (check_unsaved_changes()) { +#endif // ENABLE_PROJECT_DIRTY_STATE std::string on_snapshot; if (Config::SnapshotDB::singleton().is_on_snapshot(*app_config)) on_snapshot = app_config->get("on_snapshot"); @@ -1802,8 +1818,57 @@ void GUI_App::add_config_menu(wxMenuBar *menu) menu->Append(local_menu, _L("&Configuration")); } +#if ENABLE_PROJECT_DIRTY_STATE +bool GUI_App::has_unsaved_preset_changes() const +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (const Tab* const tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology) && tab->saved_preset_is_dirty()) + return true; + } + return false; +} + +bool GUI_App::has_current_preset_changes() const +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (const Tab* const tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology) && tab->current_preset_is_dirty()) + return true; + } + return false; +} + +void GUI_App::update_saved_preset_from_current_preset() +{ + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (Tab* tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology)) + tab->update_saved_preset_from_current_preset(); + } +} + +std::vector> GUI_App::get_selected_presets() const +{ + std::vector> ret; + PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); + for (Tab* tab : tabs_list) { + if (tab->supports_printer_technology(printer_technology)) { + const PresetCollection* presets = tab->get_presets(); + ret.push_back({ static_cast(presets->type()), presets->get_selected_preset_name() }); + } + } + return ret; +} +#endif // ENABLE_PROJECT_DIRTY_STATE + // This is called when closing the application, when loading a config file or when starting the config wizard // to notify the user whether he is aware that some preset changes will be lost. +#if ENABLE_PROJECT_DIRTY_STATE +bool GUI_App::check_and_save_current_preset_changes(const wxString& header) +{ + if (this->plater()->model().objects.empty() && has_current_preset_changes()) { +#else bool GUI_App::check_unsaved_changes(const wxString &header) { PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology(); @@ -1815,8 +1880,8 @@ bool GUI_App::check_unsaved_changes(const wxString &header) break; } - if (has_unsaved_changes) - { + if (has_unsaved_changes) { +#endif // ENABLE_PROJECT_DIRTY_STATE UnsavedChangesDialog dlg(header); if (wxGetApp().app_config->get("default_action_on_close_application") == "none" && dlg.ShowModal() == wxID_CANCEL) return false; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index bc030a1bf..2613c51b1 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -210,7 +210,15 @@ public: void update_mode(); void add_config_menu(wxMenuBar *menu); - bool check_unsaved_changes(const wxString &header = wxString()); +#if ENABLE_PROJECT_DIRTY_STATE + bool has_unsaved_preset_changes() const; + bool has_current_preset_changes() const; + void update_saved_preset_from_current_preset(); + std::vector> get_selected_presets() const; + bool check_and_save_current_preset_changes(const wxString& header = wxString()); +#else + bool check_unsaved_changes(const wxString& header = wxString()); +#endif // ENABLE_PROJECT_DIRTY_STATE bool check_print_host_queue(); bool checked_tab(Tab* tab); void load_current_presets(bool check_printer_presets = true); diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 725a396c3..d079a6ebd 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -142,7 +142,7 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range, PlusMinus auto sizer = new wxBoxSizer(wxHORIZONTAL); sizer->Add(editor); - auto temp = new wxStaticText(m_parent, wxID_ANY, _(L("mm"))); + auto temp = new wxStaticText(m_parent, wxID_ANY, _L("mm")); temp->SetBackgroundStyle(wxBG_STYLE_PAINT); temp->SetFont(wxGetApp().normal_font()); sizer->Add(temp, 0, wxLEFT, wxGetApp().em_unit()); @@ -154,15 +154,14 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range, PlusMinus void ObjectLayers::create_layers_list() { - for (const auto &layer : m_object->layer_config_ranges) - { + for (const auto &layer : m_object->layer_config_ranges) { const t_layer_height_range& range = layer.first; auto del_btn = new PlusMinusButton(m_parent, m_bmp_delete, range); - del_btn->SetToolTip(_(L("Remove layer range"))); + del_btn->SetToolTip(_L("Remove layer range")); auto add_btn = new PlusMinusButton(m_parent, m_bmp_add, range); wxString tooltip = wxGetApp().obj_list()->can_add_new_range_after_current(range); - add_btn->SetToolTip(tooltip.IsEmpty() ? _(L("Add layer range")) : tooltip); + add_btn->SetToolTip(tooltip.IsEmpty() ? _L("Add layer range") : tooltip); add_btn->Enable(tooltip.IsEmpty()); auto sizer = create_layer(range, del_btn, add_btn); @@ -242,11 +241,9 @@ void ObjectLayers::msw_rescale() // rescale edit-boxes const int cells_cnt = m_grid_sizer->GetCols() * m_grid_sizer->GetEffectiveRowsCount(); - for (int i = 0; i < cells_cnt; i++) - { + for (int i = 0; i < cells_cnt; ++i) { const wxSizerItem* item = m_grid_sizer->GetItem(i); - if (item->IsWindow()) - { + if (item->IsWindow()) { LayerRangeEditor* editor = dynamic_cast(item->GetWindow()); if (editor != nullptr) editor->msw_rescale(); @@ -283,8 +280,7 @@ void ObjectLayers::sys_color_changed() // rescale edit-boxes const int cells_cnt = m_grid_sizer->GetCols() * m_grid_sizer->GetEffectiveRowsCount(); - for (int i = 0; i < cells_cnt; i++) - { + for (int i = 0; i < cells_cnt; ++i) { const wxSizerItem* item = m_grid_sizer->GetItem(i); if (item->IsSizer()) {// case when we have editor with buttons const std::vector btns = {2, 3}; // del_btn, add_btn @@ -405,11 +401,9 @@ coordf_t LayerRangeEditor::get_value() str.Replace(",", ".", false); if (str == ".") layer_height = 0.0; - else - { - if (!str.ToCDouble(&layer_height) || layer_height < 0.0f) - { - show_error(m_parent, _(L("Invalid numeric input."))); + else { + if (!str.ToCDouble(&layer_height) || layer_height < 0.0f) { + show_error(m_parent, _L("Invalid numeric input.")); SetValue(double_to_string(layer_height)); } } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 2b72e901a..c371da2a7 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -7,6 +7,9 @@ #include "GUI_App.hpp" #include "I18N.hpp" #include "Plater.hpp" +#if ENABLE_PROJECT_DIRTY_STATE +#include "MainFrame.hpp" +#endif // ENABLE_PROJECT_DIRTY_STATE #include "OptionsGroup.hpp" #include "Tab.hpp" @@ -18,6 +21,7 @@ #include #include +#include #include "slic3r/Utils/FixModelByWin10.hpp" @@ -143,18 +147,28 @@ ObjectList::ObjectList(wxWindow* parent) : // Bind(wxEVT_KEY_DOWN, &ObjectList::OnChar, this); { // Accelerators - wxAcceleratorEntry entries[10]; - entries[0].Set(wxACCEL_CTRL, (int) 'C', wxID_COPY); - entries[1].Set(wxACCEL_CTRL, (int) 'X', wxID_CUT); - entries[2].Set(wxACCEL_CTRL, (int) 'V', wxID_PASTE); - entries[3].Set(wxACCEL_CTRL, (int) 'A', wxID_SELECTALL); - entries[4].Set(wxACCEL_CTRL, (int) 'Z', wxID_UNDO); - entries[5].Set(wxACCEL_CTRL, (int) 'Y', wxID_REDO); + wxAcceleratorEntry entries[33]; + entries[0].Set(wxACCEL_CTRL, (int)'C', wxID_COPY); + entries[1].Set(wxACCEL_CTRL, (int)'X', wxID_CUT); + entries[2].Set(wxACCEL_CTRL, (int)'V', wxID_PASTE); + entries[3].Set(wxACCEL_CTRL, (int)'A', wxID_SELECTALL); + entries[4].Set(wxACCEL_CTRL, (int)'Z', wxID_UNDO); + entries[5].Set(wxACCEL_CTRL, (int)'Y', wxID_REDO); entries[6].Set(wxACCEL_NORMAL, WXK_DELETE, wxID_DELETE); - entries[7].Set(wxACCEL_NORMAL, WXK_BACK, wxID_DELETE); - entries[8].Set(wxACCEL_NORMAL, int('+'), wxID_ADD); - entries[9].Set(wxACCEL_NORMAL, int('-'), wxID_REMOVE); - wxAcceleratorTable accel(10, entries); + entries[7].Set(wxACCEL_NORMAL, WXK_BACK, wxID_DELETE); + entries[8].Set(wxACCEL_NORMAL, int('+'), wxID_ADD); + entries[9].Set(wxACCEL_NORMAL, WXK_NUMPAD_ADD, wxID_ADD); + entries[10].Set(wxACCEL_NORMAL, int('-'), wxID_REMOVE); + entries[11].Set(wxACCEL_NORMAL, WXK_NUMPAD_SUBTRACT, wxID_REMOVE); + entries[12].Set(wxACCEL_NORMAL, int('p'), wxID_PRINT); + + int numbers_cnt = 1; + for (auto char_number : { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }) { + entries[12 + numbers_cnt].Set(wxACCEL_NORMAL, int(char_number), wxID_LAST + numbers_cnt); + entries[22 + numbers_cnt].Set(wxACCEL_NORMAL, WXK_NUMPAD0 + numbers_cnt - 1, wxID_LAST + numbers_cnt); + numbers_cnt++; + } + wxAcceleratorTable accel(33, entries); SetAcceleratorTable(accel); this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->copy(); }, wxID_COPY); @@ -165,6 +179,13 @@ ObjectList::ObjectList(wxWindow* parent) : this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->redo(); }, wxID_REDO); this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->increase_instances(); }, wxID_ADD); this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->decrease_instances(); }, wxID_REMOVE); + this->Bind(wxEVT_MENU, [this](wxCommandEvent &evt) { this->toggle_printable_state(); }, wxID_PRINT); + + for (int i = 0; i < 10; i++) + this->Bind(wxEVT_MENU, [this, i](wxCommandEvent &evt) { + if (extruders_count() > 1 && i <= extruders_count()) + this->set_extruder_for_selected_items(i); + }, wxID_LAST+i+1); } #else //__WXOSX__ Bind(wxEVT_CHAR, [this](wxKeyEvent& event) { key_event(event); }); // doesn't work on OSX @@ -1031,6 +1052,20 @@ void ObjectList::key_event(wxKeyEvent& event) increase_instances(); else if (event.GetUnicodeKey() == '-') decrease_instances(); + else if (event.GetUnicodeKey() == 'p') + toggle_printable_state(); + else if (extruders_count() > 1) { + std::vector numbers = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' }; + wxChar key_char = event.GetUnicodeKey(); + if (std::find(numbers.begin(), numbers.end(), key_char) != numbers.end()) { + long extruder_number; + if (wxNumberFormatter::FromString(wxString(key_char), &extruder_number) && + extruders_count() >= extruder_number) + set_extruder_for_selected_items(int(extruder_number)); + } + else + event.Skip(); + } else event.Skip(); } @@ -1457,12 +1492,15 @@ void ObjectList::load_shape_object(const std::string& type_name) if (obj_idx < 0) return; - take_snapshot(_(L("Add Shape"))); + take_snapshot(_L("Add Shape")); // Create mesh BoundingBoxf3 bb; TriangleMesh mesh = create_mesh(type_name, bb); - load_mesh_object(mesh, _(L("Shape")) + "-" + _(type_name)); + load_mesh_object(mesh, _L("Shape") + "-" + _(type_name)); +#if ENABLE_PROJECT_DIRTY_STATE + wxGetApp().mainframe->update_title(); +#endif // ENABLE_PROJECT_DIRTY_STATE } void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center) @@ -2113,18 +2151,15 @@ void ObjectList::part_selection_changed() const auto item = GetSelection(); - if ( multiple_selection() || (item && m_objects_model->GetItemType(item) == itInstanceRoot )) - { - og_name = _(L("Group manipulation")); + if ( multiple_selection() || (item && m_objects_model->GetItemType(item) == itInstanceRoot )) { + og_name = _L("Group manipulation"); const Selection& selection = scene_selection(); // don't show manipulation panel for case of all Object's parts selection update_and_show_manipulations = !selection.is_single_full_instance(); } - else - { - if (item) - { + else { + if (item) { const ItemType type = m_objects_model->GetItemType(item); const wxDataViewItem parent = m_objects_model->GetParent(item); const ItemType parent_type = m_objects_model->GetItemType(parent); @@ -2132,7 +2167,7 @@ void ObjectList::part_selection_changed() if (parent == wxDataViewItem(nullptr) || type == itInfo) { - og_name = _(L("Object manipulation")); + og_name = _L("Object manipulation"); m_config = &(*m_objects)[obj_idx]->config; update_and_show_manipulations = true; @@ -2152,35 +2187,35 @@ void ObjectList::part_selection_changed() else { if (type & itSettings) { if (parent_type & itObject) { - og_name = _(L("Object Settings to modify")); + og_name = _L("Object Settings to modify"); m_config = &(*m_objects)[obj_idx]->config; } else if (parent_type & itVolume) { - og_name = _(L("Part Settings to modify")); + og_name = _L("Part Settings to modify"); volume_id = m_objects_model->GetVolumeIdByItem(parent); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; } else if (parent_type & itLayer) { - og_name = _(L("Layer range Settings to modify")); + og_name = _L("Layer range Settings to modify"); m_config = &get_item_config(parent); } update_and_show_settings = true; } else if (type & itVolume) { - og_name = _(L("Part manipulation")); + og_name = _L("Part manipulation"); volume_id = m_objects_model->GetVolumeIdByItem(item); m_config = &(*m_objects)[obj_idx]->volumes[volume_id]->config; update_and_show_manipulations = true; } else if (type & itInstance) { - og_name = _(L("Instance manipulation")); + og_name = _L("Instance manipulation"); update_and_show_manipulations = true; // fill m_config by object's values m_config = &(*m_objects)[obj_idx]->config; } else if (type & (itLayerRoot|itLayer)) { - og_name = type & itLayerRoot ? _(L("Height ranges")) : _(L("Settings for height range")); + og_name = type & itLayerRoot ? _L("Height ranges") : _L("Settings for height range"); update_and_show_layers = true; if (type & itLayer) @@ -2292,6 +2327,7 @@ void ObjectList::update_info_items(size_t obj_idx) should_show = printer_technology() == ptFFF && ! model_object->layer_height_profile.empty(); break; + default: break; } if (! shows && should_show) { @@ -2474,8 +2510,8 @@ void ObjectList::select_object_item(bool is_msr_gizmo) { if (wxDataViewItem item = GetSelection()) { ItemType type = m_objects_model->GetItemType(item); - bool is_volume_item = type == itVolume || type == itSettings && m_objects_model->GetItemType(m_objects_model->GetParent(item)) == itVolume; - if (is_msr_gizmo && is_volume_item || type == itObject) + bool is_volume_item = type == itVolume || (type == itSettings && m_objects_model->GetItemType(m_objects_model->GetParent(item)) == itVolume); + if ((is_msr_gizmo && is_volume_item) || type == itObject) return; if (wxDataViewItem obj_item = m_objects_model->GetTopParent(item)) { @@ -2809,7 +2845,7 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_lay const int obj_idx = m_selected_object_id; if (obj_idx < 0) return false; - take_snapshot(_(L("Edit Height Range"))); + take_snapshot(_L("Edit Height Range")); const ItemType sel_type = m_objects_model->GetItemType(GetSelection()); @@ -3787,33 +3823,6 @@ void ObjectList::OnEditingDone(wxDataViewEvent &event) plater->set_current_canvas_as_dirty(); } -void ObjectList::extruder_selection() -{ - wxArrayString choices; - choices.Add(_(L("default"))); - for (int i = 1; i <= extruders_count(); ++i) - choices.Add(wxString::Format("%d", i)); - - const wxString& selected_extruder = wxGetSingleChoice(_(L("Select extruder number:")), - _(L("This extruder will be set for selected items")), - choices, 0, this); - if (selected_extruder.IsEmpty()) - return; - - const int extruder_num = selected_extruder == _(L("default")) ? 0 : atoi(selected_extruder.c_str()); - -// /* Another variant for an extruder selection */ -// extruder_num = wxGetNumberFromUser(_(L("Attention!!! \n" -// "It's a possibile to set an extruder number \n" -// "for whole Object(s) and/or object Part(s), \n" -// "not for an Instance. ")), -// _(L("Enter extruder number:")), -// _(L("This extruder will be set for selected items")), -// 1, 1, 5, this); - - set_extruder_for_selected_items(extruder_num); -} - void ObjectList::set_extruder_for_selected_items(const int extruder) const { wxDataViewItemArray sels; @@ -3906,6 +3915,8 @@ void ObjectList::toggle_printable_state() { wxDataViewItemArray sels; GetSelections(sels); + if (sels.IsEmpty()) + return; wxDataViewItem frst_item = sels[0]; @@ -3918,10 +3929,10 @@ void ObjectList::toggle_printable_state() int inst_idx = type == itObject ? 0 : m_objects_model->GetInstanceIdByItem(frst_item); bool printable = !object(obj_idx)->instances[inst_idx]->printable; - const wxString snapshot_text = sels.Count() > 1 ? (printable ? _L("Set Printable group") : _L("Set Unprintable group")) : - object(obj_idx)->instances.size() == 1 ? from_u8((boost::format("%1% %2%") - % (printable ? _L("Set Printable") : _L("Set Unprintable")) - % object(obj_idx)->name).str()) : + const wxString snapshot_text = sels.Count() > 1 ? + (printable ? _L("Set Printable group") : _L("Set Unprintable group")) : + object(obj_idx)->instances.size() == 1 ? + format_wxstr("%1% %2%", (printable ? _L("Set Printable") : _L("Set Unprintable")), from_u8(object(obj_idx)->name)) : (printable ? _L("Set Printable Instance") : _L("Set Unprintable Instance")); take_snapshot(snapshot_text); diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 8bcfec11c..b378b00af 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -390,7 +390,6 @@ private: void OnEditingStarted(wxDataViewEvent &event); #endif /* __WXMSW__ */ void OnEditingDone(wxDataViewEvent &event); - void extruder_selection(); }; diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index cba7cef36..095a926ad 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -26,21 +26,26 @@ const double ObjectManipulation::mm_to_in = 0.0393700787; // Helper function to be used by drop to bed button. Returns lowest point of this // volume in world coordinate system. -static double get_volume_min_z(const GLVolume* volume) +static double get_volume_min_z(const GLVolume& volume) { - const Transform3f& world_matrix = volume->world_matrix().cast(); +#if ENABLE_ALLOW_NEGATIVE_Z + return volume.transformed_convex_hull_bounding_box().min.z(); +#else + const Transform3f& world_matrix = volume.world_matrix().cast(); // need to get the ModelVolume pointer - const ModelObject* mo = wxGetApp().model().objects[volume->composite_id.object_id]; - const ModelVolume* mv = mo->volumes[volume->composite_id.volume_id]; + const ModelObject* mo = wxGetApp().model().objects[volume.composite_id.object_id]; + const ModelVolume* mv = mo->volumes[volume.composite_id.volume_id]; const TriangleMesh& hull = mv->get_convex_hull(); float min_z = std::numeric_limits::max(); for (const stl_facet& facet : hull.stl.facet_start) { - for (int i = 0; i < 3; ++ i) + for (int i = 0; i < 3; ++i) min_z = std::min(min_z, Vec3f::UnitZ().dot(world_matrix * facet.vertex[i])); } + return min_z; +#endif // ENABLE_ALLOW_NEGATIVE_Z } @@ -341,13 +346,27 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin()); const Geometry::Transformation& instance_trafo = volume->get_instance_transformation(); - Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(volume)); + const Vec3d diff = m_cache.position - instance_trafo.get_matrix(true).inverse() * Vec3d(0., 0., get_volume_min_z(*volume)); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); change_position_value(0, diff.x()); change_position_value(1, diff.y()); change_position_value(2, diff.z()); } +#if ENABLE_ALLOW_NEGATIVE_Z + else if (selection.is_single_full_instance()) { + const ModelObjectPtrs& objects = wxGetApp().model().objects; + const int idx = selection.get_object_idx(); + if (0 <= idx && idx < static_cast(objects.size())) { + const ModelObject* mo = wxGetApp().model().objects[idx]; + const double min_z = mo->bounding_box().min.z(); + if (std::abs(min_z) > EPSILON) { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Drop to bed")); + change_position_value(2, m_cache.position.z() - min_z); + } + } + } +#endif // ENABLE_ALLOW_NEGATIVE_Z }); editors_grid_sizer->Add(m_drop_to_bed_button); @@ -671,11 +690,15 @@ void ObjectManipulation::update_reset_buttons_visibility() if (selection.is_single_full_instance()) { rotation = volume->get_instance_rotation(); scale = volume->get_instance_scaling_factor(); +#if ENABLE_ALLOW_NEGATIVE_Z + min_z = wxGetApp().model().objects[volume->composite_id.object_id]->bounding_box().min.z(); +#endif // ENABLE_ALLOW_NEGATIVE_Z + } else { rotation = volume->get_volume_rotation(); scale = volume->get_volume_scaling_factor(); - min_z = get_volume_min_z(volume); + min_z = get_volume_min_z(*volume); } show_rotation = !rotation.isApprox(Vec3d::Zero()); show_scale = !scale.isApprox(Vec3d::Ones()); diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index ea289bb14..c5124cd63 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -643,7 +643,7 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee if (sla_print_technology) m_layers_slider->SetLayersTimes(plater->sla_print().print_statistics().layers_times); else { - auto print_mode_stat = m_gcode_result->time_statistics.modes.front(); + auto print_mode_stat = m_gcode_result->print_statistics.modes.front(); m_layers_slider->SetLayersTimes(print_mode_stat.layers_times, print_mode_stat.time); } @@ -661,31 +661,32 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee // if it's sign, than object have not to be a too height double height = object->height(); coord_t longer_side = std::max(object_x, object_y); - if (height / longer_side > 0.3) + auto num_layers = int(object->layers().size()); + if (height / longer_side > 0.3 || num_layers < 2) continue; const ExPolygons& bottom = object->get_layer(0)->lslices; double bottom_area = area(bottom); // at least 30% of object's height have to be a solid - size_t i; - for (i = 1; i < size_t(0.3 * object->layers().size()); i++) { + int i; + for (i = 1; i < int(0.3 * num_layers); ++ i) { double cur_area = area(object->get_layer(i)->lslices); if (cur_area != bottom_area && fabs(cur_area - bottom_area) > scale_(scale_(1))) break; } - if (i < size_t(0.3 * object->layers().size())) + if (i < int(0.3 * num_layers)) continue; // bottom layer have to be a biggest, so control relation between bottom layer and object size double prev_area = area(object->get_layer(i)->lslices); - for ( i++; i < object->layers().size(); i++) { + for ( i++; i < num_layers; i++) { double cur_area = area(object->get_layer(i)->lslices); if (cur_area > prev_area && prev_area - cur_area > scale_(scale_(1))) break; prev_area = cur_area; } - if (i < object->layers().size()) + if (i < num_layers) continue; double top_area = area(object->get_layer(int(object->layers().size()) - 1)->lslices); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp index 91aef75d9..3a932c598 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoPainterBase.cpp @@ -32,6 +32,42 @@ GLGizmoPainterBase::GLGizmoPainterBase(GLCanvas3D& parent, const std::string& ic +#if ENABLE_PROJECT_DIRTY_STATE +// port of 948bc382655993721d93d3b9fce9b0186fcfb211 +void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate) +{ + Plater* plater = wxGetApp().plater(); + + // Following is needed to prevent taking an extra snapshot when the activation of + // the internal stack happens when the gizmo is already active (such as open gizmo, + // close gizmo, undo, start painting). The internal stack does not activate on the + // undo, because that would obliterate all future of the main stack (user would + // have to close the gizmo himself, he has no access to main undo/redo after the + // internal stack opens). We don't want the "entering" snapshot taken in this case, + // because there already is one. + std::string last_snapshot_name; + plater->undo_redo_topmost_string_getter(plater->can_undo(), last_snapshot_name); + + if (activate && !m_internal_stack_active) { + std::string str = get_painter_type() == PainterGizmoType::FDM_SUPPORTS + ? _u8L("Entering Paint-on supports") + : _u8L("Entering Seam painting"); + if (last_snapshot_name != str) + Plater::TakeSnapshot(plater, str); + plater->enter_gizmos_stack(); + m_internal_stack_active = true; + } + if (!activate && m_internal_stack_active) { + plater->leave_gizmos_stack(); + std::string str = get_painter_type() == PainterGizmoType::SEAM + ? _u8L("Leaving Seam painting") + : _u8L("Leaving Paint-on supports"); + if (last_snapshot_name != str) + Plater::TakeSnapshot(plater, str); + m_internal_stack_active = false; + } +} +#else void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate) { if (activate && ! m_internal_stack_active) { @@ -51,6 +87,7 @@ void GLGizmoPainterBase::activate_internal_undo_redo_stack(bool activate) m_internal_stack_active = false; } } +#endif // ENABLE_PROJECT_DIRTY_STATE diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index b200623f4..7ff274aac 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -45,18 +45,18 @@ bool GLGizmoSlaSupports::on_init() { m_shortcut_key = WXK_CONTROL_L; - m_desc["head_diameter"] = _(L("Head diameter")) + ": "; - m_desc["lock_supports"] = _(L("Lock supports under new islands")); - m_desc["remove_selected"] = _(L("Remove selected points")); - m_desc["remove_all"] = _(L("Remove all points")); - m_desc["apply_changes"] = _(L("Apply changes")); - m_desc["discard_changes"] = _(L("Discard changes")); - m_desc["minimal_distance"] = _(L("Minimal points distance")) + ": "; - m_desc["points_density"] = _(L("Support points density")) + ": "; - m_desc["auto_generate"] = _(L("Auto-generate points")); - m_desc["manual_editing"] = _(L("Manual editing")); - m_desc["clipping_of_view"] = _(L("Clipping of view"))+ ": "; - m_desc["reset_direction"] = _(L("Reset direction")); + m_desc["head_diameter"] = _L("Head diameter") + ": "; + m_desc["lock_supports"] = _L("Lock supports under new islands"); + m_desc["remove_selected"] = _L("Remove selected points"); + m_desc["remove_all"] = _L("Remove all points"); + m_desc["apply_changes"] = _L("Apply changes"); + m_desc["discard_changes"] = _L("Discard changes"); + m_desc["minimal_distance"] = _L("Minimal points distance") + ": "; + m_desc["points_density"] = _L("Support points density") + ": "; + m_desc["auto_generate"] = _L("Auto-generate points"); + m_desc["manual_editing"] = _L("Manual editing"); + m_desc["clipping_of_view"] = _L("Clipping of view")+ ": "; + m_desc["reset_direction"] = _L("Reset direction"); return true; } @@ -372,7 +372,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous if (m_selection_empty) { std::pair pos_and_normal; if (unproject_on_mesh(mouse_position, pos_and_normal)) { // we got an intersection - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add support point"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Add support point")); m_editing_cache.emplace_back(sla::SupportPoint(pos_and_normal.first, m_new_point_head_diameter/2.f, false), false, pos_and_normal.second); m_parent.set_as_dirty(); m_wait_for_up_event = true; @@ -512,7 +512,7 @@ void GLGizmoSlaSupports::delete_selected_points(bool force) std::abort(); } - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete support point"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Delete support point")); for (unsigned int idx=0; idxconfig.set("support_points_minimal_distance", m_minimal_point_distance_stash); mo->config.set("support_points_density_relative", (int)m_density_stash); - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Support parameter change"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support parameter change")); mo->config.set("support_points_minimal_distance", minimal_point_distance); mo->config.set("support_points_density_relative", (int)density); wxGetApp().obj_list()->update_and_show_object_settings_item(); @@ -867,10 +867,9 @@ bool GLGizmoSlaSupports::on_is_selectable() const std::string GLGizmoSlaSupports::on_get_name() const { - return (_(L("SLA Support Points")) + " [L]").ToUTF8().data(); + return (_L("SLA Support Points") + " [L]").ToUTF8().data(); } - CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const { return CommonGizmosDataID( @@ -895,7 +894,11 @@ void GLGizmoSlaSupports::on_set_state() // data are not yet available, the CallAfter will postpone taking the // snapshot until they are. No, it does not feel right. wxGetApp().CallAfter([]() { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned on"))); +#if ENABLE_PROJECT_DIRTY_STATE + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Entering SLA gizmo")); +#else + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("SLA gizmo turned on")); +#endif // ENABLE_PROJECT_DIRTY_STATE }); } @@ -909,8 +912,8 @@ void GLGizmoSlaSupports::on_set_state() wxGetApp().CallAfter([this]() { // Following is called through CallAfter, because otherwise there was a problem // on OSX with the wxMessageDialog being shown several times when clicked into. - wxMessageDialog dlg(GUI::wxGetApp().mainframe, _(L("Do you want to save your manually " - "edited support points?")) + "\n",_(L("Save changes?")), wxICON_QUESTION | wxYES | wxNO); + wxMessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually " + "edited support points?") + "\n",_L("Save changes?"), wxICON_QUESTION | wxYES | wxNO); if (dlg.ShowModal() == wxID_YES) editing_mode_apply_changes(); else @@ -922,7 +925,11 @@ void GLGizmoSlaSupports::on_set_state() else { // we are actually shutting down disable_editing_mode(); // so it is not active next time the gizmo opens - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned off"))); +#if ENABLE_PROJECT_DIRTY_STATE + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Leaving SLA gizmo")); +#else + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("SLA gizmo turned off")); +#endif // ENABLE_PROJECT_DIRTY_STATE m_normal_cache.clear(); m_old_mo_id = -1; } @@ -953,7 +960,7 @@ void GLGizmoSlaSupports::on_stop_dragging() && backup.support_point.pos != m_point_before_drag.support_point.pos) // and it was moved, not just selected { m_editing_cache[m_hover_id] = m_point_before_drag; - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Move support point"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Move support point")); m_editing_cache[m_hover_id] = backup; } } @@ -1046,7 +1053,7 @@ void GLGizmoSlaSupports::editing_mode_apply_changes() disable_editing_mode(); // this leaves the editing mode undo/redo stack and must be done before the snapshot is taken if (unsaved_changes()) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Support points edit"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Support points edit")); m_normal_cache.clear(); for (const CacheEntry& ce : m_editing_cache) @@ -1125,14 +1132,14 @@ void GLGizmoSlaSupports::get_data_from_backend() void GLGizmoSlaSupports::auto_generate() { wxMessageDialog dlg(GUI::wxGetApp().plater(), - _(L("Autogeneration will erase all manually edited points.")) + "\n\n" + - _(L("Are you sure you want to do it?")) + "\n", - _(L("Warning")), wxICON_WARNING | wxYES | wxNO); + _L("Autogeneration will erase all manually edited points.") + "\n\n" + + _L("Are you sure you want to do it?") + "\n", + _L("Warning"), wxICON_WARNING | wxYES | wxNO); ModelObject* mo = m_c->selection_info()->model_object(); if (mo->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Autogenerate support points"))); + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Autogenerate support points")); wxGetApp().CallAfter([this]() { reslice_SLA_supports(); }); mo->sla_points_status = sla::PointsStatus::Generating; } @@ -1180,7 +1187,7 @@ bool GLGizmoSlaSupports::unsaved_changes() const } SlaGizmoHelpDialog::SlaGizmoHelpDialog() -: wxDialog(nullptr, wxID_ANY, _(L("SLA gizmo keyboard shortcuts")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) +: wxDialog(nullptr, wxID_ANY, _L("SLA gizmo keyboard shortcuts"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER) { SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); const wxString ctrl = GUI::shortkey_ctrl_prefix(); @@ -1191,7 +1198,7 @@ SlaGizmoHelpDialog::SlaGizmoHelpDialog() const wxFont& font = wxGetApp().small_font(); const wxFont& bold_font = wxGetApp().bold_font(); - auto note_text = new wxStaticText(this, wxID_ANY, _(L("Note: some shortcuts work in (non)editing mode only."))); + auto note_text = new wxStaticText(this, wxID_ANY, _L("Note: some shortcuts work in (non)editing mode only.")); note_text->SetFont(font); auto vsizer = new wxBoxSizer(wxVERTICAL); @@ -1209,21 +1216,21 @@ SlaGizmoHelpDialog::SlaGizmoHelpDialog() vsizer->AddSpacer(20); std::vector> shortcuts; - shortcuts.push_back(std::make_pair(_(L("Left click")), _(L("Add point")))); - shortcuts.push_back(std::make_pair(_(L("Right click")), _(L("Remove point")))); - shortcuts.push_back(std::make_pair(_(L("Drag")), _(L("Move point")))); - shortcuts.push_back(std::make_pair(ctrl+_(L("Left click")), _(L("Add point to selection")))); - shortcuts.push_back(std::make_pair(alt+_(L("Left click")), _(L("Remove point from selection")))); - shortcuts.push_back(std::make_pair(wxString("Shift+")+_(L("Drag")), _(L("Select by rectangle")))); - shortcuts.push_back(std::make_pair(alt+_(L("Drag")), _(L("Deselect by rectangle")))); - shortcuts.push_back(std::make_pair(ctrl+"A", _(L("Select all points")))); - shortcuts.push_back(std::make_pair("Delete", _(L("Remove selected points")))); - shortcuts.push_back(std::make_pair(ctrl+_(L("Mouse wheel")), _(L("Move clipping plane")))); - shortcuts.push_back(std::make_pair("R", _(L("Reset clipping plane")))); - shortcuts.push_back(std::make_pair("Enter", _(L("Apply changes")))); - shortcuts.push_back(std::make_pair("Esc", _(L("Discard changes")))); - shortcuts.push_back(std::make_pair("M", _(L("Switch to editing mode")))); - shortcuts.push_back(std::make_pair("A", _(L("Auto-generate points")))); + shortcuts.push_back(std::make_pair(_L("Left click"), _L("Add point"))); + shortcuts.push_back(std::make_pair(_L("Right click"), _L("Remove point"))); + shortcuts.push_back(std::make_pair(_L("Drag"), _L("Move point"))); + shortcuts.push_back(std::make_pair(ctrl+_L("Left click"), _L("Add point to selection"))); + shortcuts.push_back(std::make_pair(alt+_L("Left click"), _L("Remove point from selection"))); + shortcuts.push_back(std::make_pair(wxString("Shift+")+_L("Drag"), _L("Select by rectangle"))); + shortcuts.push_back(std::make_pair(alt+_(L("Drag")), _L("Deselect by rectangle"))); + shortcuts.push_back(std::make_pair(ctrl+"A", _L("Select all points"))); + shortcuts.push_back(std::make_pair("Delete", _L("Remove selected points"))); + shortcuts.push_back(std::make_pair(ctrl+_L("Mouse wheel"), _L("Move clipping plane"))); + shortcuts.push_back(std::make_pair("R", _L("Reset clipping plane"))); + shortcuts.push_back(std::make_pair("Enter", _L("Apply changes"))); + shortcuts.push_back(std::make_pair("Esc", _L("Discard changes"))); + shortcuts.push_back(std::make_pair("M", _L("Switch to editing mode"))); + shortcuts.push_back(std::make_pair("A", _L("Auto-generate points"))); for (const auto& pair : shortcuts) { auto shortcut = new wxStaticText(this, wxID_ANY, pair.first); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index db7af046b..5726477ab 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -488,8 +488,7 @@ bool ImGuiWrapper::undo_redo_list(const ImVec2& size, const bool is_undo, bool ( int i=0; const char* item_text; - while (items_getter(is_undo, i, &item_text)) - { + while (items_getter(is_undo, i, &item_text)) { ImGui::Selectable(item_text, i < hovered); if (ImGui::IsItemHovered()) { diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 1c451672c..e72975629 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -109,7 +109,11 @@ void KBShortcutsDialog::fill_shortcuts() { "0-6", L("Camera view") }, { "E", L("Show/Hide object/instance labels") }, // Configuration +#ifdef __APPLE__ + { ctrl + ",", L("Preferences") }, +#else { ctrl + "P", L("Preferences") }, +#endif // Help { "?", L("Show keyboard shortcuts list") } }; @@ -149,8 +153,13 @@ void KBShortcutsDialog::fill_shortcuts() { "Shift+Tab", L("Collapse/Expand the sidebar") }, #ifdef _WIN32 { ctrl + "M", L("Show/Hide 3Dconnexion devices settings dialog, if enabled") }, +#else +#ifdef __APPLE__ + { ctrl + "Shift+M", L("Show/Hide 3Dconnexion devices settings dialog") }, + { ctrl + "M", L("Minimize application") }, #else { ctrl + "M", L("Show/Hide 3Dconnexion devices settings dialog") }, +#endif // __APPLE__ #endif // _WIN32 #if ENABLE_RENDER_PICKING_PASS // Don't localize debugging texts. @@ -171,6 +180,14 @@ void KBShortcutsDialog::fill_shortcuts() }; m_full_shortcuts.push_back({ { _L("Gizmos"), _L("The following shortcuts are applicable when the specified gizmo is active") }, gizmos_shortcuts }); + + Shortcuts object_list_shortcuts = { + { "P", L("Set selected items as Ptrintable/Unprintable") }, + { "0", L("Set default extruder for the selected items") }, + { "1-9", L("Set extruder number for the selected items") }, + }; + + m_full_shortcuts.push_back({ { _L("Objects List"), "" }, object_list_shortcuts }); } else { Shortcuts commands_shortcuts = { diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 666e3327b..989056bfc 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -206,7 +206,14 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S // declare events Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) { +#if ENABLE_PROJECT_DIRTY_STATE + if (m_plater != nullptr) + m_plater->save_project_if_dirty(); + + if (event.CanVeto() && !wxGetApp().check_and_save_current_preset_changes()) { +#else if (event.CanVeto() && !wxGetApp().check_unsaved_changes()) { +#endif // ENABLE_PROJECT_DIRTY_STATE event.Veto(); return; } @@ -487,8 +494,14 @@ void MainFrame::update_title() // m_plater->get_project_filename() produces file name including path, but excluding extension. // Don't try to remove the extension, it would remove part of the file name after the last dot! wxString project = from_path(into_path(m_plater->get_project_filename()).filename()); +#if ENABLE_PROJECT_DIRTY_STATE + wxString dirty_marker = (!m_plater->model().objects.empty() && m_plater->is_project_dirty()) ? "*" : ""; + if (!dirty_marker.empty() || !project.empty()) + title = dirty_marker + project + " - "; +#else if (!project.empty()) title += (project + " - "); +#endif // ENABLE_PROJECT_DIRTY_STATE } std::string build_id = wxGetApp().is_editor() ? SLIC3R_BUILD_ID : GCODEVIEWER_BUILD_ID; @@ -531,9 +544,12 @@ void MainFrame::init_tabpanel() m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxBookCtrlEvent& e) { #if ENABLE_VALIDATE_CUSTOM_GCODE - Tab* old_tab = dynamic_cast(m_tabpanel->GetPage(e.GetOldSelection())); - if (old_tab) - old_tab->validate_custom_gcodes(); + if (int old_selection = e.GetOldSelection(); + old_selection != wxNOT_FOUND && old_selection < static_cast(m_tabpanel->GetPageCount())) { + Tab* old_tab = dynamic_cast(m_tabpanel->GetPage(old_selection)); + if (old_tab) + old_tab->validate_custom_gcodes(); + } #endif // ENABLE_VALIDATE_CUSTOM_GCODE wxWindow* panel = m_tabpanel->GetCurrentPage(); @@ -672,10 +688,36 @@ bool MainFrame::can_start_new_project() const return (m_plater != nullptr) && (!m_plater->get_project_filename(".3mf").IsEmpty() || !m_plater->model().objects.empty()); } +#if ENABLE_PROJECT_DIRTY_STATE +bool MainFrame::can_save() const +{ + return (m_plater != nullptr) && !m_plater->model().objects.empty() && !m_plater->get_project_filename().empty() && m_plater->is_project_dirty(); +} + +bool MainFrame::can_save_as() const +{ + return (m_plater != nullptr) && !m_plater->model().objects.empty(); +} + +void MainFrame::save_project() +{ + save_project_as(m_plater->get_project_filename(".3mf")); +} + +void MainFrame::save_project_as(const wxString& filename) +{ + bool ret = (m_plater != nullptr) ? m_plater->export_3mf(into_path(filename)) : false; + if (ret) { +// wxGetApp().update_saved_preset_from_current_preset(); + m_plater->reset_project_dirty_after_save(); + } +} +#else bool MainFrame::can_save() const { return (m_plater != nullptr) && !m_plater->model().objects.empty(); } +#endif // ENABLE_PROJECT_DIRTY_STATE bool MainFrame::can_export_model() const { @@ -984,16 +1026,27 @@ void MainFrame::init_menubar_as_editor() Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(m_recent_projects.GetCount() > 0); }, recent_projects_submenu->GetId()); +#if ENABLE_PROJECT_DIRTY_STATE + append_menu_item(fileMenu, wxID_ANY, _L("&Save Project") + "\tCtrl+S", _L("Save current project file"), + [this](wxCommandEvent&) { save_project(); }, "save", nullptr, + [this](){return m_plater != nullptr && can_save(); }, this); +#else append_menu_item(fileMenu, wxID_ANY, _L("&Save Project") + "\tCtrl+S", _L("Save current project file"), [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(into_path(m_plater->get_project_filename(".3mf"))); }, "save", nullptr, [this](){return m_plater != nullptr && can_save(); }, this); +#endif // ENABLE_PROJECT_DIRTY_STATE #ifdef __APPLE__ append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Shift+S", _L("Save current project file as"), #else append_menu_item(fileMenu, wxID_ANY, _L("Save Project &as") + dots + "\tCtrl+Alt+S", _L("Save current project file as"), #endif // __APPLE__ +#if ENABLE_PROJECT_DIRTY_STATE + [this](wxCommandEvent&) { save_project_as(); }, "save", nullptr, + [this](){return m_plater != nullptr && can_save_as(); }, this); +#else [this](wxCommandEvent&) { if (m_plater) m_plater->export_3mf(); }, "save", nullptr, [this](){return m_plater != nullptr && can_save(); }, this); +#endif // ENABLE_PROJECT_DIRTY_STATE fileMenu->AppendSeparator(); @@ -1518,7 +1571,11 @@ void MainFrame::export_config() // Load a config file containing a Print, Filament & Printer preset. void MainFrame::load_config_file() { +#if ENABLE_PROJECT_DIRTY_STATE + if (!wxGetApp().check_and_save_current_preset_changes()) +#else if (!wxGetApp().check_unsaved_changes()) +#endif // ENABLE_PROJECT_DIRTY_STATE return; wxFileDialog dlg(this, _L("Select configuration to load:"), !m_last_config.IsEmpty() ? get_dir_name(m_last_config) : wxGetApp().app_config->get_last_dir(), @@ -1547,7 +1604,11 @@ bool MainFrame::load_config_file(const std::string &path) void MainFrame::export_configbundle(bool export_physical_printers /*= false*/) { +#if ENABLE_PROJECT_DIRTY_STATE + if (!wxGetApp().check_and_save_current_preset_changes()) +#else if (!wxGetApp().check_unsaved_changes()) +#endif // ENABLE_PROJECT_DIRTY_STATE return; // validate current configuration in case it's dirty auto err = wxGetApp().preset_bundle->full_config().validate(); @@ -1579,7 +1640,11 @@ void MainFrame::export_configbundle(bool export_physical_printers /*= false*/) // but that behavior was not documented and likely buggy. void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool reset_user_profile*/) { +#if ENABLE_PROJECT_DIRTY_STATE + if (!wxGetApp().check_and_save_current_preset_changes()) +#else if (!wxGetApp().check_unsaved_changes()) +#endif // ENABLE_PROJECT_DIRTY_STATE return; if (file.IsEmpty()) { wxFileDialog dlg(this, _L("Select configuration to load:"), diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 0971fdc77..307cdf1ae 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -91,7 +91,9 @@ class MainFrame : public DPIFrame void on_value_changed(wxCommandEvent&); bool can_start_new_project() const; +#if !ENABLE_PROJECT_DIRTY_STATE bool can_save() const; +#endif // !ENABLE_PROJECT_DIRTY_STATE bool can_export_model() const; bool can_export_toolpaths() const; bool can_export_supports() const; @@ -184,6 +186,13 @@ public: // Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig void on_config_changed(DynamicPrintConfig* cfg) const ; +#if ENABLE_PROJECT_DIRTY_STATE + bool can_save() const; + bool can_save_as() const; + void save_project(); + void save_project_as(const wxString& filename = wxString()); +#endif // ENABLE_PROJECT_DIRTY_STATE + void add_to_recent_projects(const wxString& filename); PrintHostQueueDialog* printhost_queue_dlg() { return m_printhost_queue_dlg; } diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index f9ccfd0d6..c65d2b5a9 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -2,6 +2,7 @@ #include "libslic3r/Tesselate.hpp" #include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/ClipperUtils.hpp" #include "slic3r/GUI/Camera.hpp" @@ -28,7 +29,6 @@ void MeshClipper::set_mesh(const TriangleMesh& mesh) m_mesh = &mesh; m_triangles_valid = false; m_triangles2d.resize(0); - m_tms.reset(nullptr); } } @@ -67,11 +67,6 @@ void MeshClipper::render_cut() void MeshClipper::recalculate_triangles() { - if (! m_tms) { - m_tms.reset(new TriangleMeshSlicer); - m_tms->init(m_mesh, [](){}); - } - const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast(); const Vec3f& scaling = m_trafo.get_scaling_factor().cast(); // Calculate clipping plane normal in mesh coordinates. @@ -81,18 +76,18 @@ void MeshClipper::recalculate_triangles() float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm()); // Now do the cutting - std::vector list_of_expolys; - m_tms->set_up_direction(up.cast()); - m_tms->slice(std::vector{height_mesh}, SlicingMode::Regular, 0.f, &list_of_expolys, [](){}); + MeshSlicingParamsEx slicing_params; + slicing_params.trafo.rotate(Eigen::Quaternion::FromTwoVectors(up, Vec3d::UnitZ())); + + assert(m_mesh->has_shared_vertices()); + std::vector list_of_expolys = slice_mesh_ex(m_mesh->its, std::vector{height_mesh}, slicing_params); if (m_negative_mesh && !m_negative_mesh->empty()) { - TriangleMeshSlicer negative_tms{m_negative_mesh}; - negative_tms.set_up_direction(up.cast()); - - std::vector neg_polys; - negative_tms.slice(std::vector{height_mesh}, SlicingMode::Regular, 0.f, &neg_polys, [](){}); + assert(m_negative_mesh->has_shared_vertices()); + std::vector neg_polys = slice_mesh_ex(m_negative_mesh->its, std::vector{height_mesh}, slicing_params); list_of_expolys.front() = diff_ex(list_of_expolys.front(), neg_polys.front()); } + m_triangles2d = triangulate_expolygons_2f(list_of_expolys[0], m_trafo.get_matrix().matrix().determinant() < 0.); // Rotate the cut into world coords: diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 09caf199b..07b01e27f 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -12,9 +12,6 @@ namespace Slic3r { -class TriangleMesh; -class TriangleMeshSlicer; - namespace GUI { struct Camera; @@ -98,7 +95,6 @@ private: std::vector m_triangles2d; GLIndexedVertexArray m_vertex_array; bool m_triangles_valid = false; - std::unique_ptr m_tms; }; diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 7f61ad7f3..1b29b7f76 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -254,7 +254,7 @@ void NotificationManager::PopNotification::count_spaces() void NotificationManager::PopNotification::count_lines() { - std::string text = m_text1 + " " + m_hypertext; + std::string text = m_text1; size_t last_end = 0; m_lines_count = 0; @@ -302,6 +302,14 @@ void NotificationManager::PopNotification::count_lines() } m_lines_count++; } + // hypertext calculation + if (!m_hypertext.empty()) { + int prev_end = m_endlines.size() > 1 ? m_endlines[m_endlines.size() - 2] : 0; + if (ImGui::CalcTextSize((text.substr(prev_end, last_end - prev_end) + m_hypertext).c_str()).x > m_window_width - m_window_width_offset) { + m_endlines.push_back(last_end); + m_lines_count++; + } + } } void NotificationManager::PopNotification::init() @@ -312,7 +320,7 @@ void NotificationManager::PopNotification::init() count_spaces(); count_lines(); - + if (m_lines_count == 3) m_multiline = true; m_notification_start = GLCanvas3D::timestamp_now(); @@ -342,39 +350,48 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons int last_end = 0; float starting_y = m_line_height/2; float shift_y = m_line_height; + std::string line; + for (size_t i = 0; i < m_lines_count; i++) { - std::string line = m_text1.substr(last_end , m_endlines[i] - last_end); - if(i < m_lines_count - 1) - last_end = m_endlines[i] + (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); + line.clear(); ImGui::SetCursorPosX(x_offset); ImGui::SetCursorPosY(starting_y + i * shift_y); - imgui.text(line.c_str()); + if (m_endlines.size() > i && m_text1.size() >= m_endlines[i]) { + line = m_text1.substr(last_end, m_endlines[i] - last_end); + last_end = m_endlines[i]; + if (m_text1.size() > m_endlines[i]) + last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); + imgui.text(line.c_str()); + } } //hyperlink text - if (!m_hypertext.empty()) - { - render_hypertext(imgui, x_offset + ImGui::CalcTextSize(m_text1.substr(m_endlines[m_lines_count - 2] + 1, m_endlines[m_lines_count - 1] - m_endlines[m_lines_count - 2] - 1).c_str()).x, starting_y + (m_lines_count - 1) * shift_y, m_hypertext); + if (!m_hypertext.empty()) { + render_hypertext(imgui, x_offset + ImGui::CalcTextSize((line + " ").c_str()).x, starting_y + (m_lines_count - 1) * shift_y, m_hypertext); } } else { // line1 - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); - imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + if (m_text1.size() >= m_endlines[0]) { + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); + imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + } // line2 - std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); - if (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x) - { - line = line.substr(0, line.length() - 6); - line += ".."; - }else - line += " "; + std::string line; ImGui::SetCursorPosX(x_offset); ImGui::SetCursorPosY(win_size.y / 2 + win_size.y / 6 - m_line_height / 2); - imgui.text(line.c_str()); + if (m_text1.size() >= m_endlines[1]) { + line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0), m_endlines[1] - m_endlines[0] - (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); + if (ImGui::CalcTextSize(line.c_str()).x > m_window_width - m_window_width_offset - ImGui::CalcTextSize((".." + _u8L("More")).c_str()).x) { + line = line.substr(0, line.length() - 6); + line += ".."; + } else + line += " "; + imgui.text(line.c_str()); + } // "More" hypertext - render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x, win_size.y / 2 + win_size.y / 6 - m_line_height / 2, _u8L("More"), true); + render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x, win_size.y / 2 + win_size.y / 6 - m_line_height / 2, _u8L("More"), true); } } else { //text 1 @@ -382,15 +399,17 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons float cursor_x = x_offset; if(m_lines_count > 1) { // line1 - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); - imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); - // line2 - if (m_text1.length() > m_endlines[0]) { - std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); - cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2; + if (m_text1.length() >= m_endlines[0]) { // could be equal than substr takes whole string ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(cursor_y); + ImGui::SetCursorPosY(win_size.y / 2 - win_size.y / 6 - m_line_height / 2); + imgui.text(m_text1.substr(0, m_endlines[0]).c_str()); + } + // line2 + ImGui::SetCursorPosX(x_offset); + cursor_y = win_size.y / 2 + win_size.y / 6 - m_line_height / 2; + ImGui::SetCursorPosY(cursor_y); + if (m_text1.length() > m_endlines[0]) { // must be greater otherwise theres nothing to show and m_text1[m_endlines[0]] is beyond last letter + std::string line = m_text1.substr(m_endlines[0] + (m_text1[m_endlines[0]] == '\n' || m_text1[m_endlines[0]] == ' ' ? 1 : 0)); imgui.text(line.c_str()); cursor_x = x_offset + ImGui::CalcTextSize(line.c_str()).x; } @@ -401,8 +420,7 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons cursor_x = x_offset + ImGui::CalcTextSize(m_text1.c_str()).x; } //hyperlink text - if (!m_hypertext.empty()) - { + if (!m_hypertext.empty()) { render_hypertext(imgui, cursor_x + 4, cursor_y, m_hypertext); } @@ -712,15 +730,18 @@ void NotificationManager::ExportFinishedNotification::render_text(ImGuiWrapper& float starting_y = m_line_height / 2;//10; float shift_y = m_line_height;// -m_line_height / 20; for (size_t i = 0; i < m_lines_count; i++) { - std::string line = m_text1.substr(last_end, m_endlines[i] - last_end); - if (i < m_lines_count - 1) - last_end = m_endlines[i] + (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); - ImGui::SetCursorPosX(x_offset); - ImGui::SetCursorPosY(starting_y + i * shift_y); - imgui.text(line.c_str()); - //hyperlink text - if ( i == 0 ) { - render_hypertext(imgui, x_offset + ImGui::CalcTextSize(m_text1.substr(0, last_end).c_str()).x + ImGui::CalcTextSize(" ").x, starting_y, _u8L("Open Folder.")); + if (m_text1.size() >= m_endlines[i]) { + std::string line = m_text1.substr(last_end, m_endlines[i] - last_end); + last_end = m_endlines[i]; + if (m_text1.size() > m_endlines[i]) + last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(starting_y + i * shift_y); + imgui.text(line.c_str()); + //hyperlink text + if ( i == 0 ) { + render_hypertext(imgui, x_offset + ImGui::CalcTextSize(line.c_str()).x + ImGui::CalcTextSize(" ").x, starting_y, _u8L("Open Folder.")); + } } } diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 49c75f9f2..297d8e3c9 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -1180,6 +1180,15 @@ int ObjectDataViewModel::GetExtruderNumber(const wxDataViewItem& item) const return atoi(node->m_extruder.c_str()); } +wxString ObjectDataViewModel::GetColumnType(unsigned int col) const +{ + if (col == colName || col == colExtruder) + return wxT("DataViewBitmapText"); + if (col == colPrint || col == colEditing) + return wxT("DataViewBitmap"); + return wxT("string"); +} + void ObjectDataViewModel::GetValue(wxVariant &variant, const wxDataViewItem &item, unsigned int col) const { wxASSERT(item.IsOk()); diff --git a/src/slic3r/GUI/ObjectDataViewModel.hpp b/src/slic3r/GUI/ObjectDataViewModel.hpp index 1dd41bb10..93e092fc2 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.hpp +++ b/src/slic3r/GUI/ObjectDataViewModel.hpp @@ -316,7 +316,7 @@ public: // helper methods to change the model unsigned int GetColumnCount() const override { return 3;} - wxString GetColumnType(unsigned int col) const override{ return wxT("string"); } + wxString GetColumnType(unsigned int col) const override; void GetValue( wxVariant &variant, const wxDataViewItem &item, diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index aebec14ee..e306641a9 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -81,6 +81,9 @@ #include "InstanceCheck.hpp" #include "NotificationManager.hpp" #include "PresetComboBoxes.hpp" +#if ENABLE_PROJECT_DIRTY_STATE +#include "ProjectDirtyStateManager.hpp" +#endif // ENABLE_PROJECT_DIRTY_STATE #ifdef __APPLE__ #include "Gizmos/GLGizmosManager.hpp" @@ -1172,10 +1175,10 @@ void Sidebar::update_sliced_info_sizer() new_label += format_wxstr(":\n - %1%\n - %2%", _L("objects"), _L("wipe tower")); wxString info_text = is_wipe_tower ? - wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / /*1000*/koef, - (ps.total_used_filament - ps.total_wipe_tower_filament) / /*1000*/koef, - ps.total_wipe_tower_filament / /*1000*/koef) : - wxString::Format("%.2f", ps.total_used_filament / /*1000*/koef); + wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / koef, + (ps.total_used_filament - ps.total_wipe_tower_filament) / koef, + ps.total_wipe_tower_filament / koef) : + wxString::Format("%.2f", ps.total_used_filament / koef); p->sliced_info->SetTextAndShow(siFilament_m, info_text, new_label); koef = imperial_units ? pow(ObjectManipulation::mm_to_in, 3) : 1.0f; @@ -1203,7 +1206,7 @@ void Sidebar::update_sliced_info_sizer() filament_weight = ps.total_weight; else { double filament_density = filament_preset->config.opt_float("filament_density", 0); - filament_weight = filament.second * filament_density * 2.4052f * 0.001; // assumes 1.75mm filament diameter; + filament_weight = filament.second * filament_density/* *2.4052f*/ * 0.001; // assumes 1.75mm filament diameter; new_label += "\n - " + format_wxstr(_L("Filament at extruder %1%"), filament.first + 1); info_text += wxString::Format("\n%.2f", filament_weight); @@ -1357,7 +1360,8 @@ void Sidebar::update_ui_from_settings() update_sliced_info_sizer(); // update Cut gizmo, if it's open p->plater->canvas3D()->update_gizmos_on_off_state(); - p->plater->canvas3D()->request_extra_frame(); + p->plater->set_current_canvas_as_dirty(); + p->plater->get_current_canvas3D()->request_extra_frame(); } std::vector& Sidebar::combos_filament() @@ -1395,7 +1399,13 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi this->MSWUpdateDragImageOnLeave(); #endif // WIN32 +#if ENABLE_PROJECT_DIRTY_STATE + bool res = (m_plater != nullptr) ? m_plater->load_files(filenames) : false; + wxGetApp().mainframe->update_title(); + return res; +#else return (m_plater != nullptr) ? m_plater->load_files(filenames) : false; +#endif // ENABLE_PROJECT_DIRTY_STATE } // State to manage showing after export notifications and device ejecting @@ -1439,6 +1449,10 @@ struct Plater::priv Preview *preview; NotificationManager* notification_manager { nullptr }; +#if ENABLE_PROJECT_DIRTY_STATE + ProjectDirtyStateManager dirty_state; +#endif // ENABLE_PROJECT_DIRTY_STATE + BackgroundSlicingProcess background_process; bool suppressed_backround_processing_update { false }; @@ -1509,6 +1523,31 @@ struct Plater::priv priv(Plater *q, MainFrame *main_frame); ~priv(); +#if ENABLE_PROJECT_DIRTY_STATE + bool is_project_dirty() const { return dirty_state.is_dirty(); } + void update_project_dirty_from_presets() { dirty_state.update_from_presets(); } + bool save_project_if_dirty() { + if (dirty_state.is_dirty()) { + MainFrame* mainframe = wxGetApp().mainframe; + if (mainframe->can_save_as()) { + wxMessageDialog dlg(mainframe, _L("Do you want to save the changes to the current project ?"), wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL); + int res = dlg.ShowModal(); + if (res == wxID_YES) + mainframe->save_project_as(wxGetApp().plater()->get_project_filename()); + else if (res == wxID_CANCEL) + return false; + } + } + return true; + } + void reset_project_dirty_after_save() { dirty_state.reset_after_save(); } + void reset_project_dirty_initial_presets() { dirty_state.reset_initial_presets(); } + +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + void render_project_state_debug_window() const { dirty_state.render_debug_window(); } +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +#endif // ENABLE_PROJECT_DIRTY_STATE + enum class UpdateParams { FORCE_FULL_SCREEN_REFRESH = 1, FORCE_BACKGROUND_PROCESSING_UPDATE = 2, @@ -1554,7 +1593,11 @@ struct Plater::priv BoundingBox scaled_bed_shape_bb() const; std::vector load_files(const std::vector& input_files, bool load_model, bool load_config, bool used_inches = false); +#if ENABLE_ALLOW_NEGATIVE_Z + std::vector load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z = false); +#else std::vector load_model_objects(const ModelObjectPtrs &model_objects); +#endif // ENABLE_ALLOW_NEGATIVE_Z wxString get_export_file(GUI::FileType file_type); const Selection& get_selection() const; @@ -2001,6 +2044,9 @@ void Plater::priv::update(unsigned int flags) this->restart_background_process(update_status); else this->schedule_background_process(); + + if (get_config("autocenter") == "1" && this->sidebar->obj_manipul()->IsShown()) + this->sidebar->obj_manipul()->UpdateAndShow(true); } void Plater::priv::select_view(const std::string& direction) @@ -2294,11 +2340,19 @@ std::vector Plater::priv::load_files(const std::vector& input_ return obj_idxs; } +#if ENABLE_ALLOW_NEGATIVE_Z + for (ModelObject* model_object : model.objects) { + if (!type_3mf && !type_zip_amf) + model_object->center_around_origin(false); + model_object->ensure_on_bed(is_project_file); + } +#else for (ModelObject* model_object : model.objects) { if (!type_3mf && !type_zip_amf) model_object->center_around_origin(false); model_object->ensure_on_bed(); } +#endif // ENABLE_ALLOW_NEGATIVE_Z // check multi-part object adding for the SLA-printing if (printer_technology == ptSLA) { @@ -2312,7 +2366,11 @@ std::vector Plater::priv::load_files(const std::vector& input_ } if (one_by_one) { +#if ENABLE_ALLOW_NEGATIVE_Z + auto loaded_idxs = load_model_objects(model.objects, is_project_file); +#else auto loaded_idxs = load_model_objects(model.objects); +#endif // ENABLE_ALLOW_NEGATIVE_Z obj_idxs.insert(obj_idxs.end(), loaded_idxs.begin(), loaded_idxs.end()); } else { // This must be an .stl or .obj file, which may contain a maximum of one volume. @@ -2364,7 +2422,11 @@ std::vector Plater::priv::load_files(const std::vector& input_ // #define AUTOPLACEMENT_ON_LOAD +#if ENABLE_ALLOW_NEGATIVE_Z +std::vector Plater::priv::load_model_objects(const ModelObjectPtrs& model_objects, bool allow_negative_z) +#else std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &model_objects) +#endif // ENABLE_ALLOW_NEGATIVE_Z { const BoundingBoxf bed_shape = bed_shape_bb(); const Vec3d bed_size = Slic3r::to_3d(bed_shape.size().cast(), 1.0) - 2.0 * Vec3d::Ones(); @@ -2441,7 +2503,11 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode } #endif // ENABLE_MODIFIED_DOWNSCALE_ON_LOAD_OBJECTS_TOO_BIG +#if ENABLE_ALLOW_NEGATIVE_Z + object->ensure_on_bed(allow_negative_z); +#else object->ensure_on_bed(); +#endif // ENABLE_ALLOW_NEGATIVE_Z } #ifdef AUTOPLACEMENT_ON_LOAD @@ -2597,8 +2663,7 @@ int Plater::priv::get_selected_volume_idx() const void Plater::priv::selection_changed() { // if the selection is not valid to allow for layer editing, we need to turn off the tool if it is running - bool enable_layer_editing = layers_height_allowed(); - if (!enable_layer_editing && view3D->is_layers_editing_enabled()) { + if (!layers_height_allowed() && view3D->is_layers_editing_enabled()) { SimpleEvent evt(EVT_GLTOOLBAR_LAYERSEDITING); on_action_layersediting(evt); } @@ -2735,10 +2800,11 @@ void Plater::priv::split_object() Slic3r::GUI::warning_catcher(q, _L("The selected object couldn't be split because it contains only one solid part.")); else { - if (current_model_object->volumes.size() != new_objects.size()) + // If we splited object which is contain some parts/modifiers then all non-solid parts (modifiers) were deleted + if (current_model_object->volumes.size() > 1 && current_model_object->volumes.size() != new_objects.size()) notification_manager->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::RegularNotification, - _u8L("All non-solid parts (modifiers) was deleted")); + _u8L("All non-solid parts (modifiers) were deleted")); Plater::TakeSnapshot snapshot(q, _L("Split to Objects")); @@ -3039,21 +3105,19 @@ void Plater::priv::reload_from_disk() int volume_idx; // operators needed by std::algorithms - bool operator < (const SelectedVolume& other) const { return (object_idx < other.object_idx) || ((object_idx == other.object_idx) && (volume_idx < other.volume_idx)); } - bool operator == (const SelectedVolume& other) const { return (object_idx == other.object_idx) && (volume_idx == other.volume_idx); } + bool operator < (const SelectedVolume& other) const { return object_idx < other.object_idx || (object_idx == other.object_idx && volume_idx < other.volume_idx); } + bool operator == (const SelectedVolume& other) const { return object_idx == other.object_idx && volume_idx == other.volume_idx; } }; std::vector selected_volumes; // collects selected ModelVolumes const std::set& selected_volumes_idxs = selection.get_volume_idxs(); - for (unsigned int idx : selected_volumes_idxs) - { + for (unsigned int idx : selected_volumes_idxs) { const GLVolume* v = selection.get_volume(idx); int v_idx = v->volume_idx(); - if (v_idx >= 0) - { + if (v_idx >= 0) { int o_idx = v->object_idx(); - if ((0 <= o_idx) && (o_idx < (int)model.objects.size())) + if (0 <= o_idx && o_idx < (int)model.objects.size()) selected_volumes.push_back({ o_idx, v_idx }); } } @@ -3063,13 +3127,11 @@ void Plater::priv::reload_from_disk() // collects paths of files to load std::vector input_paths; std::vector missing_input_paths; - for (const SelectedVolume& v : selected_volumes) - { + for (const SelectedVolume& v : selected_volumes) { const ModelObject* object = model.objects[v.object_idx]; const ModelVolume* volume = object->volumes[v.volume_idx]; - if (!volume->source.input_file.empty()) - { + if (!volume->source.input_file.empty()) { if (fs::exists(volume->source.input_file)) input_paths.push_back(volume->source.input_file); else @@ -3082,8 +3144,7 @@ void Plater::priv::reload_from_disk() std::sort(missing_input_paths.begin(), missing_input_paths.end()); missing_input_paths.erase(std::unique(missing_input_paths.begin(), missing_input_paths.end()), missing_input_paths.end()); - while (!missing_input_paths.empty()) - { + while (!missing_input_paths.empty()) { // ask user to select the missing file fs::path search = missing_input_paths.back(); wxString title = _L("Please select the file to reload"); @@ -3097,21 +3158,18 @@ void Plater::priv::reload_from_disk() std::string sel_filename_path = dialog.GetPath().ToUTF8().data(); std::string sel_filename = fs::path(sel_filename_path).filename().string(); - if (boost::algorithm::iequals(search.filename().string(), sel_filename)) - { + if (boost::algorithm::iequals(search.filename().string(), sel_filename)) { input_paths.push_back(sel_filename_path); missing_input_paths.pop_back(); fs::path sel_path = fs::path(sel_filename_path).remove_filename().string(); std::vector::iterator it = missing_input_paths.begin(); - while (it != missing_input_paths.end()) - { + while (it != missing_input_paths.end()) { // try to use the path of the selected file with all remaining missing files fs::path repathed_filename = sel_path; repathed_filename /= it->filename(); - if (fs::exists(repathed_filename)) - { + if (fs::exists(repathed_filename)) { input_paths.push_back(repathed_filename.string()); it = missing_input_paths.erase(it); } @@ -3119,8 +3177,7 @@ void Plater::priv::reload_from_disk() ++it; } } - else - { + else { wxString message = _L("It is not allowed to change the file to reload") + " (" + from_u8(search.filename().string()) + ").\n" + _L("Do you want to retry") + " ?"; wxMessageDialog dlg(q, message, wxMessageBoxCaptionStr, wxYES_NO | wxYES_DEFAULT | wxICON_QUESTION); if (dlg.ShowModal() != wxID_YES) @@ -3134,8 +3191,7 @@ void Plater::priv::reload_from_disk() std::vector fail_list; // load one file at a time - for (size_t i = 0; i < input_paths.size(); ++i) - { + for (size_t i = 0; i < input_paths.size(); ++i) { const auto& path = input_paths[i].string(); wxBusyCursor wait; @@ -3145,8 +3201,7 @@ void Plater::priv::reload_from_disk() try { new_model = Model::read_from_file(path, nullptr, true, false); - for (ModelObject* model_object : new_model.objects) - { + for (ModelObject* model_object : new_model.objects) { model_object->center_around_origin(); model_object->ensure_on_bed(); } @@ -3158,34 +3213,31 @@ void Plater::priv::reload_from_disk() } // update the selected volumes whose source is the current file - for (const SelectedVolume& sel_v : selected_volumes) - { + for (const SelectedVolume& sel_v : selected_volumes) { ModelObject* old_model_object = model.objects[sel_v.object_idx]; ModelVolume* old_volume = old_model_object->volumes[sel_v.volume_idx]; +#if ENABLE_ALLOW_NEGATIVE_Z + bool sinking = old_model_object->bounding_box().min.z() < 0.0; +#endif // ENABLE_ALLOW_NEGATIVE_Z + bool has_source = !old_volume->source.input_file.empty() && boost::algorithm::iequals(fs::path(old_volume->source.input_file).filename().string(), fs::path(path).filename().string()); bool has_name = !old_volume->name.empty() && boost::algorithm::iequals(old_volume->name, fs::path(path).filename().string()); - if (has_source || has_name) - { + if (has_source || has_name) { int new_volume_idx = -1; int new_object_idx = -1; - if (has_source) - { + if (has_source) { // take idxs from source new_volume_idx = old_volume->source.volume_idx; new_object_idx = old_volume->source.object_idx; } - else - { + else { // take idxs from the 1st matching volume - for (size_t o = 0; o < new_model.objects.size(); ++o) - { + for (size_t o = 0; o < new_model.objects.size(); ++o) { ModelObject* obj = new_model.objects[o]; bool found = false; - for (size_t v = 0; v < obj->volumes.size(); ++v) - { - if (obj->volumes[v]->name == old_volume->name) - { + for (size_t v = 0; v < obj->volumes.size(); ++v) { + if (obj->volumes[v]->name == old_volume->name) { new_volume_idx = (int)v; new_object_idx = (int)o; found = true; @@ -3197,19 +3249,16 @@ void Plater::priv::reload_from_disk() } } - if ((new_object_idx < 0) && ((int)new_model.objects.size() <= new_object_idx)) - { + if (new_object_idx < 0 && (int)new_model.objects.size() <= new_object_idx) { fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name)); continue; } ModelObject* new_model_object = new_model.objects[new_object_idx]; - if ((new_volume_idx < 0) && ((int)new_model.objects.size() <= new_volume_idx)) - { + if (new_volume_idx < 0 && (int)new_model.objects.size() <= new_volume_idx) { fail_list.push_back(from_u8(has_source ? old_volume->source.input_file : old_volume->name)); continue; } - if (new_volume_idx < (int)new_model_object->volumes.size()) - { + if (new_volume_idx < (int)new_model_object->volumes.size()) { old_model_object->add_volume(*new_model_object->volumes[new_volume_idx]); ModelVolume* new_volume = old_model_object->volumes.back(); new_volume->set_new_unique_id(); @@ -3226,7 +3275,10 @@ void Plater::priv::reload_from_disk() new_volume->seam_facets.assign(old_volume->seam_facets); std::swap(old_model_object->volumes[sel_v.volume_idx], old_model_object->volumes.back()); old_model_object->delete_volume(old_model_object->volumes.size() - 1); - old_model_object->ensure_on_bed(); +#if ENABLE_ALLOW_NEGATIVE_Z + if (!sinking) +#endif // ENABLE_ALLOW_NEGATIVE_Z + old_model_object->ensure_on_bed(); sla::reproject_points_and_holes(old_model_object); } @@ -3234,11 +3286,9 @@ void Plater::priv::reload_from_disk() } } - if (!fail_list.empty()) - { + if (!fail_list.empty()) { wxString message = _L("Unable to reload:") + "\n"; - for (const wxString& s : fail_list) - { + for (const wxString& s : fail_list) { message += s + "\n"; } wxMessageDialog dlg(q, message, _L("Error during reload"), wxOK | wxOK_DEFAULT | wxICON_WARNING); @@ -3249,8 +3299,7 @@ void Plater::priv::reload_from_disk() update(); // new GLVolumes have been created at this point, so update their printable state - for (size_t i = 0; i < model.objects.size(); ++i) - { + for (size_t i = 0; i < model.objects.size(); ++i) { view3D->get_canvas3d()->update_instance_printable_state_for_object(i); } } @@ -3270,8 +3319,7 @@ void Plater::priv::reload_all_from_disk() reload_from_disk(); // restore previous selection selection.clear(); - for (unsigned int idx : curr_idxs) - { + for (unsigned int idx : curr_idxs) { selection.add(idx, false); } } @@ -3991,7 +4039,7 @@ void Plater::priv::reset_gcode_toolpaths() bool Plater::priv::can_set_instance_to_object() const { const int obj_idx = get_selected_object_idx(); - return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && (model.objects[obj_idx]->instances.size() > 1); + return 0 <= obj_idx && obj_idx < (int)model.objects.size() && model.objects[obj_idx]->instances.size() > 1; } bool Plater::priv::can_split(bool to_objects) const @@ -4005,7 +4053,12 @@ bool Plater::priv::layers_height_allowed() const return false; int obj_idx = get_selected_object_idx(); - return (0 <= obj_idx) && (obj_idx < (int)model.objects.size()) && config->opt_bool("variable_layer_height") && view3D->is_layers_editing_allowed(); +#if ENABLE_ALLOW_NEGATIVE_Z + return 0 <= obj_idx && obj_idx < (int)model.objects.size() && model.objects[obj_idx]->bounding_box().max.z() > 0.0 && + config->opt_bool("variable_layer_height") && view3D->is_layers_editing_allowed(); +#else + return 0 <= obj_idx && obj_idx < (int)model.objects.size() && config->opt_bool("variable_layer_height") && view3D->is_layers_editing_allowed(); +#endif // ENABLE_ALLOW_NEGATIVE_Z } bool Plater::priv::can_mirror() const @@ -4230,6 +4283,11 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name) } this->undo_redo_stack().take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), snapshot_data); this->undo_redo_stack().release_least_recently_used(); + +#if ENABLE_PROJECT_DIRTY_STATE + dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::TakeSnapshot); +#endif // ENABLE_PROJECT_DIRTY_STATE + // Save the last active preset name of a particular printer technology. ((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name(); BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack().memsize()) << log_memory_info(); @@ -4270,8 +4328,13 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator if (printer_technology_changed) { // Switching the printer technology when jumping forwards / backwards in time. Switch to the last active printer profile of the other type. std::string s_pt = (it_snapshot->snapshot_data.printer_technology == ptFFF) ? "FFF" : "SLA"; +#if ENABLE_PROJECT_DIRTY_STATE + if (!wxGetApp().check_and_save_current_preset_changes(format_wxstr(_L( + "%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."), s_pt))) +#else if (! wxGetApp().check_unsaved_changes(format_wxstr(_L( "%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."), s_pt))) +#endif // ENABLE_PROJECT_DIRTY_STATE // Don't switch the profiles. return; } @@ -4360,6 +4423,10 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active) view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting")); } + +#if ENABLE_PROJECT_DIRTY_STATE + dirty_state.update_from_undo_redo_stack(ProjectDirtyStateManager::UpdateType::UndoRedoTo); +#endif // ENABLE_PROJECT_DIRTY_STATE } void Plater::priv::update_after_undo_redo(const UndoRedo::Snapshot& snapshot, bool /* temp_snapshot_was_taken */) @@ -4443,9 +4510,16 @@ Plater::Plater(wxWindow *parent, MainFrame *main_frame) // Initialization performed in the private c-tor } -Plater::~Plater() -{ -} +#if ENABLE_PROJECT_DIRTY_STATE +bool Plater::is_project_dirty() const { return p->is_project_dirty(); } +void Plater::update_project_dirty_from_presets() { p->update_project_dirty_from_presets(); } +bool Plater::save_project_if_dirty() { return p->save_project_if_dirty(); } +void Plater::reset_project_dirty_after_save() { p->reset_project_dirty_after_save(); } +void Plater::reset_project_dirty_initial_presets() { p->reset_project_dirty_initial_presets(); } +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +void Plater::render_project_state_debug_window() const { p->render_project_state_debug_window(); } +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +#endif // ENABLE_PROJECT_DIRTY_STATE Sidebar& Plater::sidebar() { return *p->sidebar; } Model& Plater::model() { return p->model; } @@ -4456,12 +4530,30 @@ SLAPrint& Plater::sla_print() { return p->sla_print; } void Plater::new_project() { +#if ENABLE_PROJECT_DIRTY_STATE + if (!p->save_project_if_dirty()) + return; +#endif // ENABLE_PROJECT_DIRTY_STATE + p->select_view_3D("3D"); +#if ENABLE_PROJECT_DIRTY_STATE + take_snapshot(_L("New Project")); + Plater::SuppressSnapshots suppress(this); + reset(); + reset_project_dirty_initial_presets(); + update_project_dirty_from_presets(); +#else wxPostEvent(p->view3D->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); +#endif // ENABLE_PROJECT_DIRTY_STATE } void Plater::load_project() { +#if ENABLE_PROJECT_DIRTY_STATE + if (!p->save_project_if_dirty()) + return; +#endif // ENABLE_PROJECT_DIRTY_STATE + // Ask user for a project file name. wxString input_file; wxGetApp().load_project(this, input_file); @@ -4485,8 +4577,16 @@ void Plater::load_project(const wxString& filename) std::vector res = load_files(input_paths); // if res is empty no data has been loaded +#if ENABLE_PROJECT_DIRTY_STATE + if (!res.empty()) { + p->set_project_filename(filename); + reset_project_dirty_initial_presets(); + update_project_dirty_from_presets(); + } +#else if (!res.empty()) p->set_project_filename(filename); +#endif // ENABLE_PROJECT_DIRTY_STATE } void Plater::add_model(bool imperial_units/* = false*/) @@ -4517,7 +4617,13 @@ void Plater::add_model(bool imperial_units/* = false*/) } Plater::TakeSnapshot snapshot(this, snapshot_label); +#if ENABLE_PROJECT_DIRTY_STATE + std::vector res = load_files(paths, true, false, imperial_units); + if (!res.empty()) + wxGetApp().mainframe->update_title(); +#else load_files(paths, true, false, imperial_units); +#endif // ENABLE_PROJECT_DIRTY_STATE } void Plater::import_sl1_archive() @@ -5232,24 +5338,39 @@ void Plater::export_amf() } } +#if ENABLE_PROJECT_DIRTY_STATE +bool Plater::export_3mf(const boost::filesystem::path& output_path) +#else void Plater::export_3mf(const boost::filesystem::path& output_path) +#endif // ENABLE_PROJECT_DIRTY_STATE { if (p->model.objects.empty() || canvas3D()->get_gizmos_manager().is_in_editing_mode(true)) +#if ENABLE_PROJECT_DIRTY_STATE + return false; +#else return; +#endif // ENABLE_PROJECT_DIRTY_STATE wxString path; bool export_config = true; - if (output_path.empty()) - { + if (output_path.empty()) { path = p->get_export_file(FT_3MF); +#if ENABLE_PROJECT_DIRTY_STATE + if (path.empty()) { return false; } +#else if (path.empty()) { return; } +#endif // ENABLE_PROJECT_DIRTY_STATE } else path = from_path(output_path); if (!path.Lower().EndsWith(".3mf")) +#if ENABLE_PROJECT_DIRTY_STATE + return false; +#else return; +#endif // ENABLE_PROJECT_DIRTY_STATE DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure(); const std::string path_u8 = into_u8(path); @@ -5257,6 +5378,19 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1"; ThumbnailData thumbnail_data; p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true); +#if ENABLE_PROJECT_DIRTY_STATE + bool ret = Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data); + if (ret) { + // Success + p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path)); + p->set_project_filename(path); + } + else { + // Failure + p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path)); + } + return ret; +#else if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data)) { // Success p->statusbar()->set_status_text(format_wxstr(_L("3MF file exported to %s"), path)); @@ -5266,6 +5400,7 @@ void Plater::export_3mf(const boost::filesystem::path& output_path) // Failure p->statusbar()->set_status_text(format_wxstr(_L("Error exporting 3MF file %s"), path)); } +#endif // ENABLE_PROJECT_DIRTY_STATE } void Plater::reload_from_disk() @@ -5802,6 +5937,14 @@ bool Plater::set_printer_technology(PrinterTechnology printer_technology) //FIXME for SLA synchronize //p->background_process.apply(Model)! +#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA + if (printer_technology == ptSLA) { + for (ModelObject* model_object : p->model.objects) { + model_object->ensure_on_bed(); + } + } +#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA + p->label_btn_export = printer_technology == ptFFF ? L("Export G-code") : L("Export"); p->label_btn_send = printer_technology == ptFFF ? L("Send G-code") : L("Send to printer"); @@ -5821,7 +5964,15 @@ void Plater::changed_object(int obj_idx) return; // recenter and re - align to Z = 0 auto model_object = p->model.objects[obj_idx]; +#if ENABLE_ALLOW_NEGATIVE_Z +#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA + model_object->ensure_on_bed(this->p->printer_technology != ptSLA); +#else + model_object->ensure_on_bed(true); +#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA +#else model_object->ensure_on_bed(); +#endif // ENABLE_ALLOW_NEGATIVE_Z if (this->p->printer_technology == ptSLA) { // Update the SLAPrint from the current Model, so that the reload_scene() // pulls the correct data, update the 3D scene. @@ -5839,11 +5990,18 @@ void Plater::changed_objects(const std::vector& object_idxs) if (object_idxs.empty()) return; - for (size_t obj_idx : object_idxs) - { + for (size_t obj_idx : object_idxs) { +#if ENABLE_ALLOW_NEGATIVE_Z + if (obj_idx < p->model.objects.size()) { + if (p->model.objects[obj_idx]->bounding_box().min.z() >= 0.0) + // re - align to Z = 0 + p->model.objects[obj_idx]->ensure_on_bed(); + } +#else if (obj_idx < p->model.objects.size()) // recenter and re - align to Z = 0 p->model.objects[obj_idx]->ensure_on_bed(); +#endif // ENABLE_ALLOW_NEGATIVE_Z } if (this->p->printer_technology == ptSLA) { // Update the SLAPrint from the current Model, so that the reload_scene() @@ -6126,6 +6284,9 @@ bool Plater::can_mirror() const { return p->can_mirror(); } bool Plater::can_split(bool to_objects) const { return p->can_split(to_objects); } const UndoRedo::Stack& Plater::undo_redo_stack_main() const { return p->undo_redo_stack_main(); } void Plater::clear_undo_redo_stack_main() { p->undo_redo_stack_main().clear(); } +#if ENABLE_PROJECT_DIRTY_STATE +const UndoRedo::Stack& Plater::undo_redo_stack_active() const { return p->undo_redo_stack(); } +#endif // ENABLE_PROJECT_DIRTY_STATE void Plater::enter_gizmos_stack() { p->enter_gizmos_stack(); } void Plater::leave_gizmos_stack() { p->leave_gizmos_stack(); } bool Plater::inside_snapshot_capture() { return p->inside_snapshot_capture(); } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index e99feee82..e6a5160f3 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -128,7 +128,18 @@ public: Plater(const Plater &) = delete; Plater &operator=(Plater &&) = delete; Plater &operator=(const Plater &) = delete; - ~Plater(); + ~Plater() = default; + +#if ENABLE_PROJECT_DIRTY_STATE + bool is_project_dirty() const; + void update_project_dirty_from_presets(); + bool save_project_if_dirty(); + void reset_project_dirty_after_save(); + void reset_project_dirty_initial_presets(); +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + void render_project_state_debug_window() const; +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +#endif // ENABLE_PROJECT_DIRTY_STATE Sidebar& sidebar(); Model& model(); @@ -198,7 +209,11 @@ public: void export_gcode(bool prefer_removable); void export_stl(bool extended = false, bool selection_only = false); void export_amf(); +#if ENABLE_PROJECT_DIRTY_STATE + bool export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); +#else void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); +#endif // ENABLE_PROJECT_DIRTY_STATE void reload_from_disk(); void reload_all_from_disk(); bool has_toolpaths_to_export() const; @@ -228,6 +243,9 @@ public: // For the memory statistics. const Slic3r::UndoRedo::Stack& undo_redo_stack_main() const; void clear_undo_redo_stack_main(); +#if ENABLE_PROJECT_DIRTY_STATE + const Slic3r::UndoRedo::Stack& undo_redo_stack_active() const; +#endif // ENABLE_PROJECT_DIRTY_STATE // Enter / leave the Gizmos specific Undo / Redo stack. To be used by the SLA support point editing gizmo. void enter_gizmos_stack(); void leave_gizmos_stack(); diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.cpp b/src/slic3r/GUI/ProjectDirtyStateManager.cpp new file mode 100644 index 000000000..67feed4f3 --- /dev/null +++ b/src/slic3r/GUI/ProjectDirtyStateManager.cpp @@ -0,0 +1,415 @@ +#include "libslic3r/libslic3r.h" + +#include "ProjectDirtyStateManager.hpp" +#include "ImGuiWrapper.hpp" +#include "GUI_App.hpp" +#include "MainFrame.hpp" +#include "I18N.hpp" +#include "Plater.hpp" +#include "../Utils/UndoRedo.hpp" + +#include + +#include +#include + +#if ENABLE_PROJECT_DIRTY_STATE + +namespace Slic3r { +namespace GUI { + +enum class EStackType +{ + Main, + Gizmo +}; + +// returns the current active snapshot (the topmost snapshot in the undo part of the stack) in the given stack +static const UndoRedo::Snapshot* get_active_snapshot(const UndoRedo::Stack& stack) { + const std::vector& snapshots = stack.snapshots(); + const size_t active_snapshot_time = stack.active_snapshot_time(); + const auto it = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(active_snapshot_time)); + const int idx = it - snapshots.begin() - 1; + const Slic3r::UndoRedo::Snapshot* ret = (0 <= idx && (size_t)idx < snapshots.size() - 1) ? + &snapshots[idx] : nullptr; + + assert(ret != nullptr); + return ret; +} + +// returns the last saveable snapshot (the topmost snapshot in the undo part of the stack that can be saved) in the given stack +static const UndoRedo::Snapshot* get_last_saveable_snapshot(EStackType type, const UndoRedo::Stack& stack, + const ProjectDirtyStateManager::DirtyState::Gizmos& gizmos, size_t last_save_main) { + + // returns true if the given snapshot is not saveable + auto skip_main = [&gizmos, last_save_main, &stack](const UndoRedo::Snapshot& snapshot) { + auto is_gizmo_with_modifications = [&gizmos, &stack](const UndoRedo::Snapshot& snapshot) { + if (boost::starts_with(snapshot.name, _utf8("Entering"))) { + if (gizmos.current) + return true; + + std::string topmost_redo; + wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo); + if (boost::starts_with(topmost_redo, _utf8("Leaving"))) { + const std::vector& snapshots = stack.snapshots(); + const UndoRedo::Snapshot* leaving_snapshot = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(snapshot.timestamp + 1))); + if (gizmos.is_used_and_modified(*leaving_snapshot)) + return true; + } + } + return false; + }; + + if (snapshot.name == _utf8("New Project")) + return true; + else if (snapshot.name == _utf8("Reset Project")) + return true; + else if (boost::starts_with(snapshot.name, _utf8("Load Project"))) + return true; + else if (boost::starts_with(snapshot.name, _utf8("Selection"))) + return true; + else if (boost::starts_with(snapshot.name, _utf8("Entering"))) { + if (last_save_main != snapshot.timestamp + 1 && !is_gizmo_with_modifications(snapshot)) + return true; + } + else if (boost::starts_with(snapshot.name, _utf8("Leaving"))) { + if (last_save_main != snapshot.timestamp && !gizmos.is_used_and_modified(snapshot)) + return true; + } + + return false; + }; + + // returns true if the given snapshot is not saveable + auto skip_gizmo = [](const UndoRedo::Snapshot& snapshot) { + // put here any needed condition to skip the snapshot + return false; + }; + + const UndoRedo::Snapshot* curr = get_active_snapshot(stack); + const std::vector& snapshots = stack.snapshots(); + size_t shift = 1; + while (curr->timestamp > 0 && ((type == EStackType::Main && skip_main(*curr)) || (type == EStackType::Gizmo && skip_gizmo(*curr)))) { + const UndoRedo::Snapshot* temp = curr; + curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp - shift))); + shift = (curr == temp) ? shift + 1 : 1; + } + if (type == EStackType::Main) { + if (boost::starts_with(curr->name, _utf8("Entering")) && last_save_main == curr->timestamp + 1) { + std::string topmost_redo; + wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo); + if (boost::starts_with(topmost_redo, _utf8("Leaving"))) + curr = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(curr->timestamp + 1))); + } + } + return curr->timestamp > 0 ? curr : nullptr; +} + +// returns the name of the gizmo contained in the given string +static std::string extract_gizmo_name(const std::string& s) { + static const std::array prefixes = { _utf8("Entering"), _utf8("Leaving") }; + + std::string ret; + for (const std::string& prefix : prefixes) { + if (boost::starts_with(s, prefix)) + ret = s.substr(prefix.length() + 1); + + if (!ret.empty()) + break; + } + return ret; +} + +void ProjectDirtyStateManager::DirtyState::Gizmos::add_used(const UndoRedo::Snapshot& snapshot) +{ + const std::string name = extract_gizmo_name(snapshot.name); + auto it = used.find(name); + if (it == used.end()) + it = used.insert({ name, { {} } }).first; + + it->second.modified_timestamps.push_back(snapshot.timestamp); +} + +void ProjectDirtyStateManager::DirtyState::Gizmos::remove_obsolete_used(const Slic3r::UndoRedo::Stack& main_stack) +{ + const std::vector& snapshots = main_stack.snapshots(); + for (auto& item : used) { + auto it = item.second.modified_timestamps.begin(); + while (it != item.second.modified_timestamps.end()) { + size_t timestamp = *it; + auto snapshot_it = std::find_if(snapshots.begin(), snapshots.end(), [timestamp](const Slic3r::UndoRedo::Snapshot& snapshot) { return snapshot.timestamp == timestamp; }); + if (snapshot_it == snapshots.end()) + it = item.second.modified_timestamps.erase(it); + else + ++it; + } + } +} + +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +bool ProjectDirtyStateManager::DirtyState::Gizmos::any_used_modified() const +{ + for (auto& [name, gizmo] : used) { + if (!gizmo.modified_timestamps.empty()) + return true; + } + return false; +} +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + +// returns true if the given snapshot is contained in any of the gizmos caches +bool ProjectDirtyStateManager::DirtyState::Gizmos::is_used_and_modified(const UndoRedo::Snapshot& snapshot) const +{ + for (const auto& item : used) { + for (size_t i : item.second.modified_timestamps) { + if (i == snapshot.timestamp) + return true; + } + } + return false; +} + +void ProjectDirtyStateManager::DirtyState::Gizmos::reset() +{ + used.clear(); +} + +void ProjectDirtyStateManager::update_from_undo_redo_stack(UpdateType type) +{ + if (!wxGetApp().initialized()) + return; + + const Plater* plater = wxGetApp().plater(); + if (plater == nullptr) + return; + + const UndoRedo::Stack& main_stack = plater->undo_redo_stack_main(); + const UndoRedo::Stack& active_stack = plater->undo_redo_stack_active(); + + if (&main_stack == &active_stack) + update_from_undo_redo_main_stack(type, main_stack); + else + update_from_undo_redo_gizmo_stack(type, active_stack); + + wxGetApp().mainframe->update_title(); +} + +void ProjectDirtyStateManager::update_from_presets() +{ + m_state.presets = false; + std::vector> selected_presets = wxGetApp().get_selected_presets(); + for (const auto& [type, name] : selected_presets) { + m_state.presets |= !m_initial_presets[type].empty() && m_initial_presets[type] != name; + } + m_state.presets |= wxGetApp().has_unsaved_preset_changes(); + wxGetApp().mainframe->update_title(); +} + +void ProjectDirtyStateManager::reset_after_save() +{ + const Plater* plater = wxGetApp().plater(); + const UndoRedo::Stack& main_stack = plater->undo_redo_stack_main(); + const UndoRedo::Stack& active_stack = plater->undo_redo_stack_active(); + + if (&main_stack == &active_stack) { + const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, main_stack, m_state.gizmos, m_last_save.main); + m_last_save.main = (saveable_snapshot != nullptr) ? saveable_snapshot->timestamp : 0; + } + else { + const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack); + if (boost::starts_with(main_active_snapshot->name, _utf8("Entering"))) { + if (m_state.gizmos.current) + m_last_save.main = main_active_snapshot->timestamp + 1; + } + const UndoRedo::Snapshot* saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, active_stack, m_state.gizmos, m_last_save.main); + m_last_save.gizmo = saveable_snapshot->timestamp; + } + + reset_initial_presets(); + m_state.reset(); + wxGetApp().mainframe->update_title(); +} + +void ProjectDirtyStateManager::reset_initial_presets() +{ + m_initial_presets = std::array(); + std::vector> selected_presets = wxGetApp().get_selected_presets(); + for (const auto& [type, name] : selected_presets) { + m_initial_presets[type] = name; + } +} + +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW +void ProjectDirtyStateManager::render_debug_window() const +{ + ImGuiWrapper& imgui = *wxGetApp().imgui(); + + auto color = [](bool value) { + return value ? ImVec4(1.0f, 0.49f, 0.216f, 1.0f) /* orange */: ImVec4(1.0f, 1.0f, 1.0f, 1.0f) /* white */; + }; + auto bool_to_text = [](bool value) { + return value ? "true" : "false"; + }; + auto append_bool_item = [color, bool_to_text, &imgui](const std::string& name, bool value) { + imgui.text_colored(color(value), name); + ImGui::SameLine(); + imgui.text_colored(color(value), bool_to_text(value)); + }; + auto append_int_item = [&imgui](const std::string& name, int value) { + imgui.text(name); + ImGui::SameLine(); + imgui.text(std::to_string(value)); + }; + auto append_snapshot_item = [&imgui](const std::string& label, const UndoRedo::Snapshot* snapshot) { + imgui.text(label); + ImGui::SameLine(100); + if (snapshot != nullptr) + imgui.text(snapshot->name + " (" + std::to_string(snapshot->timestamp) + ")"); + else + imgui.text("-"); + }; + + imgui.begin(std::string("Project dirty state statistics"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse); + + if (ImGui::CollapsingHeader("Dirty state", ImGuiTreeNodeFlags_DefaultOpen)) { + append_bool_item("Overall:", is_dirty()); + ImGui::Separator(); + append_bool_item("Plater:", m_state.plater); + append_bool_item("Presets:", m_state.presets); + append_bool_item("Current gizmo:", m_state.gizmos.current); + } + + if (ImGui::CollapsingHeader("Last save timestamps", ImGuiTreeNodeFlags_DefaultOpen)) { + append_int_item("Main:", m_last_save.main); + append_int_item("Current gizmo:", m_last_save.gizmo); + } + + const UndoRedo::Stack& main_stack = wxGetApp().plater()->undo_redo_stack_main(); + const UndoRedo::Snapshot* main_active_snapshot = get_active_snapshot(main_stack); + const UndoRedo::Snapshot* main_last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, main_stack, m_state.gizmos, m_last_save.main); + const std::vector& main_snapshots = main_stack.snapshots(); + + if (ImGui::CollapsingHeader("Main snapshots", ImGuiTreeNodeFlags_DefaultOpen)) { + append_snapshot_item("Active:", main_active_snapshot); + append_snapshot_item("Last saveable:", main_last_saveable_snapshot); + } + + if (ImGui::CollapsingHeader("Main undo/redo stack", ImGuiTreeNodeFlags_DefaultOpen)) { + for (const UndoRedo::Snapshot& snapshot : main_snapshots) { + bool active = main_active_snapshot->timestamp == snapshot.timestamp; + imgui.text_colored(color(active), snapshot.name); + ImGui::SameLine(150); + imgui.text_colored(color(active), " (" + std::to_string(snapshot.timestamp) + ")"); + if (&snapshot == main_last_saveable_snapshot) { + ImGui::SameLine(); + imgui.text_colored(color(active), " (S)"); + } + if (m_last_save.main > 0 && m_last_save.main == snapshot.timestamp) { + ImGui::SameLine(); + imgui.text_colored(color(active), " (LS)"); + } + } + } + + const UndoRedo::Stack& active_stack = wxGetApp().plater()->undo_redo_stack_active(); + if (&active_stack != &main_stack) { + if (ImGui::CollapsingHeader("Gizmo undo/redo stack", ImGuiTreeNodeFlags_DefaultOpen)) { + const UndoRedo::Snapshot* active_active_snapshot = get_active_snapshot(active_stack); + const std::vector& active_snapshots = active_stack.snapshots(); + for (const UndoRedo::Snapshot& snapshot : active_snapshots) { + bool active = active_active_snapshot->timestamp == snapshot.timestamp; + imgui.text_colored(color(active), snapshot.name); + ImGui::SameLine(150); + imgui.text_colored(color(active), " (" + std::to_string(snapshot.timestamp) + ")"); + } + } + } + + if (m_state.gizmos.any_used_modified()) { + if (ImGui::CollapsingHeader("Gizmos", ImGuiTreeNodeFlags_DefaultOpen)) { + ImGui::Indent(10.0f); + for (const auto& [name, gizmo] : m_state.gizmos.used) { + if (!gizmo.modified_timestamps.empty()) { + if (ImGui::CollapsingHeader(name.c_str(), ImGuiTreeNodeFlags_DefaultOpen)) { + std::string modified_timestamps; + for (size_t i = 0; i < gizmo.modified_timestamps.size(); ++i) { + if (i > 0) + modified_timestamps += " | "; + modified_timestamps += std::to_string(gizmo.modified_timestamps[i]); + } + imgui.text(modified_timestamps); + } + } + } + ImGui::Unindent(10.0f); + } + } + + imgui.end(); +} +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + +void ProjectDirtyStateManager::update_from_undo_redo_main_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack) +{ + m_state.plater = false; + + if (type == UpdateType::TakeSnapshot) { + if (m_last_save.main != 0) { + const std::vector& snapshots = stack.snapshots(); + auto snapshot_it = std::find_if(snapshots.begin(), snapshots.end(), [this](const Slic3r::UndoRedo::Snapshot& snapshot) { return snapshot.timestamp == m_last_save.main; }); + if (snapshot_it == snapshots.end()) + m_last_save.main = 0; + } + m_state.gizmos.remove_obsolete_used(stack); + } + + const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); + if (active_snapshot->name == _utf8("New Project") || + active_snapshot->name == _utf8("Reset Project") || + boost::starts_with(active_snapshot->name, _utf8("Load Project"))) + return; + + if (boost::starts_with(active_snapshot->name, _utf8("Entering"))) { + if (type == UpdateType::UndoRedoTo) { + std::string topmost_redo; + wxGetApp().plater()->undo_redo_topmost_string_getter(false, topmost_redo); + if (boost::starts_with(topmost_redo, _utf8("Leaving"))) { + const std::vector& snapshots = stack.snapshots(); + const UndoRedo::Snapshot* leaving_snapshot = &(*std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(active_snapshot->timestamp + 1))); + if (m_state.gizmos.is_used_and_modified(*leaving_snapshot)) { + m_state.plater = (leaving_snapshot != nullptr && leaving_snapshot->timestamp != m_last_save.main); + return; + } + } + } + m_state.gizmos.current = false; + m_last_save.gizmo = 0; + } + else if (boost::starts_with(active_snapshot->name, _utf8("Leaving"))) { + if (m_state.gizmos.current) + m_state.gizmos.add_used(*active_snapshot); + m_state.gizmos.current = false; + m_last_save.gizmo = 0; + } + + const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Main, stack, m_state.gizmos, m_last_save.main); + m_state.plater = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.main); +} + +void ProjectDirtyStateManager::update_from_undo_redo_gizmo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack) +{ + m_state.gizmos.current = false; + + const UndoRedo::Snapshot* active_snapshot = get_active_snapshot(stack); + if (active_snapshot->name == "Gizmos-Initial") + return; + + const UndoRedo::Snapshot* last_saveable_snapshot = get_last_saveable_snapshot(EStackType::Gizmo, stack, m_state.gizmos, m_last_save.main); + m_state.gizmos.current = (last_saveable_snapshot != nullptr && last_saveable_snapshot->timestamp != m_last_save.gizmo); +} + +} // namespace GUI +} // namespace Slic3r + +#endif // ENABLE_PROJECT_DIRTY_STATE + diff --git a/src/slic3r/GUI/ProjectDirtyStateManager.hpp b/src/slic3r/GUI/ProjectDirtyStateManager.hpp new file mode 100644 index 000000000..f7ce81a62 --- /dev/null +++ b/src/slic3r/GUI/ProjectDirtyStateManager.hpp @@ -0,0 +1,96 @@ +#ifndef slic3r_ProjectDirtyStateManager_hpp_ +#define slic3r_ProjectDirtyStateManager_hpp_ + +#include "libslic3r/Preset.hpp" + +#if ENABLE_PROJECT_DIRTY_STATE + +namespace Slic3r { +namespace UndoRedo { +class Stack; +struct Snapshot; +} // namespace UndoRedo + +namespace GUI { +class ProjectDirtyStateManager +{ +public: + enum class UpdateType : unsigned char + { + TakeSnapshot, + UndoRedoTo + }; + + struct DirtyState + { + struct Gizmos + { + struct Gizmo + { + std::vector modified_timestamps; + }; + + bool current{ false }; + std::map used; + + void add_used(const UndoRedo::Snapshot& snapshot); + void remove_obsolete_used(const Slic3r::UndoRedo::Stack& main_stack); +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + bool any_used_modified() const; +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + bool is_used_and_modified(const UndoRedo::Snapshot& snapshot) const; + void reset(); + }; + + bool plater{ false }; + bool presets{ false }; + Gizmos gizmos; + + bool is_dirty() const { return plater || presets || gizmos.current; } + void reset() { + plater = false; + presets = false; + gizmos.current = false; + } + }; + +private: + struct LastSaveTimestamps + { + size_t main{ 0 }; + size_t gizmo{ 0 }; + + void reset() { + main = 0; + gizmo = 0; + } + }; + + DirtyState m_state; + LastSaveTimestamps m_last_save; + + // keeps track of initial selected presets + std::array m_initial_presets; + +public: + bool is_dirty() const { return m_state.is_dirty(); } + void update_from_undo_redo_stack(UpdateType type); + void update_from_presets(); + void reset_after_save(); + void reset_initial_presets(); +#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + void render_debug_window() const; +#endif // ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW + +private: + void update_from_undo_redo_main_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack); + void update_from_undo_redo_gizmo_stack(UpdateType type, const Slic3r::UndoRedo::Stack& stack); +}; + +} // namespace GUI +} // namespace Slic3r + +#endif // ENABLE_PROJECT_DIRTY_STATE + +#endif // slic3r_ProjectDirtyStateManager_hpp_ + diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 9ff4e82a1..21373cc89 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -7,6 +7,7 @@ #include #include "wx/dataview.h" +#include "wx/numformatter.h" #include "libslic3r/PrintConfig.hpp" #include "libslic3r/PresetBundle.hpp" @@ -45,6 +46,11 @@ static char marker_by_type(Preset::Type type, PrinterTechnology pt) } } +std::string Option::opt_key() const +{ + return boost::nowide::narrow(key).substr(2); +} + void FoundOption::get_marked_label_and_tooltip(const char** label_, const char** tooltip_) const { *label_ = marked_label.c_str(); diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index a9ced75dd..a942a89f8 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -53,7 +53,7 @@ struct Option { std::wstring category; std::wstring category_local; - std::string opt_key() const { return boost::nowide::narrow(key).substr(2); } + std::string opt_key() const; }; struct FoundOption { diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index fd6873749..4cf71363d 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -12,6 +12,9 @@ #include "Plater.hpp" #include "libslic3r/Model.hpp" +#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA +#include "libslic3r/PresetBundle.hpp" +#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA #include @@ -58,13 +61,11 @@ bool Selection::Clipboard::is_sla_compliant() const if (m_mode == Selection::Volume) return false; - for (const ModelObject* o : m_model->objects) - { + for (const ModelObject* o : m_model->objects) { if (o->is_multiparts()) return false; - for (const ModelVolume* v : o->volumes) - { + for (const ModelVolume* v : o->volumes) { if (v->is_modifier()) return false; } @@ -78,7 +79,8 @@ Selection::Clipboard::Clipboard() m_model.reset(new Model); } -void Selection::Clipboard::reset() { +void Selection::Clipboard::reset() +{ m_model->clear_objects(); } @@ -149,7 +151,7 @@ void Selection::set_model(Model* model) void Selection::add(unsigned int volume_idx, bool as_single_selection, bool check_for_already_contained) { - if (!m_valid || ((unsigned int)m_volumes->size() <= volume_idx)) + if (!m_valid || (unsigned int)m_volumes->size() <= volume_idx) return; const GLVolume* volume = (*m_volumes)[volume_idx]; @@ -167,9 +169,8 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec needs_reset |= as_single_selection && !is_any_modifier() && volume->is_modifier; needs_reset |= is_any_modifier() && !volume->is_modifier; - if (!already_contained || needs_reset) - { - wxGetApp().plater()->take_snapshot(_(L("Selection-Add"))); + if (!already_contained || needs_reset) { + wxGetApp().plater()->take_snapshot(_L("Selection-Add")); if (needs_reset) clear(); @@ -185,7 +186,7 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec { case Volume: { - if ((volume->volume_idx() >= 0) && (is_empty() || (volume->instance_idx() == get_instance_idx()))) + if (volume->volume_idx() >= 0 && (is_empty() || volume->instance_idx() == get_instance_idx())) do_add_volume(volume_idx); break; @@ -204,13 +205,13 @@ void Selection::add(unsigned int volume_idx, bool as_single_selection, bool chec void Selection::remove(unsigned int volume_idx) { - if (!m_valid || ((unsigned int)m_volumes->size() <= volume_idx)) + if (!m_valid || (unsigned int)m_volumes->size() <= volume_idx) return; if (!contains_volume(volume_idx)) return; - wxGetApp().plater()->take_snapshot(_(L("Selection-Remove"))); + wxGetApp().plater()->take_snapshot(_L("Selection-Remove")); GLVolume* volume = (*m_volumes)[volume_idx]; @@ -242,7 +243,7 @@ void Selection::add_object(unsigned int object_idx, bool as_single_selection) (as_single_selection && matches(volume_idxs))) return; - wxGetApp().plater()->take_snapshot(_(L("Selection-Add Object"))); + wxGetApp().plater()->take_snapshot(_L("Selection-Add Object")); // resets the current list if needed if (as_single_selection) @@ -261,7 +262,7 @@ void Selection::remove_object(unsigned int object_idx) if (!m_valid) return; - wxGetApp().plater()->take_snapshot(_(L("Selection-Remove Object"))); + wxGetApp().plater()->take_snapshot(_L("Selection-Remove Object")); do_remove_object(object_idx); @@ -274,12 +275,12 @@ void Selection::add_instance(unsigned int object_idx, unsigned int instance_idx, if (!m_valid) return; - std::vector volume_idxs = get_volume_idxs_from_instance(object_idx, instance_idx); + const std::vector volume_idxs = get_volume_idxs_from_instance(object_idx, instance_idx); if ((!as_single_selection && contains_all_volumes(volume_idxs)) || (as_single_selection && matches(volume_idxs))) return; - wxGetApp().plater()->take_snapshot(_(L("Selection-Add Instance"))); + wxGetApp().plater()->take_snapshot(_L("Selection-Add Instance")); // resets the current list if needed if (as_single_selection) @@ -298,7 +299,7 @@ void Selection::remove_instance(unsigned int object_idx, unsigned int instance_i if (!m_valid) return; - wxGetApp().plater()->take_snapshot(_(L("Selection-Remove Instance"))); + wxGetApp().plater()->take_snapshot(_L("Selection-Remove Instance")); do_remove_instance(object_idx, instance_idx); @@ -333,10 +334,9 @@ void Selection::remove_volume(unsigned int object_idx, unsigned int volume_idx) if (!m_valid) return; - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == (int)object_idx) && (v->volume_idx() == (int)volume_idx)) + if (v->object_idx() == (int)object_idx && v->volume_idx() == (int)volume_idx) do_remove_volume(i); } @@ -358,8 +358,7 @@ void Selection::add_volumes(EMode mode, const std::vector& volume_ clear(); m_mode = mode; - for (unsigned int i : volume_idxs) - { + for (unsigned int i : volume_idxs) { if (i < (unsigned int)m_volumes->size()) do_add_volume(i); } @@ -374,8 +373,7 @@ void Selection::remove_volumes(EMode mode, const std::vector& volu return; m_mode = mode; - for (unsigned int i : volume_idxs) - { + for (unsigned int i : volume_idxs) { if (i < (unsigned int)m_volumes->size()) do_remove_volume(i); } @@ -390,8 +388,7 @@ void Selection::add_all() return; unsigned int count = 0; - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { if (!(*m_volumes)[i]->is_wipe_tower) ++count; } @@ -404,8 +401,7 @@ void Selection::add_all() m_mode = Instance; clear(); - for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) - { + for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++i) { if (!(*m_volumes)[i]->is_wipe_tower) do_add_volume(i); } @@ -455,8 +451,7 @@ void Selection::clear() if (m_list.empty()) return; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { (*m_volumes)[i]->selected = false; } @@ -522,16 +517,15 @@ bool Selection::is_single_full_instance() const return false; int object_idx = m_valid ? get_object_idx() : -1; - if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx)) + if (object_idx < 0 || (int)m_model->objects.size() <= object_idx) return false; int instance_idx = (*m_volumes)[*m_list.begin()]->instance_idx(); std::set volumes_idxs; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { const GLVolume* v = (*m_volumes)[i]; - if ((object_idx != v->object_idx()) || (instance_idx != v->instance_idx())) + if (object_idx != v->object_idx() || instance_idx != v->instance_idx()) return false; int volume_idx = v->volume_idx(); @@ -544,8 +538,8 @@ bool Selection::is_single_full_instance() const bool Selection::is_from_single_object() const { - int idx = get_object_idx(); - return (0 <= idx) && (idx < 1000); + const int idx = get_object_idx(); + return 0 <= idx && idx < 1000; } bool Selection::is_sla_compliant() const @@ -553,8 +547,7 @@ bool Selection::is_sla_compliant() const if (m_mode == Volume) return false; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { if ((*m_volumes)[i]->is_modifier) return false; } @@ -564,8 +557,7 @@ bool Selection::is_sla_compliant() const bool Selection::contains_all_volumes(const std::vector& volume_idxs) const { - for (unsigned int i : volume_idxs) - { + for (unsigned int i : volume_idxs) { if (m_list.find(i) == m_list.end()) return false; } @@ -575,8 +567,7 @@ bool Selection::contains_all_volumes(const std::vector& volume_idx bool Selection::contains_any_volume(const std::vector& volume_idxs) const { - for (unsigned int i : volume_idxs) - { + for (unsigned int i : volume_idxs) { if (m_list.find(i) != m_list.end()) return true; } @@ -588,8 +579,7 @@ bool Selection::matches(const std::vector& volume_idxs) const { unsigned int count = 0; - for (unsigned int i : volume_idxs) - { + for (unsigned int i : volume_idxs) { if (m_list.find(i) != m_list.end()) ++count; else @@ -614,8 +604,7 @@ int Selection::get_object_idx() const int Selection::get_instance_idx() const { - if (m_cache.content.size() == 1) - { + if (m_cache.content.size() == 1) { const InstanceIdxsList& idxs = m_cache.content.begin()->second; if (idxs.size() == 1) return *idxs.begin(); @@ -672,25 +661,20 @@ void Selection::translate(const Vec3d& displacement, bool local) EMode translation_type = m_mode; - for (unsigned int i : m_list) - { - if ((m_mode == Volume) || (*m_volumes)[i]->is_wipe_tower) - { + for (unsigned int i : m_list) { + if (m_mode == Volume || (*m_volumes)[i]->is_wipe_tower) { if (local) (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + displacement); - else - { - Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; + else { + const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); } } - else if (m_mode == Instance) - { + else if (m_mode == Instance) { if (is_from_fully_selected_instance(i)) (*m_volumes)[i]->set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); - else - { - Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; + else { + const Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_instance_scale_matrix() * m_cache.volumes_data[i].get_instance_mirror_matrix()).inverse() * displacement; (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); translation_type = Volume; } @@ -718,18 +702,14 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ if (!is_wipe_tower()) { int rot_axis_max = 0; - if (rotation.isApprox(Vec3d::Zero())) - { - for (unsigned int i : m_list) - { + if (rotation.isApprox(Vec3d::Zero())) { + for (unsigned int i : m_list) { GLVolume &volume = *(*m_volumes)[i]; - if (m_mode == Instance) - { + if (m_mode == Instance) { volume.set_instance_rotation(m_cache.volumes_data[i].get_instance_rotation()); volume.set_instance_offset(m_cache.volumes_data[i].get_instance_position()); } - else if (m_mode == Volume) - { + else if (m_mode == Volume) { volume.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation()); volume.set_volume_offset(m_cache.volumes_data[i].get_volume_position()); } @@ -746,14 +726,14 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ // For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it. std::vector object_instance_first(m_model->objects.size(), -1); auto rotate_instance = [this, &rotation, &object_instance_first, rot_axis_max, transformation_type](GLVolume &volume, int i) { - int first_volume_idx = object_instance_first[volume.object_idx()]; + const int first_volume_idx = object_instance_first[volume.object_idx()]; if (rot_axis_max != 2 && first_volume_idx != -1) { // Generic rotation, but no rotation around the Z axis. // Always do a local rotation (do not consider the selection to be a rigid body). assert(is_approx(rotation.z(), 0.0)); const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; const Vec3d &rotation = first_volume.get_instance_rotation(); - double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); + const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); } else { @@ -763,7 +743,7 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation(); if (rot_axis_max == 2 && transformation_type.joint()) { // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis. - double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); + const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), new_rotation); volume.set_instance_offset(m_cache.dragging_center + Eigen::AngleAxisd(z_diff, Vec3d::UnitZ()) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center)); } volume.set_instance_rotation(new_rotation); @@ -771,19 +751,16 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ } }; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { GLVolume &volume = *(*m_volumes)[i]; if (is_single_full_instance()) rotate_instance(volume, i); - else if (is_single_volume() || is_single_modifier()) - { + else if (is_single_volume() || is_single_modifier()) { if (transformation_type.independent()) volume.set_volume_rotation(volume.get_volume_rotation() + rotation); - else - { - Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); - Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); + else { + const Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); + const Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); volume.set_volume_rotation(new_rotation); } } @@ -791,15 +768,13 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ { if (m_mode == Instance) rotate_instance(volume, i); - else if (m_mode == Volume) - { + else if (m_mode == Volume) { // extracts rotations from the composed transformation Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); - if (transformation_type.joint()) - { - Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center; - Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot); + if (transformation_type.joint()) { + const Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center; + const Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot); volume.set_volume_offset(local_pivot + offset); } volume.set_volume_rotation(new_rotation); @@ -820,8 +795,8 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ // make sure the wipe tower rotates around its center, not origin // we can assume that only Z rotation changes - Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset(); - Vec3d center_local_new = Eigen::AngleAxisd(rotation(2)-volume.get_volume_rotation()(2), Vec3d(0, 0, 1)) * center_local; + const Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset(); + const Vec3d center_local_new = Eigen::AngleAxisd(rotation(2)-volume.get_volume_rotation()(2), Vec3d(0.0, 0.0, 1.0)) * center_local; volume.set_volume_rotation(rotation); volume.set_volume_offset(volume.get_volume_offset() + center_local - center_local_new); } @@ -839,8 +814,7 @@ void Selection::flattening_rotate(const Vec3d& normal) if (!m_valid) return; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { // Normal transformed from the object coordinate space to the world coordinate space. const auto &voldata = m_cache.volumes_data[i]; Vec3d tnormal = (Geometry::assemble_transform( @@ -866,12 +840,10 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type if (!m_valid) return; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { GLVolume &volume = *(*m_volumes)[i]; if (is_single_full_instance()) { - if (transformation_type.relative()) - { + if (transformation_type.relative()) { Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); // extracts scaling factors from the composed transformation @@ -881,8 +853,7 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type volume.set_instance_scaling_factor(new_scale); } - else - { + else { if (transformation_type.world() && (std::abs(scale.x() - scale.y()) > EPSILON || std::abs(scale.x() - scale.z()) > EPSILON)) { // Non-uniform scaling. Transform the scaling factors into the local coordinate system. // This is only possible, if the instance rotation is mulitples of ninety degrees. @@ -895,11 +866,9 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type } else if (is_single_volume() || is_single_modifier()) volume.set_volume_scaling_factor(scale); - else - { + else { Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scale); - if (m_mode == Instance) - { + if (m_mode == Instance) { Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_instance_scale_matrix()).matrix().block(0, 0, 3, 3); // extracts scaling factors from the composed transformation Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); @@ -908,13 +877,11 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type volume.set_instance_scaling_factor(new_scale); } - else if (m_mode == Volume) - { + else if (m_mode == Volume) { Eigen::Matrix new_matrix = (m * m_cache.volumes_data[i].get_volume_scale_matrix()).matrix().block(0, 0, 3, 3); // extracts scaling factors from the composed transformation Vec3d new_scale(new_matrix.col(0).norm(), new_matrix.col(1).norm(), new_matrix.col(2).norm()); - if (transformation_type.joint()) - { + if (transformation_type.joint()) { Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() + m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); volume.set_volume_offset(m_cache.dragging_center - m_cache.volumes_data[i].get_instance_position() + offset); } @@ -929,35 +896,36 @@ void Selection::scale(const Vec3d& scale, TransformationType transformation_type else if (m_mode == Volume) synchronize_unselected_volumes(); #endif // !DISABLE_INSTANCES_SYNCH - - ensure_on_bed(); + +#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) + ensure_on_bed(); +#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA this->set_bounding_boxes_dirty(); } void Selection::scale_to_fit_print_volume(const DynamicPrintConfig& config) { - if (is_empty() || (m_mode == Volume)) + if (is_empty() || m_mode == Volume) return; // adds 1/100th of a mm on all sides to avoid false out of print volume detections due to floating-point roundings Vec3d box_size = get_bounding_box().size() + 0.01 * Vec3d::Ones(); const ConfigOptionPoints* opt = dynamic_cast(config.option("bed_shape")); - if (opt != nullptr) - { + if (opt != nullptr) { BoundingBox bed_box_2D = get_extents(Polygon::new_scale(opt->values)); - BoundingBoxf3 print_volume(Vec3d(unscale(bed_box_2D.min(0)), unscale(bed_box_2D.min(1)), 0.0), Vec3d(unscale(bed_box_2D.max(0)), unscale(bed_box_2D.max(1)), config.opt_float("max_print_height"))); + BoundingBoxf3 print_volume({ unscale(bed_box_2D.min(0)), unscale(bed_box_2D.min(1)), 0.0 }, { unscale(bed_box_2D.max(0)), unscale(bed_box_2D.max(1)), config.opt_float("max_print_height") }); Vec3d print_volume_size = print_volume.size(); double sx = (box_size(0) != 0.0) ? print_volume_size(0) / box_size(0) : 0.0; double sy = (box_size(1) != 0.0) ? print_volume_size(1) / box_size(1) : 0.0; double sz = (box_size(2) != 0.0) ? print_volume_size(2) / box_size(2) : 0.0; - if ((sx != 0.0) && (sy != 0.0) && (sz != 0.0)) + if (sx != 0.0 && sy != 0.0 && sz != 0.0) { double s = std::min(sx, std::min(sy, sz)); - if (s != 1.0) - { - wxGetApp().plater()->take_snapshot(_(L("Scale To Fit"))); + if (s != 1.0) { + wxGetApp().plater()->take_snapshot(_L("Scale To Fit")); TransformationType type; type.set_world(); @@ -987,8 +955,7 @@ void Selection::mirror(Axis axis) bool single_full_instance = is_single_full_instance(); - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { if (single_full_instance) (*m_volumes)[i]->set_instance_mirror(axis, -(*m_volumes)[i]->get_instance_mirror(axis)); else if (m_mode == Volume) @@ -1010,8 +977,7 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement) if (!m_valid) return; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { GLVolume* v = (*m_volumes)[i]; if (v->object_idx() == (int)object_idx) v->set_instance_offset(v->get_instance_offset() + displacement); @@ -1020,8 +986,7 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement) std::set done; // prevent processing volumes twice done.insert(m_list.begin(), m_list.end()); - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { if (done.size() == m_volumes->size()) break; @@ -1030,8 +995,7 @@ void Selection::translate(unsigned int object_idx, const Vec3d& displacement) continue; // Process unselected volumes of the object. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) - { + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { if (done.size() == m_volumes->size()) break; @@ -1055,18 +1019,16 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co if (!m_valid) return; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { GLVolume* v = (*m_volumes)[i]; - if ((v->object_idx() == (int)object_idx) && (v->instance_idx() == (int)instance_idx)) + if (v->object_idx() == (int)object_idx && v->instance_idx() == (int)instance_idx) v->set_instance_offset(v->get_instance_offset() + displacement); } std::set done; // prevent processing volumes twice done.insert(m_list.begin(), m_list.end()); - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { if (done.size() == m_volumes->size()) break; @@ -1075,8 +1037,7 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co continue; // Process unselected volumes of the object. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) - { + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { if (done.size() == m_volumes->size()) break; @@ -1084,7 +1045,7 @@ void Selection::translate(unsigned int object_idx, unsigned int instance_idx, co continue; GLVolume* v = (*m_volumes)[j]; - if ((v->object_idx() != object_idx) || (v->instance_idx() != (int)instance_idx)) + if (v->object_idx() != object_idx || v->instance_idx() != (int)instance_idx) continue; v->set_instance_offset(v->get_instance_offset() + displacement); @@ -1799,18 +1760,16 @@ void Selection::render_synchronized_volumes() const float color[3] = { 1.0f, 1.0f, 0.0f }; - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { const GLVolume* volume = (*m_volumes)[i]; int object_idx = volume->object_idx(); int volume_idx = volume->volume_idx(); - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) - { + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { if (i == j) continue; const GLVolume* v = (*m_volumes)[j]; - if ((v->object_idx() != object_idx) || (v->volume_idx() != volume_idx)) + if (v->object_idx() != object_idx || v->volume_idx() != volume_idx) continue; render_bounding_box(v->transformed_convex_hull_bounding_box(), color); @@ -2032,9 +1991,9 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co #ifndef NDEBUG static bool is_rotation_xy_synchronized(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to) { - Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); - Vec3d axis = angle_axis.axis(); - double angle = angle_axis.angle(); + const Eigen::AngleAxisd angle_axis(Geometry::rotation_xyz_diff(rot_xyz_from, rot_xyz_to)); + const Vec3d axis = angle_axis.axis(); + const double angle = angle_axis.angle(); if (std::abs(angle) < 1e-8) return true; assert(std::abs(axis.x()) < 1e-8); @@ -2071,24 +2030,22 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ std::set done; // prevent processing volumes twice done.insert(m_list.begin(), m_list.end()); - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { if (done.size() == m_volumes->size()) break; const GLVolume* volume = (*m_volumes)[i]; - int object_idx = volume->object_idx(); + const int object_idx = volume->object_idx(); if (object_idx >= 1000) continue; - int instance_idx = volume->instance_idx(); + const int instance_idx = volume->instance_idx(); const Vec3d& rotation = volume->get_instance_rotation(); const Vec3d& scaling_factor = volume->get_instance_scaling_factor(); const Vec3d& mirror = volume->get_instance_mirror(); // Process unselected instances. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) - { + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { if (done.size() == m_volumes->size()) break; @@ -2096,24 +2053,36 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ continue; GLVolume* v = (*m_volumes)[j]; - if ((v->object_idx() != object_idx) || (v->instance_idx() == instance_idx)) + if (v->object_idx() != object_idx || v->instance_idx() == instance_idx) continue; assert(is_rotation_xy_synchronized(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation())); switch (sync_rotation_type) { - case SYNC_ROTATION_NONE: + case SYNC_ROTATION_NONE: { +#if ENABLE_ALLOW_NEGATIVE_Z + // z only rotation -> synch instance z + // The X,Y rotations should be synchronized from start to end of the rotation. + assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation())); +#if DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA + if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA) +#endif // DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA + v->set_instance_offset(Z, volume->get_instance_offset().z()); + break; +#else // z only rotation -> keep instance z // The X,Y rotations should be synchronized from start to end of the rotation. assert(is_rotation_xy_synchronized(rotation, v->get_instance_rotation())); break; +#endif // ENABLE_ALLOW_NEGATIVE_Z + } case SYNC_ROTATION_FULL: // rotation comes from place on face -> force given z - v->set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2))); + v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() }); break; case SYNC_ROTATION_GENERAL: // generic rotation -> update instance z with the delta of the rotation. - double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); - v->set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); + const double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[i].get_instance_rotation(), m_cache.volumes_data[j].get_instance_rotation()); + v->set_instance_rotation({ rotation.x(), rotation.y(), rotation.z() + z_diff }); break; } @@ -2131,27 +2100,25 @@ void Selection::synchronize_unselected_instances(SyncRotationType sync_rotation_ void Selection::synchronize_unselected_volumes() { - for (unsigned int i : m_list) - { + for (unsigned int i : m_list) { const GLVolume* volume = (*m_volumes)[i]; - int object_idx = volume->object_idx(); + const int object_idx = volume->object_idx(); if (object_idx >= 1000) continue; - int volume_idx = volume->volume_idx(); + const int volume_idx = volume->volume_idx(); const Vec3d& offset = volume->get_volume_offset(); const Vec3d& rotation = volume->get_volume_rotation(); const Vec3d& scaling_factor = volume->get_volume_scaling_factor(); const Vec3d& mirror = volume->get_volume_mirror(); // Process unselected volumes. - for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) - { + for (unsigned int j = 0; j < (unsigned int)m_volumes->size(); ++j) { if (j == i) continue; GLVolume* v = (*m_volumes)[j]; - if ((v->object_idx() != object_idx) || (v->volume_idx() != volume_idx)) + if (v->object_idx() != object_idx || v->volume_idx() != volume_idx) continue; v->set_volume_offset(offset); @@ -2167,10 +2134,8 @@ void Selection::ensure_on_bed() typedef std::map, double> InstancesToZMap; InstancesToZMap instances_min_z; - for (GLVolume* volume : *m_volumes) - { - if (!volume->is_wipe_tower && !volume->is_modifier) - { + for (GLVolume* volume : *m_volumes) { + if (!volume->is_wipe_tower && !volume->is_modifier) { double min_z = volume->transformed_convex_hull_bounding_box().min(2); std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); InstancesToZMap::iterator it = instances_min_z.find(instance); @@ -2181,8 +2146,7 @@ void Selection::ensure_on_bed() } } - for (GLVolume* volume : *m_volumes) - { + for (GLVolume* volume : *m_volumes) { std::pair instance = std::make_pair(volume->object_idx(), volume->instance_idx()); InstancesToZMap::iterator it = instances_min_z.find(instance); if (it != instances_min_z.end()) diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index 8bb418baa..c28a6e867 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -129,7 +129,7 @@ private: TransformCache m_instance; public: - VolumeCache() {} + VolumeCache() = default; VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform); const Vec3d& get_volume_position() const { return m_volume.position; } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a037a3b47..5920ab5e3 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1217,9 +1217,8 @@ void Tab::apply_config_from_cache() // to update number of "filament" selection boxes when the number of extruders change. void Tab::on_presets_changed() { - if (wxGetApp().plater() == nullptr) { + if (wxGetApp().plater() == nullptr) return; - } // Instead of PostEvent (EVT_TAB_PRESETS_CHANGED) just call update_presets wxGetApp().plater()->sidebar().update_presets(m_type); @@ -1237,6 +1236,10 @@ void Tab::on_presets_changed() // clear m_dependent_tabs after first update from select_preset() // to avoid needless preset loading from update() function m_dependent_tabs.clear(); + +#if ENABLE_PROJECT_DIRTY_STATE + wxGetApp().plater()->update_project_dirty_from_presets(); +#endif // ENABLE_PROJECT_DIRTY_STATE } void Tab::build_preset_description_line(ConfigOptionsGroup* optgroup) @@ -2113,10 +2116,16 @@ wxSizer* Tab::description_line_widget(wxWindow* parent, ogStaticText* *StaticTex return sizer; } +#if ENABLE_PROJECT_DIRTY_STATE +bool Tab::saved_preset_is_dirty() const { return m_presets->saved_is_dirty(); } +void Tab::update_saved_preset_from_current_preset() { m_presets->update_saved_preset_from_current_preset(); } +bool Tab::current_preset_is_dirty() const { return m_presets->current_is_dirty(); } +#else bool Tab::current_preset_is_dirty() { return m_presets->current_is_dirty(); } +#endif // ENABLE_PROJECT_DIRTY_STATE void TabPrinter::build() { diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index 9ccbcda28..601995ea7 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -270,7 +270,11 @@ public: Preset::Type type() const { return m_type; } // The tab is already constructed. bool completed() const { return m_completed; } - virtual bool supports_printer_technology(const PrinterTechnology tech) = 0; +#if ENABLE_PROJECT_DIRTY_STATE + virtual bool supports_printer_technology(const PrinterTechnology tech) const = 0; +#else + virtual bool supports_printer_technology(const PrinterTechnology tech) = 0; +#endif // ENABLE_PROJECT_DIRTY_STATE void create_preset_tab(); void add_scaled_button(wxWindow* parent, ScalableButton** btn, const std::string& icon_name, @@ -333,7 +337,13 @@ public: Field* get_field(const t_config_option_key &opt_key, Page** selected_page, int opt_index = -1); void toggle_option(const std::string& opt_key, bool toggle, int opt_index = -1); wxSizer* description_line_widget(wxWindow* parent, ogStaticText** StaticText, wxString text = wxEmptyString); +#if ENABLE_PROJECT_DIRTY_STATE + bool current_preset_is_dirty() const; + bool saved_preset_is_dirty() const; + void update_saved_preset_from_current_preset(); +#else bool current_preset_is_dirty(); +#endif // ENABLE_PROJECT_DIRTY_STATE DynamicPrintConfig* get_config() { return m_config; } PresetCollection* get_presets() { return m_presets; } @@ -377,8 +387,8 @@ class TabPrint : public Tab { public: TabPrint(wxNotebook* parent) : -// Tab(parent, _(L("Print Settings")), L("print")) {} - Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_PRINT) {} +// Tab(parent, _L("Print Settings"), L("print")) {} + Tab(parent, _L("Print Settings"), Slic3r::Preset::TYPE_PRINT) {} ~TabPrint() {} void build() override; @@ -387,7 +397,11 @@ public: void toggle_options() override; void update() override; void clear_pages() override; - bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } +#if ENABLE_PROJECT_DIRTY_STATE + bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptFFF; } +#else + bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } +#endif // ENABLE_PROJECT_DIRTY_STATE private: ogStaticText* m_recommended_thin_wall_thickness_description_line = nullptr; @@ -407,8 +421,8 @@ private: std::map m_overrides_options; public: TabFilament(wxNotebook* parent) : -// Tab(parent, _(L("Filament Settings")), L("filament")) {} - Tab(parent, _(L("Filament Settings")), Slic3r::Preset::TYPE_FILAMENT) {} +// Tab(parent, _L("Filament Settings"), L("filament")) {} + Tab(parent, _L("Filament Settings"), Slic3r::Preset::TYPE_FILAMENT) {} ~TabFilament() {} void build() override; @@ -417,7 +431,11 @@ public: void toggle_options() override; void update() override; void clear_pages() override; - bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } +#if ENABLE_PROJECT_DIRTY_STATE + bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptFFF; } +#else + bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } +#endif // ENABLE_PROJECT_DIRTY_STATE }; class TabPrinter : public Tab @@ -450,7 +468,7 @@ public: // TabPrinter(wxNotebook* parent) : Tab(parent, _(L("Printer Settings")), L("printer")) {} TabPrinter(wxNotebook* parent) : - Tab(parent, _(L("Printer Settings")), Slic3r::Preset::TYPE_PRINTER) {} + Tab(parent, _L("Printer Settings"), Slic3r::Preset::TYPE_PRINTER) {} ~TabPrinter() {} void build() override; @@ -472,7 +490,11 @@ public: void init_options_list() override; void msw_rescale() override; void sys_color_changed() override; - bool supports_printer_technology(const PrinterTechnology /* tech */) override { return true; } +#if ENABLE_PROJECT_DIRTY_STATE + bool supports_printer_technology(const PrinterTechnology /* tech */) const override { return true; } +#else + bool supports_printer_technology(const PrinterTechnology /* tech */) override { return true; } +#endif // ENABLE_PROJECT_DIRTY_STATE wxSizer* create_bed_shape_widget(wxWindow* parent); void cache_extruder_cnt(); @@ -483,8 +505,8 @@ class TabSLAMaterial : public Tab { public: TabSLAMaterial(wxNotebook* parent) : -// Tab(parent, _(L("Material Settings")), L("sla_material")) {} - Tab(parent, _(L("Material Settings")), Slic3r::Preset::TYPE_SLA_MATERIAL) {} +// Tab(parent, _L("Material Settings"), L("sla_material")) {} + Tab(parent, _L("Material Settings"), Slic3r::Preset::TYPE_SLA_MATERIAL) {} ~TabSLAMaterial() {} void build() override; @@ -492,15 +514,19 @@ public: void toggle_options() override {}; void update() override; void init_options_list() override; - bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; } +#if ENABLE_PROJECT_DIRTY_STATE + bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptSLA; } +#else + bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; } +#endif // ENABLE_PROJECT_DIRTY_STATE }; class TabSLAPrint : public Tab { public: TabSLAPrint(wxNotebook* parent) : -// Tab(parent, _(L("Print Settings")), L("sla_print")) {} - Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_SLA_PRINT) {} +// Tab(parent, _L("Print Settings"), L("sla_print")) {} + Tab(parent, _L("Print Settings"), Slic3r::Preset::TYPE_SLA_PRINT) {} ~TabSLAPrint() {} ogStaticText* m_support_object_elevation_description_line = nullptr; @@ -511,7 +537,11 @@ public: void toggle_options() override; void update() override; void clear_pages() override; - bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; } +#if ENABLE_PROJECT_DIRTY_STATE + bool supports_printer_technology(const PrinterTechnology tech) const override { return tech == ptSLA; } +#else + bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptSLA; } +#endif // ENABLE_PROJECT_DIRTY_STATE }; } // GUI diff --git a/src/slic3r/GUI/UnsavedChangesDialog.cpp b/src/slic3r/GUI/UnsavedChangesDialog.cpp index 0a0a3dc60..dc2c56246 100644 --- a/src/slic3r/GUI/UnsavedChangesDialog.cpp +++ b/src/slic3r/GUI/UnsavedChangesDialog.cpp @@ -1694,6 +1694,9 @@ void DiffPresetDialog::update_compatibility(const std::string& preset_name, Pres technology_changed = old_printer_technology != new_printer_technology; } + // select preset + presets->select_preset_by_name(preset_name, false); + // Mark the print & filament enabled if they are compatible with the currently selected preset. // The following method should not discard changes of current print or filament presets on change of a printer profile, // if they are compatible with the current printer. diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 7933a1d69..dead35267 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "libslic3r/Model.hpp" #include "libslic3r/Print.hpp" diff --git a/tests/fff_print/test_trianglemesh.cpp b/tests/fff_print/test_trianglemesh.cpp index 233b0e515..fa6237c8b 100644 --- a/tests/fff_print/test_trianglemesh.cpp +++ b/tests/fff_print/test_trianglemesh.cpp @@ -1,6 +1,7 @@ #include #include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/Point.hpp" #include "libslic3r/Config.hpp" #include "libslic3r/Model.hpp" @@ -355,27 +356,25 @@ SCENARIO( "TriangleMeshSlicer: Cut behavior.") { TriangleMesh cube(vertices, facets); cube.repair(); WHEN( "Object is cut at the bottom") { - TriangleMesh upper {}; - TriangleMesh lower {}; - TriangleMeshSlicer slicer(&cube); - slicer.cut(0, &upper, &lower); + indexed_triangle_set upper {}; + indexed_triangle_set lower {}; + cut_mesh(cube.its, 0, &upper, &lower); THEN("Upper mesh has all facets except those belonging to the slicing plane.") { - REQUIRE(upper.facets_count() == 12); + REQUIRE(upper.indices.size() == 12); } THEN("Lower mesh has no facets.") { - REQUIRE(lower.facets_count() == 0); + REQUIRE(lower.indices.size() == 0); } } WHEN( "Object is cut at the center") { - TriangleMesh upper {}; - TriangleMesh lower {}; - TriangleMeshSlicer slicer(&cube); - slicer.cut(10, &upper, &lower); + indexed_triangle_set upper {}; + indexed_triangle_set lower {}; + cut_mesh(cube.its, 10, &upper, &lower); THEN("Upper mesh has 2 external horizontal facets, 3 facets on each side, and 6 facets on the triangulated side (2 + 12 + 6).") { - REQUIRE(upper.facets_count() == 2+12+6); + REQUIRE(upper.indices.size() == 2+12+6); } THEN("Lower mesh has 2 external horizontal facets, 3 facets on each side, and 6 facets on the triangulated side (2 + 12 + 6).") { - REQUIRE(lower.facets_count() == 2+12+6); + REQUIRE(lower.indices.size() == 2+12+6); } } } diff --git a/tests/libslic3r/test_marchingsquares.cpp b/tests/libslic3r/test_marchingsquares.cpp index 1a4b1fb72..14481f14a 100644 --- a/tests/libslic3r/test_marchingsquares.cpp +++ b/tests/libslic3r/test_marchingsquares.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -319,8 +320,8 @@ static void recreate_object_from_rasters(const std::string &objname, float lh) { mesh.translate(tr.x(), tr.y(), tr.z()); bb = mesh.bounding_box(); - std::vector layers; - slice_mesh(mesh, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh), layers, 0.f, []{}); + assert(mesh.has_shared_vertices()); + std::vector layers = slice_mesh_ex(mesh.its, grid(float(bb.min.z()) + lh, float(bb.max.z()), lh)); sla::RasterBase::Resolution res{2560, 1440}; double disp_w = 120.96; diff --git a/tests/sla_print/sla_print_tests.cpp b/tests/sla_print/sla_print_tests.cpp index 1f98463cc..54e5ea732 100644 --- a/tests/sla_print/sla_print_tests.cpp +++ b/tests/sla_print/sla_print_tests.cpp @@ -6,6 +6,7 @@ #include "sla_test_utils.hpp" +#include #include #include @@ -48,9 +49,7 @@ TEST_CASE("Support point generator should be deterministic if seeded", sla::SupportPointGenerator::Config autogencfg; autogencfg.head_diameter = float(2 * supportcfg.head_front_radius_mm); sla::SupportPointGenerator point_gen{emesh, autogencfg, [] {}, [](int) {}}; - - TriangleMeshSlicer slicer{&mesh}; - + auto bb = mesh.bounding_box(); double zmin = bb.min.z(); double zmax = bb.max.z(); @@ -58,8 +57,8 @@ TEST_CASE("Support point generator should be deterministic if seeded", auto layer_h = 0.05f; auto slicegrid = grid(float(gnd), float(zmax), layer_h); - std::vector slices; - slicer.slice(slicegrid, SlicingMode::Regular, CLOSING_RADIUS, &slices, []{}); + assert(mesh.has_shared_vertices()); + std::vector slices = slice_mesh_ex(mesh.its, slicegrid, CLOSING_RADIUS); point_gen.seed(0); point_gen.execute(slices, slicegrid); diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 1ec890beb..8a78c63b3 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -1,6 +1,9 @@ #include "sla_test_utils.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" #include "libslic3r/SLA/AGGRaster.hpp" +#include + void test_support_model_collision(const std::string &obj_filename, const sla::SupportTreeConfig &input_supportcfg, const sla::HollowingConfig &hollowingcfg, @@ -94,8 +97,6 @@ void test_supports(const std::string &obj_filename, mesh.require_shared_vertices(); } - TriangleMeshSlicer slicer{&mesh}; - auto bb = mesh.bounding_box(); double zmin = bb.min.z(); double zmax = bb.max.z(); @@ -103,7 +104,8 @@ void test_supports(const std::string &obj_filename, auto layer_h = 0.05f; out.slicegrid = grid(float(gnd), float(zmax), layer_h); - slicer.slice(out.slicegrid, SlicingMode::Regular, CLOSING_RADIUS, &out.model_slices, []{}); + assert(mesh.has_shared_vertices()); + out.model_slices = slice_mesh_ex(mesh.its, out.slicegrid, CLOSING_RADIUS); sla::cut_drainholes(out.model_slices, out.slicegrid, CLOSING_RADIUS, drainholes, []{}); // Create the special index-triangle mesh with spatial indexing which @@ -467,10 +469,10 @@ sla::SupportPoints calc_support_pts( const sla::SupportPointGenerator::Config &cfg) { // Prepare the slice grid and the slices - std::vector slices; auto bb = cast(mesh.bounding_box()); std::vector heights = grid(bb.min.z(), bb.max.z(), 0.1f); - slice_mesh(mesh, heights, slices, CLOSING_RADIUS, [] {}); + assert(mesh.has_shared_vertices()); + std::vector slices = slice_mesh_ex(mesh.its, heights, CLOSING_RADIUS); // Prepare the support point calculator sla::IndexedMesh emesh{mesh}; diff --git a/xs/xsp/Print.xsp b/xs/xsp/Print.xsp index 44d58266b..8aef9e7a3 100644 --- a/xs/xsp/Print.xsp +++ b/xs/xsp/Print.xsp @@ -105,7 +105,7 @@ _constant() SV* filament_stats() %code%{ HV* hv = newHV(); - for (std::map::const_iterator it = THIS->print_statistics().filament_stats.begin(); it != THIS->print_statistics().filament_stats.end(); ++it) { + for (std::map::const_iterator it = THIS->print_statistics().filament_stats.begin(); it != THIS->print_statistics().filament_stats.end(); ++it) { // stringify extruder_id std::ostringstream ss; ss << it->first; diff --git a/xs/xsp/TriangleMesh.xsp b/xs/xsp/TriangleMesh.xsp index 230f8b2a5..2b07c78ee 100644 --- a/xs/xsp/TriangleMesh.xsp +++ b/xs/xsp/TriangleMesh.xsp @@ -3,6 +3,7 @@ %{ #include #include "libslic3r/TriangleMesh.hpp" +#include "libslic3r/TriangleMeshSlicer.hpp" %} %name{Slic3r::TriangleMesh} class TriangleMesh { @@ -180,9 +181,7 @@ TriangleMesh::slice(z) // convert doubles to floats std::vector z_f = cast(z); - std::vector layers; - TriangleMeshSlicer mslicer(THIS); - mslicer.slice(z_f, SlicingMode::Regular, 0.049f, &layers, [](){}); + std::vector layers = slice_mesh_ex(THIS->its, z_f, 0.049f); AV* layers_av = newAV(); size_t len = layers.size(); @@ -202,14 +201,18 @@ TriangleMesh::slice(z) RETVAL void -TriangleMesh::cut(z, upper, lower) +TriangleMesh::cut(z, upper_mesh, lower_mesh) float z; - TriangleMesh* upper; - TriangleMesh* lower; + TriangleMesh* upper_mesh; + TriangleMesh* lower_mesh; CODE: THIS->require_shared_vertices(); // TriangleMeshSlicer needs this - TriangleMeshSlicer mslicer(THIS); - mslicer.cut(z, upper, lower); + indexed_triangle_set upper, lower; + cut_mesh(THIS->its, z, upper_mesh ? &upper : nullptr, lower_mesh ? &lower : nullptr); + if (upper_mesh) + *upper_mesh = TriangleMesh(upper); + if (lower_mesh) + *lower_mesh = TriangleMesh(lower); std::vector TriangleMesh::bb3()