diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 74bf6eb53eb..71894582864 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -152,53 +152,54 @@ include(MPFR/MPFR.cmake) include(CGAL/CGAL.cmake) include(wxWidgets/wxWidgets.cmake) -if (ZLIB_PKG) +if (NOT "${ZLIB_PKG}" STREQUAL "") add_dependencies(dep_blosc ${ZLIB_PKG}) add_dependencies(dep_openexr ${ZLIB_PKG}) endif () -if (MSVC) - - add_custom_target(deps ALL - DEPENDS - dep_boost - dep_tbb - dep_libcurl - dep_wxWidgets - dep_gtest - dep_cereal - dep_nlopt - # dep_qhull # Experimental - dep_openvdb - dep_OpenCSG - dep_CGAL - ${PNG_PKG} - ${ZLIB_PKG} - ${EXPAT_PKG} +set(_dep_list + dep_boost + dep_tbb + dep_libcurl + dep_wxWidgets + dep_gtest + dep_cereal + dep_nlopt + dep_openvdb + dep_OpenCSG + dep_CGAL + ${PNG_PKG} + ${ZLIB_PKG} + ${EXPAT_PKG} ) -else() - - add_custom_target(deps ALL - DEPENDS - dep_boost - dep_tbb - dep_libcurl - dep_wxWidgets - dep_gtest - dep_cereal - dep_nlopt - dep_qhull - dep_openvdb - dep_OpenCSG - dep_CGAL - ${PNG_PKG} - ${ZLIB_PKG} - ${EXPAT_PKG} - # dep_libigl # Not working, static build has different Eigen +if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") + # Patch the boost::polygon library with a custom one. + ExternalProject_Add(dep_boost_polygon + EXCLUDE_FROM_ALL ON + GIT_REPOSITORY "https://github.com/prusa3d/polygon" + GIT_TAG prusaslicer_gmp + DEPENDS dep_boost + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_CURRENT_BINARY_DIR}/dep_boost_polygon-prefix/src/dep_boost_polygon/include/boost/polygon" + "${DESTDIR}/usr/local/include/boost/polygon" ) + # Only override boost::Polygon Voronoi implementation with Vojtech's GMP hacks on 64bit platforms. + list(APPEND _dep_list "dep_boost_polygon") +endif () +if (MSVC) + # Experimental + #list(APPEND _dep_list "dep_qhull") +else() + list(APPEND _dep_list "dep_qhull") + # Not working, static build has different Eigen + #list(APPEND _dep_list "dep_libigl") endif() +add_custom_target(deps ALL DEPENDS ${_dep_list}) + # Note: I'm not using any of the LOG_xxx options in ExternalProject_Add() commands # because they seem to generate bogus build files (possibly a bug in ExternalProject). diff --git a/deps/deps-macos.cmake b/deps/deps-macos.cmake index ef00b80d50d..a71a0ebfc0f 100644 --- a/deps/deps-macos.cmake +++ b/deps/deps-macos.cmake @@ -9,6 +9,8 @@ set(DEP_CMAKE_OPTS "-DCMAKE_OSX_DEPLOYMENT_TARGET=${DEP_OSX_TARGET}" "-DCMAKE_CXX_FLAGS=${DEP_WERRORS_SDK}" "-DCMAKE_C_FLAGS=${DEP_WERRORS_SDK}" + "-DCMAKE_FIND_FRAMEWORK=LAST" + "-DCMAKE_FIND_APPBUNDLE=LAST" ) include("deps-unix-common.cmake") diff --git a/resources/icons/Pmetal_001.png b/resources/icons/Pmetal_001.png new file mode 100644 index 00000000000..c848f839c50 Binary files /dev/null and b/resources/icons/Pmetal_001.png differ diff --git a/resources/icons/cog_.svg b/resources/icons/cog_.svg new file mode 100644 index 00000000000..94cab0a8eba --- /dev/null +++ b/resources/icons/cog_.svg @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/resources/icons/white/dot.svg b/resources/icons/white/dot.svg new file mode 100644 index 00000000000..90fbaf7fb10 --- /dev/null +++ b/resources/icons/white/dot.svg @@ -0,0 +1,8 @@ + + + + + + + diff --git a/resources/icons/white/drop_to_bed.svg b/resources/icons/white/drop_to_bed.svg new file mode 100644 index 00000000000..76243f89768 --- /dev/null +++ b/resources/icons/white/drop_to_bed.svg @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/resources/icons/white/edit_uni.svg b/resources/icons/white/edit_uni.svg new file mode 100644 index 00000000000..661924763ca --- /dev/null +++ b/resources/icons/white/edit_uni.svg @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/resources/icons/white/eye_closed.svg b/resources/icons/white/eye_closed.svg new file mode 100644 index 00000000000..0cdd16ae006 --- /dev/null +++ b/resources/icons/white/eye_closed.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/resources/icons/white/eye_open.svg b/resources/icons/white/eye_open.svg new file mode 100644 index 00000000000..1b320da079c --- /dev/null +++ b/resources/icons/white/eye_open.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/resources/icons/white/lock_closed_f.svg b/resources/icons/white/lock_closed_f.svg new file mode 100644 index 00000000000..412c93c1647 --- /dev/null +++ b/resources/icons/white/lock_closed_f.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/resources/icons/white/search.svg b/resources/icons/white/search.svg new file mode 100644 index 00000000000..679bb30f71c --- /dev/null +++ b/resources/icons/white/search.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/resources/shaders/gouraud.fs b/resources/shaders/gouraud.fs index 853a5512c1e..45175acc21f 100644 --- a/resources/shaders/gouraud.fs +++ b/resources/shaders/gouraud.fs @@ -17,6 +17,9 @@ struct SlopeDetection uniform vec4 uniform_color; uniform SlopeDetection slope; +uniform sampler2D environment_tex; +uniform bool use_environment_tex; + varying vec3 clipping_planes_dots; // x = tainted, y = specular; @@ -26,6 +29,7 @@ varying vec3 delta_box_min; varying vec3 delta_box_max; varying float world_normal_z; +varying vec3 eye_normal; vec3 slope_color() { @@ -40,5 +44,8 @@ void main() vec3 color = slope.actived ? slope_color() : uniform_color.rgb; // 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; - gl_FragColor = vec4(vec3(intensity.y, intensity.y, intensity.y) + color * intensity.x, uniform_color.a); + 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, uniform_color.a); + else + gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, uniform_color.a); } diff --git a/resources/shaders/gouraud.vs b/resources/shaders/gouraud.vs index 2644d48e4eb..d60f6eae8a4 100644 --- a/resources/shaders/gouraud.vs +++ b/resources/shaders/gouraud.vs @@ -51,22 +51,23 @@ varying vec3 delta_box_max; varying vec3 clipping_planes_dots; varying float world_normal_z; +varying vec3 eye_normal; void main() { // First transform the normal into camera space and normalize the result. - vec3 normal = normalize(gl_NormalMatrix * gl_Normal); + eye_normal = normalize(gl_NormalMatrix * gl_Normal); // Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex. // Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range. - float NdotL = max(dot(normal, LIGHT_TOP_DIR), 0.0); + float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0); intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE; vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz; - intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, normal)), 0.0), LIGHT_TOP_SHININESS); + intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS); // Perform the same lighting calculation for the 2nd light source (no specular applied). - NdotL = max(dot(normal, LIGHT_FRONT_DIR), 0.0); + NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0); intensity.x += NdotL * LIGHT_FRONT_DIFFUSE; // compute deltas for out of print volume detection (world coordinates) diff --git a/sandboxes/CMakeLists.txt b/sandboxes/CMakeLists.txt index a2bd13bb0b3..23c15f0899f 100644 --- a/sandboxes/CMakeLists.txt +++ b/sandboxes/CMakeLists.txt @@ -2,3 +2,4 @@ #add_subdirectory(openvdb) add_subdirectory(meshboolean) add_subdirectory(opencsg) +#add_subdirectory(aabb-evaluation) \ No newline at end of file diff --git a/sandboxes/aabb-evaluation/CMakeLists.txt b/sandboxes/aabb-evaluation/CMakeLists.txt new file mode 100644 index 00000000000..20011e34509 --- /dev/null +++ b/sandboxes/aabb-evaluation/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(aabb-evaluation aabb-evaluation.cpp) +target_link_libraries(aabb-evaluation libslic3r ${Boost_LIBRARIES} ${TBB_LIBRARIES} ${Boost_LIBRARIES} ${CMAKE_DL_LIBS}) diff --git a/sandboxes/aabb-evaluation/aabb-evaluation.cpp b/sandboxes/aabb-evaluation/aabb-evaluation.cpp new file mode 100644 index 00000000000..9ec7451e50e --- /dev/null +++ b/sandboxes/aabb-evaluation/aabb-evaluation.cpp @@ -0,0 +1,224 @@ +#include +#include +#include + +#include +#include +#include + +#include + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4244) +#pragma warning(disable: 4267) +#endif +#include +#include +#include +#include +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +const std::string USAGE_STR = { + "Usage: aabb-evaluation stlfilename.stl" +}; + +using namespace Slic3r; + +void profile(const TriangleMesh &mesh) +{ + Eigen::MatrixXd V; + Eigen::MatrixXi F; + Eigen::MatrixXd vertex_normals; + sla::to_eigen_mesh(mesh, V, F); + igl::per_vertex_normals(V, F, vertex_normals); + + static constexpr int num_samples = 100; + const int num_vertices = std::min(10000, int(mesh.its.vertices.size())); + const Eigen::MatrixXd dirs = igl::random_dir_stratified(num_samples).cast(); + + Eigen::MatrixXd occlusion_output0; + { + AABBTreeIndirect::Tree3f tree; + { + PROFILE_BLOCK(AABBIndirect_Init); + tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(mesh.its.vertices, mesh.its.indices); + } + { + PROFILE_BLOCK(EigenMesh3D_AABBIndirectF_AmbientOcclusion); + occlusion_output0.resize(num_vertices, 1); + for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) { + const Eigen::Vector3d origin = mesh.its.vertices[ivertex].template cast(); + const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast(); + int num_hits = 0; + for (int s = 0; s < num_samples; s++) { + Eigen::Vector3d d = dirs.row(s); + if(d.dot(normal) < 0) { + // reverse ray + d *= -1; + } + igl::Hit hit; + if (AABBTreeIndirect::intersect_ray_first_hit(mesh.its.vertices, mesh.its.indices, tree, (origin + 1e-4 * d).eval(), d, hit)) + ++ num_hits; + } + occlusion_output0(ivertex) = (double)num_hits/(double)num_samples; + } + } + + { + PROFILE_BLOCK(EigenMesh3D_AABBIndirectFF_AmbientOcclusion); + occlusion_output0.resize(num_vertices, 1); + for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) { + const Eigen::Vector3d origin = mesh.its.vertices[ivertex].template cast(); + const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast(); + int num_hits = 0; + for (int s = 0; s < num_samples; s++) { + Eigen::Vector3d d = dirs.row(s); + if(d.dot(normal) < 0) { + // reverse ray + d *= -1; + } + igl::Hit hit; + if (AABBTreeIndirect::intersect_ray_first_hit(mesh.its.vertices, mesh.its.indices, tree, + Eigen::Vector3f((origin + 1e-4 * d).template cast()), + Eigen::Vector3f(d.template cast()), hit)) + ++ num_hits; + } + occlusion_output0(ivertex) = (double)num_hits/(double)num_samples; + } + } + } + + Eigen::MatrixXd occlusion_output1; + { + std::vector vertices; + std::vector triangles; + for (int i = 0; i < V.rows(); ++ i) + vertices.emplace_back(V.row(i).transpose()); + for (int i = 0; i < F.rows(); ++ i) + triangles.emplace_back(F.row(i).transpose()); + AABBTreeIndirect::Tree3d tree; + { + PROFILE_BLOCK(AABBIndirectD_Init); + tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(vertices, triangles); + } + + { + PROFILE_BLOCK(EigenMesh3D_AABBIndirectD_AmbientOcclusion); + occlusion_output1.resize(num_vertices, 1); + for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) { + const Eigen::Vector3d origin = V.row(ivertex).template cast(); + const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast(); + int num_hits = 0; + for (int s = 0; s < num_samples; s++) { + Eigen::Vector3d d = dirs.row(s); + if(d.dot(normal) < 0) { + // reverse ray + d *= -1; + } + igl::Hit hit; + if (AABBTreeIndirect::intersect_ray_first_hit(vertices, triangles, tree, Eigen::Vector3d(origin + 1e-4 * d), d, hit)) + ++ num_hits; + } + occlusion_output1(ivertex) = (double)num_hits/(double)num_samples; + } + } + } + + // Build the AABB accelaration tree + + Eigen::MatrixXd occlusion_output2; + { + igl::AABB AABB; + { + PROFILE_BLOCK(EigenMesh3D_AABB_Init); + AABB.init(V, F); + } + { + PROFILE_BLOCK(EigenMesh3D_AABB_AmbientOcclusion); + occlusion_output2.resize(num_vertices, 1); + for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) { + const Eigen::Vector3d origin = V.row(ivertex).template cast(); + const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast(); + int num_hits = 0; + for (int s = 0; s < num_samples; s++) { + Eigen::Vector3d d = dirs.row(s); + if(d.dot(normal) < 0) { + // reverse ray + d *= -1; + } + igl::Hit hit; + if (AABB.intersect_ray(V, F, origin + 1e-4 * d, d, hit)) + ++ num_hits; + } + occlusion_output2(ivertex) = (double)num_hits/(double)num_samples; + } + } + } + + Eigen::MatrixXd occlusion_output3; + { + typedef Eigen::Map> MapMatrixXfUnaligned; + typedef Eigen::Map> MapMatrixXiUnaligned; + igl::AABB AABB; + auto vertices = MapMatrixXfUnaligned(mesh.its.vertices.front().data(), mesh.its.vertices.size(), 3); + auto faces = MapMatrixXiUnaligned(mesh.its.indices.front().data(), mesh.its.indices.size(), 3); + { + PROFILE_BLOCK(EigenMesh3D_AABBf_Init); + AABB.init( + vertices, + faces); + } + + { + PROFILE_BLOCK(EigenMesh3D_AABBf_AmbientOcclusion); + occlusion_output3.resize(num_vertices, 1); + for (int ivertex = 0; ivertex < num_vertices; ++ ivertex) { + const Eigen::Vector3d origin = mesh.its.vertices[ivertex].template cast(); + const Eigen::Vector3d normal = vertex_normals.row(ivertex).template cast(); + int num_hits = 0; + for (int s = 0; s < num_samples; s++) { + Eigen::Vector3d d = dirs.row(s); + if(d.dot(normal) < 0) { + // reverse ray + d *= -1; + } + igl::Hit hit; + if (AABB.intersect_ray(vertices, faces, (origin + 1e-4 * d).eval().template cast(), d.template cast(), hit)) + ++ num_hits; + } + occlusion_output3(ivertex) = (double)num_hits/(double)num_samples; + } + } + } + + PROFILE_UPDATE(); + PROFILE_OUTPUT(nullptr); +} + +int main(const int argc, const char *argv[]) +{ + if(argc < 2) { + std::cout << USAGE_STR << std::endl; + return EXIT_SUCCESS; + } + + TriangleMesh mesh; + if (! mesh.ReadSTLFile(argv[1])) { + std::cerr << "Error loading " << argv[1] << std::endl; + return -1; + } + + mesh.repair(); + if (mesh.facets_count() == 0) { + std::cerr << "Error loading " << argv[1] << " . It is empty." << std::endl; + return -1; + } + + profile(mesh); + + return EXIT_SUCCESS; +} diff --git a/src/PrusaSlicer.cpp b/src/PrusaSlicer.cpp index 6ef58799410..a0422f5fa0d 100644 --- a/src/PrusaSlicer.cpp +++ b/src/PrusaSlicer.cpp @@ -53,6 +53,8 @@ #include "slic3r/GUI/3DScene.hpp" #include "slic3r/GUI/InstanceCheck.hpp" #include "slic3r/GUI/AppConfig.hpp" + #include "slic3r/GUI/MainFrame.hpp" + #include "slic3r/GUI/Plater.hpp" #endif /* SLIC3R_GUI */ using namespace Slic3r; @@ -532,9 +534,6 @@ int CLI::run(int argc, char **argv) return -1; } - //gui->app_config = app_config; - //app_config = nullptr; - // gui->autosave = m_config.opt_string("autosave"); GUI::GUI_App::SetInstance(gui); gui->CallAfter([gui, this, &load_configs] { diff --git a/src/Shiny/ShinyOutput.c b/src/Shiny/ShinyOutput.c index ad02ea003c6..c2c624d589b 100644 --- a/src/Shiny/ShinyOutput.c +++ b/src/Shiny/ShinyOutput.c @@ -40,8 +40,8 @@ THE SOFTWARE. /*---------------------------------------------------------------------------*/ #define OUTPUT_WIDTH_CALL 6 -#define OUTPUT_WIDTH_TIME 6 -#define OUTPUT_WIDTH_PERC 4 +#define OUTPUT_WIDTH_TIME (6+3) +#define OUTPUT_WIDTH_PERC (4+3) #define OUTPUT_WIDTH_SUM 120 #define OUTPUT_WIDTH_DATA (1+OUTPUT_WIDTH_CALL + 1 + 2*(OUTPUT_WIDTH_TIME+4+OUTPUT_WIDTH_PERC+1) + 1) @@ -70,7 +70,7 @@ SHINY_INLINE char* printData(char *output, const ShinyData *a_data, float a_tope const ShinyTimeUnit *totalUnit = ShinyGetTimeUnit(totalTicksAvg); snprintf(output, OUTPUT_WIDTH_DATA + TRAILING, - " %*.1f %*.0f %-2s %*.0f%% %*.0f %-2s %*.0f%%", + " %*.1f %*.2f %-2s %*.2f%% %*.2f %-2s %*.2f%%", OUTPUT_WIDTH_CALL, a_data->entryCount.avg, OUTPUT_WIDTH_TIME, a_data->selfTicks.avg * selfUnit->invTickFreq, selfUnit->suffix, OUTPUT_WIDTH_PERC, a_data->selfTicks.avg * a_topercent, diff --git a/src/clipper/CMakeLists.txt b/src/clipper/CMakeLists.txt index 412ab53c7d4..8a4e9285268 100644 --- a/src/clipper/CMakeLists.txt +++ b/src/clipper/CMakeLists.txt @@ -7,3 +7,7 @@ add_library(clipper STATIC clipper_z.cpp clipper_z.hpp ) + +if(SLIC3R_PROFILE) + target_link_libraries(clipper Shiny) +endif() diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index eb47459eabf..d32f64aa4bd 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -107,7 +107,7 @@ namespace ImGui const char ColorMarkerStart = 0x2; // STX const char ColorMarkerEnd = 0x3; // ETX - // Special ASCII characters are used here as a ikons markers + // Special ASCII characters are used here as an ikons markers const char PrintIconMarker = 0x4; const char PrinterIconMarker = 0x5; const char PrinterSlaIconMarker = 0x6; diff --git a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp index 57da6ec12eb..dd80322bcc5 100644 --- a/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp +++ b/src/libnest2d/include/libnest2d/backends/clipper/geometries.hpp @@ -77,7 +77,7 @@ inline void offset(PolygonImpl& sh, TCoord distance, const PolygonTag #define DISABLE_BOOST_OFFSET using ClipperLib::ClipperOffset; - using ClipperLib::jtMiter; + using ClipperLib::jtSquare; using ClipperLib::etClosedPolygon; using ClipperLib::Paths; @@ -85,8 +85,8 @@ inline void offset(PolygonImpl& sh, TCoord distance, const PolygonTag try { ClipperOffset offs; - offs.AddPath(sh.Contour, jtMiter, etClosedPolygon); - offs.AddPaths(sh.Holes, jtMiter, etClosedPolygon); + offs.AddPath(sh.Contour, jtSquare, etClosedPolygon); + offs.AddPaths(sh.Holes, jtSquare, etClosedPolygon); offs.Execute(result, static_cast(distance)); } catch (ClipperLib::clipperException &) { throw GeometryException(GeomErr::OFFSET); diff --git a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp index 91f04cb9330..6cdaadd2570 100644 --- a/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp +++ b/src/libnest2d/include/libnest2d/placers/nfpplacer.hpp @@ -528,15 +528,12 @@ class _NofitPolyPlacer: public PlacerBoilerplate<_NofitPolyPlacer 0) diff += wdiff; - if(hdiff > 0) diff += hdiff; + auto wdiff = TCompute(bb.width()) - bin.width(); + auto hdiff = TCompute(bb.height()) - bin.height(); + double diff = .0; + if(wdiff > 0) diff += double(wdiff); + if(hdiff > 0) diff += double(hdiff); + return diff; } diff --git a/src/libslic3r/AABBTreeIndirect.hpp b/src/libslic3r/AABBTreeIndirect.hpp new file mode 100644 index 00000000000..ec9b14a7ae8 --- /dev/null +++ b/src/libslic3r/AABBTreeIndirect.hpp @@ -0,0 +1,698 @@ +// AABB tree built upon external data set, referencing the external data by integer indices. +// The AABB tree balancing and traversal (ray casting, closest triangle of an indexed triangle mesh) +// were adapted from libigl AABB.{cpp,hpp} Copyright (C) 2015 Alec Jacobson +// while the implicit balanced tree representation and memory optimizations are Vojtech's. + +#ifndef slic3r_AABBTreeIndirect_hpp_ +#define slic3r_AABBTreeIndirect_hpp_ + +#include +#include +#include +#include + +#include "Utils.hpp" // for next_highest_power_of_2() + +extern "C" +{ +// Ray-Triangle Intersection Test Routines by Tomas Moller, May 2000 +#include +} +// Definition of the ray intersection hit structure. +#include + +namespace Slic3r { +namespace AABBTreeIndirect { + +// Static balanced AABB tree for raycasting and closest triangle search. +// The balanced tree is built over a single large std::vector of nodes, where the children of nodes +// are addressed implicitely using a power of two indexing rule. +// Memory for a full balanced tree is allocated, but not all nodes at the last level are used. +// This may seem like a waste of memory, but one saves memory for the node links and there is zero +// overhead of a memory allocator management (usually the memory allocator adds at least one pointer +// before the memory returned). However, allocating memory in a single vector is very fast even +// in multi-threaded environment and it is cache friendly. +// +// A balanced tree is built upon a vector of bounding boxes and their centroids, storing the reference +// to the source entity (a 3D triangle, a 2D segment etc, a 3D or 2D point etc). +// The source bounding boxes may have an epsilon applied to fight numeric rounding errors when +// traversing the AABB tree. +template +class Tree +{ +public: + static constexpr int NumDimensions = ANumDimensions; + using CoordType = ACoordType; + using VectorType = Eigen::Matrix; + using BoundingBox = Eigen::AlignedBox; + // Following could be static constexpr size_t, but that would not link in C++11 + enum : size_t { + // Node is not used. + npos = size_t(-1), + // Inner node (not leaf). + inner = size_t(-2) + }; + + // Single node of the implicit balanced AABB tree. There are no links to the children nodes, + // as these links are calculated implicitely using a power of two rule. + struct Node { + // Index of the external source entity, for which this AABB tree was built, npos for internal nodes. + size_t idx = npos; + // Bounding box around this entity, possibly with epsilons applied to fight numeric rounding errors + // when traversing the AABB tree. + BoundingBox bbox; + + bool is_valid() const { return this->idx != npos; } + bool is_inner() const { return this->idx == inner; } + bool is_leaf() const { return ! this->is_inner(); } + + template + void set(const SourceNode &rhs) { + this->idx = rhs.idx(); + this->bbox = rhs.bbox(); + } + }; + + void clear() { m_nodes.clear(); } + + // SourceNode shall implement + // size_t SourceNode::idx() const + // - Index to the outside entity (triangle, edge, point etc). + // const VectorType& SourceNode::centroid() const + // - Centroid of this node. The centroid is used for balancing the tree. + // const BoundingBox& SourceNode::bbox() const + // - Bounding box of this node, likely expanded with epsilon to account for numeric rounding during tree traversal. + // Union of bounding boxes at a single level of the AABB tree is used for deciding the longest axis aligned dimension + // to split around. + template + void build(std::vector &&input) + { + if (input.empty()) + clear(); + else { + // Allocate enough memory for a full binary tree. + m_nodes.assign(next_highest_power_of_2(input.size()) * 2 - 1, Node()); + build_recursive(input, 0, 0, input.size() - 1); + } + input.clear(); + } + + const std::vector& nodes() const { return m_nodes; } + const Node& node(size_t idx) const { return m_nodes[idx]; } + bool empty() const { return m_nodes.empty(); } + + // Addressing the child nodes using the power of two rule. + static size_t left_child_idx(size_t idx) { return idx * 2 + 1; } + static size_t right_child_idx(size_t idx) { return left_child_idx(idx) + 1; } + const Node& left_child(size_t idx) const { return m_nodes[left_child_idx(idx)]; } + const Node& right_child(size_t idx) const { return m_nodes[right_child_idx(idx)]; } + + template + void build(const std::vector &input) + { + std::vector copy(input); + this->build(std::move(copy)); + } + +private: + // Build a balanced tree by splitting the input sequence by an axis aligned plane at a dimension. + template + void build_recursive(std::vector &input, size_t node, const size_t left, const size_t right) + { + assert(node < m_nodes.size()); + assert(left <= right); + + if (left == right) { + // Insert a node into the balanced tree. + m_nodes[node].set(input[left]); + return; + } + + // Calculate bounding box of the input. + BoundingBox bbox(input[left].bbox()); + for (size_t i = left + 1; i <= right; ++ i) + bbox.extend(input[i].bbox()); + int dimension = -1; + bbox.diagonal().maxCoeff(&dimension); + + // Partition the input to left / right pieces of the same length to produce a balanced tree. + size_t center = (left + right) / 2; + partition_input(input, size_t(dimension), left, right, center); + // Insert an inner node into the tree. Inner node does not reference any input entity (triangle, line segment etc). + m_nodes[node].idx = inner; + m_nodes[node].bbox = bbox; + build_recursive(input, node * 2 + 1, left, center); + build_recursive(input, node * 2 + 2, center + 1, right); + } + + // Partition the input m_nodes at "k" and "dimension" using the QuickSelect method: + // https://en.wikipedia.org/wiki/Quickselect + // Items left of the k'th item are lower than the k'th item in the "dimension", + // items right of the k'th item are higher than the k'th item in the "dimension", + template + void partition_input(std::vector &input, const size_t dimension, size_t left, size_t right, const size_t k) const + { + while (left < right) { + size_t center = (left + right) / 2; + CoordType pivot; + { + // Bubble sort the input[left], input[center], input[right], so that a median of the three values + // will end up in input[center]. + CoordType left_value = input[left ].centroid()(dimension); + CoordType center_value = input[center].centroid()(dimension); + CoordType right_value = input[right ].centroid()(dimension); + if (left_value > center_value) { + std::swap(input[left], input[center]); + std::swap(left_value, center_value); + } + if (left_value > right_value) { + std::swap(input[left], input[right]); + right_value = left_value; + } + if (center_value > right_value) { + std::swap(input[center], input[right]); + center_value = right_value; + } + pivot = center_value; + } + if (right <= left + 2) + // The interval is already sorted. + break; + size_t i = left; + size_t j = right - 1; + std::swap(input[center], input[j]); + // Partition the set based on the pivot. + for (;;) { + // Skip left points that are already at correct positions. + // Search will certainly stop at position (right - 1), which stores the pivot. + while (input[++ i].centroid()(dimension) < pivot) ; + // Skip right points that are already at correct positions. + while (input[-- j].centroid()(dimension) > pivot && i < j) ; + if (i >= j) + break; + std::swap(input[i], input[j]); + } + // Restore pivot to the center of the sequence. + std::swap(input[i], input[right - 1]); + // Which side the kth element is in? + if (k < i) + right = i - 1; + else if (k == i) + // Sequence is partitioned, kth element is at its place. + break; + else + left = i + 1; + } + } + + // The balanced tree storage. + std::vector m_nodes; +}; + +using Tree2f = Tree<2, float>; +using Tree3f = Tree<3, float>; +using Tree2d = Tree<2, double>; +using Tree3d = Tree<3, double>; + +namespace detail { + template + struct RayIntersector { + using VertexType = AVertexType; + using IndexedFaceType = AIndexedFaceType; + using TreeType = ATreeType; + using VectorType = AVectorType; + + const std::vector &vertices; + const std::vector &faces; + const TreeType &tree; + + const VectorType origin; + const VectorType dir; + const VectorType invdir; + }; + + template + struct RayIntersectorHits : RayIntersector { + std::vector hits; + }; + + //FIXME implement SSE for float AABB trees with float ray queries. + // SSE/SSE2 is supported by any Intel/AMD x64 processor. + // SSE support requires 16 byte alignment of the AABB nodes, representing the bounding boxes with 4+4 floats, + // storing the node index as the 4th element of the bounding box min value etc. + // https://www.flipcode.com/archives/SSE_RayBox_Intersection_Test.shtml + template + inline bool ray_box_intersect_invdir( + const Eigen::MatrixBase &origin, + const Eigen::MatrixBase &inv_dir, + Eigen::AlignedBox box, + const Scalar &t0, + const Scalar &t1) { + // http://people.csail.mit.edu/amy/papers/box-jgt.pdf + // "An Efficient and Robust Ray–Box Intersection Algorithm" + if (inv_dir.x() < 0) + std::swap(box.min().x(), box.max().x()); + if (inv_dir.y() < 0) + std::swap(box.min().y(), box.max().y()); + Scalar tmin = (box.min().x() - origin.x()) * inv_dir.x(); + Scalar tymax = (box.max().y() - origin.y()) * inv_dir.y(); + if (tmin > tymax) + return false; + Scalar tmax = (box.max().x() - origin.x()) * inv_dir.x(); + Scalar tymin = (box.min().y() - origin.y()) * inv_dir.y(); + if (tymin > tmax) + return false; + if (tymin > tmin) + tmin = tymin; + if (tymax < tmax) + tmax = tymax; + if (inv_dir.z() < 0) + std::swap(box.min().z(), box.max().z()); + Scalar tzmin = (box.min().z() - origin.z()) * inv_dir.z(); + if (tzmin > tmax) + return false; + Scalar tzmax = (box.max().z() - origin.z()) * inv_dir.z(); + if (tmin > tzmax) + return false; + if (tzmin > tmin) + tmin = tzmin; + if (tzmax < tmax) + tmax = tzmax; + return tmin < t1 && tmax > t0; + } + + template + std::enable_if_t::value && std::is_same::value, bool> + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + return intersect_triangle1(const_cast(origin.data()), const_cast(dir.data()), + const_cast(v0.data()), const_cast(v1.data()), const_cast(v2.data()), + &t, &u, &v); + } + + template + std::enable_if_t::value && !std::is_same::value, bool> + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + using Vector = Eigen::Matrix; + Vector w0 = v0.template cast(); + Vector w1 = v1.template cast(); + Vector w2 = v2.template cast(); + return intersect_triangle1(const_cast(origin.data()), const_cast(dir.data()), + w0.data(), w1.data(), w2.data(), &t, &u, &v); + } + + template + std::enable_if_t::value && std::is_same::value, bool> + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + using Vector = Eigen::Matrix; + Vector o = origin.template cast(); + Vector d = dir.template cast(); + return intersect_triangle1(o.data(), d.data(), const_cast(v0.data()), const_cast(v1.data()), const_cast(v2.data()), &t, &u, &v); + } + + template + std::enable_if_t::value && ! std::is_same::value, bool> + intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { + using Vector = Eigen::Matrix; + Vector o = origin.template cast(); + Vector d = dir.template cast(); + Vector w0 = v0.template cast(); + Vector w1 = v1.template cast(); + Vector w2 = v2.template cast(); + return intersect_triangle1(o.data(), d.data(), w0.data(), w1.data(), w2.data(), &t, &u, &v); + } + + template + static inline bool intersect_ray_recursive_first_hit( + RayIntersectorType &ray_intersector, + size_t node_idx, + Scalar min_t, + igl::Hit &hit) + { + const auto &node = ray_intersector.tree.node(node_idx); + assert(node.is_valid()); + + if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast(), Scalar(0), min_t)) + return false; + + if (node.is_leaf()) { + // shoot ray, record hit + auto face = ray_intersector.faces[node.idx]; + double t, u, v; + if (intersect_triangle( + ray_intersector.origin, ray_intersector.dir, + ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)], + t, u, v) + && t > 0.) { + hit = igl::Hit { int(node.idx), -1, float(u), float(v), float(t) }; + return true; + } else + return false; + } else { + // Left / right child node index. + size_t left = node_idx * 2 + 1; + size_t right = left + 1; + igl::Hit left_hit; + igl::Hit right_hit; + bool left_ret = intersect_ray_recursive_first_hit(ray_intersector, left, min_t, left_hit); + if (left_ret && left_hit.t < min_t) { + min_t = left_hit.t; + hit = left_hit; + } else + left_ret = false; + bool right_ret = intersect_ray_recursive_first_hit(ray_intersector, right, min_t, right_hit); + if (right_ret && right_hit.t < min_t) + hit = right_hit; + else + right_ret = false; + return left_ret || right_ret; + } + } + + template + static inline void intersect_ray_recursive_all_hits(RayIntersectorType &ray_intersector, size_t node_idx) + { + using Scalar = typename RayIntersectorType::VectorType::Scalar; + + const auto &node = ray_intersector.tree.node(node_idx); + assert(node.is_valid()); + + if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast(), + Scalar(0), std::numeric_limits::infinity())) + return; + + if (node.is_leaf()) { + auto face = ray_intersector.faces[node.idx]; + double t, u, v; + if (intersect_triangle( + ray_intersector.origin, ray_intersector.dir, + ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)], + t, u, v) + && t > 0.) { + ray_intersector.hits.emplace_back(igl::Hit{ int(node.idx), -1, float(u), float(v), float(t) }); + } + } else { + // Left / right child node index. + size_t left = node_idx * 2 + 1; + size_t right = left + 1; + intersect_ray_recursive_all_hits(ray_intersector, left); + intersect_ray_recursive_all_hits(ray_intersector, right); + } + } + + // Nothing to do with COVID-19 social distancing. + template + struct IndexedTriangleSetDistancer { + using VertexType = AVertexType; + using IndexedFaceType = AIndexedFaceType; + using TreeType = ATreeType; + using VectorType = AVectorType; + + const std::vector &vertices; + const std::vector &faces; + const TreeType &tree; + + const VectorType origin; + }; + + // Real-time collision detection, Ericson, Chapter 5 + template + static inline Vector closest_point_to_triangle(const Vector &p, const Vector &a, const Vector &b, const Vector &c) + { + using Scalar = typename Vector::Scalar; + // Check if P in vertex region outside A + Vector ab = b - a; + Vector ac = c - a; + Vector ap = p - a; + Scalar d1 = ab.dot(ap); + Scalar d2 = ac.dot(ap); + if (d1 <= 0 && d2 <= 0) + return a; + // Check if P in vertex region outside B + Vector bp = p - b; + Scalar d3 = ab.dot(bp); + Scalar d4 = ac.dot(bp); + if (d3 >= 0 && d4 <= d3) + return b; + // Check if P in edge region of AB, if so return projection of P onto AB + Scalar vc = d1*d4 - d3*d2; + if (a != b && vc <= 0 && d1 >= 0 && d3 <= 0) { + Scalar v = d1 / (d1 - d3); + return a + v * ab; + } + // Check if P in vertex region outside C + Vector cp = p - c; + Scalar d5 = ab.dot(cp); + Scalar d6 = ac.dot(cp); + if (d6 >= 0 && d5 <= d6) + return c; + // Check if P in edge region of AC, if so return projection of P onto AC + Scalar vb = d5*d2 - d1*d6; + if (vb <= 0 && d2 >= 0 && d6 <= 0) { + Scalar w = d2 / (d2 - d6); + return a + w * ac; + } + // Check if P in edge region of BC, if so return projection of P onto BC + Scalar va = d3*d6 - d5*d4; + if (va <= 0 && (d4 - d3) >= 0 && (d5 - d6) >= 0) { + Scalar w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); + return b + w * (c - b); + } + // P inside face region. Compute Q through its barycentric coordinates (u,v,w) + Scalar denom = Scalar(1.0) / (va + vb + vc); + Scalar v = vb * denom; + Scalar w = vc * denom; + return a + ab * v + ac * w; // = u*a + v*b + w*c, u = va * denom = 1.0-v-w + }; + + template + static inline Scalar squared_distance_to_indexed_triangle_set_recursive( + IndexedTriangleSetDistancerType &distancer, + size_t node_idx, + Scalar low_sqr_d, + Scalar up_sqr_d, + size_t &i, + Eigen::PlainObjectBase &c) + { + using Vector = typename IndexedTriangleSetDistancerType::VectorType; + + if (low_sqr_d > up_sqr_d) + return low_sqr_d; + + // Save the best achieved hit. + auto set_min = [&i, &c, &up_sqr_d](const Scalar sqr_d_candidate, const size_t i_candidate, const Vector &c_candidate) { + if (sqr_d_candidate < up_sqr_d) { + i = i_candidate; + c = c_candidate; + up_sqr_d = sqr_d_candidate; + } + }; + + const auto &node = distancer.tree.node(node_idx); + assert(node.is_valid()); + if (node.is_leaf()) + { + const auto &triangle = distancer.faces[node.idx]; + Vector c_candidate = closest_point_to_triangle( + distancer.origin, + distancer.vertices[triangle(0)].template cast(), + distancer.vertices[triangle(1)].template cast(), + distancer.vertices[triangle(2)].template cast()); + set_min((c_candidate - distancer.origin).squaredNorm(), node.idx, c_candidate); + } + else + { + size_t left_node_idx = node_idx * 2 + 1; + size_t right_node_idx = left_node_idx + 1; + const auto &node_left = distancer.tree.node(left_node_idx); + const auto &node_right = distancer.tree.node(right_node_idx); + assert(node_left.is_valid()); + assert(node_right.is_valid()); + + bool looked_left = false; + bool looked_right = false; + const auto &look_left = [&]() + { + size_t i_left; + Vector c_left = c; + Scalar sqr_d_left = squared_distance_to_indexed_triangle_set_recursive(distancer, left_node_idx, low_sqr_d, up_sqr_d, i_left, c_left); + set_min(sqr_d_left, i_left, c_left); + looked_left = true; + }; + const auto &look_right = [&]() + { + size_t i_right; + Vector c_right = c; + Scalar sqr_d_right = squared_distance_to_indexed_triangle_set_recursive(distancer, right_node_idx, low_sqr_d, up_sqr_d, i_right, c_right); + set_min(sqr_d_right, i_right, c_right); + looked_right = true; + }; + + // must look left or right if in box + using BBoxScalar = typename IndexedTriangleSetDistancerType::TreeType::BoundingBox::Scalar; + if (node_left.bbox.contains(distancer.origin.template cast())) + look_left(); + if (node_right.bbox.contains(distancer.origin.template cast())) + look_right(); + // if haven't looked left and could be less than current min, then look + Scalar left_up_sqr_d = node_left.bbox.squaredExteriorDistance(distancer.origin); + Scalar right_up_sqr_d = node_right.bbox.squaredExteriorDistance(distancer.origin); + if (left_up_sqr_d < right_up_sqr_d) { + if (! looked_left && left_up_sqr_d < up_sqr_d) + look_left(); + if (! looked_right && right_up_sqr_d < up_sqr_d) + look_right(); + } else { + if (! looked_right && right_up_sqr_d < up_sqr_d) + look_right(); + if (! looked_left && left_up_sqr_d < up_sqr_d) + look_left(); + } + } + return up_sqr_d; + } + +} // namespace detail + +// Build a balanced AABB Tree over an indexed triangles set, balancing the tree +// on centroids of the triangles. +// Epsilon is applied to the bounding boxes of the AABB Tree to cope with numeric inaccuracies +// during tree traversal. +template +inline Tree<3, typename VertexType::Scalar> build_aabb_tree_over_indexed_triangle_set( + // Indexed triangle set - 3D vertices. + const std::vector &vertices, + // Indexed triangle set - triangular faces, references to vertices. + const std::vector &faces, + //FIXME do we want to apply an epsilon? + const typename VertexType::Scalar eps = 0) +{ + using TreeType = Tree<3, typename VertexType::Scalar>; +// using CoordType = typename TreeType::CoordType; + using VectorType = typename TreeType::VectorType; + using BoundingBox = typename TreeType::BoundingBox; + + struct InputType { + size_t idx() const { return m_idx; } + const BoundingBox& bbox() const { return m_bbox; } + const VectorType& centroid() const { return m_centroid; } + + size_t m_idx; + BoundingBox m_bbox; + VectorType m_centroid; + }; + + std::vector input; + input.reserve(faces.size()); + const VectorType veps(eps, eps, eps); + for (size_t i = 0; i < faces.size(); ++ i) { + const IndexedFaceType &face = faces[i]; + const VertexType &v1 = vertices[face(0)]; + const VertexType &v2 = vertices[face(1)]; + const VertexType &v3 = vertices[face(2)]; + InputType n; + n.m_idx = i; + n.m_centroid = (1./3.) * (v1 + v2 + v3); + n.m_bbox = BoundingBox(v1, v1); + n.m_bbox.extend(v2); + n.m_bbox.extend(v3); + n.m_bbox.min() -= veps; + n.m_bbox.max() += veps; + input.emplace_back(n); + } + + TreeType out; + out.build(std::move(input)); + return out; +} + +// Find a first intersection of a ray with indexed triangle set. +// Intersection test is calculated with the accuracy of VectorType::Scalar +// even if the triangle mesh and the AABB Tree are built with floats. +template +inline bool intersect_ray_first_hit( + // Indexed triangle set - 3D vertices. + const std::vector &vertices, + // Indexed triangle set - triangular faces, references to vertices. + const std::vector &faces, + // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices. + const TreeType &tree, + // Origin of the ray. + const VectorType &origin, + // Direction of the ray. + const VectorType &dir, + // First intersection of the ray with the indexed triangle set. + igl::Hit &hit) +{ + using Scalar = typename VectorType::Scalar; + auto ray_intersector = detail::RayIntersector { + vertices, faces, tree, + origin, dir, VectorType(dir.cwiseInverse()) + }; + return ! tree.empty() && detail::intersect_ray_recursive_first_hit( + ray_intersector, size_t(0), std::numeric_limits::infinity(), hit); +} + +// Find all intersections of a ray with indexed triangle set. +// Intersection test is calculated with the accuracy of VectorType::Scalar +// even if the triangle mesh and the AABB Tree are built with floats. +// The output hits are sorted by the ray parameter. +// If the ray intersects a shared edge of two triangles, hits for both triangles are returned. +template +inline bool intersect_ray_all_hits( + // Indexed triangle set - 3D vertices. + const std::vector &vertices, + // Indexed triangle set - triangular faces, references to vertices. + const std::vector &faces, + // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices. + const TreeType &tree, + // Origin of the ray. + const VectorType &origin, + // Direction of the ray. + const VectorType &dir, + // All intersections of the ray with the indexed triangle set, sorted by parameter t. + std::vector &hits) +{ + auto ray_intersector = detail::RayIntersectorHits { + vertices, faces, tree, + origin, dir, VectorType(dir.cwiseInverse()) + }; + if (! tree.empty()) { + ray_intersector.hits.reserve(8); + detail::intersect_ray_recursive_all_hits(ray_intersector, 0); + std::swap(hits, ray_intersector.hits); + std::sort(hits.begin(), hits.end(), [](const auto &l, const auto &r) { return l.t < r.t; }); + } + return ! hits.empty(); +} + +// Finding a closest triangle, its closest point and squared distance to the closest point +// on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree. +// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar +// even if the triangle mesh and the AABB Tree are built with floats. +// Returns squared distance to the closest point or -1 if the input is empty. +template +inline typename VectorType::Scalar squared_distance_to_indexed_triangle_set( + // Indexed triangle set - 3D vertices. + const std::vector &vertices, + // Indexed triangle set - triangular faces, references to vertices. + const std::vector &faces, + // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices. + const TreeType &tree, + // Point to which the closest point on the indexed triangle set is searched for. + const VectorType &point, + // Index of the closest triangle in faces. + size_t &hit_idx_out, + // Position of the closest point on the indexed triangle set. + Eigen::PlainObjectBase &hit_point_out) +{ + using Scalar = typename VectorType::Scalar; + auto distancer = detail::IndexedTriangleSetDistancer + { vertices, faces, tree, point }; + return tree.empty() ? Scalar(-1) : + detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits::infinity(), hit_idx_out, hit_point_out); +} + +} // namespace AABBTreeIndirect +} // namespace Slic3r + +#endif /* slic3r_AABBTreeIndirect_hpp_ */ diff --git a/src/libslic3r/Arrange.cpp b/src/libslic3r/Arrange.cpp index 5b048b0ffbd..6ae7dd6a2e1 100644 --- a/src/libslic3r/Arrange.cpp +++ b/src/libslic3r/Arrange.cpp @@ -1,7 +1,8 @@ #include "Arrange.hpp" -//#include "Geometry.hpp" #include "SVG.hpp" +#include "BoundingBox.hpp" + #include #include #include @@ -51,8 +52,8 @@ template struct NfpImpl namespace Slic3r { template, int...EigenArgs> -inline SLIC3R_CONSTEXPR Eigen::Matrix unscaled( - const ClipperLib::IntPoint &v) SLIC3R_NOEXCEPT +inline constexpr Eigen::Matrix unscaled( + const ClipperLib::IntPoint &v) noexcept { return Eigen::Matrix{unscaled(v.X), unscaled(v.Y)}; @@ -395,11 +396,14 @@ template<> std::function AutoArranger::get_objfn() double score = std::get<0>(result); auto& fullbb = std::get<1>(result); - double miss = Placer::overfit(fullbb, m_bin); + auto bin = m_bin; + sl::offset(bin, -EPSILON * (m_bin.width() + m_bin.height())); + + double miss = Placer::overfit(fullbb, bin); miss = miss > 0? miss : 0; score += miss*miss; - return score; + return score; }; } diff --git a/src/libslic3r/Arrange.hpp b/src/libslic3r/Arrange.hpp index 8ff1e37a9fa..7630ab3e8e5 100644 --- a/src/libslic3r/Arrange.hpp +++ b/src/libslic3r/Arrange.hpp @@ -2,9 +2,12 @@ #define ARRANGE_HPP #include "ExPolygon.hpp" -#include "BoundingBox.hpp" -namespace Slic3r { namespace arrangement { +namespace Slic3r { + +class BoundingBox; + +namespace arrangement { /// A geometry abstraction for a circular print bed. Similarly to BoundingBox. class CircleBed { diff --git a/src/libslic3r/BoundingBox.cpp b/src/libslic3r/BoundingBox.cpp index d3cca7ff243..e3f93950922 100644 --- a/src/libslic3r/BoundingBox.cpp +++ b/src/libslic3r/BoundingBox.cpp @@ -11,17 +11,6 @@ template BoundingBoxBase::BoundingBoxBase(const std::vector &point template BoundingBox3Base::BoundingBox3Base(const std::vector &points); -BoundingBox::BoundingBox(const Lines &lines) -{ - Points points; - points.reserve(lines.size()); - for (const Line &line : lines) { - points.emplace_back(line.a); - points.emplace_back(line.b); - } - *this = BoundingBox(points); -} - void BoundingBox::polygon(Polygon* polygon) const { polygon->points.clear(); diff --git a/src/libslic3r/BoundingBox.hpp b/src/libslic3r/BoundingBox.hpp index 2216d788822..f03733dd868 100644 --- a/src/libslic3r/BoundingBox.hpp +++ b/src/libslic3r/BoundingBox.hpp @@ -20,17 +20,19 @@ class BoundingBoxBase min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {} BoundingBoxBase(const std::vector& points) : min(PointClass::Zero()), max(PointClass::Zero()) { - if (points.empty()) - throw std::invalid_argument("Empty point set supplied to BoundingBoxBase constructor"); - - typename std::vector::const_iterator it = points.begin(); - this->min = *it; - this->max = *it; - for (++ it; it != points.end(); ++ it) { - this->min = this->min.cwiseMin(*it); - this->max = this->max.cwiseMax(*it); + if (points.empty()) { + this->defined = false; + // throw std::invalid_argument("Empty point set supplied to BoundingBoxBase constructor"); + } else { + typename std::vector::const_iterator it = points.begin(); + this->min = *it; + this->max = *it; + for (++ it; it != points.end(); ++ it) { + this->min = this->min.cwiseMin(*it); + this->max = this->max.cwiseMax(*it); + } + this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)); } - this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)); } void reset() { this->defined = false; this->min = PointClass::Zero(); this->max = PointClass::Zero(); } void merge(const PointClass &point); @@ -143,7 +145,6 @@ class BoundingBox : public BoundingBoxBase BoundingBox() : BoundingBoxBase() {} BoundingBox(const Point &pmin, const Point &pmax) : BoundingBoxBase(pmin, pmax) {} BoundingBox(const Points &points) : BoundingBoxBase(points) {} - BoundingBox(const Lines &lines); friend BoundingBox get_extents_rotated(const Points &points, double angle); }; diff --git a/src/libslic3r/BridgeDetector.cpp b/src/libslic3r/BridgeDetector.cpp index ce7c960fa3b..bf8907c3fe2 100644 --- a/src/libslic3r/BridgeDetector.cpp +++ b/src/libslic3r/BridgeDetector.cpp @@ -53,7 +53,7 @@ void BridgeDetector::initialize() this->_edges = intersection_pl(to_polylines(grown), contours); #ifdef SLIC3R_DEBUG - printf(" bridge has " PRINTF_ZU " support(s)\n", this->_edges.size()); + printf(" bridge has %zu support(s)\n", this->_edges.size()); #endif // detect anchors as intersection between our bridge expolygon and the lower slices diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 12e61d7f3c0..9f566b4051b 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -187,7 +187,12 @@ add_library(libslic3r STATIC Utils.hpp Time.cpp Time.hpp + TriangleSelector.cpp + TriangleSelector.hpp MTUtils.hpp + VoronoiOffset.cpp + VoronoiOffset.hpp + VoronoiVisualUtils.hpp Zipper.hpp Zipper.cpp MinAreaBoundingBox.hpp @@ -304,7 +309,7 @@ if(WIN32) endif() if(SLIC3R_PROFILE) - target_link_libraries(slic3r Shiny) + target_link_libraries(libslic3r Shiny) endif() if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) diff --git a/src/libslic3r/CustomGCode.cpp b/src/libslic3r/CustomGCode.cpp index 824bcdd93c7..ba1890a1ffe 100644 --- a/src/libslic3r/CustomGCode.cpp +++ b/src/libslic3r/CustomGCode.cpp @@ -23,7 +23,7 @@ extern void update_custom_gcode_per_print_z_from_config(Info& info, DynamicPrint info.gcodes.reserve(colorprint_values.size()); int i = 0; for (auto val : colorprint_values) - info.gcodes.emplace_back(Item{ val, ColorChangeCode, 1, colors[(++i)%7] }); + info.gcodes.emplace_back(Item{ val, ColorChange, 1, colors[(++i)%7] }); info.mode = SingleExtruder; } @@ -43,11 +43,11 @@ extern void check_mode_for_custom_gcode_per_print_z(Info& info) bool is_single_extruder = true; for (auto item : info.gcodes) { - if (item.gcode == ToolChangeCode) { + if (item.type == ToolChange) { info.mode = MultiAsSingle; return; } - if (item.gcode == ColorChangeCode && item.extruder > 1) + if (item.type == ColorChange && item.extruder > 1) is_single_extruder = false; } @@ -60,7 +60,7 @@ std::vector> custom_tool_changes(const Info& cus { std::vector> custom_tool_changes; for (const Item& custom_gcode : custom_gcode_per_print_z.gcodes) - if (custom_gcode.gcode == ToolChangeCode) { + if (custom_gcode.type == ToolChange) { // If extruder count in PrinterSettings was changed, use default (0) extruder for extruders, more than num_extruders assert(custom_gcode.extruder >= 0); custom_tool_changes.emplace_back(custom_gcode.print_z, static_cast(size_t(custom_gcode.extruder) > num_extruders ? 1 : custom_gcode.extruder)); diff --git a/src/libslic3r/CustomGCode.hpp b/src/libslic3r/CustomGCode.hpp index a5ef1cc2ea0..334b4043130 100644 --- a/src/libslic3r/CustomGCode.hpp +++ b/src/libslic3r/CustomGCode.hpp @@ -8,38 +8,40 @@ namespace Slic3r { class DynamicPrintConfig; -// Additional Codes which can be set by user using DoubleSlider -static constexpr char ColorChangeCode[] = "M600"; -static constexpr char PausePrintCode[] = "M601"; -static constexpr char ToolChangeCode[] = "tool_change"; +namespace CustomGCode { -enum CustomGcodeType +enum Type { - cgtColorChange, - cgtPausePrint, + ColorChange, + PausePrint, + ToolChange, + Template, + Custom }; -namespace CustomGCode { - struct Item { bool operator<(const Item& rhs) const { return this->print_z < rhs.print_z; } bool operator==(const Item& rhs) const { return (rhs.print_z == this->print_z ) && - (rhs.gcode == this->gcode ) && + (rhs.type == this->type ) && (rhs.extruder == this->extruder ) && - (rhs.color == this->color ); + (rhs.color == this->color ) && + (rhs.extra == this->extra ); } bool operator!=(const Item& rhs) const { return ! (*this == rhs); } double print_z; - std::string gcode; + Type type; int extruder; // Informative value for ColorChangeCode and ToolChangeCode // "gcode" == ColorChangeCode => M600 will be applied for "extruder" extruder // "gcode" == ToolChangeCode => for whole print tool will be switched to "extruder" extruder std::string color; // if gcode is equal to PausePrintCode, // this field is used for save a short message shown on Printer display + std::string extra; // this field is used for the extra data like : + // - G-code text for the Type::Custom + // - message text for the Type::PausePrint }; enum Mode diff --git a/src/libslic3r/EdgeGrid.cpp b/src/libslic3r/EdgeGrid.cpp index 24095105382..486a7b1aa8a 100644 --- a/src/libslic3r/EdgeGrid.cpp +++ b/src/libslic3r/EdgeGrid.cpp @@ -1586,12 +1586,17 @@ std::vector> ++ cnt; } } - len /= double(cnt); - bbox.offset(20); - EdgeGrid::Grid grid; - grid.set_bbox(bbox); - grid.create(polygons, len); - return grid.intersecting_edges(); + + std::vector> out; + if (cnt > 0) { + len /= double(cnt); + bbox.offset(20); + EdgeGrid::Grid grid; + grid.set_bbox(bbox); + grid.create(polygons, len); + out = grid.intersecting_edges(); + } + return out; } // Find all pairs of intersectiong edges from the set of polygons, highlight them in an SVG. diff --git a/src/libslic3r/ExPolygon.cpp b/src/libslic3r/ExPolygon.cpp index 4a896604471..daaab47555f 100644 --- a/src/libslic3r/ExPolygon.cpp +++ b/src/libslic3r/ExPolygon.cpp @@ -404,7 +404,7 @@ void ExPolygon::triangulate_pp(Polygons* polygons) const { TPPLPoly p; p.Init(int(ex->contour.points.size())); - //printf(PRINTF_ZU "\n0\n", ex->contour.points.size()); + //printf("%zu\n0\n", ex->contour.points.size()); for (const Point &point : ex->contour.points) { size_t i = &point - &ex->contour.points.front(); p[i].x = point(0); @@ -419,7 +419,7 @@ void ExPolygon::triangulate_pp(Polygons* polygons) const for (Polygons::const_iterator hole = ex->holes.begin(); hole != ex->holes.end(); ++hole) { TPPLPoly p; p.Init(hole->points.size()); - //printf(PRINTF_ZU "\n1\n", hole->points.size()); + //printf("%zu\n1\n", hole->points.size()); for (const Point &point : hole->points) { size_t i = &point - &hole->points.front(); p[i].x = point(0); diff --git a/src/libslic3r/ExtrusionEntity.cpp b/src/libslic3r/ExtrusionEntity.cpp index c482f7edba7..69b3a6455d6 100644 --- a/src/libslic3r/ExtrusionEntity.cpp +++ b/src/libslic3r/ExtrusionEntity.cpp @@ -312,8 +312,8 @@ std::string ExtrusionEntity::role_to_string(ExtrusionRole role) case erOverhangPerimeter : return L("Overhang perimeter"); case erInternalInfill : return L("Internal infill"); case erSolidInfill : return L("Solid infill"); - case erIroning : return L("Ironing"); case erTopSolidInfill : return L("Top solid infill"); + case erIroning : return L("Ironing"); case erBridgeInfill : return L("Bridge infill"); case erGapFill : return L("Gap fill"); case erSkirt : return L("Skirt"); diff --git a/src/libslic3r/Fill/Fill.hpp b/src/libslic3r/Fill/Fill.hpp index 9e354508494..64963495a31 100644 --- a/src/libslic3r/Fill/Fill.hpp +++ b/src/libslic3r/Fill/Fill.hpp @@ -6,7 +6,6 @@ #include #include "../libslic3r.h" -#include "../BoundingBox.hpp" #include "../PrintConfig.hpp" #include "FillBase.hpp" diff --git a/src/libslic3r/Fill/FillBase.hpp b/src/libslic3r/Fill/FillBase.hpp index 083cb86cef8..2e9b6473542 100644 --- a/src/libslic3r/Fill/FillBase.hpp +++ b/src/libslic3r/Fill/FillBase.hpp @@ -11,13 +11,13 @@ #include "../libslic3r.h" #include "../BoundingBox.hpp" -#include "../PrintConfig.hpp" #include "../Utils.hpp" namespace Slic3r { class ExPolygon; class Surface; +enum InfillPattern : int; class InfillFailedException : public std::runtime_error { public: diff --git a/src/libslic3r/Fill/FillRectilinear2.cpp b/src/libslic3r/Fill/FillRectilinear2.cpp index d8324993901..5b862e8cbdb 100644 --- a/src/libslic3r/Fill/FillRectilinear2.cpp +++ b/src/libslic3r/Fill/FillRectilinear2.cpp @@ -901,7 +901,7 @@ static void connect_segment_intersections_by_contours( const SegmentedIntersectionLine *il_prev = i_vline > 0 ? &segs[i_vline - 1] : nullptr; const SegmentedIntersectionLine *il_next = i_vline + 1 < segs.size() ? &segs[i_vline + 1] : nullptr; - for (int i_intersection = 0; i_intersection < il.intersections.size(); ++ i_intersection) { + for (int i_intersection = 0; i_intersection < int(il.intersections.size()); ++ i_intersection) { SegmentIntersection &itsct = il.intersections[i_intersection]; const Polygon &poly = poly_with_offset.contour(itsct.iContour); const bool forward = itsct.is_low(); // == poly_with_offset.is_contour_ccw(intrsctn->iContour); @@ -914,7 +914,7 @@ static void connect_segment_intersections_by_contours( int iprev = -1; int d_prev = std::numeric_limits::max(); if (il_prev) { - for (int i = 0; i < il_prev->intersections.size(); ++ i) { + for (int i = 0; i < int(il_prev->intersections.size()); ++ i) { const SegmentIntersection &itsct2 = il_prev->intersections[i]; if (itsct.iContour == itsct2.iContour && itsct.type == itsct2.type) { // The intersection points lie on the same contour and have the same orientation. @@ -932,7 +932,7 @@ static void connect_segment_intersections_by_contours( int inext = -1; int d_next = std::numeric_limits::max(); if (il_next) { - for (int i = 0; i < il_next->intersections.size(); ++ i) { + for (int i = 0; i < int(il_next->intersections.size()); ++ i) { const SegmentIntersection &itsct2 = il_next->intersections[i]; if (itsct.iContour == itsct2.iContour && itsct.type == itsct2.type) { // The intersection points lie on the same contour and have the same orientation. @@ -950,7 +950,7 @@ static void connect_segment_intersections_by_contours( bool same_prev = false; bool same_next = false; // Does the perimeter intersect the current vertical line above intrsctn? - for (int i = 0; i < il.intersections.size(); ++ i) + for (int i = 0; i < int(il.intersections.size()); ++ i) if (const SegmentIntersection &it2 = il.intersections[i]; i != i_intersection && it2.iContour == itsct.iContour && it2.type != itsct.type) { int d = distance_of_segmens(poly, it2.iSegment, itsct.iSegment, forward); @@ -1040,7 +1040,7 @@ static void connect_segment_intersections_by_contours( } // Make the LinkQuality::Invalid symmetric on vertical connections. - for (int i_intersection = 0; i_intersection < il.intersections.size(); ++ i_intersection) { + for (int i_intersection = 0; i_intersection < int(il.intersections.size()); ++ i_intersection) { SegmentIntersection &it = il.intersections[i_intersection]; if (it.has_left_vertical() && it.prev_on_contour_quality == SegmentIntersection::LinkQuality::Invalid) { SegmentIntersection &it2 = il.intersections[it.left_vertical()]; @@ -1157,9 +1157,9 @@ static void traverse_graph_generate_polylines( { // For each outer only chords, measure their maximum distance to the bow of the outer contour. // Mark an outer only chord as consumed, if the distance is low. - for (int i_vline = 0; i_vline < segs.size(); ++ i_vline) { + for (int i_vline = 0; i_vline < int(segs.size()); ++ i_vline) { SegmentedIntersectionLine &vline = segs[i_vline]; - for (int i_intersection = 0; i_intersection + 1 < vline.intersections.size(); ++ i_intersection) { + for (int i_intersection = 0; i_intersection + 1 < int(vline.intersections.size()); ++ i_intersection) { if (vline.intersections[i_intersection].type == SegmentIntersection::OUTER_LOW && vline.intersections[i_intersection + 1].type == SegmentIntersection::OUTER_HIGH) { bool consumed = false; @@ -1189,14 +1189,14 @@ static void traverse_graph_generate_polylines( if (i_intersection == -1) { // The path has been interrupted. Find a next starting point, closest to the previous extruder position. coordf_t dist2min = std::numeric_limits().max(); - for (int i_vline2 = 0; i_vline2 < segs.size(); ++ i_vline2) { + for (int i_vline2 = 0; i_vline2 < int(segs.size()); ++ i_vline2) { const SegmentedIntersectionLine &vline = segs[i_vline2]; if (! vline.intersections.empty()) { assert(vline.intersections.size() > 1); // Even number of intersections with the loops. assert((vline.intersections.size() & 1) == 0); assert(vline.intersections.front().type == SegmentIntersection::OUTER_LOW); - for (int i = 0; i < vline.intersections.size(); ++ i) { + for (int i = 0; i < int(vline.intersections.size()); ++ i) { const SegmentIntersection& intrsctn = vline.intersections[i]; if (intrsctn.is_outer()) { assert(intrsctn.is_low() || i > 0); @@ -1674,13 +1674,13 @@ static std::vector generate_montonous_regions(std::vector generate_montonous_regions(std::vectorconsumed_vertical_up = true; int num_lines = 1; - while (++ i_vline < segs.size()) { + while (++ i_vline < int(segs.size())) { SegmentedIntersectionLine &vline_left = segs[i_vline - 1]; SegmentedIntersectionLine &vline_right = segs[i_vline]; std::pair right = right_overlap(left, vline_left, vline_right); @@ -1860,7 +1860,7 @@ static void connect_monotonous_regions(std::vector ®ions, c } } } - if (region.right.vline + 1 < segs.size()) { + if (region.right.vline + 1 < int(segs.size())) { auto &vline = segs[region.right.vline]; auto &vline_right = segs[region.right.vline + 1]; auto [rbegin, rend] = right_overlap(vline.intersections[region.right.low], vline.intersections[region.right.high], vline, vline_right); @@ -2100,7 +2100,7 @@ static std::vector chain_monotonous_regions( AntPath &path2 = path_matrix(region, dir, *next, true); if (path1.visibility > next_candidate.probability) next_candidate = { next, &path1, &path1, path1.visibility, false }; - if (path2.visibility > next_candidate.probability) + if (path2.visibility > next_candidate.probability) next_candidate = { next, &path2, &path2, path2.visibility, true }; } } @@ -2111,7 +2111,7 @@ static std::vector chain_monotonous_regions( AntPath &path2 = path_matrix(region, dir, *next, true); if (path1.visibility > next_candidate.probability) next_candidate = { next, &path1, &path1, path1.visibility, false }; - if (path2.visibility > next_candidate.probability) + if (path2.visibility > next_candidate.probability) next_candidate = { next, &path2, &path2, path2.visibility, true }; } } diff --git a/src/libslic3r/Format/3mf.cpp b/src/libslic3r/Format/3mf.cpp index f355d400d90..3612e6898cc 100644 --- a/src/libslic3r/Format/3mf.cpp +++ b/src/libslic3r/Format/3mf.cpp @@ -4,6 +4,7 @@ #include "../GCode.hpp" #include "../Geometry.hpp" #include "../GCode/ThumbnailData.hpp" +#include "../Time.hpp" #include "../I18N.hpp" @@ -85,6 +86,7 @@ const char* OBJECTID_ATTR = "objectid"; const char* TRANSFORM_ATTR = "transform"; const char* PRINTABLE_ATTR = "printable"; const char* INSTANCESCOUNT_ATTR = "instances_count"; +const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports"; const char* KEY_ATTR = "key"; const char* VALUE_ATTR = "value"; @@ -282,6 +284,7 @@ namespace Slic3r { { std::vector vertices; std::vector triangles; + std::vector custom_supports; bool empty() { @@ -292,6 +295,7 @@ namespace Slic3r { { vertices.clear(); triangles.clear(); + custom_supports.clear(); } }; @@ -1197,13 +1201,32 @@ namespace Slic3r { } if (code.first != "code") continue; + pt::ptree tree = code.second; - double print_z = tree.get (".print_z" ); - std::string gcode = tree.get (".gcode" ); - int extruder = tree.get (".extruder" ); - std::string color = tree.get (".color" ); + double print_z = tree.get (".print_z" ); + int extruder = tree.get (".extruder"); + std::string color = tree.get (".color" ); - m_model->custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, gcode, extruder, color}) ; + CustomGCode::Type type; + std::string extra; + if (tree.find("type") == tree.not_found()) + { + // It means that data was saved in old version (2.2.0 and older) of PrusaSlicer + // read old data ... + std::string gcode = tree.get (".gcode"); + // ... and interpret them to the new data + type = gcode == "M600" ? CustomGCode::ColorChange : + gcode == "M601" ? CustomGCode::PausePrint : + gcode == "tool_change" ? CustomGCode::ToolChange : CustomGCode::Custom; + extra = type == CustomGCode::PausePrint ? color : + type == CustomGCode::Custom ? gcode : ""; + } + else + { + type = static_cast(tree.get(".type")); + extra = tree.get(".extra"); + } + m_model->custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra}) ; } } } @@ -1519,6 +1542,8 @@ namespace Slic3r { m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V1_ATTR)); m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V2_ATTR)); m_curr_object.geometry.triangles.push_back((unsigned int)get_attribute_value_int(attributes, num_attributes, V3_ATTR)); + + m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR)); return true; } @@ -1852,6 +1877,13 @@ namespace Slic3r { volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object); volume->calculate_convex_hull(); + // recreate custom supports from previously loaded attribute + assert(geometry.custom_supports.size() == triangles_count); + for (unsigned i=0; im_supported_facets.set_triangle_from_string(i, geometry.custom_supports[i]); + } + // apply the remaining volume's metadata for (const Metadata& metadata : volume_data.metadata) { @@ -1972,7 +2004,7 @@ namespace Slic3r { bool _add_content_types_file_to_archive(mz_zip_archive& archive); bool _add_thumbnail_file_to_archive(mz_zip_archive& archive, const ThumbnailData& thumbnail_data); bool _add_relationships_file_to_archive(mz_zip_archive& archive); - bool _add_model_file_to_archive(mz_zip_archive& archive, const Model& model, IdToObjectDataMap &objects_data); + bool _add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, IdToObjectDataMap& objects_data); bool _add_object_to_model_stream(std::stringstream& stream, unsigned int& object_id, ModelObject& object, BuildItemsList& build_items, VolumeToOffsetsMap& volumes_offsets); bool _add_mesh_to_object_stream(std::stringstream& stream, ModelObject& object, VolumeToOffsetsMap& volumes_offsets); bool _add_build_to_model_stream(std::stringstream& stream, const BuildItemsList& build_items); @@ -1982,7 +2014,7 @@ namespace Slic3r { bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model); bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config); bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data); - bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model); + bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config); }; bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data) @@ -2035,7 +2067,7 @@ namespace Slic3r { // Adds model file ("3D/3dmodel.model"). // This is the one and only file that contains all the geometry (vertices and triangles) of all ModelVolumes. IdToObjectDataMap objects_data; - if (!_add_model_file_to_archive(archive, model, objects_data)) + if (!_add_model_file_to_archive(filename, archive, model, objects_data)) { close_zip_writer(&archive); boost::filesystem::remove(filename); @@ -2082,7 +2114,7 @@ namespace Slic3r { // Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml"). // All custom gcode per height of whole Model are stored here - if (!_add_custom_gcode_per_print_z_file_to_archive(archive, model)) + if (!_add_custom_gcode_per_print_z_file_to_archive(archive, model, config)) { close_zip_writer(&archive); boost::filesystem::remove(filename); @@ -2184,7 +2216,7 @@ namespace Slic3r { return true; } - bool _3MF_Exporter::_add_model_file_to_archive(mz_zip_archive& archive, const Model& model, IdToObjectDataMap &objects_data) + bool _3MF_Exporter::_add_model_file_to_archive(const std::string& filename, mz_zip_archive& archive, const Model& model, IdToObjectDataMap& objects_data) { std::stringstream stream; // https://en.cppreference.com/w/cpp/types/numeric_limits/max_digits10 @@ -2195,6 +2227,19 @@ namespace Slic3r { stream << "\n"; stream << "<" << MODEL_TAG << " unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"http://schemas.microsoft.com/3dmanufacturing/core/2015/02\" xmlns:slic3rpe=\"http://schemas.slic3r.org/3mf/2017/06\">\n"; stream << " <" << METADATA_TAG << " name=\"" << SLIC3RPE_3MF_VERSION << "\">" << VERSION_3MF << "\n"; + std::string name = boost::filesystem::path(filename).stem().string(); + stream << " <" << METADATA_TAG << " name=\"Title\">" << name << "\n"; + stream << " <" << METADATA_TAG << " name=\"Designer\">" << "\n"; + stream << " <" << METADATA_TAG << " name=\"Description\">" << name << "\n"; + stream << " <" << METADATA_TAG << " name=\"Copyright\">" << "\n"; + stream << " <" << METADATA_TAG << " name=\"LicenseTerms\">" << "\n"; + stream << " <" << METADATA_TAG << " name=\"Rating\">" << "\n"; + std::string date = Slic3r::Utils::utc_timestamp(Slic3r::Utils::get_current_time_utc()); + // keep only the date part of the string + date = date.substr(0, 10); + stream << " <" << METADATA_TAG << " name=\"CreationDate\">" << date << "\n"; + stream << " <" << METADATA_TAG << " name=\"ModificationDate\">" << date << "\n"; + stream << " <" << METADATA_TAG << " name=\"Application\">" << SLIC3R_APP_KEY << "-" << SLIC3R_VERSION << "\n"; stream << " <" << RESOURCES_TAG << ">\n"; // Instance transformations, indexed by the 3MF object ID (which is a linear serialization of all instances of all ModelObjects). @@ -2350,6 +2395,11 @@ namespace Slic3r { { stream << "v" << j + 1 << "=\"" << its.indices[i][j] + volume_it->second.first_vertex_id << "\" "; } + + std::string custom_supports_data_string = volume->m_supported_facets.get_triangle_as_string(i); + if (! custom_supports_data_string.empty()) + stream << CUSTOM_SUPPORTS_ATTR << "=\"" << custom_supports_data_string << "\" "; + stream << "/>\n"; } } @@ -2702,7 +2752,7 @@ namespace Slic3r { return true; } -bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archive& archive, Model& model) +bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config) { std::string out = ""; @@ -2714,11 +2764,20 @@ bool _3MF_Exporter::_add_custom_gcode_per_print_z_file_to_archive( mz_zip_archiv for (const CustomGCode::Item& code : model.custom_gcode_per_print_z.gcodes) { pt::ptree& code_tree = main_tree.add("code", ""); - // store minX and maxZ + + // store data of custom_gcode_per_print_z code_tree.put(".print_z" , code.print_z ); - code_tree.put(".gcode" , code.gcode ); + code_tree.put(".type" , static_cast(code.type)); code_tree.put(".extruder" , code.extruder ); code_tree.put(".color" , code.color ); + code_tree.put(".extra" , code.extra ); + + // add gcode field data for the old version of the PrusaSlicer + std::string gcode = code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") : + code.type == CustomGCode::PausePrint ? config->opt_string("pause_print_gcode") : + code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") : + code.type == CustomGCode::ToolChange ? "tool_change" : code.extra; + code_tree.put(".gcode" , gcode ); } pt::ptree& mode_tree = main_tree.add("mode", ""); diff --git a/src/libslic3r/Format/AMF.cpp b/src/libslic3r/Format/AMF.cpp index 2dc4ea0ba5b..af7b9b1b60a 100644 --- a/src/libslic3r/Format/AMF.cpp +++ b/src/libslic3r/Format/AMF.cpp @@ -240,7 +240,7 @@ struct AMFParserContext // Current instance allocated for an amf/constellation/instance subtree. Instance *m_instance; // Generic string buffer for vertices, face indices, metadata etc. - std::string m_value[4]; + std::string m_value[5]; // Pointer to config to update if config data are stored inside the amf file DynamicPrintConfig *m_config; @@ -314,9 +314,26 @@ void AMFParserContext::startElement(const char *name, const char **atts) if (strcmp(name, "code") == 0) { node_type_new = NODE_TYPE_GCODE_PER_HEIGHT; m_value[0] = get_attribute(atts, "print_z"); - m_value[1] = get_attribute(atts, "gcode"); - m_value[2] = get_attribute(atts, "extruder"); - m_value[3] = get_attribute(atts, "color"); + m_value[1] = get_attribute(atts, "extruder"); + m_value[2] = get_attribute(atts, "color"); + if (get_attribute(atts, "type")) + { + m_value[3] = get_attribute(atts, "type"); + m_value[4] = get_attribute(atts, "extra"); + } + else + { + // It means that data was saved in old version (2.2.0 and older) of PrusaSlicer + // read old data ... + std::string gcode = get_attribute(atts, "gcode"); + // ... and interpret them to the new data + CustomGCode::Type type= gcode == "M600" ? CustomGCode::ColorChange : + gcode == "M601" ? CustomGCode::PausePrint : + gcode == "tool_change" ? CustomGCode::ToolChange : CustomGCode::Custom; + m_value[3] = std::to_string(static_cast(type)); + m_value[4] = type == CustomGCode::PausePrint ? m_value[2] : + type == CustomGCode::Custom ? gcode : ""; + } } else if (strcmp(name, "mode") == 0) { node_type_new = NODE_TYPE_CUSTOM_GCODE_MODE; @@ -640,12 +657,13 @@ void AMFParserContext::endElement(const char * /* name */) break; case NODE_TYPE_GCODE_PER_HEIGHT: { - double print_z = double(atof(m_value[0].c_str())); - const std::string& gcode = m_value[1]; - int extruder = atoi(m_value[2].c_str()); - const std::string& color = m_value[3]; + double print_z = double(atof(m_value[0].c_str())); + int extruder = atoi(m_value[1].c_str()); + const std::string& color= m_value[2]; + CustomGCode::Type type = static_cast(atoi(m_value[3].c_str())); + const std::string& extra= m_value[4]; - m_model.custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, gcode, extruder, color}); + m_model.custom_gcode_per_print_z.gcodes.push_back(CustomGCode::Item{print_z, type, extruder, color, extra}); for (std::string& val: m_value) val.clear(); @@ -1200,7 +1218,7 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, for (ModelInstance *instance : object->instances) { char buf[512]; sprintf(buf, - " \n" + " \n" " %lf\n" " %lf\n" " %lf\n" @@ -1253,9 +1271,17 @@ bool store_amf(const char* path, Model* model, const DynamicPrintConfig* config, pt::ptree& code_tree = main_tree.add("code", ""); // store custom_gcode_per_print_z gcodes information code_tree.put(".print_z" , code.print_z ); - code_tree.put(".gcode" , code.gcode ); + code_tree.put(".type" , static_cast(code.type)); code_tree.put(".extruder" , code.extruder ); code_tree.put(".color" , code.color ); + code_tree.put(".extra" , code.extra ); + + // add gcode field data for the old version of the PrusaSlicer + std::string gcode = code.type == CustomGCode::ColorChange ? config->opt_string("color_change_gcode") : + code.type == CustomGCode::PausePrint ? config->opt_string("pause_print_gcode") : + code.type == CustomGCode::Template ? config->opt_string("template_custom_gcode") : + code.type == CustomGCode::ToolChange ? "tool_change" : code.extra; + code_tree.put(".gcode" , gcode ); } pt::ptree& mode_tree = main_tree.add("mode", ""); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index b5890f57843..35dc5a53bd6 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -7,6 +7,7 @@ #include "GCode/PrintExtents.hpp" #include "GCode/WipeTower.hpp" #include "ShortestPath.hpp" +#include "Print.hpp" #include "Utils.hpp" #include "libslic3r.h" @@ -616,6 +617,15 @@ std::vector GCode::collect_layers_to_print(const PrintObjec layers_to_print.emplace_back(layer_to_print); + bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) + || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()); + + // Check that there are extrusions on the very first layer. + if (layers_to_print.size() == 1u) { + if (! has_extrusions) + throw std::runtime_error(_(L("There is an object with no extrusions on the first layer."))); + } + // In case there are extrusions on this layer, check there is a layer to lay it on. if ((layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) // Allow empty support layers, as the support generator may produce no extrusions for non-empty support regions. @@ -629,18 +639,18 @@ std::vector GCode::collect_layers_to_print(const PrintObjec // Negative support_contact_z is not taken into account, it can result in false positives in cases // where previous layer has object extrusions too (https://github.com/prusa3d/PrusaSlicer/issues/2752) - // Only check this layer in case it has some extrusions. - bool has_extrusions = (layer_to_print.object_layer && layer_to_print.object_layer->has_extrusions()) - || (layer_to_print.support_layer && layer_to_print.support_layer->has_extrusions()); - - if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) - throw std::runtime_error(_(L("Empty layers detected, the output would not be printable.")) + "\n\n" + + if (has_extrusions && layer_to_print.print_z() > maximal_print_z + 2. * EPSILON) { + const_cast(object.print())->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL, + _(L("Empty layers detected, the output would not be printable.")) + "\n\n" + _(L("Object name")) + ": " + object.model_object()->name + "\n" + _(L("Print z")) + ": " + std::to_string(layers_to_print.back().print_z()) + "\n\n" + _(L("This is " "usually caused by negligibly small extrusions or by a faulty model. Try to repair " "the model or change its orientation on the bed."))); + } + // Remember last layer with extrusions. - last_extrusion_layer = &layers_to_print.back(); + if (has_extrusions) + last_extrusion_layer = &layers_to_print.back(); } } @@ -1297,7 +1307,28 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu m_placeholder_parser.set("has_wipe_tower", has_wipe_tower); m_placeholder_parser.set("has_single_extruder_multi_material_priming", has_wipe_tower && print.config().single_extruder_multi_material_priming); m_placeholder_parser.set("total_toolchanges", std::max(0, print.wipe_tower_data().number_of_toolchanges)); // Check for negative toolchanges (single extruder mode) and set to 0 (no tool change). - + { + BoundingBoxf bbox(print.config().bed_shape.values); + m_placeholder_parser.set("print_bed_min", new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() })); + m_placeholder_parser.set("print_bed_max", new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() })); + m_placeholder_parser.set("print_bed_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() })); + } + { + // Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line. + // It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower. + // It does NOT encompass user extrusions generated by custom G-code, + // therefore it does NOT encompass the initial purge line. + // It does NOT encompass MMU/MMU2 starting (wipe) areas. + auto pts = std::make_unique(); + pts->values.reserve(print.first_layer_convex_hull().size()); + for (const Point &pt : print.first_layer_convex_hull().points) + pts->values.emplace_back(unscale(pt)); + BoundingBoxf bbox(pts->values); + m_placeholder_parser.set("first_layer_print_convex_hull", pts.release()); + m_placeholder_parser.set("first_layer_print_min", new ConfigOptionFloats({ bbox.min.x(), bbox.min.y() })); + m_placeholder_parser.set("first_layer_print_max", new ConfigOptionFloats({ bbox.max.x(), bbox.max.y() })); + m_placeholder_parser.set("first_layer_print_size", new ConfigOptionFloats({ bbox.size().x(), bbox.size().y() })); + } std::string start_gcode = this->placeholder_parser_process("start_gcode", print.config().start_gcode.value, initial_extruder_id); // Set bed temperature if the start G-code does not contain any bed temp control G-codes. this->_print_first_layer_bed_temperature(file, print, start_gcode, initial_extruder_id, true); @@ -1756,17 +1787,18 @@ namespace ProcessLayer const CustomGCode::Item *custom_gcode, // ID of the first extruder printing this layer. unsigned int first_extruder_id, - bool single_extruder_printer) + const PrintConfig &config) { std::string gcode; + bool single_extruder_printer = config.nozzle_diameter.size() == 1; if (custom_gcode != nullptr) { // Extruder switches are processed by LayerTools, they should be filtered out. - assert(custom_gcode->gcode != ToolChangeCode); + assert(custom_gcode->type != CustomGCode::ToolChange); - const std::string &custom_code = custom_gcode->gcode; - bool color_change = custom_code == ColorChangeCode; - bool tool_change = custom_code == ToolChangeCode; + CustomGCode::Type gcode_type = custom_gcode->type; + bool color_change = gcode_type == CustomGCode::ColorChange; + bool tool_change = gcode_type == CustomGCode::ToolChange; // Tool Change is applied as Color Change for a single extruder printer only. assert(! tool_change || single_extruder_printer); @@ -1774,8 +1806,8 @@ namespace ProcessLayer int m600_extruder_before_layer = -1; if (color_change && custom_gcode->extruder > 0) m600_extruder_before_layer = custom_gcode->extruder - 1; - else if (custom_code == PausePrintCode) - pause_print_msg = custom_gcode->color; + else if (gcode_type == CustomGCode::PausePrint) + pause_print_msg = custom_gcode->extra; // we should add or not colorprint_change in respect to nozzle_diameter count instead of really used extruders count if (color_change || tool_change) @@ -1791,17 +1823,18 @@ namespace ProcessLayer // && !MMU1 ) { //! FIXME_in_fw show message during print pause - gcode += "M601\n"; // pause print + gcode += config.pause_print_gcode;// pause print + gcode += "\n"; gcode += "M117 Change filament for Extruder " + std::to_string(m600_extruder_before_layer) + "\n"; } else { - gcode += ColorChangeCode; + gcode += config.color_change_gcode;//ColorChangeCode; gcode += "\n"; } } else { - if (custom_code == PausePrintCode) // Pause print + if (gcode_type == CustomGCode::PausePrint) // Pause print { // add tag for analyzer gcode += "; " + GCodeAnalyzer::Pause_Print_Tag + "\n"; @@ -1810,15 +1843,21 @@ namespace ProcessLayer gcode += "M117 " + pause_print_msg + "\n"; // add tag for time estimator gcode += "; " + GCodeTimeEstimator::Pause_Print_Tag + "\n"; + gcode += config.pause_print_gcode; } - else // custom Gcode + else { // add tag for analyzer gcode += "; " + GCodeAnalyzer::Custom_Code_Tag + "\n"; // add tag for time estimator //gcode += "; " + GCodeTimeEstimator::Custom_Code_Tag + "\n"; + if (gcode_type == CustomGCode::Template) // Template Cistom Gcode + gcode += config.template_custom_gcode; + else // custom Gcode + gcode += custom_gcode->extra; + } - gcode += custom_code + "\n"; + gcode += "\n"; } } @@ -1909,7 +1948,6 @@ void GCode::process_layer( const size_t single_object_instance_idx) { assert(! layers.empty()); -// assert(! layer_tools.extruders.empty()); // Either printing all copies of all objects, or just a single copy of a single object. assert(single_object_instance_idx == size_t(-1) || layers.size() == 1); @@ -1995,7 +2033,7 @@ void GCode::process_layer( if (single_object_instance_idx == size_t(-1)) { // Normal (non-sequential) print. - gcode += ProcessLayer::emit_custom_gcode_per_print_z(layer_tools.custom_gcode, first_extruder_id, print.config().nozzle_diameter.size() == 1); + gcode += ProcessLayer::emit_custom_gcode_per_print_z(layer_tools.custom_gcode, first_extruder_id, print.config()); } // Extrude skirt at the print_z of the raft layers and normal object layers // not at the print_z of the interlaced support material layers. diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 2daf0fe16e3..8d473378320 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -8,7 +8,6 @@ #include "MotionPlanner.hpp" #include "Point.hpp" #include "PlaceholderParser.hpp" -#include "Print.hpp" #include "PrintConfig.hpp" #include "GCode/CoolingBuffer.hpp" #include "GCode/SpiralVase.hpp" @@ -32,6 +31,10 @@ namespace Slic3r { class GCode; class GCodePreviewData; +namespace { struct Item; } +struct PrintInstance; +using PrintObjectPtrs = std::vector; + class AvoidCrossingPerimeters { public: diff --git a/src/libslic3r/GCode/PrintExtents.cpp b/src/libslic3r/GCode/PrintExtents.cpp index 7a8271e3009..4a6624531b2 100644 --- a/src/libslic3r/GCode/PrintExtents.cpp +++ b/src/libslic3r/GCode/PrintExtents.cpp @@ -6,6 +6,7 @@ #include "../BoundingBox.hpp" #include "../ExtrusionEntity.hpp" #include "../ExtrusionEntityCollection.hpp" +#include "../Layer.hpp" #include "../Print.hpp" #include "PrintExtents.hpp" diff --git a/src/libslic3r/GCode/ThumbnailData.cpp b/src/libslic3r/GCode/ThumbnailData.cpp index 09d564e95df..a5941bff169 100644 --- a/src/libslic3r/GCode/ThumbnailData.cpp +++ b/src/libslic3r/GCode/ThumbnailData.cpp @@ -1,4 +1,3 @@ -#include "libslic3r/libslic3r.h" #include "ThumbnailData.hpp" namespace Slic3r { diff --git a/src/libslic3r/GCode/ThumbnailData.hpp b/src/libslic3r/GCode/ThumbnailData.hpp index 473ef274b91..2a302ed8553 100644 --- a/src/libslic3r/GCode/ThumbnailData.hpp +++ b/src/libslic3r/GCode/ThumbnailData.hpp @@ -24,4 +24,4 @@ typedef std::function -#include "../GCodeWriter.hpp" namespace Slic3r { @@ -355,7 +355,7 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ max_layer_height = std::min(max_layer_height, mlh); } // The Prusa3D Fast (0.35mm layer height) print profile sets a higher layer height than what is normally allowed - // by the nozzle. This is a hack and it works by increasing extrusion width. + // by the nozzle. This is a hack and it works by increasing extrusion width. See GH #3919. max_layer_height = std::max(max_layer_height, max_object_layer_height); for (size_t i = 0; i + 1 < m_layer_tools.size(); ++ i) { @@ -400,47 +400,21 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ // and maybe other problems. We will therefore go through layer_tools and detect and fix this. // So, if there is a non-object layer starting with different extruder than the last one ended with (or containing more than one extruder), // we'll mark it with has_wipe tower. - assert(! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower); - if (! m_layer_tools.empty() && m_layer_tools.front().has_wipe_tower) { - for (size_t i = 0; i + 1 < m_layer_tools.size();) { - const LayerTools < = m_layer_tools[i]; - assert(lt.has_wipe_tower); - assert(! lt.extruders.empty()); - // Find the next layer with wipe tower or mark a layer as such. - size_t j = i + 1; - for (; j < m_layer_tools.size() && ! m_layer_tools[j].has_wipe_tower; ++ j) { - LayerTools <_next = m_layer_tools[j]; - if (lt_next.extruders.empty()) { - //FIXME Vojtech: Lukasi, proc? - j = m_layer_tools.size(); - break; - } - if (lt_next.extruders.front() != lt.extruders.back() || lt_next.extruders.size() > 1) { - // Support only layer, soluble layers? Otherwise the layer should have been already marked as having wipe tower. - assert(lt_next.has_support && ! lt_next.has_object); - lt_next.has_wipe_tower = true; - break; - } - } - if (j == m_layer_tools.size()) - // No wipe tower above layer i, therefore no need to add any wipe tower layer above i. - break; - // We should also check that the next wipe tower layer is no further than max_layer_height. - // This algorith may in theory create very thin wipe layer j if layer closely below j is marked as wipe tower. - // This may happen if printing with non-soluble break away supports. - // On the other side it should not hurt as there will be no wipe, just perimeter and sparse infill printed - // at that particular wipe tower layer without extruder change. - double last_wipe_tower_print_z = lt.print_z; - assert(m_layer_tools[j].has_wipe_tower); - for (size_t k = i + 1; k < j; ++k) { - assert(! m_layer_tools[k].has_wipe_tower); - if (m_layer_tools[k + 1].print_z - last_wipe_tower_print_z > max_layer_height + EPSILON) { - m_layer_tools[k].has_wipe_tower = true; - last_wipe_tower_print_z = m_layer_tools[k].print_z; - } + for (unsigned int i=0; i+1 1)) + lt_next.has_wipe_tower = true; + // We should also check that the next wipe tower layer is no further than max_layer_height: + unsigned int j = i+1; + double last_wipe_tower_print_z = lt_next.print_z; + while (++j < m_layer_tools.size()-1 && !m_layer_tools[j].has_wipe_tower) + if (m_layer_tools[j+1].print_z - last_wipe_tower_print_z > max_layer_height + EPSILON) { + m_layer_tools[j].has_wipe_tower = true; + last_wipe_tower_print_z = m_layer_tools[j].print_z; } - i = j; - } } // Calculate the wipe_tower_layer_height values. @@ -518,7 +492,7 @@ void ToolOrdering::assign_custom_gcodes(const Print &print) for (unsigned int i : lt.extruders) extruder_printing_above[i] = true; // Skip all custom G-codes above this layer and skip all extruder switches. - for (; custom_gcode_it != custom_gcode_per_print_z.gcodes.rend() && (custom_gcode_it->print_z > lt.print_z + EPSILON || custom_gcode_it->gcode == ToolChangeCode); ++ custom_gcode_it); + for (; custom_gcode_it != custom_gcode_per_print_z.gcodes.rend() && (custom_gcode_it->print_z > lt.print_z + EPSILON || custom_gcode_it->type == CustomGCode::ToolChange); ++ custom_gcode_it); if (custom_gcode_it == custom_gcode_per_print_z.gcodes.rend()) // Custom G-codes were processed. break; @@ -530,8 +504,8 @@ void ToolOrdering::assign_custom_gcodes(const Print &print) print_z_below = it_lt_below->print_z; if (custom_gcode.print_z > print_z_below + 0.5 * EPSILON) { // The custom G-code applies to the current layer. - bool color_change = custom_gcode.gcode == ColorChangeCode; - bool tool_change = custom_gcode.gcode == ToolChangeCode; + bool color_change = custom_gcode.type == CustomGCode::ColorChange; + bool tool_change = custom_gcode.type == CustomGCode::ToolChange; bool pause_or_custom_gcode = ! color_change && ! tool_change; bool apply_color_change = ! ignore_tool_and_color_changes && // If it is color change, it will actually be useful as the exturder above will print. diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 5fe27516dc7..e2e07533faf 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -14,6 +14,8 @@ namespace Slic3r { class Print; class PrintObject; class LayerTools; +namespace CustomGCode { struct Item; } +class PrintRegion; diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index d31adbd8fcc..c0f778687c3 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -102,7 +102,9 @@ class WipeTowerWriter } WipeTowerWriter& disable_linear_advance() { - m_gcode += (m_gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n")); + m_gcode += (m_gcode_flavor == gcfRepRap + ? (std::string("M572 D") + std::to_string(m_current_tool) + " S0\n") + : std::string("M900 K0\n")); return *this; } diff --git a/src/libslic3r/GCodeSender.cpp b/src/libslic3r/GCodeSender.cpp index 0988091ce38..9567e07d284 100644 --- a/src/libslic3r/GCodeSender.cpp +++ b/src/libslic3r/GCodeSender.cpp @@ -393,7 +393,7 @@ GCodeSender::on_read(const boost::system::error_code& error, } this->send(); } else { - printf("Cannot resend " PRINTF_ZU " (oldest we have is " PRINTF_ZU ")\n", toresend, this->sent - this->last_sent.size()); + printf("Cannot resend %zu (oldest we have is %zu)\n", toresend, this->sent - this->last_sent.size()); } } else if (boost::starts_with(line, "wait")) { // ignore diff --git a/src/libslic3r/GCodeTimeEstimator.cpp b/src/libslic3r/GCodeTimeEstimator.cpp index 6d7f5f65992..9e8137ef0ed 100644 --- a/src/libslic3r/GCodeTimeEstimator.cpp +++ b/src/libslic3r/GCodeTimeEstimator.cpp @@ -188,7 +188,7 @@ namespace Slic3r { _calculate_time(0); if (m_needs_custom_gcode_times && (m_custom_gcode_time_cache != 0.0f)) - m_custom_gcode_times.push_back({ cgtColorChange, m_custom_gcode_time_cache }); + m_custom_gcode_times.push_back({CustomGCode::ColorChange, m_custom_gcode_time_cache }); #if ENABLE_MOVE_STATS _log_moves_stats(); @@ -282,7 +282,7 @@ namespace Slic3r { }; GCodeReader parser; - unsigned int g1_lines_count = 0; + int g1_lines_count = 0; int normal_g1_line_id = 0; float normal_last_recorded_time = 0.0f; int silent_g1_line_id = 0; @@ -678,7 +678,7 @@ namespace Slic3r { return _get_time_minutes(get_time()); } - std::vector> GCodeTimeEstimator::get_custom_gcode_times() const + std::vector> GCodeTimeEstimator::get_custom_gcode_times() const { return m_custom_gcode_times; } @@ -722,9 +722,9 @@ namespace Slic3r { return ret; } - std::vector> GCodeTimeEstimator::get_custom_gcode_times_dhm(bool include_remaining) const + std::vector> GCodeTimeEstimator::get_custom_gcode_times_dhm(bool include_remaining) const { - std::vector> ret; + std::vector> ret; float total_time = 0.0f; for (auto t : m_custom_gcode_times) @@ -1470,7 +1470,7 @@ namespace Slic3r { size_t pos = comment.find(Color_Change_Tag); if (pos != comment.npos) { - _process_custom_gcode_tag(cgtColorChange); + _process_custom_gcode_tag(CustomGCode::ColorChange); return true; } @@ -1478,14 +1478,14 @@ namespace Slic3r { pos = comment.find(Pause_Print_Tag); if (pos != comment.npos) { - _process_custom_gcode_tag(cgtPausePrint); + _process_custom_gcode_tag(CustomGCode::PausePrint); return true; } return false; } - void GCodeTimeEstimator::_process_custom_gcode_tag(CustomGcodeType code) + void GCodeTimeEstimator::_process_custom_gcode_tag(CustomGCode::Type code) { PROFILE_FUNC(); m_needs_custom_gcode_times = true; diff --git a/src/libslic3r/GCodeTimeEstimator.hpp b/src/libslic3r/GCodeTimeEstimator.hpp index 836d1ceb4a8..63e11c4faa4 100644 --- a/src/libslic3r/GCodeTimeEstimator.hpp +++ b/src/libslic3r/GCodeTimeEstimator.hpp @@ -234,7 +234,7 @@ namespace Slic3r { // data to calculate custom code times bool m_needs_custom_gcode_times; - std::vector> m_custom_gcode_times; + std::vector> m_custom_gcode_times; float m_custom_gcode_time_cache; #if ENABLE_MOVE_STATS @@ -358,7 +358,7 @@ namespace Slic3r { std::string get_time_minutes() const; // Returns the estimated time, in seconds, for each custom gcode - std::vector> get_custom_gcode_times() const; + std::vector> get_custom_gcode_times() const; // Returns the estimated time, in format DDd HHh MMm SSs, for each color // If include_remaining==true the strings will be formatted as: "time for color (remaining time at color start)" @@ -370,7 +370,7 @@ namespace Slic3r { // Returns the estimated time, in format DDd HHh MMm, for each custom_gcode // If include_remaining==true the strings will be formatted as: "time for custom_gcode (remaining time at color start)" - std::vector> get_custom_gcode_times_dhm(bool include_remaining) const; + std::vector> get_custom_gcode_times_dhm(bool include_remaining) const; // Return an estimate of the memory consumed by the time estimator. size_t memory_used() const; @@ -453,7 +453,7 @@ namespace Slic3r { bool _process_tags(const GCodeReader::GCodeLine& line); // Processes ColorChangeTag and PausePrintTag - void _process_custom_gcode_tag(CustomGcodeType code); + void _process_custom_gcode_tag(CustomGCode::Type code); // Simulates firmware st_synchronize() call void _simulate_st_synchronize(float additional_time); diff --git a/src/libslic3r/Geometry.cpp b/src/libslic3r/Geometry.cpp index b9e4d6e78ca..00a4ad47c3f 100644 --- a/src/libslic3r/Geometry.cpp +++ b/src/libslic3r/Geometry.cpp @@ -471,7 +471,7 @@ Pointfs arrange(size_t num_parts, const Vec2d &part_size, coordf_t gap, const Bo size_t cellw = size_t(floor((bed_bbox.size()(0) + gap) / cell_size(0))); size_t cellh = size_t(floor((bed_bbox.size()(1) + gap) / cell_size(1))); if (num_parts > cellw * cellh) - throw std::invalid_argument(PRINTF_ZU " parts won't fit in your print area!\n", num_parts); + throw std::invalid_argument("%zu parts won't fit in your print area!\n", num_parts); // Get a bounding box of cellw x cellh cells, centered at the center of the bed. Vec2d cells_size(cellw * cell_size(0) - gap, cellh * cell_size(1) - gap); diff --git a/src/libslic3r/Geometry.hpp b/src/libslic3r/Geometry.hpp index 1fa15ba6350..75f3708d253 100644 --- a/src/libslic3r/Geometry.hpp +++ b/src/libslic3r/Geometry.hpp @@ -10,6 +10,7 @@ // Serialization through the Cereal library #include +#define BOOST_VORONOI_USE_GMP 1 #include "boost/polygon/voronoi.hpp" namespace ClipperLib { @@ -114,32 +115,94 @@ inline bool segment_segment_intersection(const Vec2d &p1, const Vec2d &v1, const return true; } - -inline int segments_could_intersect( +inline bool segments_intersect( const Slic3r::Point &ip1, const Slic3r::Point &ip2, const Slic3r::Point &jp1, const Slic3r::Point &jp2) +{ + assert(ip1 != ip2); + assert(jp1 != jp2); + + auto segments_could_intersect = []( + const Slic3r::Point &ip1, const Slic3r::Point &ip2, + const Slic3r::Point &jp1, const Slic3r::Point &jp2) -> std::pair + { + Vec2i64 iv = (ip2 - ip1).cast(); + Vec2i64 vij1 = (jp1 - ip1).cast(); + Vec2i64 vij2 = (jp2 - ip1).cast(); + int64_t tij1 = cross2(iv, vij1); + int64_t tij2 = cross2(iv, vij2); + return std::make_pair( + // signum + (tij1 > 0) ? 1 : ((tij1 < 0) ? -1 : 0), + (tij2 > 0) ? 1 : ((tij2 < 0) ? -1 : 0)); + }; + + std::pair sign1 = segments_could_intersect(ip1, ip2, jp1, jp2); + std::pair sign2 = segments_could_intersect(jp1, jp2, ip1, ip2); + int test1 = sign1.first * sign1.second; + int test2 = sign2.first * sign2.second; + if (test1 <= 0 && test2 <= 0) { + // The segments possibly intersect. They may also be collinear, but not intersect. + if (test1 != 0 || test2 != 0) + // Certainly not collinear, then the segments intersect. + return true; + // If the first segment is collinear with the other, the other is collinear with the first segment. + assert((sign1.first == 0 && sign1.second == 0) == (sign2.first == 0 && sign2.second == 0)); + if (sign1.first == 0 && sign1.second == 0) { + // The segments are certainly collinear. Now verify whether they overlap. + Slic3r::Point vi = ip2 - ip1; + // Project both on the longer coordinate of vi. + int axis = std::abs(vi.x()) > std::abs(vi.y()) ? 0 : 1; + coord_t i = ip1(axis); + coord_t j = ip2(axis); + coord_t k = jp1(axis); + coord_t l = jp2(axis); + if (i > j) + std::swap(i, j); + if (k > l) + std::swap(k, l); + return (k >= i && k <= j) || (i >= k && i <= l); + } + } + return false; +} + +template inline T foot_pt(const T &line_pt, const T &line_dir, const T &pt) { - Vec2i64 iv = (ip2 - ip1).cast(); - Vec2i64 vij1 = (jp1 - ip1).cast(); - Vec2i64 vij2 = (jp2 - ip1).cast(); - int64_t tij1 = cross2(iv, vij1); - int64_t tij2 = cross2(iv, vij2); - int sij1 = (tij1 > 0) ? 1 : ((tij1 < 0) ? -1 : 0); // signum - int sij2 = (tij2 > 0) ? 1 : ((tij2 < 0) ? -1 : 0); - return sij1 * sij2; + T v = pt - line_pt; + auto l2 = line_dir.squaredNorm(); + auto t = (l2 == 0) ? 0 : v.dot(line_dir) / l2; + return line_pt + line_dir * t; } -inline bool segments_intersect( - const Slic3r::Point &ip1, const Slic3r::Point &ip2, - const Slic3r::Point &jp1, const Slic3r::Point &jp2) +inline Vec2d foot_pt(const Line &iline, const Point &ipt) +{ + return foot_pt(iline.a.cast(), (iline.b - iline.a).cast(), ipt.cast()); +} + +template inline auto ray_point_distance_squared(const T &ray_pt, const T &ray_dir, const T &pt) { - return segments_could_intersect(ip1, ip2, jp1, jp2) <= 0 && - segments_could_intersect(jp1, jp2, ip1, ip2) <= 0; + return (foot_pt(ray_pt, ray_dir, pt) - pt).squaredNorm(); +} + +template inline auto ray_point_distance(const T &ray_pt, const T &ray_dir, const T &pt) +{ + return (foot_pt(ray_pt, ray_dir, pt) - pt).norm(); +} + +inline double ray_point_distance_squared(const Line &iline, const Point &ipt) +{ + return (foot_pt(iline, ipt) - ipt.cast()).squaredNorm(); +} + +inline double ray_point_distance(const Line &iline, const Point &ipt) +{ + return (foot_pt(iline, ipt) - ipt.cast()).norm(); } // Based on Liang-Barsky function by Daniel White @ http://www.skytopia.com/project/articles/compsci/clipping.html template -bool liang_barsky_line_clipping( +inline bool liang_barsky_line_clipping( // Start and end points of the source line, result will be stored there as well. Eigen::Matrix &x0, Eigen::Matrix &x1, @@ -251,8 +314,16 @@ bool arrange( // output Pointfs &positions); +class VoronoiDiagram : public boost::polygon::voronoi_diagram { +public: + typedef double coord_type; + typedef boost::polygon::point_data point_type; + typedef boost::polygon::segment_data segment_type; + typedef boost::polygon::rectangle_data rect_type; +}; + class MedialAxis { - public: +public: Lines lines; const ExPolygon* expolygon; double max_width; @@ -262,14 +333,8 @@ class MedialAxis { void build(ThickPolylines* polylines); void build(Polylines* polylines); - private: - class VD : public boost::polygon::voronoi_diagram { - public: - typedef double coord_type; - typedef boost::polygon::point_data point_type; - typedef boost::polygon::segment_data segment_type; - typedef boost::polygon::rectangle_data rect_type; - }; +private: + using VD = VoronoiDiagram; VD vd; std::set edges, valid_edges; std::map > thickness; diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 7f579742d55..5e1f09c204b 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -168,6 +168,7 @@ class SupportLayer : public Layer { public: // Polygons covered by the supports: base, interface and contact areas. + // Used to suppress retraction if moving for a support extrusion over these support_islands. ExPolygonCollection support_islands; // Extrusion paths for the support base and for the support interface and contacts. ExtrusionEntityCollection support_fills; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index e1d7ca18537..fc24c719b75 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -264,7 +264,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly this->flow(frInfill, true).scaled_width() ); #ifdef SLIC3R_DEBUG - printf("Processing bridge at layer " PRINTF_ZU ":\n", this->layer()->id()); + printf("Processing bridge at layer %zu:\n", this->layer()->id()); #endif double custom_angle = Geometry::deg2rad(this->region()->config().bridge_angle.value); if (bd.detect_angle(custom_angle)) { diff --git a/src/libslic3r/Line.cpp b/src/libslic3r/Line.cpp index 05cbfee4e66..974f585dc2c 100644 --- a/src/libslic3r/Line.cpp +++ b/src/libslic3r/Line.cpp @@ -125,4 +125,14 @@ Vec3d Linef3::intersect_plane(double z) const return Vec3d(this->a(0) + v(0) * t, this->a(1) + v(1) * t, z); } +BoundingBox get_extents(const Lines &lines) +{ + BoundingBox bbox; + for (const Line &line : lines) { + bbox.merge(line.a); + bbox.merge(line.b); + } + return bbox; } + +} // namespace Slic3r diff --git a/src/libslic3r/Line.hpp b/src/libslic3r/Line.hpp index 06809c02a27..caab809f5c1 100644 --- a/src/libslic3r/Line.hpp +++ b/src/libslic3r/Line.hpp @@ -103,6 +103,8 @@ class Linef3 Vec3d b; }; +BoundingBox get_extents(const Lines &lines); + } // namespace Slic3r // start Boost @@ -123,4 +125,4 @@ namespace boost { namespace polygon { } } // end Boost -#endif +#endif // slic3r_Line_hpp_ diff --git a/src/libslic3r/MTUtils.hpp b/src/libslic3r/MTUtils.hpp index 294f9ae6f59..a6c8d61622b 100644 --- a/src/libslic3r/MTUtils.hpp +++ b/src/libslic3r/MTUtils.hpp @@ -10,8 +10,6 @@ #include #include "libslic3r.h" -#include "Point.hpp" -#include "BoundingBox.hpp" namespace Slic3r { diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 98f595d91e1..3beb74f2350 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -2,6 +2,7 @@ #include "ModelArrange.hpp" #include "Geometry.hpp" #include "MTUtils.hpp" +#include "TriangleSelector.hpp" #include "Format/AMF.hpp" #include "Format/OBJ.hpp" @@ -448,6 +449,26 @@ void Model::convert_multipart_object(unsigned int max_extruders) this->objects.push_back(object); } +bool Model::looks_like_imperial_units() const +{ + if (this->objects.size() == 0) + return false; + + for (ModelObject* obj : this->objects) + if (obj->get_object_stl_stats().volume < 9.0) // 9 = 3*3*3; + return true; + + return false; +} + +void Model::convert_from_imperial_units() +{ + double in_to_mm = 25.4; + for (ModelObject* obj : this->objects) + if (obj->get_object_stl_stats().volume < 9.0) // 9 = 3*3*3; + obj->scale_mesh_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm)); +} + void Model::adjust_min_z() { if (objects.empty()) @@ -966,6 +987,56 @@ void ModelObject::scale_mesh_after_creation(const Vec3d &versor) this->invalidate_bounding_box(); } +void ModelObject::convert_units(ModelObjectPtrs& new_objects, bool from_imperial, std::vector volume_idxs) +{ + BOOST_LOG_TRIVIAL(trace) << "ModelObject::convert_units - start"; + + ModelObject* new_object = new_clone(*this); + + double koef = from_imperial ? 25.4 : 0.0393700787; + const Vec3d versor = Vec3d(koef, koef, koef); + + new_object->set_model(nullptr); + new_object->sla_support_points.clear(); + new_object->sla_drain_holes.clear(); + new_object->sla_points_status = sla::PointsStatus::NoPoints; + new_object->clear_volumes(); + new_object->input_file.clear(); + + int vol_idx = 0; + for (ModelVolume* volume : volumes) + { + volume->m_supported_facets.clear(); + if (!volume->mesh().empty()) { + TriangleMesh mesh(volume->mesh()); + mesh.require_shared_vertices(); + + ModelVolume* vol = new_object->add_volume(mesh); + vol->name = volume->name; + // Don't copy the config's ID. + static_cast(vol->config) = static_cast(volume->config); + assert(vol->config.id().valid()); + assert(vol->config.id() != volume->config.id()); + vol->set_material(volume->material_id(), *volume->material()); + + // Perform conversion + if (volume_idxs.empty() || + std::find(volume_idxs.begin(), volume_idxs.end(), vol_idx) != volume_idxs.end()) { + vol->scale_geometry_after_creation(versor); + vol->set_offset(versor.cwiseProduct(vol->get_offset())); + } + else + vol->set_offset(volume->get_offset()); + } + vol_idx ++; + } + new_object->invalidate_bounding_box(); + + new_objects.push_back(new_object); + + BOOST_LOG_TRIVIAL(trace) << "ModelObject::convert_units - end"; +} + size_t ModelObject::materials_count() const { std::set material_ids; @@ -1196,6 +1267,27 @@ void ModelObject::split(ModelObjectPtrs* new_objects) return; } +void ModelObject::merge() +{ + if (this->volumes.size() == 1) { + // We can't merge meshes if there's just one volume + return; + } + + TriangleMesh mesh; + + for (ModelVolume* volume : volumes) + if (!volume->mesh().empty()) + mesh.merge(volume->mesh()); + mesh.repair(); + + this->clear_volumes(); + ModelVolume* vol = this->add_volume(mesh); + + if (!vol) + return; +} + // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees, // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure. // This situation is solved by baking in the instance transformation into the mesh vertices. @@ -1308,8 +1400,8 @@ unsigned int ModelObject::check_instances_print_volume_state(const BoundingBoxf3 inside_outside |= OUTSIDE; } model_instance->print_volume_state = - (inside_outside == (INSIDE | OUTSIDE)) ? ModelInstance::PVS_Partly_Outside : - (inside_outside == INSIDE) ? ModelInstance::PVS_Inside : ModelInstance::PVS_Fully_Outside; + (inside_outside == (INSIDE | OUTSIDE)) ? ModelInstancePVS_Partly_Outside : + (inside_outside == INSIDE) ? ModelInstancePVS_Inside : ModelInstancePVS_Fully_Outside; if (inside_outside == INSIDE) ++ num_printable; } @@ -1389,9 +1481,6 @@ stl_stats ModelObject::get_object_stl_stats() const // fill full_stats from all objet's meshes for (ModelVolume* volume : this->volumes) { - if (volume->id() == this->volumes[0]->id()) - continue; - const stl_stats& stats = volume->mesh().stl.stats; // initialize full_stats (for repaired errors) @@ -1742,28 +1831,25 @@ arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const } -std::vector FacetsAnnotation::get_facets(FacetSupportType type) const +indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, FacetSupportType type) const { - std::vector out; - for (auto& [facet_idx, this_type] : m_data) - if (this_type == type) - out.push_back(facet_idx); + TriangleSelector selector(mv.mesh()); + selector.deserialize(m_data); + indexed_triangle_set out = selector.get_facets(type); return out; } -void FacetsAnnotation::set_facet(int idx, FacetSupportType type) +bool FacetsAnnotation::set(const TriangleSelector& selector) { - bool changed = true; - - if (type == FacetSupportType::NONE) - changed = m_data.erase(idx) != 0; - else - m_data[idx] = type; - - if (changed) + std::map> sel_map = selector.serialize(); + if (sel_map != m_data) { + m_data = sel_map; update_timestamp(); + return true; + } + return false; } @@ -1776,6 +1862,64 @@ void FacetsAnnotation::clear() +// Following function takes data from a triangle and encodes it as string +// of hexadecimal numbers (one digit per triangle). Used for 3MF export, +// changing it may break backwards compatibility !!!!! +std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const +{ + std::string out; + + auto triangle_it = m_data.find(triangle_idx); + if (triangle_it != m_data.end()) { + const std::vector& code = triangle_it->second; + int offset = 0; + while (offset < int(code.size())) { + int next_code = 0; + for (int i=3; i>=0; --i) { + next_code = next_code << 1; + next_code |= int(code[offset + i]); + } + offset += 4; + + assert(next_code >=0 && next_code <= 15); + char digit = next_code < 10 ? next_code + '0' : (next_code-10)+'A'; + out.insert(out.begin(), digit); + } + } + return out; +} + + + +// Recover triangle splitting & state from string of hexadecimal values previously +// generated by get_triangle_as_string. Used to load from 3MF. +void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::string& str) +{ + assert(! str.empty()); + m_data[triangle_id] = std::vector(); // zero current state or create new + std::vector& code = m_data[triangle_id]; + + for (auto it = str.crbegin(); it != str.crend(); ++it) { + const char ch = *it; + int dec = 0; + if (ch >= '0' && ch<='9') + dec = int(ch - '0'); + else if (ch >='A' && ch <= 'F') + dec = 10 + int(ch - 'A'); + else + assert(false); + + // Convert to binary and append into code. + for (int i=0; i<4; ++i) { + code.insert(code.end(), bool(dec & (1 << i))); + } + } + + +} + + + // Test whether the two models contain the same number of ModelObjects with the same set of IDs // ordered in the same order. In that case it is not necessary to kill the background processing. bool model_object_list_equal(const Model &model_old, const Model &model_new) @@ -1847,7 +1991,7 @@ bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject return true; } return false; -}; +} extern bool model_has_multi_part_objects(const Model &model) { diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 7b56030c74c..92dc84d17a3 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -3,7 +3,6 @@ #include "libslic3r.h" #include "Geometry.hpp" -#include "Layer.hpp" #include "ObjectID.hpp" #include "Point.hpp" #include "PrintConfig.hpp" @@ -40,6 +39,7 @@ class ModelVolume; class ModelWipeTower; class Print; class SLAPrint; +class TriangleSelector; namespace UndoRedo { class StackImpl; @@ -281,12 +281,14 @@ class ModelObject final : public ObjectBase // This method could only be called before the meshes of this ModelVolumes are not shared! void scale_mesh_after_creation(const Vec3d& versor); + void convert_units(ModelObjectPtrs&new_objects, bool from_imperial, std::vector volume_idxs); size_t materials_count() const; size_t facets_count() const; bool needed_repair() const; ModelObjectPtrs cut(size_t instance, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); // Note: z is in world coordinates void split(ModelObjectPtrs* new_objects); + void merge(); // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees, // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure. // This situation is solved by baking in the instance transformation into the mesh vertices. @@ -393,6 +395,7 @@ enum class ModelVolumeType : int { }; enum class FacetSupportType : int8_t { + // Maximum is 3. The value is serialized in TriangleSelector into 2 bits! NONE = 0, ENFORCER = 1, BLOCKER = 2 @@ -402,9 +405,12 @@ class FacetsAnnotation { public: using ClockType = std::chrono::steady_clock; - std::vector get_facets(FacetSupportType type) const; - void set_facet(int idx, FacetSupportType type); + const std::map>& get_data() const { return m_data; } + bool set(const TriangleSelector& selector); + indexed_triangle_set get_facets(const ModelVolume& mv, FacetSupportType type) const; void clear(); + std::string get_triangle_as_string(int i) const; + void set_triangle_from_string(int triangle_id, const std::string& str); ClockType::time_point get_timestamp() const { return timestamp; } bool is_same_as(const FacetsAnnotation& other) const { @@ -417,7 +423,7 @@ class FacetsAnnotation { } private: - std::map m_data; + std::map> m_data; ClockType::time_point timestamp; void update_timestamp() { @@ -640,25 +646,26 @@ class ModelVolume final : public ObjectBase } }; + +enum ModelInstanceEPrintVolumeState : unsigned char +{ + ModelInstancePVS_Inside, + ModelInstancePVS_Partly_Outside, + ModelInstancePVS_Fully_Outside, + ModelInstanceNum_BedStates +}; + + // A single instance of a ModelObject. // Knows the affine transformation of an object. class ModelInstance final : public ObjectBase { -public: - enum EPrintVolumeState : unsigned char - { - PVS_Inside, - PVS_Partly_Outside, - PVS_Fully_Outside, - Num_BedStates - }; - private: Geometry::Transformation m_transformation; public: // flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state()) - EPrintVolumeState print_volume_state; + ModelInstanceEPrintVolumeState print_volume_state; // Whether or not this instance is printable bool printable; @@ -705,7 +712,7 @@ class ModelInstance final : public ObjectBase const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); } - bool is_printable() const { return object->printable && printable && (print_volume_state == PVS_Inside); } + bool is_printable() const { return object->printable && printable && (print_volume_state == ModelInstancePVS_Inside); } // Getting the input polygon for arrange arrangement::ArrangePolygon get_arrange_polygon() const; @@ -734,10 +741,10 @@ class ModelInstance final : public ObjectBase ModelObject* object; // Constructor, which assigns a new unique ID. - explicit ModelInstance(ModelObject* object) : print_volume_state(PVS_Inside), printable(true), object(object) { assert(this->id().valid()); } + explicit ModelInstance(ModelObject* object) : print_volume_state(ModelInstancePVS_Inside), printable(true), object(object) { assert(this->id().valid()); } // Constructor, which assigns a new unique ID. explicit ModelInstance(ModelObject *object, const ModelInstance &other) : - m_transformation(other.m_transformation), print_volume_state(PVS_Inside), printable(other.printable), object(object) { assert(this->id().valid() && this->id() != other.id()); } + m_transformation(other.m_transformation), print_volume_state(ModelInstancePVS_Inside), printable(other.printable), object(object) { assert(this->id().valid() && this->id() != other.id()); } explicit ModelInstance(ModelInstance &&rhs) = delete; ModelInstance& operator=(const ModelInstance &rhs) = delete; @@ -752,6 +759,7 @@ class ModelInstance final : public ObjectBase } }; + class ModelWipeTower final : public ObjectBase { public: @@ -851,6 +859,8 @@ class Model final : public ObjectBase bool looks_like_multipart_object() const; void convert_multipart_object(unsigned int max_extruders); + bool looks_like_imperial_units() const; + void convert_from_imperial_units(); // Ensures that the min z of the model is not negative void adjust_min_z(); diff --git a/src/libslic3r/ModelArrange.cpp b/src/libslic3r/ModelArrange.cpp index 85aa25a5fb5..230b04de5d5 100644 --- a/src/libslic3r/ModelArrange.cpp +++ b/src/libslic3r/ModelArrange.cpp @@ -1,4 +1,6 @@ #include "ModelArrange.hpp" + +#include #include "MTUtils.hpp" namespace Slic3r { diff --git a/src/libslic3r/ModelArrange.hpp b/src/libslic3r/ModelArrange.hpp index d65b0fd6de3..afe146d4381 100644 --- a/src/libslic3r/ModelArrange.hpp +++ b/src/libslic3r/ModelArrange.hpp @@ -1,11 +1,14 @@ #ifndef MODELARRANGE_HPP #define MODELARRANGE_HPP -#include #include namespace Slic3r { +class Model; +class ModelInstance; +using ModelInstancePtrs = std::vector; + using arrangement::ArrangePolygon; using arrangement::ArrangePolygons; using arrangement::ArrangeParams; diff --git a/src/libslic3r/MotionPlanner.cpp b/src/libslic3r/MotionPlanner.cpp index 45a80671c95..ae50df8f49d 100644 --- a/src/libslic3r/MotionPlanner.cpp +++ b/src/libslic3r/MotionPlanner.cpp @@ -6,6 +6,7 @@ #include // for numeric_limits #include +#define BOOST_VORONOI_USE_GMP 1 #include "boost/polygon/voronoi.hpp" using boost::polygon::voronoi_builder; using boost::polygon::voronoi_diagram; diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index 484d1173ba0..920f512de34 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -42,6 +42,8 @@ class ObjectID // Base for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial to provide a unique ID // to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject). +// Also base for Print, PrintObject, SLAPrint, SLAPrintObject to provide a unique ID for matching Model / ModelObject +// with their corresponding Print / PrintObject objects by the notification center at the UI when processing back-end warnings. // Achtung! The s_last_id counter is not thread safe, so it is expected, that the ObjectBase derived instances // are only instantiated from the main thread. class ObjectBase diff --git a/src/libslic3r/Point.hpp b/src/libslic3r/Point.hpp index e511a631676..b818cd8bedf 100644 --- a/src/libslic3r/Point.hpp +++ b/src/libslic3r/Point.hpp @@ -369,7 +369,7 @@ namespace boost { namespace polygon { typedef coord_t coordinate_type; static inline coordinate_type get(const Slic3r::Point& point, orientation_2d orient) { - return (orient == HORIZONTAL) ? (coordinate_type)point(0) : (coordinate_type)point(1); + return (coordinate_type)point((orient == HORIZONTAL) ? 0 : 1); } }; @@ -377,16 +377,10 @@ namespace boost { namespace polygon { struct point_mutable_traits { typedef coord_t coordinate_type; static inline void set(Slic3r::Point& point, orientation_2d orient, coord_t value) { - if (orient == HORIZONTAL) - point(0) = value; - else - point(1) = value; + point((orient == HORIZONTAL) ? 0 : 1) = value; } static inline Slic3r::Point construct(coord_t x_value, coord_t y_value) { - Slic3r::Point retval; - retval(0) = x_value; - retval(1) = y_value; - return retval; + return Slic3r::Point(x_value, y_value); } }; } } diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index c6678e2d83d..ab7c171e3c3 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -153,9 +153,11 @@ inline Lines to_lines(const Polygon &poly) { Lines lines; lines.reserve(poly.points.size()); - for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it) - lines.push_back(Line(*it, *(it + 1))); - lines.push_back(Line(poly.points.back(), poly.points.front())); + if (poly.points.size() > 2) { + for (Points::const_iterator it = poly.points.begin(); it != poly.points.end()-1; ++it) + lines.push_back(Line(*it, *(it + 1))); + lines.push_back(Line(poly.points.back(), poly.points.front())); + } return lines; } diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index f8b51ece25c..dfd8b6739bf 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -245,7 +245,6 @@ bool Print::invalidate_step(PrintStep step) { bool invalidated = Inherited::invalidate_step(step); // Propagate to dependent steps. - //FIXME Why should skirt invalidate brim? Shouldn't it be vice versa? if (step == psSkirt) invalidated |= Inherited::invalidate_step(psBrim); if (step != psGCodeExport) @@ -501,12 +500,12 @@ static bool custom_per_printz_gcodes_tool_changes_differ(const std::vectorgcode != ToolChangeCode) { + if (it_a != va.end() && it_a->type != CustomGCode::ToolChange) { // Skip any CustomGCode items, which are not tool changes. ++ it_a; continue; } - if (it_b != vb.end() && it_b->gcode != ToolChangeCode) { + if (it_b != vb.end() && it_b->type != CustomGCode::ToolChange) { // Skip any CustomGCode items, which are not tool changes. ++ it_b; continue; @@ -514,8 +513,8 @@ static bool custom_per_printz_gcodes_tool_changes_differ(const std::vectorgcode == ToolChangeCode); - assert(it_b->gcode == ToolChangeCode); + assert(it_a->type == CustomGCode::ToolChange); + assert(it_b->type == CustomGCode::ToolChange); if (*it_a != *it_b) // The two Tool Changes differ. return true; @@ -1607,6 +1606,8 @@ void Print::process() } if (this->set_started(psSkirt)) { m_skirt.clear(); + m_skirt_convex_hull.clear(); + m_first_layer_convex_hull.points.clear(); if (this->has_skirt()) { this->set_status(88, L("Generating skirt")); this->_make_skirt(); @@ -1615,11 +1616,15 @@ void Print::process() } if (this->set_started(psBrim)) { m_brim.clear(); + m_first_layer_convex_hull.points.clear(); if (m_config.brim_width > 0) { this->set_status(88, L("Generating brim")); this->_make_brim(); } - this->set_done(psBrim); + // Brim depends on skirt (brim lines are trimmed by the skirt lines), therefore if + // the skirt gets invalidated, brim gets invalidated as well and the following line is called. + this->finalize_first_layer_convex_hull(); + this->set_done(psBrim); } BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info(); } @@ -1698,22 +1703,7 @@ void Print::_make_skirt() } // Include the wipe tower. - if (has_wipe_tower() && ! m_wipe_tower_data.tool_changes.empty()) { - double width = m_config.wipe_tower_width + 2*m_wipe_tower_data.brim_width; - double depth = m_wipe_tower_data.depth + 2*m_wipe_tower_data.brim_width; - Vec2d pt = Vec2d(-m_wipe_tower_data.brim_width, -m_wipe_tower_data.brim_width); - - std::vector pts; - pts.push_back(Vec2d(pt.x(), pt.y())); - pts.push_back(Vec2d(pt.x()+width, pt.y())); - pts.push_back(Vec2d(pt.x()+width, pt.y()+depth)); - pts.push_back(Vec2d(pt.x(), pt.y()+depth)); - for (Vec2d& pt : pts) { - pt = Eigen::Rotation2Dd(Geometry::deg2rad(m_config.wipe_tower_rotation_angle.value)) * pt; - pt += Vec2d(m_config.wipe_tower_x.value, m_config.wipe_tower_y.value); - points.push_back(Point(scale_(pt.x()), scale_(pt.y()))); - } - } + append(points, this->first_layer_wipe_tower_corners()); if (points.size() < 3) // At least three points required for a convex hull. @@ -1797,28 +1787,19 @@ void Print::_make_skirt() } // Brims were generated inside out, reverse to print the outmost contour first. m_skirt.reverse(); + + // Remember the outer edge of the last skirt line extruded as m_skirt_convex_hull. + for (Polygon &poly : offset(convex_hull, distance + 0.5f * float(scale_(spacing)), ClipperLib::jtRound, float(scale_(0.1)))) + append(m_skirt_convex_hull, std::move(poly.points)); } void Print::_make_brim() { // Brim is only printed on first layer and uses perimeter extruder. + Polygons islands = this->first_layer_islands(); + Polygons loops; Flow flow = this->brim_flow(); - Polygons islands; - for (PrintObject *object : m_objects) { - Polygons object_islands; - for (ExPolygon &expoly : object->m_layers.front()->lslices) - object_islands.push_back(expoly.contour); - if (! object->support_layers().empty()) - object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON)); - islands.reserve(islands.size() + object_islands.size() * object->instances().size()); - for (const PrintInstance &instance : object->instances()) - for (Polygon &poly : object_islands) { - islands.push_back(poly); - islands.back().translate(instance.shift); - } - } - Polygons loops; - size_t num_loops = size_t(floor(m_config.brim_width.value / flow.spacing())); + size_t num_loops = size_t(floor(m_config.brim_width.value / flow.spacing())); for (size_t i = 0; i < num_loops; ++ i) { this->throw_if_canceled(); islands = offset(islands, float(flow.scaled_spacing()), jtSquare); @@ -1829,6 +1810,11 @@ void Print::_make_brim() p.pop_back(); poly.points = std::move(p); } + if (i + 1 == num_loops) { + // Remember the outer edge of the last brim line extruded as m_first_layer_convex_hull. + for (Polygon &poly : islands) + append(m_first_layer_convex_hull.points, poly.points); + } polygons_append(loops, offset(islands, -0.5f * float(flow.scaled_spacing()))); } loops = union_pt_chained(loops, false); @@ -1968,6 +1954,58 @@ void Print::_make_brim() } } +Polygons Print::first_layer_islands() const +{ + Polygons islands; + for (PrintObject *object : m_objects) { + Polygons object_islands; + for (ExPolygon &expoly : object->m_layers.front()->lslices) + object_islands.push_back(expoly.contour); + if (! object->support_layers().empty()) + object->support_layers().front()->support_fills.polygons_covered_by_spacing(object_islands, float(SCALED_EPSILON)); + islands.reserve(islands.size() + object_islands.size() * object->instances().size()); + for (const PrintInstance &instance : object->instances()) + for (Polygon &poly : object_islands) { + islands.push_back(poly); + islands.back().translate(instance.shift); + } + } + return islands; +} + +std::vector Print::first_layer_wipe_tower_corners() const +{ + std::vector corners; + if (has_wipe_tower() && ! m_wipe_tower_data.tool_changes.empty()) { + double width = m_config.wipe_tower_width + 2*m_wipe_tower_data.brim_width; + double depth = m_wipe_tower_data.depth + 2*m_wipe_tower_data.brim_width; + Vec2d pt0(-m_wipe_tower_data.brim_width, -m_wipe_tower_data.brim_width); + for (Vec2d pt : { + pt0, + Vec2d(pt0.x()+width, pt0.y() ), + Vec2d(pt0.x()+width, pt0.y()+depth), + Vec2d(pt0.x(), pt0.y()+depth) + }) { + pt = Eigen::Rotation2Dd(Geometry::deg2rad(m_config.wipe_tower_rotation_angle.value)) * pt; + pt += Vec2d(m_config.wipe_tower_x.value, m_config.wipe_tower_y.value); + corners.emplace_back(Point(scale_(pt.x()), scale_(pt.y()))); + } + } + return corners; +} + +void Print::finalize_first_layer_convex_hull() +{ + append(m_first_layer_convex_hull.points, m_skirt_convex_hull); + if (m_first_layer_convex_hull.empty()) { + // Neither skirt nor brim was extruded. Collect points of printed objects from 1st layer. + for (Polygon &poly : this->first_layer_islands()) + append(m_first_layer_convex_hull.points, std::move(poly.points)); + } + append(m_first_layer_convex_hull.points, this->first_layer_wipe_tower_corners()); + m_first_layer_convex_hull = Geometry::convex_hull(m_first_layer_convex_hull.points); +} + // Wipe tower support. bool Print::has_wipe_tower() const { @@ -1992,7 +2030,6 @@ const WipeTowerData& Print::wipe_tower_data(size_t extruders_cnt, double first_l return m_wipe_tower_data; } - void Print::_make_wipe_tower() { m_wipe_tower_data.clear(); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 54ebceeb661..05929dd2ef3 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -4,10 +4,9 @@ #include "PrintBase.hpp" #include "BoundingBox.hpp" +#include "ExtrusionEntityCollection.hpp" #include "Flow.hpp" #include "Point.hpp" -#include "Layer.hpp" -#include "Model.hpp" #include "Slicing.hpp" #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" @@ -23,6 +22,8 @@ class ModelObject; class GCode; class GCodePreviewData; enum class SlicingMode : uint32_t; +class Layer; +class SupportLayer; // Print step IDs for keeping track of the print state. enum PrintStep { @@ -147,18 +148,11 @@ class PrintObject : public PrintObjectBaseWithStateprint_z < print_z; }); - return (it == m_layers.end() || (*it)->print_z != print_z) ? nullptr : *it; - } - Layer* get_layer_at_printz(coordf_t print_z) { return const_cast(std::as_const(*this).get_layer_at_printz(print_z)); } + const Layer* get_layer_at_printz(coordf_t print_z) const; + Layer* get_layer_at_printz(coordf_t print_z); // Get a layer approximately at print_z. - const Layer* get_layer_at_printz(coordf_t print_z, coordf_t epsilon) const { - coordf_t limit = print_z - epsilon; - auto it = Slic3r::lower_bound_by_predicate(m_layers.begin(), m_layers.end(), [limit](const Layer *layer) { return layer->print_z < limit; }); - return (it == m_layers.end() || (*it)->print_z > print_z + epsilon) ? nullptr : *it; - } - Layer* get_layer_at_printz(coordf_t print_z, coordf_t epsilon) { return const_cast(std::as_const(*this).get_layer_at_printz(print_z, epsilon)); } + const Layer* get_layer_at_printz(coordf_t print_z, coordf_t epsilon) const; + Layer* get_layer_at_printz(coordf_t print_z, coordf_t epsilon); // print_z: top of the layer; slice_z: center of the layer. Layer* add_layer(int id, coordf_t height, coordf_t print_z, coordf_t slice_z); @@ -306,8 +300,8 @@ struct PrintStatistics PrintStatistics() { clear(); } std::string estimated_normal_print_time; std::string estimated_silent_print_time; - std::vector> estimated_normal_custom_gcode_print_times; - std::vector> estimated_silent_custom_gcode_print_times; + std::vector> estimated_normal_custom_gcode_print_times; + std::vector> estimated_silent_custom_gcode_print_times; double total_used_filament; double total_extruded_volume; double total_cost; @@ -401,6 +395,13 @@ class Print : public PrintBaseWithState const PrintObjectPtrs& objects() const { return m_objects; } PrintObject* get_object(size_t idx) { return m_objects[idx]; } const PrintObject* get_object(size_t idx) const { return m_objects[idx]; } + // PrintObject by its ObjectID, to be used to uniquely bind slicing warnings to their source PrintObjects + // in the notification center. + const PrintObject* get_object(ObjectID object_id) const { + auto it = std::find_if(m_objects.begin(), m_objects.end(), + [object_id](const PrintObject *obj) { return obj->id() == object_id; }); + return (it == m_objects.end()) ? nullptr : *it; + } const PrintRegionPtrs& regions() const { return m_regions; } // How many of PrintObject::copies() over all print objects are there? // If zero, then the print is empty and the print shall not be executed. @@ -408,6 +409,12 @@ class Print : public PrintBaseWithState const ExtrusionEntityCollection& skirt() const { return m_skirt; } const ExtrusionEntityCollection& brim() const { return m_brim; } + // Convex hull of the 1st layer extrusions, for bed leveling and placing the initial purge line. + // It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower. + // It does NOT encompass user extrusions generated by custom G-code, + // therefore it does NOT encompass the initial purge line. + // It does NOT encompass MMU/MMU2 starting (wipe) areas. + const Polygon& first_layer_convex_hull() const { return m_first_layer_convex_hull; } const PrintStatistics& print_statistics() const { return m_print_statistics; } @@ -443,6 +450,12 @@ class Print : public PrintBaseWithState void _make_skirt(); void _make_brim(); void _make_wipe_tower(); + void finalize_first_layer_convex_hull(); + + // Islands of objects and their supports extruded at the 1st layer. + Polygons first_layer_islands() const; + // Return 4 wipe tower corners in the world coordinates (shifted and rotated), including the wipe tower brim. + std::vector first_layer_wipe_tower_corners() const; // Declared here to have access to Model / ModelObject / ModelInstance static void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_src); @@ -456,6 +469,13 @@ class Print : public PrintBaseWithState // Ordered collections of extrusion paths to build skirt loops and brim. ExtrusionEntityCollection m_skirt; ExtrusionEntityCollection m_brim; + // Convex hull of the 1st layer extrusions. + // It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower. + // It does NOT encompass user extrusions generated by custom G-code, + // therefore it does NOT encompass the initial purge line. + // It does NOT encompass MMU/MMU2 starting (wipe) areas. + Polygon m_first_layer_convex_hull; + Points m_skirt_convex_hull; // Following section will be consumed by the GCodeGenerator. ToolOrdering m_tool_ordering; diff --git a/src/libslic3r/PrintBase.cpp b/src/libslic3r/PrintBase.cpp index 81affe04d4d..ab6ca3d3504 100644 --- a/src/libslic3r/PrintBase.cpp +++ b/src/libslic3r/PrintBase.cpp @@ -88,6 +88,14 @@ std::string PrintBase::output_filepath(const std::string &path, const std::strin return path; } +void PrintBase::status_update_warnings(ObjectID object_id, int step, PrintStateBase::WarningLevel /* warning_level */, const std::string &message) +{ + if (this->m_status_callback) + m_status_callback(SlicingStatus(*this, step)); + else if (! message.empty()) + printf("%s warning: %s\n", (object_id == this->id()) ? "print" : "print object", message.c_str()); +} + tbb::mutex& PrintObjectBase::state_mutex(PrintBase *print) { return print->state_mutex(); @@ -98,4 +106,9 @@ std::function PrintObjectBase::cancel_callback(PrintBase *print) return print->cancel_callback(); } +void PrintObjectBase::status_update_warnings(PrintBase *print, int step, PrintStateBase::WarningLevel warning_level, const std::string &message) +{ + print->status_update_warnings(this->id(), step, warning_level, message); +} + } // namespace Slic3r diff --git a/src/libslic3r/PrintBase.hpp b/src/libslic3r/PrintBase.hpp index 05d884cc88a..5e94e011a73 100644 --- a/src/libslic3r/PrintBase.hpp +++ b/src/libslic3r/PrintBase.hpp @@ -13,6 +13,7 @@ #endif #include "tbb/mutex.h" +#include "ObjectID.hpp" #include "Model.hpp" #include "PlaceholderParser.hpp" #include "PrintConfig.hpp" @@ -32,6 +33,11 @@ class PrintStateBase { DONE, }; + enum class WarningLevel { + NON_CRITICAL, + CRITICAL + }; + typedef size_t TimeStamp; // A new unique timestamp is being assigned to the step every time the step changes its state. @@ -42,6 +48,28 @@ class PrintStateBase { TimeStamp timestamp; }; + struct Warning + { + // Critical warnings will be displayed on G-code export in a modal dialog, so that the user cannot miss them. + WarningLevel level; + // If the warning is not current, then it is in an unknown state. It may or may not be valid. + // A current warning will become non-current if its milestone gets invalidated. + // A non-current warning will either become current or it will be removed at the end of a milestone. + bool current; + // Message to be shown to the user, UTF8, localized. + std::string message; + // If message_id == 0, then the message is expected to identify the warning uniquely. + // Otherwise message_id identifies the message. For example, if the message contains a varying number, then + // it cannot itself identify the message type. + int message_id; + }; + + struct StateWithWarnings : public StateWithTimeStamp + { + void mark_warnings_non_current() { for (auto &w : warnings) w.current = false; } + std::vector warnings; + }; + protected: //FIXME last timestamp is shared between Print & SLAPrint, // and if multiple Print or SLAPrint instances are executed in parallel, modification of g_last_timestamp @@ -56,12 +84,18 @@ class PrintState : public PrintStateBase public: PrintState() {} - StateWithTimeStamp state_with_timestamp(StepType step, tbb::mutex &mtx) const { + StateWithTimeStamp state_with_timestamp(StepType step, tbb::mutex &mtx) const { tbb::mutex::scoped_lock lock(mtx); StateWithTimeStamp state = m_state[step]; return state; } + StateWithWarnings state_with_warnings(StepType step, tbb::mutex &mtx) const { + tbb::mutex::scoped_lock lock(mtx); + StateWithWarnings state = m_state[step]; + return state; + } + bool is_started(StepType step, tbb::mutex &mtx) const { return this->state_with_timestamp(step, mtx).state == STARTED; } @@ -91,24 +125,53 @@ class PrintState : public PrintStateBase tbb::mutex::scoped_lock lock(mtx); // If canceled, throw before changing the step state. throw_if_canceled(); +#ifndef NDEBUG +// The following test is not necessarily valid after the background processing thread +// is stopped with throw_if_canceled(), as the CanceledException is not being catched +// by the Print or PrintObject to update m_step_active or m_state[...].state. +// This should not be a problem as long as the caller calls set_started() / set_done() / +// active_step_add_warning() consistently. From the robustness point of view it would be +// be better to catch CanceledException and do the updates. From the performance point of view, +// the current implementation is optimal. +// +// assert(m_step_active == -1); +// for (int i = 0; i < int(COUNT); ++ i) +// assert(m_state[i].state != STARTED); +#endif // NDEBUG if (m_state[step].state == DONE) return false; - m_state[step].state = STARTED; - m_state[step].timestamp = ++ g_last_timestamp; + PrintStateBase::StateWithWarnings &state = m_state[step]; + state.state = STARTED; + state.timestamp = ++ g_last_timestamp; + state.mark_warnings_non_current(); + m_step_active = static_cast(step); return true; } // Set the step as done. Block on mutex while the Print / PrintObject / PrintRegion objects are being // modified by the UI thread. + // Return value: + // Timestamp when this stepentered the DONE state. + // bool indicates whether the UI has to update the slicing warnings of this step or not. template - TimeStamp set_done(StepType step, tbb::mutex &mtx, ThrowIfCanceled throw_if_canceled) { + std::pair set_done(StepType step, tbb::mutex &mtx, ThrowIfCanceled throw_if_canceled) { tbb::mutex::scoped_lock lock(mtx); // If canceled, throw before changing the step state. throw_if_canceled(); - assert(m_state[step].state != DONE); - m_state[step].state = DONE; - m_state[step].timestamp = ++ g_last_timestamp; - return m_state[step].timestamp; + assert(m_state[step].state == STARTED); + assert(m_step_active == static_cast(step)); + PrintStateBase::StateWithWarnings &state = m_state[step]; + state.state = DONE; + state.timestamp = ++ g_last_timestamp; + m_step_active = -1; + // Remove all non-current warnings. + auto it = std::remove_if(state.warnings.begin(), state.warnings.end(), [](const auto &w) { return ! w.current; }); + bool update_warning_ui = false; + if (it != state.warnings.end()) { + state.warnings.erase(it, state.warnings.end()); + update_warning_ui = true; + } + return std::make_pair(state.timestamp, update_warning_ui); } // Make the step invalid. @@ -124,13 +187,18 @@ class PrintState : public PrintStateBase printf("Not held!\n"); } #endif - m_state[step].state = INVALID; - m_state[step].timestamp = ++ g_last_timestamp; + PrintStateBase::StateWithWarnings &state = m_state[step]; + state.state = INVALID; + state.timestamp = ++ g_last_timestamp; // Raise the mutex, so that the following cancel() callback could cancel // the background processing. // Internally the cancel() callback shall unlock the PrintBase::m_status_mutex to let - // the working thread to proceed. + // the working thread proceed. cancel(); + // Now the worker thread should be stopped, therefore it cannot write into the warnings field. + // It is safe to modify it. + state.mark_warnings_non_current(); + m_step_active = -1; } return invalidated; } @@ -157,6 +225,11 @@ class PrintState : public PrintStateBase // Internally the cancel() callback shall unlock the PrintBase::m_status_mutex to let // the working thread to proceed. cancel(); + // Now the worker thread should be stopped, therefore it cannot write into the warnings field. + // It is safe to modify the warnings. + for (StepTypeIterator it = step_begin; it != step_end; ++ it) + m_state[*it].mark_warnings_non_current(); + m_step_active = -1; } return invalidated; } @@ -176,18 +249,62 @@ class PrintState : public PrintStateBase state.timestamp = ++ g_last_timestamp; } } - if (invalidated) + if (invalidated) { cancel(); + // Now the worker thread should be stopped, therefore it cannot write into the warnings field. + // It is safe to modify the warnings. + for (size_t i = 0; i < COUNT; ++ i) + m_state[i].mark_warnings_non_current(); + m_step_active = -1; + } return invalidated; } + // Update list of warnings of the current milestone with a new warning. + // The warning may already exist in the list, marked as current or not current. + // If it already exists, mark it as current. + // Return value: + // Current milestone (StepType). + // bool indicates whether the UI has to be updated or not. + std::pair active_step_add_warning(PrintStateBase::WarningLevel warning_level, const std::string &message, int message_id, tbb::mutex &mtx) + { + tbb::mutex::scoped_lock lock(mtx); + assert(m_step_active != -1); + StateWithWarnings &state = m_state[m_step_active]; + assert(state.state == STARTED); + std::pair retval(static_cast(m_step_active), true); + // Does a warning of the same level and message or message_id exist already? + auto it = (message_id == 0) ? + std::find_if(state.warnings.begin(), state.warnings.end(), [&message](const auto &w) { return w.message_id == 0 && w.message == message; }) : + std::find_if(state.warnings.begin(), state.warnings.end(), [message_id](const auto& w) { return w.message_id == message_id; }); + if (it == state.warnings.end()) + // No, create a new warning and update UI. + state.warnings.emplace_back(PrintStateBase::Warning{ warning_level, true, message, message_id }); + else if (it->message != message || it->level != warning_level) { + // Yes, however it needs an update. + it->message = message; + it->level = warning_level; + it->current = true; + } else if (it->current) + // Yes, and it is current. Don't update UI. + retval.second = false; + else + // Yes, but it is not current. Mark it as current. + it->current = true; + return retval; + } + private: - StateWithTimeStamp m_state[COUNT]; + StateWithWarnings m_state[COUNT]; + // Active class StepType or -1 if none is active. + // If the background processing is canceled, m_step_active may not be resetted + // to -1, see the comment in this->set_started(). + int m_step_active = -1; }; class PrintBase; -class PrintObjectBase +class PrintObjectBase : public ObjectBase { public: const ModelObject* model_object() const { return m_model_object; } @@ -197,8 +314,12 @@ class PrintObjectBase PrintObjectBase(ModelObject *model_object) : m_model_object(model_object) {} virtual ~PrintObjectBase() {} // Declared here to allow access from PrintBase through friendship. - static tbb::mutex& state_mutex(PrintBase *print); - static std::function cancel_callback(PrintBase *print); + static tbb::mutex& state_mutex(PrintBase *print); + static std::function cancel_callback(PrintBase *print); + // Notify UI about a new warning of a milestone "step" on this PrintObjectBase. + // The UI will be notified by calling a status callback registered on print. + // If no status callback is registered, the message is printed to console. + void status_update_warnings(PrintBase *print, int step, PrintStateBase::WarningLevel warning_level, const std::string &message); ModelObject *m_model_object; }; @@ -214,7 +335,7 @@ class PrintObjectBase * The PrintBase class will abstract this flow for different technologies. * */ -class PrintBase +class PrintBase : public ObjectBase { public: PrintBase() : m_placeholder_parser(&m_full_print_config) { this->restart(); } @@ -264,17 +385,29 @@ class PrintBase struct SlicingStatus { SlicingStatus(int percent, const std::string &text, unsigned int flags = 0) : percent(percent), text(text), flags(flags) {} - int percent; + SlicingStatus(const PrintBase &print, int warning_step) : + flags(UPDATE_PRINT_STEP_WARNINGS), warning_object_id(print.id()), warning_step(warning_step) {} + SlicingStatus(const PrintObjectBase &print_object, int warning_step) : + flags(UPDATE_PRINT_OBJECT_STEP_WARNINGS), warning_object_id(print_object.id()), warning_step(warning_step) {} + int percent { -1 }; std::string text; // Bitmap of flags. enum FlagBits { - DEFAULT = 0, - RELOAD_SCENE = 1 << 1, - RELOAD_SLA_SUPPORT_POINTS = 1 << 2, - RELOAD_SLA_PREVIEW = 1 << 3, + DEFAULT = 0, + RELOAD_SCENE = 1 << 1, + RELOAD_SLA_SUPPORT_POINTS = 1 << 2, + RELOAD_SLA_PREVIEW = 1 << 3, + // UPDATE_PRINT_STEP_WARNINGS is mutually exclusive with UPDATE_PRINT_OBJECT_STEP_WARNINGS. + UPDATE_PRINT_STEP_WARNINGS = 1 << 4, + UPDATE_PRINT_OBJECT_STEP_WARNINGS = 1 << 5 }; // Bitmap of FlagBits unsigned int flags; + // set to an ObjectID of a Print or a PrintObject based on flags + // (whether UPDATE_PRINT_STEP_WARNINGS or UPDATE_PRINT_OBJECT_STEP_WARNINGS is set). + ObjectID warning_object_id; + // For which Print or PrintObject step a new warning is beeing issued? + int warning_step { -1 }; }; typedef std::function status_callback_type; // Default status console print out in the form of percent => message. @@ -329,6 +462,10 @@ class PrintBase tbb::mutex& state_mutex() const { return m_state_mutex; } std::function cancel_callback() { return m_cancel_callback; } void call_cancel_callback() { m_cancel_callback(); } + // Notify UI about a new warning of a milestone "step" on this PrintBase. + // The UI will be notified by calling a status callback. + // If no status callback is registered, the message is printed to console. + void status_update_warnings(ObjectID object_id, int step, PrintStateBase::WarningLevel warning_level, const std::string &message); // If the background processing stop was requested, throw CanceledException. // To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly. @@ -343,11 +480,12 @@ class PrintBase DynamicPrintConfig m_full_print_config; PlaceholderParser m_placeholder_parser; -private: - tbb::atomic m_cancel_status; // Callback to be evoked regularly to update state of the UI thread. status_callback_type m_status_callback; +private: + tbb::atomic m_cancel_status; + // Callback to be evoked to stop the background processing before a state is updated. cancel_callback_type m_cancel_callback = [](){}; @@ -363,10 +501,16 @@ class PrintBaseWithState : public PrintBase public: bool is_step_done(PrintStepEnum step) const { return m_state.is_done(step, this->state_mutex()); } PrintStateBase::StateWithTimeStamp step_state_with_timestamp(PrintStepEnum step) const { return m_state.state_with_timestamp(step, this->state_mutex()); } + PrintStateBase::StateWithWarnings step_state_with_warnings(PrintStepEnum step) const { return m_state.state_with_warnings(step, this->state_mutex()); } protected: bool set_started(PrintStepEnum step) { return m_state.set_started(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); } - PrintStateBase::TimeStamp set_done(PrintStepEnum step) { return m_state.set_done(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); } + PrintStateBase::TimeStamp set_done(PrintStepEnum step) { + std::pair status = m_state.set_done(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); + if (status.second) + this->status_update_warnings(this->id(), static_cast(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string()); + return status.first; + } bool invalidate_step(PrintStepEnum step) { return m_state.invalidate(step, this->cancel_callback()); } template @@ -380,6 +524,15 @@ class PrintBaseWithState : public PrintBase bool is_step_started_unguarded(PrintStepEnum step) const { return m_state.is_started_unguarded(step); } bool is_step_done_unguarded(PrintStepEnum step) const { return m_state.is_done_unguarded(step); } + // Add a slicing warning to the active Print step and send a status notification. + // This method could be called multiple times between this->set_started() and this->set_done(). + void active_step_add_warning(PrintStateBase::WarningLevel warning_level, const std::string &message, int message_id = 0) { + std::pair active_step = m_state.active_step_add_warning(warning_level, message, message_id, this->state_mutex()); + if (active_step.second) + // Update UI. + this->status_update_warnings(this->id(), static_cast(active_step.first), warning_level, message); + } + private: PrintState m_state; }; @@ -394,14 +547,19 @@ class PrintObjectBaseWithState : public PrintObjectBase typedef PrintState PrintObjectState; bool is_step_done(PrintObjectStepEnum step) const { return m_state.is_done(step, PrintObjectBase::state_mutex(m_print)); } PrintStateBase::StateWithTimeStamp step_state_with_timestamp(PrintObjectStepEnum step) const { return m_state.state_with_timestamp(step, PrintObjectBase::state_mutex(m_print)); } + PrintStateBase::StateWithWarnings step_state_with_warnings(PrintObjectStepEnum step) const { return m_state.state_with_warnings(step, PrintObjectBase::state_mutex(m_print)); } protected: PrintObjectBaseWithState(PrintType *print, ModelObject *model_object) : PrintObjectBase(model_object), m_print(print) {} bool set_started(PrintObjectStepEnum step) { return m_state.set_started(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); } - PrintStateBase::TimeStamp set_done(PrintObjectStepEnum step) - { return m_state.set_done(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); } + PrintStateBase::TimeStamp set_done(PrintObjectStepEnum step) { + std::pair status = m_state.set_done(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); + if (status.second) + this->status_update_warnings(m_print, static_cast(step), PrintStateBase::WarningLevel::NON_CRITICAL, std::string()); + return status.first; + } bool invalidate_step(PrintObjectStepEnum step) { return m_state.invalidate(step, PrintObjectBase::cancel_callback(m_print)); } @@ -416,6 +574,14 @@ class PrintObjectBaseWithState : public PrintObjectBase bool is_step_started_unguarded(PrintObjectStepEnum step) const { return m_state.is_started_unguarded(step); } bool is_step_done_unguarded(PrintObjectStepEnum step) const { return m_state.is_done_unguarded(step); } + // Add a slicing warning to the active PrintObject step and send a status notification. + // This method could be called multiple times between this->set_started() and this->set_done(). + void active_step_add_warning(PrintStateBase::WarningLevel warning_level, const std::string &message, int message_id = 0) { + std::pair active_step = m_state.active_step_add_warning(warning_level, message, message_id, PrintObjectBase::state_mutex(m_print)); + if (active_step.second) + this->status_update_warnings(m_print, static_cast(active_step.first), warning_level, message); + } + protected: // If the background processing stop was requested, throw CanceledException. // To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 0bbf773ecd1..e1bd3ad675b 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -85,6 +85,8 @@ void PrintConfigDef::init_common_params() def->label = L("Max print height"); def->tooltip = L("Set this to the maximum height that can be reached by your extruder while printing."); def->sidetext = L("mm"); + def->min = 0; + def->max = 1200; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(200.0)); @@ -1106,8 +1108,9 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionBool(false)); def = this->add("ironing_type", coEnum); - def->label = L("Ironingy Type"); - def->tooltip = L("Ironingy Type"); + def->label = L("Ironing Type"); + def->category = L("Ironing"); + def->tooltip = L("Ironing Type"); def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("top"); def->enum_values.push_back("topmost"); @@ -1130,7 +1133,8 @@ void PrintConfigDef::init_fff_params() def = this->add("ironing_spacing", coFloat); def->label = L("Spacing between ironing passes"); - def->tooltip = L("Distance between ironing lins"); + def->category = L("Ironing"); + def->tooltip = L("Distance between ironing lines"); def->sidetext = L("mm"); def->min = 0; def->mode = comExpert; @@ -1917,6 +1921,33 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert; def->set_default_value(new ConfigOptionStrings { "; Filament gcode\n" }); + def = this->add("color_change_gcode", coString); + def->label = L("Color change G-code"); + def->tooltip = L("This G-code will be used as a code for the color change"); + def->multiline = true; + def->full_width = true; + def->height = 12; + def->mode = comExpert; + def->set_default_value(new ConfigOptionString("M600")); + + def = this->add("pause_print_gcode", coString); + def->label = L("Pause Print G-code"); + def->tooltip = L("This G-code will be used as a code for the pause print"); + def->multiline = true; + def->full_width = true; + def->height = 12; + def->mode = comExpert; + def->set_default_value(new ConfigOptionString("M601")); + + def = this->add("template_custom_gcode", coString); + def->label = L("Custom G-code"); + def->tooltip = L("This G-code will be used as a custom code"); + def->multiline = true; + def->full_width = true; + def->height = 12; + def->mode = comExpert; + def->set_default_value(new ConfigOptionString("")); + def = this->add("single_extruder_multi_material", coBool); def->label = L("Single Extruder Multi Material"); def->tooltip = L("The printer multiplexes filaments into a single hot end."); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index c7afc250f7d..5116e21abfb 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -33,7 +33,7 @@ enum PrintHostType { htOctoPrint, htDuet, htFlashAir, htAstroBox }; -enum InfillPattern { +enum InfillPattern : int { ipRectilinear, ipMonotonous, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb, ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipCount, }; @@ -701,6 +701,9 @@ class GCodeConfig : public StaticPrintConfig ConfigOptionBool remaining_times; ConfigOptionBool silent_mode; ConfigOptionFloat extra_loading_move; + ConfigOptionString color_change_gcode; + ConfigOptionString pause_print_gcode; + ConfigOptionString template_custom_gcode; std::string get_extrusion_axis() const { @@ -774,6 +777,9 @@ class GCodeConfig : public StaticPrintConfig OPT_PTR(remaining_times); OPT_PTR(silent_mode); OPT_PTR(extra_loading_move); + OPT_PTR(color_change_gcode); + OPT_PTR(pause_print_gcode); + OPT_PTR(template_custom_gcode); } }; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 530416ababb..7ad0b3a8b4e 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -4,6 +4,7 @@ #include "ElephantFootCompensation.hpp" #include "Geometry.hpp" #include "I18N.hpp" +#include "Layer.hpp" #include "SupportMaterial.hpp" #include "Surface.hpp" #include "Slicing.hpp" @@ -1437,7 +1438,7 @@ void PrintObject::bridge_over_infill() } #ifdef SLIC3R_DEBUG - printf("Bridging " PRINTF_ZU " internal areas at layer " PRINTF_ZU "\n", to_bridge.size(), layer->id()); + printf("Bridging %zu internal areas at layer %zu\n", to_bridge.size(), layer->id()); #endif // compute the remaning internal solid surfaces as difference @@ -1583,7 +1584,9 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c bool updated = false; if (layer_height_profile.empty()) { - layer_height_profile = model_object.layer_height_profile; + // use the constructor because the assignement is crashing on ASAN OsX + layer_height_profile = std::vector(model_object.layer_height_profile); +// layer_height_profile = model_object.layer_height_profile; updated = true; } @@ -2677,14 +2680,14 @@ void PrintObject::project_and_append_custom_supports( FacetSupportType type, std::vector& expolys) const { for (const ModelVolume* mv : this->model_object()->volumes) { - const std::vector custom_facets = mv->m_supported_facets.get_facets(type); - if (custom_facets.empty()) + const indexed_triangle_set custom_facets = mv->m_supported_facets.get_facets(*mv, type); + if (custom_facets.indices.empty()) continue; - const TriangleMesh& mesh = mv->mesh(); const Transform3f& tr1 = mv->get_matrix().cast(); const Transform3f& tr2 = this->trafo().cast(); const Transform3f tr = tr2 * tr1; + const float tr_det_sign = (tr.matrix().determinant() > 0. ? 1.f : -1.f); // The projection will be at most a pentagon. Let's minimize heap @@ -2709,11 +2712,11 @@ void PrintObject::project_and_append_custom_supports( }; // Vector to collect resulting projections from each triangle. - std::vector projections_of_triangles(custom_facets.size()); + std::vector projections_of_triangles(custom_facets.indices.size()); // Iterate over all triangles. tbb::parallel_for( - tbb::blocked_range(0, custom_facets.size()), + tbb::blocked_range(0, custom_facets.indices.size()), [&](const tbb::blocked_range& range) { for (size_t idx = range.begin(); idx < range.end(); ++ idx) { @@ -2721,10 +2724,11 @@ void PrintObject::project_and_append_custom_supports( // Transform the triangle into worlds coords. for (int i=0; i<3; ++i) - facet[i] = tr * mesh.its.vertices[mesh.its.indices[custom_facets[idx]](i)]; + facet[i] = tr * custom_facets.vertices[custom_facets.indices[idx](i)]; - // Ignore triangles with upward-pointing normal. - if ((facet[1]-facet[0]).cross(facet[2]-facet[0]).z() > 0.) + // Ignore triangles with upward-pointing normal. Don't forget about mirroring. + float z_comp = (facet[1]-facet[0]).cross(facet[2]-facet[0]).z(); + if (tr_det_sign * z_comp > 0.) continue; // Sort the three vertices according to z-coordinate. @@ -2836,4 +2840,28 @@ void PrintObject::project_and_append_custom_supports( } // loop over ModelVolumes } + + +const Layer* PrintObject::get_layer_at_printz(coordf_t print_z) const { + auto it = Slic3r::lower_bound_by_predicate(m_layers.begin(), m_layers.end(), [print_z](const Layer *layer) { return layer->print_z < print_z; }); + return (it == m_layers.end() || (*it)->print_z != print_z) ? nullptr : *it; +} + + + +Layer* PrintObject::get_layer_at_printz(coordf_t print_z) { return const_cast(std::as_const(*this).get_layer_at_printz(print_z)); } + + + +// Get a layer approximately at print_z. +const Layer* PrintObject::get_layer_at_printz(coordf_t print_z, coordf_t epsilon) const { + coordf_t limit = print_z - epsilon; + auto it = Slic3r::lower_bound_by_predicate(m_layers.begin(), m_layers.end(), [limit](const Layer *layer) { return layer->print_z < limit; }); + return (it == m_layers.end() || (*it)->print_z > print_z + epsilon) ? nullptr : *it; +} + + + +Layer* PrintObject::get_layer_at_printz(coordf_t print_z, coordf_t epsilon) { return const_cast(std::as_const(*this).get_layer_at_printz(print_z, epsilon)); } + } // namespace Slic3r diff --git a/src/libslic3r/SLA/Common.cpp b/src/libslic3r/SLA/Common.cpp index b57039ad139..a7420a7fb8c 100644 --- a/src/libslic3r/SLA/Common.cpp +++ b/src/libslic3r/SLA/Common.cpp @@ -1,18 +1,11 @@ #include #include #include -#include #include #include #include #include -#include - - -// Workaround: IGL signed_distance.h will define PI in the igl namespace. -#undef PI - -// HEAVY headers... takes eternity to compile +#include // for concave hull merging decisions #include @@ -23,24 +16,23 @@ #pragma warning(disable: 4244) #pragma warning(disable: 4267) #endif -#include -#include + + #include -#include -#include + +#ifdef SLIC3R_HOLE_RAYCASTER + #include +#endif + + #ifdef _MSC_VER #pragma warning(pop) #endif -#include - -#include "ClipperUtils.hpp" namespace Slic3r { namespace sla { -// Bring back PI from the igl namespace -using igl::PI; /* ************************************************************************** * PointIndex implementation @@ -188,100 +180,72 @@ void BoxIndex::foreach(std::function fn) * EigenMesh3D implementation * ****************************************************************************/ -class EigenMesh3D::AABBImpl: public igl::AABB { -public: -#ifdef SLIC3R_SLA_NEEDS_WINDTREE - igl::WindingNumberAABB windtree; -#endif /* SLIC3R_SLA_NEEDS_WINDTREE */ -}; -static const constexpr double MESH_EPS = 1e-6; +class EigenMesh3D::AABBImpl { +private: + AABBTreeIndirect::Tree3f m_tree; -void to_eigen_mesh(const TriangleMesh &tmesh, Eigen::MatrixXd &V, Eigen::MatrixXi &F) -{ - const stl_file& stl = tmesh.stl; - - V.resize(3*stl.stats.number_of_facets, 3); - F.resize(stl.stats.number_of_facets, 3); - for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) { - const stl_facet &facet = stl.facet_start[i]; - V.block<1, 3>(3 * i + 0, 0) = facet.vertex[0].cast(); - V.block<1, 3>(3 * i + 1, 0) = facet.vertex[1].cast(); - V.block<1, 3>(3 * i + 2, 0) = facet.vertex[2].cast(); - F(i, 0) = int(3*i+0); - F(i, 1) = int(3*i+1); - F(i, 2) = int(3*i+2); +public: + void init(const TriangleMesh& tm) + { + m_tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set( + tm.its.vertices, tm.its.indices); } - - if (!tmesh.has_shared_vertices()) + + void intersect_ray(const TriangleMesh& tm, + const Vec3d& s, const Vec3d& dir, igl::Hit& hit) { - Eigen::MatrixXd rV; - Eigen::MatrixXi rF; - // We will convert this to a proper 3d mesh with no duplicate points. - Eigen::VectorXi SVI, SVJ; - igl::remove_duplicate_vertices(V, F, MESH_EPS, rV, SVI, SVJ, rF); - V = std::move(rV); - F = std::move(rF); + AABBTreeIndirect::intersect_ray_first_hit(tm.its.vertices, + tm.its.indices, + m_tree, + s, dir, hit); } -} -void to_triangle_mesh(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, TriangleMesh &out) -{ - Pointf3s points(size_t(V.rows())); - std::vector facets(size_t(F.rows())); - - for (Eigen::Index i = 0; i < V.rows(); ++i) - points[size_t(i)] = V.row(i); - - for (Eigen::Index i = 0; i < F.rows(); ++i) - facets[size_t(i)] = F.row(i); - - out = {points, facets}; -} + void intersect_ray(const TriangleMesh& tm, + const Vec3d& s, const Vec3d& dir, std::vector& hits) + { + AABBTreeIndirect::intersect_ray_all_hits(tm.its.vertices, + tm.its.indices, + m_tree, + s, dir, hits); + } + + double squared_distance(const TriangleMesh& tm, + const Vec3d& point, int& i, Eigen::Matrix& closest) { + size_t idx_unsigned = 0; + Vec3d closest_vec3d(closest); + double dist = AABBTreeIndirect::squared_distance_to_indexed_triangle_set( + tm.its.vertices, + tm.its.indices, + m_tree, point, idx_unsigned, closest_vec3d); + i = int(idx_unsigned); + closest = closest_vec3d; + return dist; + } +}; + +static const constexpr double MESH_EPS = 1e-6; -EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { +EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh) + : m_aabb(new AABBImpl()), m_tm(&tmesh) +{ auto&& bb = tmesh.bounding_box(); m_ground_level += bb.min(Z); - to_eigen_mesh(tmesh, m_V, m_F); - // Build the AABB accelaration tree - m_aabb->init(m_V, m_F); -#ifdef SLIC3R_SLA_NEEDS_WINDTREE - m_aabb->windtree.set_mesh(m_V, m_F); -#endif /* SLIC3R_SLA_NEEDS_WINDTREE */ + m_aabb->init(tmesh); } EigenMesh3D::~EigenMesh3D() {} EigenMesh3D::EigenMesh3D(const EigenMesh3D &other): - m_V(other.m_V), m_F(other.m_F), m_ground_level(other.m_ground_level), + m_tm(other.m_tm), m_ground_level(other.m_ground_level), m_aabb( new AABBImpl(*other.m_aabb) ) {} -EigenMesh3D::EigenMesh3D(const Contour3D &other) -{ - m_V.resize(Eigen::Index(other.points.size()), 3); - m_F.resize(Eigen::Index(other.faces3.size() + 2 * other.faces4.size()), 3); - - for (Eigen::Index i = 0; i < Eigen::Index(other.points.size()); ++i) - m_V.row(i) = other.points[size_t(i)]; - - for (Eigen::Index i = 0; i < Eigen::Index(other.faces3.size()); ++i) - m_F.row(i) = other.faces3[size_t(i)]; - - size_t N = other.faces3.size() + 2 * other.faces4.size(); - for (size_t i = other.faces3.size(); i < N; i += 2) { - size_t quad_idx = (i - other.faces3.size()) / 2; - auto & quad = other.faces4[quad_idx]; - m_F.row(Eigen::Index(i)) = Vec3i{quad(0), quad(1), quad(2)}; - m_F.row(Eigen::Index(i + 1)) = Vec3i{quad(2), quad(3), quad(0)}; - } -} EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other) { - m_V = other.m_V; - m_F = other.m_F; + m_tm = other.m_tm; m_ground_level = other.m_ground_level; m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this; } @@ -290,6 +254,42 @@ EigenMesh3D &EigenMesh3D::operator=(EigenMesh3D &&other) = default; EigenMesh3D::EigenMesh3D(EigenMesh3D &&other) = default; + + +const std::vector& EigenMesh3D::vertices() const +{ + return m_tm->its.vertices; +} + + + +const std::vector& EigenMesh3D::indices() const +{ + return m_tm->its.indices; +} + + + +const Vec3f& EigenMesh3D::vertices(size_t idx) const +{ + return m_tm->its.vertices[idx]; +} + + + +const Vec3i& EigenMesh3D::indices(size_t idx) const +{ + return m_tm->its.indices[idx]; +} + + + +Vec3d EigenMesh3D::normal_by_face_id(int face_id) const { + return m_tm->stl.facet_start[face_id].normal.cast(); +} + + + EigenMesh3D::hit_result EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const { @@ -297,24 +297,26 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const igl::Hit hit; hit.t = std::numeric_limits::infinity(); - if (m_holes.empty()) { - m_aabb->intersect_ray(m_V, m_F, s, dir, hit); - hit_result ret(*this); - ret.m_t = double(hit.t); - ret.m_dir = dir; - ret.m_source = s; - if(!std::isinf(hit.t) && !std::isnan(hit.t)) { - ret.m_normal = this->normal_by_face_id(hit.id); - ret.m_face_id = hit.id; - } +#ifdef SLIC3R_HOLE_RAYCASTER + if (! m_holes.empty()) { - return ret; - } - else { // If there are holes, the hit_results will be made by // query_ray_hits (object) and filter_hits (holes): return filter_hits(query_ray_hits(s, dir)); } +#endif + + m_aabb->intersect_ray(*m_tm, s, dir, hit); + hit_result ret(*this); + ret.m_t = double(hit.t); + ret.m_dir = dir; + ret.m_source = s; + if(!std::isinf(hit.t) && !std::isnan(hit.t)) { + ret.m_normal = this->normal_by_face_id(hit.id); + ret.m_face_id = hit.id; + } + + return ret; } std::vector @@ -322,7 +324,7 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const { std::vector outs; std::vector hits; - m_aabb->intersect_ray(m_V, m_F, s, dir, hits); + m_aabb->intersect_ray(*m_tm, s, dir, hits); // The sort is necessary, the hits are not always sorted. std::sort(hits.begin(), hits.end(), @@ -351,6 +353,8 @@ EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const return outs; } + +#ifdef SLIC3R_HOLE_RAYCASTER EigenMesh3D::hit_result EigenMesh3D::filter_hits( const std::vector& object_hits) const { @@ -445,26 +449,14 @@ EigenMesh3D::hit_result EigenMesh3D::filter_hits( // if we got here, the ray ended up in infinity return out; } +#endif -#ifdef SLIC3R_SLA_NEEDS_WINDTREE -EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const { - double sign = 0; double sqdst = 0; int i = 0; Vec3d c; - igl::signed_distance_winding_number(*m_aabb, m_V, m_F, m_aabb->windtree, - p, sign, sqdst, i, c); - - return si_result(sign * std::sqrt(sqdst), i, c); -} - -bool EigenMesh3D::inside(const Vec3d &p) const { - return m_aabb->windtree.inside(p); -} -#endif /* SLIC3R_SLA_NEEDS_WINDTREE */ double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const { double sqdst = 0; Eigen::Matrix pp = p; Eigen::Matrix cc; - sqdst = m_aabb->squared_distance(m_V, m_F, pp, i, cc); + sqdst = m_aabb->squared_distance(*m_tm, pp, i, cc); c = cc; return sqdst; } @@ -498,7 +490,7 @@ PointSet normals(const PointSet& points, std::function thr, // throw on cancel const std::vector& pt_indices) { - if (points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0) + if (points.rows() == 0 || mesh.vertices().empty() || mesh.indices().empty()) return {}; std::vector range = pt_indices; @@ -520,11 +512,11 @@ PointSet normals(const PointSet& points, mesh.squared_distance(points.row(eidx), faceid, p); - auto trindex = mesh.F().row(faceid); + auto trindex = mesh.indices(faceid); - const Vec3d &p1 = mesh.V().row(trindex(0)); - const Vec3d &p2 = mesh.V().row(trindex(1)); - const Vec3d &p3 = mesh.V().row(trindex(2)); + const Vec3d &p1 = mesh.vertices(trindex(0)).cast(); + const Vec3d &p2 = mesh.vertices(trindex(1)).cast(); + const Vec3d &p3 = mesh.vertices(trindex(2)).cast(); // We should check if the point lies on an edge of the hosting // triangle. If it does then all the other triangles using the @@ -557,36 +549,30 @@ PointSet normals(const PointSet& points, } // vector for the neigboring triangles including the detected one. - std::vector neigh; + std::vector neigh; if (ic >= 0) { // The point is right on a vertex of the triangle - for (int n = 0; n < mesh.F().rows(); ++n) { + for (size_t n = 0; n < mesh.indices().size(); ++n) { thr(); - Vec3i ni = mesh.F().row(n); + Vec3i ni = mesh.indices(n); if ((ni(X) == ic || ni(Y) == ic || ni(Z) == ic)) - neigh.emplace_back(ni); + neigh.emplace_back(n); } } else if (ia >= 0 && ib >= 0) { // the point is on and edge // now get all the neigboring triangles - for (int n = 0; n < mesh.F().rows(); ++n) { + for (size_t n = 0; n < mesh.indices().size(); ++n) { thr(); - Vec3i ni = mesh.F().row(n); + Vec3i ni = mesh.indices(n); if ((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) && (ni(X) == ib || ni(Y) == ib || ni(Z) == ib)) - neigh.emplace_back(ni); + neigh.emplace_back(n); } } // Calculate the normals for the neighboring triangles std::vector neighnorms; neighnorms.reserve(neigh.size()); - for (const Vec3i &tri : neigh) { - const Vec3d & pt1 = mesh.V().row(tri(0)); - const Vec3d & pt2 = mesh.V().row(tri(1)); - const Vec3d & pt3 = mesh.V().row(tri(2)); - Eigen::Vector3d U = pt2 - pt1; - Eigen::Vector3d V = pt3 - pt1; - neighnorms.emplace_back(U.cross(V).normalized()); - } + for (size_t &tri_id : neigh) + neighnorms.emplace_back(mesh.normal_by_face_id(tri_id)); // Throw out duplicates. They would cause trouble with summing. We // will use std::unique which works on sorted ranges. We will sort diff --git a/src/libslic3r/SLA/Common.hpp b/src/libslic3r/SLA/Common.hpp index e1c5930e2aa..ca616cabce9 100644 --- a/src/libslic3r/SLA/Common.hpp +++ b/src/libslic3r/SLA/Common.hpp @@ -7,18 +7,13 @@ #include #include -//#include "SLASpatIndex.hpp" - -//#include -//#include - -// #define SLIC3R_SLA_NEEDS_WINDTREE namespace Slic3r { // Typedefs from Point.hpp typedef Eigen::Matrix Vec3f; typedef Eigen::Matrix Vec3d; +typedef Eigen::Matrix Vec3i; typedef Eigen::Matrix Vec4i; namespace sla { diff --git a/src/libslic3r/SLA/Contour3D.cpp b/src/libslic3r/SLA/Contour3D.cpp index e39672b8bdd..408465d43ec 100644 --- a/src/libslic3r/SLA/Contour3D.cpp +++ b/src/libslic3r/SLA/Contour3D.cpp @@ -28,14 +28,14 @@ Contour3D::Contour3D(TriangleMesh &&trmesh) } Contour3D::Contour3D(const EigenMesh3D &emesh) { - points.reserve(size_t(emesh.V().rows())); - faces3.reserve(size_t(emesh.F().rows())); + points.reserve(emesh.vertices().size()); + faces3.reserve(emesh.indices().size()); - for (int r = 0; r < emesh.V().rows(); r++) - points.emplace_back(emesh.V().row(r).cast()); + for (const Vec3f& vert : emesh.vertices()) + points.emplace_back(vert.cast()); - for (int i = 0; i < emesh.F().rows(); i++) - faces3.emplace_back(emesh.F().row(i)); + for (const auto& ind : emesh.indices()) + faces3.emplace_back(ind); } Contour3D &Contour3D::merge(const Contour3D &ctr) diff --git a/src/libslic3r/SLA/EigenMesh3D.hpp b/src/libslic3r/SLA/EigenMesh3D.hpp index 014a57e8208..b932c0c18e2 100644 --- a/src/libslic3r/SLA/EigenMesh3D.hpp +++ b/src/libslic3r/SLA/EigenMesh3D.hpp @@ -2,7 +2,16 @@ #define SLA_EIGENMESH3D_H #include -#include "libslic3r/SLA/Hollowing.hpp" + + +// There is an implementation of a hole-aware raycaster that was eventually +// not used in production version. It is now hidden under following define +// for possible future use. +// #define SLIC3R_HOLE_RAYCASTER + +#ifdef SLIC3R_HOLE_RAYCASTER + #include "libslic3r/SLA/Hollowing.hpp" +#endif namespace Slic3r { @@ -10,31 +19,26 @@ class TriangleMesh; namespace sla { -struct Contour3D; - -void to_eigen_mesh(const TriangleMesh &mesh, Eigen::MatrixXd &V, Eigen::MatrixXi &F); -void to_triangle_mesh(const Eigen::MatrixXd &V, const Eigen::MatrixXi &F, TriangleMesh &); - /// An index-triangle structure for libIGL functions. Also serves as an /// alternative (raw) input format for the SLASupportTree. // Implemented in libslic3r/SLA/Common.cpp class EigenMesh3D { class AABBImpl; - Eigen::MatrixXd m_V; - Eigen::MatrixXi m_F; + const TriangleMesh* m_tm; double m_ground_level = 0, m_gnd_offset = 0; std::unique_ptr m_aabb; +#ifdef SLIC3R_HOLE_RAYCASTER // This holds a copy of holes in the mesh. Initialized externally // by load_mesh setter. std::vector m_holes; +#endif public: explicit EigenMesh3D(const TriangleMesh&); - explicit EigenMesh3D(const Contour3D &other); EigenMesh3D(const EigenMesh3D& other); EigenMesh3D& operator=(const EigenMesh3D&); @@ -48,8 +52,10 @@ class EigenMesh3D { inline void ground_level_offset(double o) { m_gnd_offset = o; } inline double ground_level_offset() const { return m_gnd_offset; } - inline const Eigen::MatrixXd& V() const { return m_V; } - inline const Eigen::MatrixXi& F() const { return m_F; } + const std::vector& vertices() const; + const std::vector& indices() const; + const Vec3f& vertices(size_t idx) const; + const Vec3i& indices(size_t idx) const; // Result of a raycast class hit_result { @@ -88,51 +94,28 @@ class EigenMesh3D { return is_hit() && normal().dot(m_dir) > 0; } }; - + +#ifdef SLIC3R_HOLE_RAYCASTER // Inform the object about location of holes // creates internal copy of the vector void load_holes(const std::vector& holes) { m_holes = holes; } - // Casting a ray on the mesh, returns the distance where the hit occures. - hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const; - - // Casts a ray on the mesh and returns all hits - std::vector query_ray_hits(const Vec3d &s, const Vec3d &dir) const; - // Iterates over hits and holes and returns the true hit, possibly // on the inside of a hole. // This function is currently not used anywhere, it was written when the // holes were subtracted on slices, that is, before we started using CGAL // to actually cut the holes into the mesh. hit_result filter_hits(const std::vector& obj_hits) const; +#endif - class si_result { - double m_value; - int m_fidx; - Vec3d m_p; - si_result(double val, int i, const Vec3d& c): - m_value(val), m_fidx(i), m_p(c) {} - friend class EigenMesh3D; - public: - - si_result() = delete; - - double value() const { return m_value; } - operator double() const { return m_value; } - const Vec3d& point_on_mesh() const { return m_p; } - int F_idx() const { return m_fidx; } - }; - -#ifdef SLIC3R_SLA_NEEDS_WINDTREE - // The signed distance from a point to the mesh. Outputs the distance, - // the index of the triangle and the closest point in mesh coordinate space. - si_result signed_distance(const Vec3d& p) const; - - bool inside(const Vec3d& p) const; -#endif /* SLIC3R_SLA_NEEDS_WINDTREE */ + // Casting a ray on the mesh, returns the distance where the hit occures. + hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const; + // Casts a ray on the mesh and returns all hits + std::vector query_ray_hits(const Vec3d &s, const Vec3d &dir) const; + double squared_distance(const Vec3d& p, int& i, Vec3d& c) const; inline double squared_distance(const Vec3d &p) const { @@ -141,15 +124,7 @@ class EigenMesh3D { return squared_distance(p, i, c); } - Vec3d normal_by_face_id(int face_id) const { - auto trindex = F().row(face_id); - const Vec3d& p1 = V().row(trindex(0)); - const Vec3d& p2 = V().row(trindex(1)); - const Vec3d& p3 = V().row(trindex(2)); - Eigen::Vector3d U = p2 - p1; - Eigen::Vector3d V = p3 - p1; - return U.cross(V).normalized(); - } + Vec3d normal_by_face_id(int face_id) const; }; // Calculate the normals for the selected points (from 'points' set) on the diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index c4a616d93aa..0dd9436a1d1 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -97,7 +97,7 @@ std::unique_ptr generate_interior(const TriangleMesh & mesh, _generate_interior(mesh, ctl, hc.min_thickness, voxel_scale, hc.closing_distance)); - if (meshptr) { + if (meshptr && !meshptr->empty()) { // This flips the normals to be outward facing... meshptr->require_shared_vertices(); diff --git a/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp b/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp index fa919a1170b..702d1bce182 100644 --- a/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp +++ b/src/libslic3r/SLA/ReprojectPointsOnMesh.hpp @@ -28,17 +28,25 @@ void reproject_support_points(const EigenMesh3D &mesh, std::vector &p inline void reproject_points_and_holes(ModelObject *object) { bool has_sppoints = !object->sla_support_points.empty(); - bool has_holes = !object->sla_drain_holes.empty(); - if (!object || (!has_holes && !has_sppoints)) return; + // Disabling reprojection of holes as they have a significant offset away + // from the model body which tolerates minor geometrical changes. + // + // TODO: uncomment and ensure the right offset of the hole points if + // reprojection would still be necessary. + // bool has_holes = !object->sla_drain_holes.empty(); - EigenMesh3D emesh{object->raw_mesh()}; + if (!object || (/*!has_holes &&*/ !has_sppoints)) return; + + TriangleMesh rmsh = object->raw_mesh(); + rmsh.require_shared_vertices(); + EigenMesh3D emesh{rmsh}; if (has_sppoints) reproject_support_points(emesh, object->sla_support_points); - if (has_holes) - reproject_support_points(emesh, object->sla_drain_holes); +// if (has_holes) +// reproject_support_points(emesh, object->sla_drain_holes); } }} diff --git a/src/libslic3r/SLA/Rotfinder.cpp b/src/libslic3r/SLA/Rotfinder.cpp index 9ec9837e2aa..fda8383b11a 100644 --- a/src/libslic3r/SLA/Rotfinder.cpp +++ b/src/libslic3r/SLA/Rotfinder.cpp @@ -28,7 +28,7 @@ std::array find_best_rotation(const ModelObject& modelobj, // We will use only one instance of this converted mesh to examine different // rotations - EigenMesh3D emesh(modelobj.raw_mesh()); + const TriangleMesh& mesh = modelobj.raw_mesh(); // For current iteration number unsigned status = 0; @@ -44,10 +44,10 @@ std::array find_best_rotation(const ModelObject& modelobj, // call the status callback in each iteration but the actual value may be // the same for subsequent iterations (status goes from 0 to 100 but // iterations can be many more) - auto objfunc = [&emesh, &status, &statuscb, &stopcond, max_tries] + auto objfunc = [&mesh, &status, &statuscb, &stopcond, max_tries] (double rx, double ry, double rz) { - EigenMesh3D& m = emesh; + const TriangleMesh& m = mesh; // prepare the rotation transformation Transform3d rt = Transform3d::Identity(); @@ -68,18 +68,8 @@ std::array find_best_rotation(const ModelObject& modelobj, // area. The current function is only an example of how to optimize. // Later we can add more criteria like the number of overhangs, etc... - for(int i = 0; i < m.F().rows(); i++) { - auto idx = m.F().row(i); - - Vec3d p1 = m.V().row(idx(0)); - Vec3d p2 = m.V().row(idx(1)); - Vec3d p3 = m.V().row(idx(2)); - - Eigen::Vector3d U = p2 - p1; - Eigen::Vector3d V = p3 - p1; - - // So this is the normal - auto n = U.cross(V).normalized(); + for(size_t i = 0; i < m.stl.facet_start.size(); i++) { + Vec3d n = m.stl.facet_start[i].normal.cast(); // rotate the normal with the current rotation given by the solver n = rt * n; diff --git a/src/libslic3r/SLA/SupportPointGenerator.hpp b/src/libslic3r/SLA/SupportPointGenerator.hpp index 738fc57303c..2fe8e11fc7f 100644 --- a/src/libslic3r/SLA/SupportPointGenerator.hpp +++ b/src/libslic3r/SLA/SupportPointGenerator.hpp @@ -7,9 +7,9 @@ #include #include +#include #include #include -#include #include diff --git a/src/libslic3r/SLA/SupportTreeIGL.cpp b/src/libslic3r/SLA/SupportTreeIGL.cpp deleted file mode 100644 index ea2be6be11c..00000000000 --- a/src/libslic3r/SLA/SupportTreeIGL.cpp +++ /dev/null @@ -1,621 +0,0 @@ -#include -#include "SLA/SLASupportTree.hpp" -#include "SLA/SLACommon.hpp" -#include "SLA/SLASpatIndex.hpp" - -// Workaround: IGL signed_distance.h will define PI in the igl namespace. -#undef PI - -// HEAVY headers... takes eternity to compile - -// for concave hull merging decisions -#include "SLABoostAdapter.hpp" -#include "boost/geometry/index/rtree.hpp" - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4244) -#pragma warning(disable: 4267) -#endif -#include -#include -#include -#include -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -#include - -#include "SLASpatIndex.hpp" -#include "ClipperUtils.hpp" - -namespace Slic3r { -namespace sla { - -// Bring back PI from the igl namespace -using igl::PI; - -/* ************************************************************************** - * PointIndex implementation - * ************************************************************************** */ - -class PointIndex::Impl { -public: - using BoostIndex = boost::geometry::index::rtree< PointIndexEl, - boost::geometry::index::rstar<16, 4> /* ? */ >; - - BoostIndex m_store; -}; - -PointIndex::PointIndex(): m_impl(new Impl()) {} -PointIndex::~PointIndex() {} - -PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} -PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} - -PointIndex& PointIndex::operator=(const PointIndex &cpy) -{ - m_impl.reset(new Impl(*cpy.m_impl)); - return *this; -} - -PointIndex& PointIndex::operator=(PointIndex &&cpy) -{ - m_impl.swap(cpy.m_impl); - return *this; -} - -void PointIndex::insert(const PointIndexEl &el) -{ - m_impl->m_store.insert(el); -} - -bool PointIndex::remove(const PointIndexEl& el) -{ - return m_impl->m_store.remove(el) == 1; -} - -std::vector -PointIndex::query(std::function fn) const -{ - namespace bgi = boost::geometry::index; - - std::vector ret; - m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret)); - return ret; -} - -std::vector PointIndex::nearest(const Vec3d &el, unsigned k = 1) const -{ - namespace bgi = boost::geometry::index; - std::vector ret; ret.reserve(k); - m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret)); - return ret; -} - -size_t PointIndex::size() const -{ - return m_impl->m_store.size(); -} - -void PointIndex::foreach(std::function fn) -{ - for(auto& el : m_impl->m_store) fn(el); -} - -void PointIndex::foreach(std::function fn) const -{ - for(const auto &el : m_impl->m_store) fn(el); -} - -/* ************************************************************************** - * BoxIndex implementation - * ************************************************************************** */ - -class BoxIndex::Impl { -public: - using BoostIndex = boost::geometry::index:: - rtree /* ? */>; - - BoostIndex m_store; -}; - -BoxIndex::BoxIndex(): m_impl(new Impl()) {} -BoxIndex::~BoxIndex() {} - -BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {} -BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {} - -BoxIndex& BoxIndex::operator=(const BoxIndex &cpy) -{ - m_impl.reset(new Impl(*cpy.m_impl)); - return *this; -} - -BoxIndex& BoxIndex::operator=(BoxIndex &&cpy) -{ - m_impl.swap(cpy.m_impl); - return *this; -} - -void BoxIndex::insert(const BoxIndexEl &el) -{ - m_impl->m_store.insert(el); -} - -bool BoxIndex::remove(const BoxIndexEl& el) -{ - return m_impl->m_store.remove(el) == 1; -} - -std::vector BoxIndex::query(const BoundingBox &qrbb, - BoxIndex::QueryType qt) -{ - namespace bgi = boost::geometry::index; - - std::vector ret; ret.reserve(m_impl->m_store.size()); - - switch (qt) { - case qtIntersects: - m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret)); - break; - case qtWithin: - m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret)); - } - - return ret; -} - -size_t BoxIndex::size() const -{ - return m_impl->m_store.size(); -} - -void BoxIndex::foreach(std::function fn) -{ - for(auto& el : m_impl->m_store) fn(el); -} - -/* **************************************************************************** - * EigenMesh3D implementation - * ****************************************************************************/ - -class EigenMesh3D::AABBImpl: public igl::AABB { -public: -#ifdef SLIC3R_SLA_NEEDS_WINDTREE - igl::WindingNumberAABB windtree; -#endif /* SLIC3R_SLA_NEEDS_WINDTREE */ -}; - -EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) { - static const double dEPS = 1e-6; - - const stl_file& stl = tmesh.stl; - - auto&& bb = tmesh.bounding_box(); - m_ground_level += bb.min(Z); - - Eigen::MatrixXd V; - Eigen::MatrixXi F; - - V.resize(3*stl.stats.number_of_facets, 3); - F.resize(stl.stats.number_of_facets, 3); - for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) { - const stl_facet &facet = stl.facet_start[i]; - V.block<1, 3>(3 * i + 0, 0) = facet.vertex[0].cast(); - V.block<1, 3>(3 * i + 1, 0) = facet.vertex[1].cast(); - V.block<1, 3>(3 * i + 2, 0) = facet.vertex[2].cast(); - F(i, 0) = int(3*i+0); - F(i, 1) = int(3*i+1); - F(i, 2) = int(3*i+2); - } - - // We will convert this to a proper 3d mesh with no duplicate points. - Eigen::VectorXi SVI, SVJ; - igl::remove_duplicate_vertices(V, F, dEPS, m_V, SVI, SVJ, m_F); - - // Build the AABB accelaration tree - m_aabb->init(m_V, m_F); -#ifdef SLIC3R_SLA_NEEDS_WINDTREE - m_aabb->windtree.set_mesh(m_V, m_F); -#endif /* SLIC3R_SLA_NEEDS_WINDTREE */ -} - -EigenMesh3D::~EigenMesh3D() {} - -EigenMesh3D::EigenMesh3D(const EigenMesh3D &other): - m_V(other.m_V), m_F(other.m_F), m_ground_level(other.m_ground_level), - m_aabb( new AABBImpl(*other.m_aabb) ) {} - -EigenMesh3D::EigenMesh3D(const Contour3D &other) -{ - m_V.resize(Eigen::Index(other.points.size()), 3); - m_F.resize(Eigen::Index(other.faces3.size() + 2 * other.faces4.size()), 3); - - for (Eigen::Index i = 0; i < Eigen::Index(other.points.size()); ++i) - m_V.row(i) = other.points[size_t(i)]; - - for (Eigen::Index i = 0; i < Eigen::Index(other.faces3.size()); ++i) - m_F.row(i) = other.faces3[size_t(i)]; - - size_t N = other.faces3.size() + 2 * other.faces4.size(); - for (size_t i = other.faces3.size(); i < N; i += 2) { - size_t quad_idx = (i - other.faces3.size()) / 2; - auto & quad = other.faces4[quad_idx]; - m_F.row(Eigen::Index(i)) = Vec3i{quad(0), quad(1), quad(2)}; - m_F.row(Eigen::Index(i + 1)) = Vec3i{quad(2), quad(3), quad(0)}; - } -} - -EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other) -{ - m_V = other.m_V; - m_F = other.m_F; - m_ground_level = other.m_ground_level; - m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this; -} - -EigenMesh3D::hit_result -EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const -{ - igl::Hit hit; - hit.t = std::numeric_limits::infinity(); - m_aabb->intersect_ray(m_V, m_F, s, dir, hit); - - hit_result ret(*this); - ret.m_t = double(hit.t); - ret.m_dir = dir; - ret.m_source = s; - if(!std::isinf(hit.t) && !std::isnan(hit.t)) ret.m_face_id = hit.id; - - return ret; -} - -std::vector -EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const -{ - std::vector outs; - std::vector hits; - m_aabb->intersect_ray(m_V, m_F, s, dir, hits); - - // The sort is necessary, the hits are not always sorted. - std::sort(hits.begin(), hits.end(), - [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; }); - - // Convert the igl::Hit into hit_result - outs.reserve(hits.size()); - for (const igl::Hit& hit : hits) { - outs.emplace_back(EigenMesh3D::hit_result(*this)); - outs.back().m_t = double(hit.t); - outs.back().m_dir = dir; - outs.back().m_source = s; - if(!std::isinf(hit.t) && !std::isnan(hit.t)) - outs.back().m_face_id = hit.id; - } - - return outs; -} - -#ifdef SLIC3R_SLA_NEEDS_WINDTREE -EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const { - double sign = 0; double sqdst = 0; int i = 0; Vec3d c; - igl::signed_distance_winding_number(*m_aabb, m_V, m_F, m_aabb->windtree, - p, sign, sqdst, i, c); - - return si_result(sign * std::sqrt(sqdst), i, c); -} - -bool EigenMesh3D::inside(const Vec3d &p) const { - return m_aabb->windtree.inside(p); -} -#endif /* SLIC3R_SLA_NEEDS_WINDTREE */ - -double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const { - double sqdst = 0; - Eigen::Matrix pp = p; - Eigen::Matrix cc; - sqdst = m_aabb->squared_distance(m_V, m_F, pp, i, cc); - c = cc; - return sqdst; -} - -/* **************************************************************************** - * Misc functions - * ****************************************************************************/ - -namespace { - -bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2, - double eps = 0.05) -{ - using Line3D = Eigen::ParametrizedLine; - - auto line = Line3D::Through(e1, e2); - double d = line.distance(p); - return std::abs(d) < eps; -} - -template double distance(const Vec& pp1, const Vec& pp2) { - auto p = pp2 - pp1; - return std::sqrt(p.transpose() * p); -} - -} - -PointSet normals(const PointSet& points, - const EigenMesh3D& mesh, - double eps, - std::function thr, // throw on cancel - const std::vector& pt_indices) -{ - if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0) - return {}; - - std::vector range = pt_indices; - if(range.empty()) { - range.resize(size_t(points.rows()), 0); - std::iota(range.begin(), range.end(), 0); - } - - PointSet ret(range.size(), 3); - -// for (size_t ridx = 0; ridx < range.size(); ++ridx) - tbb::parallel_for(size_t(0), range.size(), - [&ret, &range, &mesh, &points, thr, eps](size_t ridx) - { - thr(); - auto eidx = Eigen::Index(range[ridx]); - int faceid = 0; - Vec3d p; - - mesh.squared_distance(points.row(eidx), faceid, p); - - auto trindex = mesh.F().row(faceid); - - const Vec3d& p1 = mesh.V().row(trindex(0)); - const Vec3d& p2 = mesh.V().row(trindex(1)); - const Vec3d& p3 = mesh.V().row(trindex(2)); - - // We should check if the point lies on an edge of the hosting triangle. - // If it does then all the other triangles using the same two points - // have to be searched and the final normal should be some kind of - // aggregation of the participating triangle normals. We should also - // consider the cases where the support point lies right on a vertex - // of its triangle. The procedure is the same, get the neighbor - // triangles and calculate an average normal. - - // mark the vertex indices of the edge. ia and ib marks and edge ic - // will mark a single vertex. - int ia = -1, ib = -1, ic = -1; - - if(std::abs(distance(p, p1)) < eps) { - ic = trindex(0); - } - else if(std::abs(distance(p, p2)) < eps) { - ic = trindex(1); - } - else if(std::abs(distance(p, p3)) < eps) { - ic = trindex(2); - } - else if(point_on_edge(p, p1, p2, eps)) { - ia = trindex(0); ib = trindex(1); - } - else if(point_on_edge(p, p2, p3, eps)) { - ia = trindex(1); ib = trindex(2); - } - else if(point_on_edge(p, p1, p3, eps)) { - ia = trindex(0); ib = trindex(2); - } - - // vector for the neigboring triangles including the detected one. - std::vector neigh; - if(ic >= 0) { // The point is right on a vertex of the triangle - for(int n = 0; n < mesh.F().rows(); ++n) { - thr(); - Vec3i ni = mesh.F().row(n); - if((ni(X) == ic || ni(Y) == ic || ni(Z) == ic)) - neigh.emplace_back(ni); - } - } - else if(ia >= 0 && ib >= 0) { // the point is on and edge - // now get all the neigboring triangles - for(int n = 0; n < mesh.F().rows(); ++n) { - thr(); - Vec3i ni = mesh.F().row(n); - if((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) && - (ni(X) == ib || ni(Y) == ib || ni(Z) == ib)) - neigh.emplace_back(ni); - } - } - - // Calculate the normals for the neighboring triangles - std::vector neighnorms; neighnorms.reserve(neigh.size()); - for(const Vec3i& tri : neigh) { - const Vec3d& pt1 = mesh.V().row(tri(0)); - const Vec3d& pt2 = mesh.V().row(tri(1)); - const Vec3d& pt3 = mesh.V().row(tri(2)); - Eigen::Vector3d U = pt2 - pt1; - Eigen::Vector3d V = pt3 - pt1; - neighnorms.emplace_back(U.cross(V).normalized()); - } - - // Throw out duplicates. They would cause trouble with summing. We will - // use std::unique which works on sorted ranges. We will sort by the - // coefficient-wise sum of the normals. It should force the same - // elements to be consecutive. - std::sort(neighnorms.begin(), neighnorms.end(), - [](const Vec3d& v1, const Vec3d& v2){ - return v1.sum() < v2.sum(); - }); - - auto lend = std::unique(neighnorms.begin(), neighnorms.end(), - [](const Vec3d& n1, const Vec3d& n2) { - // Compare normals for equivalence. This is controvers stuff. - auto deq = [](double a, double b) { return std::abs(a-b) < 1e-3; }; - return deq(n1(X), n2(X)) && deq(n1(Y), n2(Y)) && deq(n1(Z), n2(Z)); - }); - - if(!neighnorms.empty()) { // there were neighbors to count with - // sum up the normals and then normalize the result again. - // This unification seems to be enough. - Vec3d sumnorm(0, 0, 0); - sumnorm = std::accumulate(neighnorms.begin(), lend, sumnorm); - sumnorm.normalize(); - ret.row(long(ridx)) = sumnorm; - } - else { // point lies safely within its triangle - Eigen::Vector3d U = p2 - p1; - Eigen::Vector3d V = p3 - p1; - ret.row(long(ridx)) = U.cross(V).normalized(); - } - }); - - return ret; -} - -namespace bgi = boost::geometry::index; -using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >; - -namespace { - -bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2) -{ - return e1.second < e2.second; -}; - -ClusteredPoints cluster(Index3D &sindex, - unsigned max_points, - std::function( - const Index3D &, const PointIndexEl &)> qfn) -{ - using Elems = std::vector; - - // Recursive function for visiting all the points in a given distance to - // each other - std::function group = - [&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster) - { - for(auto& p : pts) { - std::vector tmp = qfn(sindex, p); - - std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements); - - Elems newpts; - std::set_difference(tmp.begin(), tmp.end(), - cluster.begin(), cluster.end(), - std::back_inserter(newpts), cmp_ptidx_elements); - - int c = max_points && newpts.size() + cluster.size() > max_points? - int(max_points - cluster.size()) : int(newpts.size()); - - cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c); - std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements); - - if(!newpts.empty() && (!max_points || cluster.size() < max_points)) - group(newpts, cluster); - } - }; - - std::vector clusters; - for(auto it = sindex.begin(); it != sindex.end();) { - Elems cluster = {}; - Elems pts = {*it}; - group(pts, cluster); - - for(auto& c : cluster) sindex.remove(c); - it = sindex.begin(); - - clusters.emplace_back(cluster); - } - - ClusteredPoints result; - for(auto& cluster : clusters) { - result.emplace_back(); - for(auto c : cluster) result.back().emplace_back(c.second); - } - - return result; -} - -std::vector distance_queryfn(const Index3D& sindex, - const PointIndexEl& p, - double dist, - unsigned max_points) -{ - std::vector tmp; tmp.reserve(max_points); - sindex.query( - bgi::nearest(p.first, max_points), - std::back_inserter(tmp) - ); - - for(auto it = tmp.begin(); it < tmp.end(); ++it) - if(distance(p.first, it->first) > dist) it = tmp.erase(it); - - return tmp; -} - -} // namespace - -// Clustering a set of points by the given criteria -ClusteredPoints cluster( - const std::vector& indices, - std::function pointfn, - double dist, - unsigned max_points) -{ - // A spatial index for querying the nearest points - Index3D sindex; - - // Build the index - for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); - - return cluster(sindex, max_points, - [dist, max_points](const Index3D& sidx, const PointIndexEl& p) - { - return distance_queryfn(sidx, p, dist, max_points); - }); -} - -// Clustering a set of points by the given criteria -ClusteredPoints cluster( - const std::vector& indices, - std::function pointfn, - std::function predicate, - unsigned max_points) -{ - // A spatial index for querying the nearest points - Index3D sindex; - - // Build the index - for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx)); - - return cluster(sindex, max_points, - [max_points, predicate](const Index3D& sidx, const PointIndexEl& p) - { - std::vector tmp; tmp.reserve(max_points); - sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){ - return predicate(p, e); - }), std::back_inserter(tmp)); - return tmp; - }); -} - -ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points) -{ - // A spatial index for querying the nearest points - Index3D sindex; - - // Build the index - for(Eigen::Index i = 0; i < pts.rows(); i++) - sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i))); - - return cluster(sindex, max_points, - [dist, max_points](const Index3D& sidx, const PointIndexEl& p) - { - return distance_queryfn(sidx, p, dist, max_points); - }); -} - -} // namespace sla -} // namespace Slic3r diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index a2720756560..9d41586ee64 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -429,6 +429,13 @@ class SLAPrint : public PrintBaseWithState bool finished() const override { return this->is_step_done(slaposSliceSupports) && this->Inherited::is_step_done(slapsRasterize); } const PrintObjects& objects() const { return m_objects; } + // PrintObject by its ObjectID, to be used to uniquely bind slicing warnings to their source PrintObjects + // in the notification center. + const SLAPrintObject* get_object(ObjectID object_id) const { + auto it = std::find_if(m_objects.begin(), m_objects.end(), + [object_id](const SLAPrintObject *obj) { return obj->id() == object_id; }); + return (it == m_objects.end()) ? nullptr : *it; + } const SLAPrintConfig& print_config() const { return m_print_config; } const SLAPrinterConfig& printer_config() const { return m_printer_config; } diff --git a/src/libslic3r/SVG.cpp b/src/libslic3r/SVG.cpp index 6e4b973eac9..1c1c906c9fc 100644 --- a/src/libslic3r/SVG.cpp +++ b/src/libslic3r/SVG.cpp @@ -21,6 +21,7 @@ bool SVG::open(const char* afilename) " \n" " \n" ); + fprintf(this->f, "\n", 2000.f, 2000.f); return true; } @@ -42,6 +43,7 @@ bool SVG::open(const char* afilename, const BoundingBox &bbox, const coord_t bbo " \n" " \n", h, w); + fprintf(this->f, "\n", w, h); return true; } diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index 95cf6891b21..2fd609b2c50 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -11,7 +11,6 @@ #include "libslic3r.h" #include "Utils.hpp" -#include "PrintConfig.hpp" namespace Slic3r { @@ -19,6 +18,7 @@ namespace Slic3r class PrintConfig; class PrintObjectConfig; class ModelObject; +class DynamicPrintConfig; // Parameters to guide object slicing and support generation. // The slicing parameters account for a raft and whether the 1st object layer is printed with a normal or a bridging flow diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index a8285064b59..c6991c057bc 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -34,17 +34,6 @@ #define ENABLE_HACK_CLOSING_ON_OSX_10_9_5 (1 && ENABLE_2_2_0_RC1) -//================== -// 2.2.0.final techs -//================== -#define ENABLE_2_2_0_FINAL 1 - -// Enable tooltips for GLCanvas3D using ImGUI -#define ENABLE_CANVAS_TOOLTIP_USING_IMGUI (1 && ENABLE_2_2_0_FINAL) -// Enable fix for dragging mouse event handling for gizmobar -#define ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX (1 && ENABLE_2_2_0_FINAL) - - //=================== // 2.3.0.alpha1 techs //=================== @@ -53,5 +42,20 @@ // Enable rendering of objects colored by facets' slope #define ENABLE_SLOPE_RENDERING (1 && ENABLE_2_3_0_ALPHA1) +// Enable rendering of objects using environment map +#define ENABLE_ENVIRONMENT_MAP (1 && ENABLE_2_3_0_ALPHA1) + +// Enable smoothing of objects normals +#define ENABLE_SMOOTH_NORMALS (0 && ENABLE_2_3_0_ALPHA1) + +// Enable error logging for OpenGL calls when SLIC3R_LOGLEVEL >= 5 +#define ENABLE_OPENGL_ERROR_LOGGING (1 && ENABLE_2_3_0_ALPHA1) + +// Enable built-in DPI changed event handler of wxWidgets 3.1.3 +#define ENABLE_WX_3_1_3_DPI_CHANGED_EVENT (1 && ENABLE_2_3_0_ALPHA1) + +// Enable changing application layout without the need to restart +#define ENABLE_LAYOUT_NO_RESTART (1 && ENABLE_2_3_0_ALPHA1) + #endif // _prusaslicer_technologies_h_ diff --git a/src/libslic3r/TriangleMesh.cpp b/src/libslic3r/TriangleMesh.cpp index f2deb5cbab9..17edf1b5a81 100644 --- a/src/libslic3r/TriangleMesh.cpp +++ b/src/libslic3r/TriangleMesh.cpp @@ -952,7 +952,7 @@ void TriangleMeshSlicer::slice(const std::vector &z, SlicingMode mode, co [&layers_p, 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 " PRINTF_ZU " (slice_z = %.2f):\n", layer_id, z[layer_id]); + printf("Layer %zu (slice_z = %.2f):\n", layer_id, z[layer_id]); #endif throw_on_cancel(); ExPolygons &expolygons = (*layers)[layer_id]; @@ -1779,7 +1779,7 @@ void TriangleMeshSlicer::make_expolygons(const Polygons &loops, const float clos size_t holes_count = 0; for (ExPolygons::const_iterator e = ex_slices.begin(); e != ex_slices.end(); ++ e) holes_count += e->holes.size(); - printf(PRINTF_ZU " surface(s) having " PRINTF_ZU " holes detected from " PRINTF_ZU " polylines\n", + printf("%zu surface(s) having %zu holes detected from %zu polylines\n", ex_slices.size(), holes_count, loops.size()); #endif diff --git a/src/libslic3r/TriangleSelector.cpp b/src/libslic3r/TriangleSelector.cpp new file mode 100644 index 00000000000..763bf586162 --- /dev/null +++ b/src/libslic3r/TriangleSelector.cpp @@ -0,0 +1,689 @@ +#include "TriangleSelector.hpp" +#include "Model.hpp" + + +namespace Slic3r { + + + +// sides_to_split==-1 : just restore previous split +void TriangleSelector::Triangle::set_division(int sides_to_split, int special_side_idx) +{ + assert(sides_to_split >=-1 && sides_to_split <= 3); + assert(special_side_idx >=-1 && special_side_idx < 3); + + // If splitting one or two sides, second argument must be provided. + assert(sides_to_split != 1 || special_side_idx != -1); + assert(sides_to_split != 2 || special_side_idx != -1); + + if (sides_to_split != -1) { + this->number_of_splits = sides_to_split; + if (sides_to_split != 0) { + assert(old_number_of_splits == 0); + this->special_side_idx = special_side_idx; + this->old_number_of_splits = sides_to_split; + } + } + else { + assert(old_number_of_splits != 0); + this->number_of_splits = old_number_of_splits; + // indices of children should still be there. + } +} + + + +void TriangleSelector::select_patch(const Vec3f& hit, int facet_start, + const Vec3f& source, const Vec3f& dir, + float radius, FacetSupportType new_state) +{ + assert(facet_start < m_orig_size_indices); + assert(is_approx(dir.norm(), 1.f)); + + // Save current cursor center, squared radius and camera direction, + // so we don't have to pass it around. + m_cursor = {hit, source, dir, radius*radius}; + + // In case user changed cursor size since last time, update triangle edge limit. + if (m_old_cursor_radius != radius) { + set_edge_limit(radius / 5.f); + m_old_cursor_radius = radius; + } + + // Now start with the facet the pointer points to and check all adjacent facets. + std::vector facets_to_check{facet_start}; + std::vector visited(m_orig_size_indices, false); // keep track of facets we already processed + int facet_idx = 0; // index into facets_to_check + while (facet_idx < int(facets_to_check.size())) { + int facet = facets_to_check[facet_idx]; + if (! visited[facet]) { + if (select_triangle(facet, new_state)) { + // add neighboring facets to list to be proccessed later + for (int n=0; n<3; ++n) { + if (faces_camera(m_mesh->stl.neighbors_start[facet].neighbor[n])) + facets_to_check.push_back(m_mesh->stl.neighbors_start[facet].neighbor[n]); + } + } + } + visited[facet] = true; + ++facet_idx; + } +} + + + +// Selects either the whole triangle (discarding any children it had), or divides +// the triangle recursively, selecting just subtriangles truly inside the circle. +// This is done by an actual recursive call. Returns false if the triangle is +// outside the cursor. +bool TriangleSelector::select_triangle(int facet_idx, FacetSupportType type, bool recursive_call) +{ + assert(facet_idx < int(m_triangles.size())); + + Triangle* tr = &m_triangles[facet_idx]; + if (! tr->valid) + return false; + + int num_of_inside_vertices = vertices_inside(facet_idx); + + if (num_of_inside_vertices == 0 + && ! is_pointer_in_triangle(facet_idx) + && ! is_edge_inside_cursor(facet_idx)) + return false; + + if (num_of_inside_vertices == 3) { + // dump any subdivision and select whole triangle + undivide_triangle(facet_idx); + tr->set_state(type); + } else { + // the triangle is partially inside, let's recursively divide it + // (if not already) and try selecting its children. + + if (! tr->is_split() && tr->get_state() == type) { + // This is leaf triangle that is already of correct type as a whole. + // No need to split, all children would end up selected anyway. + return true; + } + + split_triangle(facet_idx); + tr = &m_triangles[facet_idx]; // might have been invalidated + + + int num_of_children = tr->number_of_split_sides() + 1; + if (num_of_children != 1) { + for (int i=0; ichildren.size())); + assert(tr->children[i] < int(m_triangles.size())); + + select_triangle(tr->children[i], type, true); + tr = &m_triangles[facet_idx]; // might have been invalidated + } + } + } + + if (! recursive_call) { + // In case that all children are leafs and have the same state now, + // they may be removed and substituted by the parent triangle. + remove_useless_children(facet_idx); + + // Make sure that we did not lose track of invalid triangles. + assert(m_invalid_triangles == std::count_if(m_triangles.begin(), m_triangles.end(), + [](const Triangle& tr) { return ! tr.valid; })); + + // Do garbage collection maybe? + if (2*m_invalid_triangles > int(m_triangles.size())) + garbage_collect(); + } + return true; +} + + + +void TriangleSelector::set_facet(int facet_idx, FacetSupportType state) +{ + assert(facet_idx < m_orig_size_indices); + undivide_triangle(facet_idx); + assert(! m_triangles[facet_idx].is_split()); + m_triangles[facet_idx].set_state(state); +} + +void TriangleSelector::split_triangle(int facet_idx) +{ + if (m_triangles[facet_idx].is_split()) { + // The triangle is divided already. + return; + } + + Triangle* tr = &m_triangles[facet_idx]; + + FacetSupportType old_type = tr->get_state(); + + if (tr->was_split_before() != 0) { + // This triangle is not split at the moment, but was at one point + // in history. We can just restore it and resurrect its children. + tr->set_division(-1); + for (int i=0; i<=tr->number_of_split_sides(); ++i) { + m_triangles[tr->children[i]].set_state(old_type); + m_triangles[tr->children[i]].valid = true; + --m_invalid_triangles; + } + return; + } + + // If we got here, we are about to actually split the triangle. + const double limit_squared = m_edge_limit_sqr; + + std::array& facet = tr->verts_idxs; + const stl_vertex* pts[3] = { &m_vertices[facet[0]].v, &m_vertices[facet[1]].v, &m_vertices[facet[2]].v}; + double sides[3] = { (*pts[2]-*pts[1]).squaredNorm(), + (*pts[0]-*pts[2]).squaredNorm(), + (*pts[1]-*pts[0]).squaredNorm() }; + + std::vector sides_to_split; + int side_to_keep = -1; + for (int pt_idx = 0; pt_idx<3; ++pt_idx) { + if (sides[pt_idx] > limit_squared) + sides_to_split.push_back(pt_idx); + else + side_to_keep = pt_idx; + } + if (sides_to_split.empty()) { + // This shall be unselected. + tr->set_division(0); + return; + } + + // Save how the triangle will be split. Second argument makes sense only for one + // or two split sides, otherwise the value is ignored. + tr->set_division(sides_to_split.size(), + sides_to_split.size() == 2 ? side_to_keep : sides_to_split[0]); + + perform_split(facet_idx, old_type); +} + + +// Calculate distance of a point from a line. +bool TriangleSelector::is_point_inside_cursor(const Vec3f& point) const +{ + Vec3f diff = m_cursor.center - point; + return (diff - diff.dot(m_cursor.dir) * m_cursor.dir).squaredNorm() < m_cursor.radius_sqr; +} + + +// Is pointer in a triangle? +bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const +{ + auto signed_volume_sign = [](const Vec3f& a, const Vec3f& b, + const Vec3f& c, const Vec3f& d) -> bool { + return ((b-a).cross(c-a)).dot(d-a) > 0.; + }; + + const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]].v; + const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]].v; + const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]].v; + const Vec3f& q1 = m_cursor.center + m_cursor.dir; + const Vec3f q2 = m_cursor.center - m_cursor.dir; + + if (signed_volume_sign(q1,p1,p2,p3) != signed_volume_sign(q2,p1,p2,p3)) { + bool pos = signed_volume_sign(q1,q2,p1,p2); + if (signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos) + return true; + } + return false; +} + + + +// Determine whether this facet is potentially visible (still can be obscured). +bool TriangleSelector::faces_camera(int facet) const +{ + assert(facet < m_orig_size_indices); + // The normal is cached in mesh->stl, use it. + return (m_mesh->stl.facet_start[facet].normal.dot(m_cursor.dir) < 0.); +} + + +// How many vertices of a triangle are inside the circle? +int TriangleSelector::vertices_inside(int facet_idx) const +{ + int inside = 0; + for (size_t i=0; i<3; ++i) { + if (is_point_inside_cursor(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v)) + ++inside; + } + return inside; +} + + +// Is edge inside cursor? +bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const +{ + Vec3f pts[3]; + for (int i=0; i<3; ++i) + pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]].v; + + const Vec3f& p = m_cursor.center; + + for (int side = 0; side < 3; ++side) { + const Vec3f& a = pts[side]; + const Vec3f& b = pts[side<2 ? side+1 : 0]; + Vec3f s = (b-a).normalized(); + float t = (p-a).dot(s); + Vec3f vector = a+t*s - p; + + // vector is 3D vector from center to the intersection. What we want to + // measure is length of its projection onto plane perpendicular to dir. + float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(m_cursor.dir), 2.f); + if (dist_sqr < m_cursor.radius_sqr && t>=0.f && t<=(b-a).norm()) + return true; + } + return false; +} + + + +// Recursively remove all subtriangles. +void TriangleSelector::undivide_triangle(int facet_idx) +{ + assert(facet_idx < int(m_triangles.size())); + Triangle& tr = m_triangles[facet_idx]; + + if (tr.is_split()) { + for (int i=0; i<=tr.number_of_split_sides(); ++i) { + undivide_triangle(tr.children[i]); + m_triangles[tr.children[i]].valid = false; + ++m_invalid_triangles; + } + tr.set_division(0); // not split + } +} + + +void TriangleSelector::remove_useless_children(int facet_idx) +{ + // Check that all children are leafs of the same type. If not, try to + // make them (recursive call). Remove them if sucessful. + + assert(facet_idx < int(m_triangles.size()) && m_triangles[facet_idx].valid); + Triangle& tr = m_triangles[facet_idx]; + + if (! tr.is_split()) { + // This is a leaf, there nothing to do. This can happen during the + // first (non-recursive call). Shouldn't otherwise. + return; + } + + // Call this for all non-leaf children. + for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + assert(child_idx < int(m_triangles.size()) && m_triangles[child_idx].valid); + if (m_triangles[tr.children[child_idx]].is_split()) + remove_useless_children(tr.children[child_idx]); + } + + + // Return if a child is not leaf or two children differ in type. + FacetSupportType first_child_type = FacetSupportType::NONE; + for (int child_idx=0; child_idx<=tr.number_of_split_sides(); ++child_idx) { + if (m_triangles[tr.children[child_idx]].is_split()) + return; + if (child_idx == 0) + first_child_type = m_triangles[tr.children[0]].get_state(); + else if (m_triangles[tr.children[child_idx]].get_state() != first_child_type) + return; + } + + // If we got here, the children can be removed. + undivide_triangle(facet_idx); + tr.set_state(first_child_type); +} + + + +void TriangleSelector::garbage_collect() +{ + // First make a map from old to new triangle indices. + int new_idx = m_orig_size_indices; + std::vector new_triangle_indices(m_triangles.size(), -1); + for (int i = m_orig_size_indices; i new_vertices_indices(m_vertices.size(), -1); + for (int i=m_orig_size_vertices; i= 0); + if (m_vertices[i].ref_cnt != 0) { + new_vertices_indices[i] = new_idx; + ++new_idx; + } + } + + // We can remove all invalid triangles and vertices that are no longer referenced. + m_triangles.erase(std::remove_if(m_triangles.begin()+m_orig_size_indices, m_triangles.end(), + [](const Triangle& tr) { return ! tr.valid; }), + m_triangles.end()); + m_vertices.erase(std::remove_if(m_vertices.begin()+m_orig_size_vertices, m_vertices.end(), + [](const Vertex& vert) { return vert.ref_cnt == 0; }), + m_vertices.end()); + + // Now go through all remaining triangles and update changed indices. + for (Triangle& tr : m_triangles) { + assert(tr.valid); + + if (tr.is_split()) { + // There are children. Update their indices. + for (int j=0; j<=tr.number_of_split_sides(); ++j) { + assert(new_triangle_indices[tr.children[j]] != -1); + tr.children[j] = new_triangle_indices[tr.children[j]]; + } + } + + // Update indices into m_vertices. The original vertices are never + // touched and need not be reindexed. + for (int& idx : tr.verts_idxs) { + if (idx >= m_orig_size_vertices) { + assert(new_vertices_indices[idx] != -1); + idx = new_vertices_indices[idx]; + } + } + + // If this triangle was split before, forget it. + // Children referenced in the cache are dead by now. + tr.forget_history(); + } + + m_invalid_triangles = 0; +} + +TriangleSelector::TriangleSelector(const TriangleMesh& mesh) + : m_mesh{&mesh} +{ + reset(); +} + + +void TriangleSelector::reset() +{ + if (! m_orig_size_indices != 0) // unless this is run from constructor + garbage_collect(); + m_vertices.clear(); + m_triangles.clear(); + for (const stl_vertex& vert : m_mesh->its.vertices) + m_vertices.emplace_back(vert); + for (const stl_triangle_vertex_indices& ind : m_mesh->its.indices) + push_triangle(ind[0], ind[1], ind[2]); + m_orig_size_vertices = m_vertices.size(); + m_orig_size_indices = m_triangles.size(); + m_invalid_triangles = 0; +} + + + + + +void TriangleSelector::set_edge_limit(float edge_limit) +{ + float new_limit_sqr = std::pow(edge_limit, 2.f); + + if (new_limit_sqr != m_edge_limit_sqr) { + m_edge_limit_sqr = new_limit_sqr; + + // The way how triangles split may be different now, forget + // all cached splits. + garbage_collect(); + } +} + + + +void TriangleSelector::push_triangle(int a, int b, int c) +{ + for (int i : {a, b, c}) { + assert(i >= 0 && i < int(m_vertices.size())); + ++m_vertices[i].ref_cnt; + } + m_triangles.emplace_back(a, b, c); +} + + +void TriangleSelector::perform_split(int facet_idx, FacetSupportType old_state) +{ + Triangle* tr = &m_triangles[facet_idx]; + + assert(tr->is_split()); + + // Read info about how to split this triangle. + int sides_to_split = tr->number_of_split_sides(); + + // indices of triangle vertices + std::vector verts_idxs; + int idx = tr->special_side(); + for (int j=0; j<3; ++j) { + verts_idxs.push_back(tr->verts_idxs[idx++]); + if (idx == 3) + idx = 0; + } + + if (sides_to_split == 1) { + m_vertices.emplace_back((m_vertices[verts_idxs[1]].v + m_vertices[verts_idxs[2]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+2, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[2]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[0]); + } + + if (sides_to_split == 2) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[3]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+4, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[4]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[4]); + push_triangle(verts_idxs[2], verts_idxs[3], verts_idxs[4]); + } + + if (sides_to_split == 3) { + m_vertices.emplace_back((m_vertices[verts_idxs[0]].v + m_vertices[verts_idxs[1]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+1, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[2]].v + m_vertices[verts_idxs[3]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+3, m_vertices.size() - 1); + m_vertices.emplace_back((m_vertices[verts_idxs[4]].v + m_vertices[verts_idxs[0]].v)/2.); + verts_idxs.insert(verts_idxs.begin()+5, m_vertices.size() - 1); + + push_triangle(verts_idxs[0], verts_idxs[1], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[2], verts_idxs[3]); + push_triangle(verts_idxs[3], verts_idxs[4], verts_idxs[5]); + push_triangle(verts_idxs[1], verts_idxs[3], verts_idxs[5]); + } + + tr = &m_triangles[facet_idx]; // may have been invalidated + + // And save the children. All children should start in the same state as the triangle we just split. + assert(sides_to_split <= 3); + for (int i=0; i<=sides_to_split; ++i) { + tr->children[i] = m_triangles.size()-1-i; + m_triangles[tr->children[i]].set_state(old_state); + } +} + + + +indexed_triangle_set TriangleSelector::get_facets(FacetSupportType state) const +{ + indexed_triangle_set out; + for (const Triangle& tr : m_triangles) { + if (tr.valid && ! tr.is_split() && tr.get_state() == state) { + stl_triangle_vertex_indices indices; + for (int i=0; i<3; ++i) { + out.vertices.emplace_back(m_vertices[tr.verts_idxs[i]].v); + indices[i] = out.vertices.size() - 1; + } + out.indices.emplace_back(indices); + } + } + return out; +} + + + +std::map> TriangleSelector::serialize() const +{ + // Each original triangle of the mesh is assigned a number encoding its state + // or how it is split. Each triangle is encoded by 4 bits (xxyy): + // leaf triangle: xx = FacetSupportType, yy = 0 + // non-leaf: xx = special side, yy = number of split sides + // These are bitwise appended and formed into one 64-bit integer. + + // The function returns a map from original triangle indices to + // stream of bits encoding state and offsprings. + + std::map> out; + for (int i=0; i data; // complete encoding of this mesh triangle + int stored_triangles = 0; // how many have been already encoded + + std::function serialize_recursive; + serialize_recursive = [this, &serialize_recursive, &stored_triangles, &data](int facet_idx) { + const Triangle& tr = m_triangles[facet_idx]; + + // Always save number of split sides. It is zero for unsplit triangles. + int split_sides = tr.number_of_split_sides(); + assert(split_sides >= 0 && split_sides <= 3); + + //data |= (split_sides << (stored_triangles * 4)); + data.push_back(split_sides & 0b01); + data.push_back(split_sides & 0b10); + + if (tr.is_split()) { + // If this triangle is split, save which side is split (in case + // of one split) or kept (in case of two splits). The value will + // be ignored for 3-side split. + assert(split_sides > 0); + assert(tr.special_side() >= 0 && tr.special_side() <= 3); + data.push_back(tr.special_side() & 0b01); + data.push_back(tr.special_side() & 0b10); + ++stored_triangles; + // Now save all children. + for (int child_idx=0; child_idx<=split_sides; ++child_idx) + serialize_recursive(tr.children[child_idx]); + } else { + // In case this is leaf, we better save information about its state. + assert(int(tr.get_state()) <= 3); + data.push_back(int(tr.get_state()) & 0b01); + data.push_back(int(tr.get_state()) & 0b10); + ++stored_triangles; + } + }; + + serialize_recursive(i); + out[i] = data; + } + + return out; +} + +void TriangleSelector::deserialize(const std::map> data) +{ + reset(); // dump any current state + for (const auto& [triangle_id, code] : data) { + assert(triangle_id < int(m_triangles.size())); + assert(! code.empty()); + int processed_triangles = 0; + struct ProcessingInfo { + int facet_id = 0; + int processed_children = 0; + int total_children = 0; + }; + + // Vector to store all parents that have offsprings. + std::vector parents; + + while (true) { + // Read next triangle info. + int next_code = 0; + for (int i=3; i>=0; --i) { + next_code = next_code << 1; + next_code |= int(code[4 * processed_triangles + i]); + } + ++processed_triangles; + + int num_of_split_sides = (next_code & 0b11); + int num_of_children = num_of_split_sides != 0 ? num_of_split_sides + 1 : 0; + bool is_split = num_of_children != 0; + FacetSupportType state = FacetSupportType(next_code >> 2); + int special_side = (next_code >> 2); + + // Take care of the first iteration separately, so handling of the others is simpler. + if (parents.empty()) { + if (! is_split) { + // root is not split. just set the state and that's it. + m_triangles[triangle_id].set_state(state); + break; + } else { + // root is split, add it into list of parents and split it. + // then go to the next. + parents.push_back({triangle_id, 0, num_of_children}); + m_triangles[triangle_id].set_division(num_of_children-1, special_side); + perform_split(triangle_id, FacetSupportType::NONE); + continue; + } + } + + // This is not the first iteration. This triangle is a child of last seen parent. + assert(! parents.empty()); + assert(parents.back().processed_children < parents.back().total_children); + + if (is_split) { + // split the triangle and save it as parent of the next ones. + const ProcessingInfo& last = parents.back(); + int this_idx = m_triangles[last.facet_id].children[last.processed_children]; + m_triangles[this_idx].set_division(num_of_children-1, special_side); + perform_split(this_idx, FacetSupportType::NONE); + parents.push_back({this_idx, 0, num_of_children}); + } else { + // this triangle belongs to last split one + m_triangles[m_triangles[parents.back().facet_id].children[parents.back().processed_children]].set_state(state); + ++parents.back().processed_children; + } + + + // If all children of the past parent triangle are claimed, move to grandparent. + while (parents.back().processed_children == parents.back().total_children) { + parents.pop_back(); + + if (parents.empty()) + break; + + // And increment the grandparent children counter, because + // we have just finished that branch and got back here. + ++parents.back().processed_children; + } + + // In case we popped back the root, we should be done. + if (parents.empty()) + break; + } + + } +} + + + + +} // namespace Slic3r diff --git a/src/libslic3r/TriangleSelector.hpp b/src/libslic3r/TriangleSelector.hpp new file mode 100644 index 00000000000..fb90cff7695 --- /dev/null +++ b/src/libslic3r/TriangleSelector.hpp @@ -0,0 +1,155 @@ +#ifndef libslic3r_TriangleSelector_hpp_ +#define libslic3r_TriangleSelector_hpp_ + +// #define PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + + +#include "Point.hpp" +#include "TriangleMesh.hpp" + +namespace Slic3r { + +enum class FacetSupportType : int8_t; + + + +// Following class holds information about selected triangles. It also has power +// to recursively subdivide the triangles and make the selection finer. +class TriangleSelector { +public: + void set_edge_limit(float edge_limit); + + // Create new object on a TriangleMesh. The referenced mesh must + // stay valid, a ptr to it is saved and used. + explicit TriangleSelector(const TriangleMesh& mesh); + + // Select all triangles fully inside the circle, subdivide where needed. + void select_patch(const Vec3f& hit, // point where to start + int facet_start, // facet that point belongs to + const Vec3f& source, // camera position (mesh coords) + const Vec3f& dir, // direction of the ray (mesh coords) + float radius, // radius of the cursor + FacetSupportType new_state); // enforcer or blocker? + + // Get facets currently in the given state. + indexed_triangle_set get_facets(FacetSupportType state) const; + + // Set facet of the mesh to a given state. Only works for original triangles. + void set_facet(int facet_idx, FacetSupportType state); + + // Clear everything and make the tree empty. + void reset(); + + // Remove all unnecessary data. + void garbage_collect(); + + // Store the division trees in compact form (a long stream of + // bits for each triangle of the original mesh). + std::map> serialize() const; + + // Load serialized data. Assumes that correct mesh is loaded. + void deserialize(const std::map> data); + + +protected: + // Triangle and info about how it's split. + class Triangle { + public: + // Use TriangleSelector::push_triangle to create a new triangle. + // It increments/decrements reference counter on vertices. + Triangle(int a, int b, int c) + : verts_idxs{a, b, c}, + state{FacetSupportType(0)}, + number_of_splits{0}, + special_side_idx{0}, + old_number_of_splits{0} + {} + // Indices into m_vertices. + std::array verts_idxs; + + // Is this triangle valid or marked to be removed? + bool valid{true}; + + // Children triangles. + std::array children; + + // Set the division type. + void set_division(int sides_to_split, int special_side_idx = -1); + + // Get/set current state. + void set_state(FacetSupportType type) { assert(! is_split()); state = type; } + FacetSupportType get_state() const { assert(! is_split()); return state; } + + // Get info on how it's split. + bool is_split() const { return number_of_split_sides() != 0; } + int number_of_split_sides() const { return number_of_splits; } + int special_side() const { assert(is_split()); return special_side_idx; } + bool was_split_before() const { return old_number_of_splits != 0; } + void forget_history() { old_number_of_splits = 0; } + + private: + int number_of_splits; + int special_side_idx; + FacetSupportType state; + + // How many children were spawned during last split? + // Is not reset on remerging the triangle. + int old_number_of_splits; + }; + + struct Vertex { + explicit Vertex(const stl_vertex& vert) + : v{vert}, + ref_cnt{0} + {} + stl_vertex v; + int ref_cnt; + }; + + // Lists of vertices and triangles, both original and new + std::vector m_vertices; + std::vector m_triangles; + const TriangleMesh* m_mesh; + + // Number of invalid triangles (to trigger garbage collection). + int m_invalid_triangles; + + // Limiting length of triangle side (squared). + float m_edge_limit_sqr = 1.f; + + // Number of original vertices and triangles. + int m_orig_size_vertices = 0; + int m_orig_size_indices = 0; + + // Cache for cursor position, radius and direction. + struct Cursor { + Vec3f center; + Vec3f source; + Vec3f dir; + float radius_sqr; + }; + + Cursor m_cursor; + float m_old_cursor_radius; + + // Private functions: + bool select_triangle(int facet_idx, FacetSupportType type, + bool recursive_call = false); + bool is_point_inside_cursor(const Vec3f& point) const; + int vertices_inside(int facet_idx) const; + bool faces_camera(int facet) const; + void undivide_triangle(int facet_idx); + void split_triangle(int facet_idx); + void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant. + bool is_pointer_in_triangle(int facet_idx) const; + bool is_edge_inside_cursor(int facet_idx) const; + void push_triangle(int a, int b, int c); + void perform_split(int facet_idx, FacetSupportType old_state); +}; + + + + +} // namespace Slic3r + +#endif // libslic3r_TriangleSelector_hpp_ diff --git a/src/libslic3r/VoronoiOffset.cpp b/src/libslic3r/VoronoiOffset.cpp new file mode 100644 index 00000000000..c0541bd9f96 --- /dev/null +++ b/src/libslic3r/VoronoiOffset.cpp @@ -0,0 +1,855 @@ +// Polygon offsetting using Voronoi diagram prodiced by boost::polygon. + +#include "VoronoiOffset.hpp" + +#include + +// #define VORONOI_DEBUG_OUT + +#ifdef VORONOI_DEBUG_OUT +#include +#endif + +namespace Slic3r { + +using VD = Geometry::VoronoiDiagram; + +namespace detail { + // Intersect a circle with a ray, return the two parameters. + // Currently used for unbounded Voronoi edges only. + double first_circle_segment_intersection_parameter( + const Vec2d ¢er, const double r, const Vec2d &pt, const Vec2d &v) + { + const Vec2d d = pt - center; +#ifndef NDEBUG + double d0 = (pt - center).norm(); + double d1 = (pt + v - center).norm(); + assert(r < std::max(d0, d1) + EPSILON); +#endif /* NDEBUG */ + const double a = v.squaredNorm(); + const double b = 2. * d.dot(v); + const double c = d.squaredNorm() - r * r; + std::pair> out; + double u = b * b - 4. * a * c; + assert(u > - EPSILON); + double t; + if (u <= 0) { + // Degenerate to a single closest point. + t = - b / (2. * a); + assert(t >= - EPSILON && t <= 1. + EPSILON); + return Slic3r::clamp(0., 1., t); + } else { + u = sqrt(u); + out.first = 2; + double t0 = (- b - u) / (2. * a); + double t1 = (- b + u) / (2. * a); + // One of the intersections shall be found inside the segment. + assert((t0 >= - EPSILON && t0 <= 1. + EPSILON) || (t1 >= - EPSILON && t1 <= 1. + EPSILON)); + if (t1 < 0.) + return 0.; + if (t0 > 1.) + return 1.; + return (t0 > 0.) ? t0 : t1; + } + } + + struct Intersections + { + int count; + Vec2d pts[2]; + }; + + // Return maximum two points, that are at distance "d" from both points + Intersections point_point_equal_distance_points(const Point &pt1, const Point &pt2, const double d) + { + // Calculate the two intersection points. + // With the help of Python package sympy: + // res = solve([(x - cx)**2 + (y - cy)**2 - d**2, x**2 + y**2 - d**2], [x, y]) + // ccode(cse((res[0][0], res[0][1], res[1][0], res[1][1]))) + // where cx, cy is the center of pt1 relative to pt2, + // d is distance from the line and the point (0, 0). + // The result is then shifted to pt2. + auto cx = double(pt1.x() - pt2.x()); + auto cy = double(pt1.y() - pt2.y()); + double cl = cx * cx + cy * cy; + double discr = 4. * d * d - cl; + if (discr < 0.) { + // No intersection point found, the two circles are too far away. + return Intersections { 0, { Vec2d(), Vec2d() } }; + } + // Avoid division by zero if a gets too small. + bool xy_swapped = std::abs(cx) < std::abs(cy); + if (xy_swapped) + std::swap(cx, cy); + double u; + int cnt; + if (discr == 0.) { + cnt = 1; + u = 0; + } else { + cnt = 2; + u = 0.5 * cx * sqrt(cl * discr) / cl; + } + double v = 0.5 * cy - u; + double w = 2. * cy; + double e = 0.5 / cx; + double f = 0.5 * cy + u; + Intersections out { cnt, { Vec2d(-e * (v * w - cl), v), + Vec2d(-e * (w * f - cl), f) } }; + if (xy_swapped) { + std::swap(out.pts[0].x(), out.pts[0].y()); + std::swap(out.pts[1].x(), out.pts[1].y()); + } + out.pts[0] += pt2.cast(); + out.pts[1] += pt2.cast(); + + assert(std::abs((out.pts[0] - pt1.cast()).norm() - d) < SCALED_EPSILON); + assert(std::abs((out.pts[1] - pt1.cast()).norm() - d) < SCALED_EPSILON); + assert(std::abs((out.pts[0] - pt2.cast()).norm() - d) < SCALED_EPSILON); + assert(std::abs((out.pts[1] - pt2.cast()).norm() - d) < SCALED_EPSILON); + return out; + } + + // Return maximum two points, that are at distance "d" from both the line and point. + Intersections line_point_equal_distance_points(const Line &line, const Point &ipt, const double d) + { + assert(line.a != ipt && line.b != ipt); + // Calculating two points of distance "d" to a ray and a point. + // Point. + Vec2d pt = ipt.cast(); + Vec2d lv = (line.b - line.a).cast(); + double l2 = lv.squaredNorm(); + Vec2d lpv = (line.a - ipt).cast(); + double c = cross2(lpv, lv); + if (c < 0) { + lv = - lv; + c = - c; + } + + // Line equation (ax + by + c - d * sqrt(l2)). + auto a = - lv.y(); + auto b = lv.x(); + // Line point shifted by -ipt is on the line. + assert(std::abs(lpv.x() * a + lpv.y() * b + c) < SCALED_EPSILON); + // Line vector (a, b) points towards ipt. + assert(a * lpv.x() + b * lpv.y() < - SCALED_EPSILON); + +#ifndef NDEBUG + { + // Foot point of ipt on line. + Vec2d ft = Geometry::foot_pt(line, ipt); + // Center point between ipt and line, its distance to both line and ipt is equal. + Vec2d centerpt = 0.5 * (ft + pt) - pt; + double dcenter = 0.5 * (ft - pt).norm(); + // Verify that the center point + assert(std::abs(centerpt.x() * a + centerpt.y() * b + c - dcenter * sqrt(l2)) < SCALED_EPSILON * sqrt(l2)); + } +#endif // NDEBUG + + // Calculate the two intersection points. + // With the help of Python package sympy: + // res = solve([a * x + b * y + c - d * sqrt(a**2 + b**2), x**2 + y**2 - d**2], [x, y]) + // ccode(cse((res[0][0], res[0][1], res[1][0], res[1][1]))) + // where (a, b, c, d) is the line equation, not normalized (vector a,b is not normalized), + // d is distance from the line and the point (0, 0). + // The result is then shifted to ipt. + + double dscaled = d * sqrt(l2); + double s = c * (2. * dscaled - c); + if (s < 0.) + // Distance of pt from line is bigger than 2 * d. + return Intersections { 0 }; + double u; + int cnt; + // Avoid division by zero if a gets too small. + bool xy_swapped = std::abs(a) < std::abs(b); + if (xy_swapped) + std::swap(a, b); + if (s == 0.) { + // Distance of pt from line is 2 * d. + cnt = 1; + u = 0.; + } else { + // Distance of pt from line is smaller than 2 * d. + cnt = 2; + u = a * sqrt(s) / l2; + } + double e = dscaled - c; + double f = b * e / l2; + double g = f - u; + double h = f + u; + Intersections out { cnt, { Vec2d((- b * g + e) / a, g), + Vec2d((- b * h + e) / a, h) } }; + if (xy_swapped) { + std::swap(out.pts[0].x(), out.pts[0].y()); + std::swap(out.pts[1].x(), out.pts[1].y()); + } + out.pts[0] += pt; + out.pts[1] += pt; + + assert(std::abs(Geometry::ray_point_distance(line.a.cast(), (line.b - line.a).cast(), out.pts[0]) - d) < SCALED_EPSILON); + assert(std::abs(Geometry::ray_point_distance(line.a.cast(), (line.b - line.a).cast(), out.pts[1]) - d) < SCALED_EPSILON); + assert(std::abs((out.pts[0] - ipt.cast()).norm() - d) < SCALED_EPSILON); + assert(std::abs((out.pts[1] - ipt.cast()).norm() - d) < SCALED_EPSILON); + return out; + } + +} // namespace detail + +Polygons voronoi_offset( + const Geometry::VoronoiDiagram &vd, + const Lines &lines, + double offset_distance, + double discretization_error) +{ +#ifndef NDEBUG + // Verify that twin halfedges are stored next to the other in vd. + for (size_t i = 0; i < vd.num_edges(); i += 2) { + const VD::edge_type &e = vd.edges()[i]; + const VD::edge_type &e2 = vd.edges()[i + 1]; + assert(e.twin() == &e2); + assert(e2.twin() == &e); + assert(e.is_secondary() == e2.is_secondary()); + if (e.is_secondary()) { + assert(e.cell()->contains_point() != e2.cell()->contains_point()); + const VD::edge_type &ex = (e.cell()->contains_point() ? e : e2); + // Verify that the Point defining the cell left of ex is an end point of a segment + // defining the cell right of ex. + const Line &line0 = lines[ex.cell()->source_index()]; + const Line &line1 = lines[ex.twin()->cell()->source_index()]; + const Point &pt = (ex.cell()->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; + assert(pt == line1.a || pt == line1.b); + } + } +#endif // NDEBUG + + enum class EdgeState : unsigned char { + // Initial state, don't know. + Unknown, + // This edge will certainly not be intersected by the offset curve. + Inactive, + // This edge will certainly be intersected by the offset curve. + Active, + // This edge will possibly be intersected by the offset curve. + Possible + }; + + enum class CellState : unsigned char { + // Initial state, don't know. + Unknown, + // Inactive cell is inside for outside curves and outside for inside curves. + Inactive, + // Active cell is outside for outside curves and inside for inside curves. + Active, + // Boundary cell is intersected by the input segment, part of it is active. + Boundary + }; + + // Mark edges with outward vertex pointing outside the polygons, thus there is a chance + // that such an edge will have an intersection with our desired offset curve. + bool outside = offset_distance > 0.; + std::vector edge_state(vd.num_edges(), EdgeState::Unknown); + std::vector cell_state(vd.num_cells(), CellState::Unknown); + const VD::edge_type *front_edge = &vd.edges().front(); + const VD::cell_type *front_cell = &vd.cells().front(); + auto set_edge_state_initial = [&edge_state, front_edge](const VD::edge_type *edge, EdgeState new_edge_type) { + EdgeState &edge_type = edge_state[edge - front_edge]; + assert(edge_type == EdgeState::Unknown || edge_type == new_edge_type); + assert(new_edge_type == EdgeState::Possible || new_edge_type == EdgeState::Inactive); + edge_type = new_edge_type; + }; + auto set_edge_state_final = [&edge_state, front_edge](const size_t edge_id, EdgeState new_edge_type) { + EdgeState &edge_type = edge_state[edge_id]; + assert(edge_type == EdgeState::Possible || edge_type == new_edge_type); + assert(new_edge_type == EdgeState::Active || new_edge_type == EdgeState::Inactive); + edge_type = new_edge_type; + }; + auto set_cell_state = [&cell_state, front_cell](const VD::cell_type *cell, CellState new_cell_type) -> bool { + CellState &cell_type = cell_state[cell - front_cell]; + assert(cell_type == CellState::Active || cell_type == CellState::Inactive || cell_type == CellState::Boundary || cell_type == CellState::Unknown); + assert(new_cell_type == CellState::Active || new_cell_type == CellState::Inactive || new_cell_type == CellState::Boundary); + switch (cell_type) { + case CellState::Unknown: + break; + case CellState::Active: + if (new_cell_type == CellState::Inactive) + new_cell_type = CellState::Boundary; + break; + case CellState::Inactive: + if (new_cell_type == CellState::Active) + new_cell_type = CellState::Boundary; + break; + case CellState::Boundary: + return false; + } + if (cell_type != new_cell_type) { + cell_type = new_cell_type; + return true; + } + return false; + }; + + for (const VD::edge_type &edge : vd.edges()) + if (edge.vertex1() == nullptr) { + // Infinite Voronoi edge separating two Point sites or a Point site and a Segment site. + // Infinite edge is always outside and it has at least one valid vertex. + assert(edge.vertex0() != nullptr); + set_edge_state_initial(&edge, outside ? EdgeState::Possible : EdgeState::Inactive); + // Opposite edge of an infinite edge is certainly not active. + set_edge_state_initial(edge.twin(), EdgeState::Inactive); + if (edge.is_secondary()) { + // edge.vertex0() must lie on source contour. + const VD::cell_type *cell = edge.cell(); + const VD::cell_type *cell2 = edge.twin()->cell(); + if (cell->contains_segment()) + std::swap(cell, cell2); + // State of a cell containing a boundary point is known. + assert(cell->contains_point()); + set_cell_state(cell, outside ? CellState::Active : CellState::Inactive); + // State of a cell containing a boundary edge is Boundary. + assert(cell2->contains_segment()); + set_cell_state(cell2, CellState::Boundary); + } + } else if (edge.vertex0() != nullptr) { + // Finite edge. + const VD::cell_type *cell = edge.cell(); + const Line *line = cell->contains_segment() ? &lines[cell->source_index()] : nullptr; + if (line == nullptr) { + cell = edge.twin()->cell(); + line = cell->contains_segment() ? &lines[cell->source_index()] : nullptr; + } + if (line) { + const VD::vertex_type *v1 = edge.vertex1(); + const VD::cell_type *cell2 = (cell == edge.cell()) ? edge.twin()->cell() : edge.cell(); + assert(v1); + const Point *pt_on_contour = nullptr; + if (cell == edge.cell() && edge.twin()->cell()->contains_segment()) { + // Constrained bisector of two segments. + // If the two segments share a point, then one end of the current Voronoi edge shares this point as well. + // Find pt_on_contour if it exists. + const Line &line2 = lines[cell2->source_index()]; + if (line->a == line2.b) + pt_on_contour = &line->a; + else if (line->b == line2.a) + pt_on_contour = &line->b; + } else if (edge.is_secondary()) { + assert(edge.is_linear()); + // One end of the current Voronoi edge shares a point of a contour. + assert(edge.cell()->contains_point() != edge.twin()->cell()->contains_point()); + const Line &line2 = lines[cell2->source_index()]; + pt_on_contour = &((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line2.a : line2.b); + } + if (pt_on_contour) { + // One end of the current Voronoi edge shares a point of a contour. + // Find out which one it is. + const VD::vertex_type *v0 = edge.vertex0(); + Vec2d vec0(v0->x() - pt_on_contour->x(), v0->y() - pt_on_contour->y()); + Vec2d vec1(v1->x() - pt_on_contour->x(), v1->y() - pt_on_contour->y()); + double d0 = vec0.squaredNorm(); + double d1 = vec1.squaredNorm(); + assert(std::min(d0, d1) < SCALED_EPSILON * SCALED_EPSILON); + if (d0 < d1) { + // v0 is equal to pt. + } else { + // Skip secondary edge pointing to a contour point. + set_edge_state_initial(&edge, EdgeState::Inactive); + continue; + } + } + Vec2d l0(line->a.cast()); + Vec2d lv((line->b - line->a).cast()); + double side = cross2(lv, Vec2d(v1->x(), v1->y()) - l0); + bool edge_active = outside ? (side < 0.) : (side > 0.); + set_edge_state_initial(&edge, edge_active ? EdgeState::Possible : EdgeState::Inactive); + assert(cell->contains_segment()); + set_cell_state(cell, + pt_on_contour ? CellState::Boundary : + edge_active ? CellState::Active : CellState::Inactive); + set_cell_state(cell2, + (pt_on_contour && cell2->contains_segment()) ? + CellState::Boundary : + edge_active ? CellState::Active : CellState::Inactive); + } + } + { + // Perform one round of expansion marking Voronoi edges and cells next to boundary cells as active / inactive. + std::vector cell_queue; + for (const VD::edge_type &edge : vd.edges()) + if (edge_state[&edge - front_edge] == EdgeState::Unknown) { + assert(edge.cell()->contains_point() && edge.twin()->cell()->contains_point()); + // Edge separating two point sources, not yet classified as inside / outside. + CellState cs = cell_state[edge.cell() - front_cell]; + CellState cs2 = cell_state[edge.twin()->cell() - front_cell]; + if (cs != CellState::Unknown || cs2 != CellState::Unknown) { + if (cs == CellState::Unknown) { + cs = cs2; + if (set_cell_state(edge.cell(), cs)) + cell_queue.emplace_back(edge.cell()); + } else if (set_cell_state(edge.twin()->cell(), cs)) + cell_queue.emplace_back(edge.twin()->cell()); + EdgeState es = (cs == CellState::Active) ? EdgeState::Possible : EdgeState::Inactive; + set_edge_state_initial(&edge, es); + set_edge_state_initial(edge.twin(), es); + } else { + const VD::edge_type *e = edge.twin()->rot_prev(); + do { + EdgeState es = edge_state[e->twin() - front_edge]; + if (es != EdgeState::Unknown) { + assert(es == EdgeState::Possible || es == EdgeState::Inactive); + set_edge_state_initial(&edge, es); + CellState cs = (es == EdgeState::Possible) ? CellState::Active : CellState::Inactive; + if (set_cell_state(edge.cell(), cs)) + cell_queue.emplace_back(edge.cell()); + if (set_cell_state(edge.twin()->cell(), cs)) + cell_queue.emplace_back(edge.twin()->cell()); + break; + } + e = e->rot_prev(); + } while (e != edge.twin()); + } + } + // Do a final seed fill over Voronoi cells and unmarked Voronoi edges. + while (! cell_queue.empty()) { + const VD::cell_type *cell = cell_queue.back(); + const CellState cs = cell_state[cell - front_cell]; + cell_queue.pop_back(); + const VD::edge_type *first_edge = cell->incident_edge(); + const VD::edge_type *edge = cell->incident_edge(); + EdgeState es = (cs == CellState::Active) ? EdgeState::Possible : EdgeState::Inactive; + do { + if (set_cell_state(edge->twin()->cell(), cs)) { + set_edge_state_initial(edge, es); + set_edge_state_initial(edge->twin(), es); + cell_queue.emplace_back(edge->twin()->cell()); + } + edge = edge->next(); + } while (edge != first_edge); + } + } + + if (! outside) + offset_distance = - offset_distance; + +#ifdef VORONOI_DEBUG_OUT + BoundingBox bbox; + { + bbox.merge(get_extents(lines)); + bbox.min -= (0.01 * bbox.size().cast()).cast(); + bbox.max += (0.01 * bbox.size().cast()).cast(); + } + static int irun = 0; + ++ irun; + { + Lines helper_lines; + for (const VD::edge_type &edge : vd.edges()) + if (edge_state[&edge - front_edge] == EdgeState::Possible) { + const VD::vertex_type *v0 = edge.vertex0(); + const VD::vertex_type *v1 = edge.vertex1(); + assert(v0 != nullptr); + Vec2d pt1(v0->x(), v0->y()); + Vec2d pt2; + if (v1 == nullptr) { + // Unconstrained edge. Calculate a trimmed position. + assert(edge.is_linear()); + const VD::cell_type *cell = edge.cell(); + const VD::cell_type *cell2 = edge.twin()->cell(); + const Line &line0 = lines[cell->source_index()]; + const Line &line1 = lines[cell2->source_index()]; + if (cell->contains_point() && cell2->contains_point()) { + const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + // Direction vector of this unconstrained Voronoi edge. + Vec2d dir(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x())); + pt2 = Vec2d(v0->x(), v0->y()) + dir.normalized() * scale_(10.); + } else { + // Infinite edges could not be created by two segment sites. + assert(cell->contains_point() != cell2->contains_point()); + // Linear edge goes through the endpoint of a segment. + assert(edge.is_secondary()); + const Point &ipt = cell->contains_segment() ? + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) : + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b); + // Infinite edge starts at an input contour, therefore there is always an intersection with an offset curve. + const Line &line = cell->contains_segment() ? line0 : line1; + assert(line.a == ipt || line.b == ipt); + // dir is perpendicular to line. + Vec2d dir(line.a.y() - line.b.y(), line.b.x() - line.a.x()); + assert(dir.norm() > 0.); + if (((line.a == ipt) == cell->contains_point()) == (v0 == nullptr)) + dir = - dir; + pt2 = ipt.cast() + dir.normalized() * scale_(10.); + } + } else { + pt2 = Vec2d(v1->x(), v1->y()); + // Clip the line by the bounding box, so that the coloring of the line will be visible. + Geometry::liang_barsky_line_clipping(pt1, pt2, BoundingBoxf(bbox.min.cast(), bbox.max.cast())); + } + helper_lines.emplace_back(Line(Point(pt1.cast()), Point(((pt1 + pt2) * 0.5).cast()))); + } + dump_voronoi_to_svg(debug_out_path("voronoi-offset-candidates1-%d.svg", irun).c_str(), vd, Points(), lines, Polygons(), helper_lines); + } +#endif // VORONOI_DEBUG_OUT + + std::vector edge_offset_point(vd.num_edges(), Vec2d()); + const double offset_distance2 = offset_distance * offset_distance; + for (const VD::edge_type &edge : vd.edges()) { + assert(edge_state[&edge - front_edge] != EdgeState::Unknown); + size_t edge_idx = &edge - front_edge; + if (edge_state[edge_idx] == EdgeState::Possible) { + // Edge candidate, intersection points were not calculated yet. + const VD::vertex_type *v0 = edge.vertex0(); + const VD::vertex_type *v1 = edge.vertex1(); + assert(v0 != nullptr); + const VD::cell_type *cell = edge.cell(); + const VD::cell_type *cell2 = edge.twin()->cell(); + const Line &line0 = lines[cell->source_index()]; + const Line &line1 = lines[cell2->source_index()]; + size_t edge_idx2 = edge.twin() - front_edge; + if (v1 == nullptr) { + assert(edge.is_infinite()); + assert(edge.is_linear()); + assert(edge_state[edge_idx2] == EdgeState::Inactive); + if (cell->contains_point() && cell2->contains_point()) { + assert(! edge.is_secondary()); + const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + double dmin2 = (Vec2d(v0->x(), v0->y()) - pt0.cast()).squaredNorm(); + assert(dmin2 >= SCALED_EPSILON * SCALED_EPSILON); + if (dmin2 <= offset_distance2) { + // There shall be an intersection of this unconstrained edge with the offset curve. + // Direction vector of this unconstrained Voronoi edge. + Vec2d dir(double(pt0.y() - pt1.y()), double(pt1.x() - pt0.x())); + Vec2d pt(v0->x(), v0->y()); + double t = detail::first_circle_segment_intersection_parameter(Vec2d(pt0.x(), pt0.y()), offset_distance, pt, dir); + edge_offset_point[edge_idx] = pt + t * dir; + set_edge_state_final(edge_idx, EdgeState::Active); + } else + set_edge_state_final(edge_idx, EdgeState::Inactive); + } else { + // Infinite edges could not be created by two segment sites. + assert(cell->contains_point() != cell2->contains_point()); + // Linear edge goes through the endpoint of a segment. + assert(edge.is_secondary()); + const Point &ipt = cell->contains_segment() ? + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b) : + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b); + #ifndef NDEBUG + if (cell->contains_segment()) { + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + assert((pt1.x() == line0.a.x() && pt1.y() == line0.a.y()) || + (pt1.x() == line0.b.x() && pt1.y() == line0.b.y())); + } else { + const Point &pt0 = (cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b; + assert((pt0.x() == line1.a.x() && pt0.y() == line1.a.y()) || + (pt0.x() == line1.b.x() && pt0.y() == line1.b.y())); + } + assert((Vec2d(v0->x(), v0->y()) - ipt.cast()).norm() < SCALED_EPSILON); + #endif /* NDEBUG */ + // Infinite edge starts at an input contour, therefore there is always an intersection with an offset curve. + const Line &line = cell->contains_segment() ? line0 : line1; + assert(line.a == ipt || line.b == ipt); + edge_offset_point[edge_idx] = ipt.cast() + offset_distance * Vec2d(line.b.y() - line.a.y(), line.a.x() - line.b.x()).normalized(); + set_edge_state_final(edge_idx, EdgeState::Active); + } + // The other edge of an unconstrained edge starting with null vertex shall never be intersected. + set_edge_state_final(edge_idx2, EdgeState::Inactive); + } else if (edge.is_secondary()) { + assert(edge.is_linear()); + assert(cell->contains_point() != cell2->contains_point()); + const Line &line0 = lines[edge.cell()->source_index()]; + const Line &line1 = lines[edge.twin()->cell()->source_index()]; + const Point &pt = cell->contains_point() ? + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) : + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b); + const Line &line = cell->contains_segment() ? line0 : line1; + assert(pt == line.a || pt == line.b); + assert((pt.cast() - Vec2d(v0->x(), v0->y())).norm() < SCALED_EPSILON); + Vec2d dir(v1->x() - v0->x(), v1->y() - v0->y()); + double l2 = dir.squaredNorm(); + if (offset_distance2 <= l2) { + edge_offset_point[edge_idx] = pt.cast() + (offset_distance / sqrt(l2)) * dir; + set_edge_state_final(edge_idx, EdgeState::Active); + } else { + set_edge_state_final(edge_idx, EdgeState::Inactive); + } + set_edge_state_final(edge_idx2, EdgeState::Inactive); + } else { + // Finite edge has valid points at both sides. + bool done = false; + if (cell->contains_segment() && cell2->contains_segment()) { + // This edge is a bisector of two line segments. Project v0, v1 onto one of the line segments. + Vec2d pt(line0.a.cast()); + Vec2d dir(line0.b.cast() - pt); + Vec2d vec0 = Vec2d(v0->x(), v0->y()) - pt; + Vec2d vec1 = Vec2d(v1->x(), v1->y()) - pt; + double l2 = dir.squaredNorm(); + assert(l2 > 0.); + double dmin = (dir * (vec0.dot(dir) / l2) - vec0).squaredNorm(); + double dmax = (dir * (vec1.dot(dir) / l2) - vec1).squaredNorm(); + bool flip = dmin > dmax; + if (flip) + std::swap(dmin, dmax); + if (offset_distance2 >= dmin && offset_distance2 <= dmax) { + // Intersect. Maximum one intersection will be found. + // This edge is a bisector of two line segments. Distance to the input polygon increases/decreases monotonically. + dmin = sqrt(dmin); + dmax = sqrt(dmax); + assert(offset_distance > dmin - EPSILON && offset_distance < dmax + EPSILON); + double ddif = dmax - dmin; + if (ddif == 0.) { + // line, line2 are exactly parallel. This is a singular case, the offset curve should miss it. + } else { + if (flip) { + std::swap(edge_idx, edge_idx2); + std::swap(v0, v1); + } + double t = clamp(0., 1., (offset_distance - dmin) / ddif); + edge_offset_point[edge_idx] = Vec2d(lerp(v0->x(), v1->x(), t), lerp(v0->y(), v1->y(), t)); + set_edge_state_final(edge_idx, EdgeState::Active); + set_edge_state_final(edge_idx2, EdgeState::Inactive); + done = true; + } + } + } else { + assert(cell->contains_point() || cell2->contains_point()); + bool point_vs_segment = cell->contains_point() != cell2->contains_point(); + const Point &pt0 = cell->contains_point() ? + ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b) : + ((cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b); + // Project p0 to line segment . + Vec2d p0(v0->x(), v0->y()); + Vec2d p1(v1->x(), v1->y()); + Vec2d px(pt0.x(), pt0.y()); + double d0 = (p0 - px).squaredNorm(); + double d1 = (p1 - px).squaredNorm(); + double dmin = std::min(d0, d1); + double dmax = std::max(d0, d1); + bool has_intersection = false; + bool possibly_two_points = false; + if (offset_distance2 <= dmax) { + if (offset_distance2 >= dmin) { + has_intersection = true; + } else { + double dmin_new = dmin; + if (point_vs_segment) { + // Project on the source segment. + const Line &line = cell->contains_segment() ? line0 : line1; + const Vec2d pt_line = line.a.cast(); + const Vec2d v_line = (line.b - line.a).cast(); + double t0 = (p0 - pt_line).dot(v_line); + double t1 = (p1 - pt_line).dot(v_line); + double tx = (px - pt_line).dot(v_line); + if ((tx >= t0 && tx <= t1) || (tx >= t1 && tx <= t0)) { + // Projection of the Point site falls between the projections of the Voronoi edge end points + // onto the Line site. + Vec2d ft = pt_line + (tx / v_line.squaredNorm()) * v_line; + dmin_new = (ft - px).squaredNorm() * 0.25; + } + } else { + // Point-Point Voronoi sites. Project point site onto the current Voronoi edge. + Vec2d v = p1 - p0; + auto l2 = v.squaredNorm(); + assert(l2 > 0); + auto t = v.dot(px - p0); + if (t >= 0. && t <= l2) { + // Projection falls onto the Voronoi edge. Calculate foot point and distance. + Vec2d ft = p0 + (t / l2) * v; + dmin_new = (ft - px).squaredNorm(); + } + } + assert(dmin_new < dmax + SCALED_EPSILON); + assert(dmin_new < dmin + SCALED_EPSILON); + if (dmin_new < dmin) { + dmin = dmin_new; + has_intersection = possibly_two_points = offset_distance2 >= dmin; + } + } + } + if (has_intersection) { + detail::Intersections intersections; + if (point_vs_segment) { + assert(cell->contains_point() || cell2->contains_point()); + intersections = detail::line_point_equal_distance_points(cell->contains_segment() ? line0 : line1, pt0, offset_distance); + } else { + const Point &pt1 = (cell2->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line1.a : line1.b; + intersections = detail::point_point_equal_distance_points(pt0, pt1, offset_distance); + } + // If the span of distances of start / end point / foot point to the point site indicate an intersection, + // we should find one. + assert(intersections.count > 0); + if (intersections.count == 2) { + // Now decide which points fall on this Voronoi edge. + // Tangential points (single intersection) are ignored. + if (possibly_two_points) { + Vec2d v = p1 - p0; + double l2 = v.squaredNorm(); + double t0 = v.dot(intersections.pts[0] - p0); + double t1 = v.dot(intersections.pts[1] - p0); + if (t0 > t1) { + std::swap(t0, t1); + std::swap(intersections.pts[0], intersections.pts[1]); + } + // Remove points outside of the line range. + if (t0 < 0. || t0 > l2) { + if (t1 < 0. || t1 > l2) + intersections.count = 0; + else { + -- intersections.count; + t0 = t1; + intersections.pts[0] = intersections.pts[1]; + } + } else if (t1 < 0. || t1 > l2) + -- intersections.count; + } else { + // Take the point furthest from the end points of the Voronoi edge or a Voronoi parabolic arc. + double d0 = std::max((intersections.pts[0] - p0).squaredNorm(), (intersections.pts[0] - p1).squaredNorm()); + double d1 = std::max((intersections.pts[1] - p0).squaredNorm(), (intersections.pts[1] - p1).squaredNorm()); + if (d0 > d1) + intersections.pts[0] = intersections.pts[1]; + -- intersections.count; + } + assert(intersections.count > 0); + if (intersections.count == 2) { + set_edge_state_final(edge_idx, EdgeState::Active); + set_edge_state_final(edge_idx2, EdgeState::Active); + edge_offset_point[edge_idx] = intersections.pts[1]; + edge_offset_point[edge_idx2] = intersections.pts[0]; + done = true; + } else if (intersections.count == 1) { + if (d1 < d0) + std::swap(edge_idx, edge_idx2); + set_edge_state_final(edge_idx, EdgeState::Active); + set_edge_state_final(edge_idx2, EdgeState::Inactive); + edge_offset_point[edge_idx] = intersections.pts[0]; + done = true; + } + } + } + } + if (! done) { + set_edge_state_final(edge_idx, EdgeState::Inactive); + set_edge_state_final(edge_idx2, EdgeState::Inactive); + } + } + } + } + +#ifndef NDEBUG + for (const VD::edge_type &edge : vd.edges()) { + assert(edge_state[&edge - front_edge] == EdgeState::Inactive || edge_state[&edge - front_edge] == EdgeState::Active); + // None of a new edge candidate may start with null vertex. + assert(edge_state[&edge - front_edge] == EdgeState::Inactive || edge.vertex0() != nullptr); + assert(edge_state[edge.twin() - front_edge] == EdgeState::Inactive || edge.twin()->vertex0() != nullptr); + } +#endif // NDEBUG + +#ifdef VORONOI_DEBUG_OUT + { + Lines helper_lines; + for (const VD::edge_type &edge : vd.edges()) + if (edge_state[&edge - front_edge] == EdgeState::Active) + helper_lines.emplace_back(Line(Point(edge.vertex0()->x(), edge.vertex0()->y()), Point(edge_offset_point[&edge - front_edge].cast()))); + dump_voronoi_to_svg(debug_out_path("voronoi-offset-candidates2-%d.svg", irun).c_str(), vd, Points(), lines, Polygons(), helper_lines); + } +#endif // VORONOI_DEBUG_OUT + + auto next_offset_edge = [&edge_state, front_edge](const VD::edge_type *start_edge) -> const VD::edge_type* { + for (const VD::edge_type *edge = start_edge->next(); edge != start_edge; edge = edge->next()) + if (edge_state[edge->twin() - front_edge] == EdgeState::Active) + return edge->twin(); + // assert(false); + return nullptr; + }; + +#ifndef NDEBUG + auto dist_to_site = [&lines](const VD::cell_type &cell, const Vec2d &point) { + const Line &line = lines[cell.source_index()]; + return cell.contains_point() ? + (((cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line.a : line.b).cast() - point).norm() : + (Geometry::foot_pt(line.a.cast(), (line.b - line.a).cast(), point) - point).norm(); + }; +#endif /* NDEBUG */ + + // Track the offset curves. + Polygons out; + double angle_step = 2. * acos((offset_distance - discretization_error) / offset_distance); + double cos_threshold = cos(angle_step); + for (size_t seed_edge_idx = 0; seed_edge_idx < vd.num_edges(); ++ seed_edge_idx) + if (edge_state[seed_edge_idx] == EdgeState::Active) { + const VD::edge_type *start_edge = &vd.edges()[seed_edge_idx]; + const VD::edge_type *edge = start_edge; + Polygon poly; + do { + // find the next edge + const VD::edge_type *next_edge = next_offset_edge(edge); +#ifdef VORONOI_DEBUG_OUT + if (next_edge == nullptr) { + Lines helper_lines; + dump_voronoi_to_svg(debug_out_path("voronoi-offset-open-loop-%d.svg", irun).c_str(), vd, Points(), lines, Polygons(), to_lines(poly)); + } +#endif // VORONOI_DEBUG_OUT + assert(next_edge); + //std::cout << "offset-output: "; print_edge(edge); std::cout << " to "; print_edge(next_edge); std::cout << "\n"; + // Interpolate a circular segment or insert a linear segment between edge and next_edge. + const VD::cell_type *cell = edge->cell(); + edge_state[next_edge - front_edge] = EdgeState::Inactive; + Vec2d p1 = edge_offset_point[edge - front_edge]; + Vec2d p2 = edge_offset_point[next_edge - front_edge]; +#ifndef NDEBUG + { + double err = dist_to_site(*cell, p1) - offset_distance; + double err2 = dist_to_site(*cell, p2) - offset_distance; +#ifdef VORONOI_DEBUG_OUT + if (std::max(err, err2) >= SCALED_EPSILON) { + Lines helper_lines; + dump_voronoi_to_svg(debug_out_path("voronoi-offset-incorrect_pt-%d.svg", irun).c_str(), vd, Points(), lines, Polygons(), to_lines(poly)); + } +#endif // VORONOI_DEBUG_OUT + assert(std::abs(err) < SCALED_EPSILON); + assert(std::abs(err2) < SCALED_EPSILON); + } +#endif /* NDEBUG */ + if (cell->contains_point()) { + // Discretize an arc from p1 to p2 with radius = offset_distance and discretization_error. + // The extracted contour is CCW oriented, extracted holes are CW oriented. + // The extracted arc will have the same orientation. As the Voronoi regions are convex, the angle covered by the arc will be convex as well. + const Line &line0 = lines[cell->source_index()]; + const Vec2d ¢er = ((cell->source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? line0.a : line0.b).cast(); + const Vec2d v1 = p1 - center; + const Vec2d v2 = p2 - center; + bool ccw = cross2(v1, v2) > 0; + double cos_a = v1.dot(v2); + double norm = v1.norm() * v2.norm(); + assert(norm > 0.); + if (cos_a < cos_threshold * norm) { + // Angle is bigger than the threshold, therefore the arc will be discretized. + cos_a /= norm; + assert(cos_a > -1. - EPSILON && cos_a < 1. + EPSILON); + double angle = acos(std::max(-1., std::min(1., cos_a))); + size_t n_steps = size_t(ceil(angle / angle_step)); + double astep = angle / n_steps; + if (! ccw) + astep *= -1.; + double a = astep; + for (size_t i = 1; i < n_steps; ++ i, a += astep) { + double c = cos(a); + double s = sin(a); + Vec2d p = center + Vec2d(c * v1.x() - s * v1.y(), s * v1.x() + c * v1.y()); + poly.points.emplace_back(Point(coord_t(p.x()), coord_t(p.y()))); + } + } + } + { + Point pt_last(coord_t(p2.x()), coord_t(p2.y())); + if (poly.empty() || poly.points.back() != pt_last) + poly.points.emplace_back(pt_last); + } + edge = next_edge; + } while (edge != start_edge); + out.emplace_back(std::move(poly)); + } + + return out; +} + +} // namespace Slic3r diff --git a/src/libslic3r/VoronoiOffset.hpp b/src/libslic3r/VoronoiOffset.hpp new file mode 100644 index 00000000000..a21b44f93e5 --- /dev/null +++ b/src/libslic3r/VoronoiOffset.hpp @@ -0,0 +1,25 @@ +// Polygon offsetting using Voronoi diagram prodiced by boost::polygon. + +#ifndef slic3r_VoronoiOffset_hpp_ +#define slic3r_VoronoiOffset_hpp_ + +#include "libslic3r.h" + +#include "Geometry.hpp" + +namespace Slic3r { + +// Offset a polygon or a set of polygons possibly with holes by traversing a Voronoi diagram. +// The input polygons are stored in lines and lines are referenced by vd. +// Outer curve will be extracted for a positive offset_distance, +// inner curve will be extracted for a negative offset_distance. +// Circular arches will be discretized to achieve discretization_error. +Polygons voronoi_offset( + const Geometry::VoronoiDiagram &vd, + const Lines &lines, + double offset_distance, + double discretization_error); + +} // namespace Slic3r + +#endif // slic3r_VoronoiOffset_hpp_ diff --git a/src/libslic3r/VoronoiVisualUtils.hpp b/src/libslic3r/VoronoiVisualUtils.hpp new file mode 100644 index 00000000000..fa6a3424181 --- /dev/null +++ b/src/libslic3r/VoronoiVisualUtils.hpp @@ -0,0 +1,415 @@ +#include + +#include +#include +#include +#include + +namespace boost { namespace polygon { + +// The following code for the visualization of the boost Voronoi diagram is based on: +// +// Boost.Polygon library voronoi_graphic_utils.hpp header file +// Copyright Andrii Sydorchuk 2010-2012. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +template +class voronoi_visual_utils { + public: + // Discretize parabolic Voronoi edge. + // Parabolic Voronoi edges are always formed by one point and one segment + // from the initial input set. + // + // Args: + // point: input point. + // segment: input segment. + // max_dist: maximum discretization distance. + // discretization: point discretization of the given Voronoi edge. + // + // Template arguments: + // InCT: coordinate type of the input geometries (usually integer). + // Point: point type, should model point concept. + // Segment: segment type, should model segment concept. + // + // Important: + // discretization should contain both edge endpoints initially. + template class Point, + template class Segment> + static + typename enable_if< + typename gtl_and< + typename gtl_if< + typename is_point_concept< + typename geometry_concept< Point >::type + >::type + >::type, + typename gtl_if< + typename is_segment_concept< + typename geometry_concept< Segment >::type + >::type + >::type + >::type, + void + >::type discretize( + const Point& point, + const Segment& segment, + const CT max_dist, + std::vector< Point >* discretization) { + // Apply the linear transformation to move start point of the segment to + // the point with coordinates (0, 0) and the direction of the segment to + // coincide the positive direction of the x-axis. + CT segm_vec_x = cast(x(high(segment))) - cast(x(low(segment))); + CT segm_vec_y = cast(y(high(segment))) - cast(y(low(segment))); + CT sqr_segment_length = segm_vec_x * segm_vec_x + segm_vec_y * segm_vec_y; + + // Compute x-coordinates of the endpoints of the edge + // in the transformed space. + CT projection_start = sqr_segment_length * + get_point_projection((*discretization)[0], segment); + CT projection_end = sqr_segment_length * + get_point_projection((*discretization)[1], segment); + + // Compute parabola parameters in the transformed space. + // Parabola has next representation: + // f(x) = ((x-rot_x)^2 + rot_y^2) / (2.0*rot_y). + CT point_vec_x = cast(x(point)) - cast(x(low(segment))); + CT point_vec_y = cast(y(point)) - cast(y(low(segment))); + CT rot_x = segm_vec_x * point_vec_x + segm_vec_y * point_vec_y; + CT rot_y = segm_vec_x * point_vec_y - segm_vec_y * point_vec_x; + + // Save the last point. + Point last_point = (*discretization)[1]; + discretization->pop_back(); + + // Use stack to avoid recursion. + std::stack point_stack; + point_stack.push(projection_end); + CT cur_x = projection_start; + CT cur_y = parabola_y(cur_x, rot_x, rot_y); + + // Adjust max_dist parameter in the transformed space. + const CT max_dist_transformed = max_dist * max_dist * sqr_segment_length; + while (!point_stack.empty()) { + CT new_x = point_stack.top(); + CT new_y = parabola_y(new_x, rot_x, rot_y); + + // Compute coordinates of the point of the parabola that is + // furthest from the current line segment. + CT mid_x = (new_y - cur_y) / (new_x - cur_x) * rot_y + rot_x; + CT mid_y = parabola_y(mid_x, rot_x, rot_y); + + // Compute maximum distance between the given parabolic arc + // and line segment that discretize it. + CT dist = (new_y - cur_y) * (mid_x - cur_x) - + (new_x - cur_x) * (mid_y - cur_y); + dist = dist * dist / ((new_y - cur_y) * (new_y - cur_y) + + (new_x - cur_x) * (new_x - cur_x)); + if (dist <= max_dist_transformed) { + // Distance between parabola and line segment is less than max_dist. + point_stack.pop(); + CT inter_x = (segm_vec_x * new_x - segm_vec_y * new_y) / + sqr_segment_length + cast(x(low(segment))); + CT inter_y = (segm_vec_x * new_y + segm_vec_y * new_x) / + sqr_segment_length + cast(y(low(segment))); + discretization->push_back(Point(inter_x, inter_y)); + cur_x = new_x; + cur_y = new_y; + } else { + point_stack.push(mid_x); + } + } + + // Update last point. + discretization->back() = last_point; + } + + private: + // Compute y(x) = ((x - a) * (x - a) + b * b) / (2 * b). + static CT parabola_y(CT x, CT a, CT b) { + return ((x - a) * (x - a) + b * b) / (b + b); + } + + // Get normalized length of the distance between: + // 1) point projection onto the segment + // 2) start point of the segment + // Return this length divided by the segment length. This is made to avoid + // sqrt computation during transformation from the initial space to the + // transformed one and vice versa. The assumption is made that projection of + // the point lies between the start-point and endpoint of the segment. + template class Point, + template class Segment> + static + typename enable_if< + typename gtl_and< + typename gtl_if< + typename is_point_concept< + typename geometry_concept< Point >::type + >::type + >::type, + typename gtl_if< + typename is_segment_concept< + typename geometry_concept< Segment >::type + >::type + >::type + >::type, + CT + >::type get_point_projection( + const Point& point, const Segment& segment) { + CT segment_vec_x = cast(x(high(segment))) - cast(x(low(segment))); + CT segment_vec_y = cast(y(high(segment))) - cast(y(low(segment))); + CT point_vec_x = x(point) - cast(x(low(segment))); + CT point_vec_y = y(point) - cast(y(low(segment))); + CT sqr_segment_length = + segment_vec_x * segment_vec_x + segment_vec_y * segment_vec_y; + CT vec_dot = segment_vec_x * point_vec_x + segment_vec_y * point_vec_y; + return vec_dot / sqr_segment_length; + } + + template + static CT cast(const InCT& value) { + return static_cast(value); + } +}; + +} } // namespace boost::polygon + + +namespace Slic3r +{ + +// The following code for the visualization of the boost Voronoi diagram is based on: +// +// Boost.Polygon library voronoi_visualizer.cpp file +// Copyright Andrii Sydorchuk 2010-2012. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +namespace Voronoi { namespace Internal { + + using VD = Geometry::VoronoiDiagram; + typedef double coordinate_type; + typedef boost::polygon::point_data point_type; + typedef boost::polygon::segment_data segment_type; + typedef boost::polygon::rectangle_data rect_type; + typedef VD::cell_type cell_type; + typedef VD::cell_type::source_index_type source_index_type; + typedef VD::cell_type::source_category_type source_category_type; + typedef VD::edge_type edge_type; + typedef VD::cell_container_type cell_container_type; + typedef VD::cell_container_type vertex_container_type; + typedef VD::edge_container_type edge_container_type; + typedef VD::const_cell_iterator const_cell_iterator; + typedef VD::const_vertex_iterator const_vertex_iterator; + typedef VD::const_edge_iterator const_edge_iterator; + + static const std::size_t EXTERNAL_COLOR = 1; + + inline void color_exterior(const VD::edge_type* edge) + { + if (edge->color() == EXTERNAL_COLOR) + return; + edge->color(EXTERNAL_COLOR); + edge->twin()->color(EXTERNAL_COLOR); + const VD::vertex_type* v = edge->vertex1(); + if (v == NULL || !edge->is_primary()) + return; + v->color(EXTERNAL_COLOR); + const VD::edge_type* e = v->incident_edge(); + do { + color_exterior(e); + e = e->rot_next(); + } while (e != v->incident_edge()); + } + + inline point_type retrieve_point(const Points &points, const std::vector &segments, const cell_type& cell) + { + assert(cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT || cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_END_POINT || + cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT); + return cell.source_category() == boost::polygon::SOURCE_CATEGORY_SINGLE_POINT ? + Voronoi::Internal::point_type(double(points[cell.source_index()].x()), double(points[cell.source_index()].y())) : + (cell.source_category() == boost::polygon::SOURCE_CATEGORY_SEGMENT_START_POINT) ? + low(segments[cell.source_index()]) : high(segments[cell.source_index()]); + } + + inline void clip_infinite_edge(const Points &points, const std::vector &segments, const edge_type& edge, coordinate_type bbox_max_size, std::vector* clipped_edge) + { + const cell_type& cell1 = *edge.cell(); + const cell_type& cell2 = *edge.twin()->cell(); + point_type origin, direction; + // Infinite edges could not be created by two segment sites. + if (! cell1.contains_point() && ! cell2.contains_point()) { + printf("Error! clip_infinite_edge - infinite edge separates two segment cells\n"); + return; + } + if (cell1.contains_point() && cell2.contains_point()) { + point_type p1 = retrieve_point(points, segments, cell1); + point_type p2 = retrieve_point(points, segments, cell2); + origin.x((p1.x() + p2.x()) * 0.5); + origin.y((p1.y() + p2.y()) * 0.5); + direction.x(p1.y() - p2.y()); + direction.y(p2.x() - p1.x()); + } else { + origin = cell1.contains_segment() ? retrieve_point(points, segments, cell2) : retrieve_point(points, segments, cell1); + segment_type segment = cell1.contains_segment() ? segments[cell1.source_index()] : segments[cell2.source_index()]; + coordinate_type dx = high(segment).x() - low(segment).x(); + coordinate_type dy = high(segment).y() - low(segment).y(); + if ((low(segment) == origin) ^ cell1.contains_point()) { + direction.x(dy); + direction.y(-dx); + } else { + direction.x(-dy); + direction.y(dx); + } + } + coordinate_type koef = bbox_max_size / (std::max)(fabs(direction.x()), fabs(direction.y())); + if (edge.vertex0() == NULL) { + clipped_edge->push_back(point_type( + origin.x() - direction.x() * koef, + origin.y() - direction.y() * koef)); + } else { + clipped_edge->push_back( + point_type(edge.vertex0()->x(), edge.vertex0()->y())); + } + if (edge.vertex1() == NULL) { + clipped_edge->push_back(point_type( + origin.x() + direction.x() * koef, + origin.y() + direction.y() * koef)); + } else { + clipped_edge->push_back( + point_type(edge.vertex1()->x(), edge.vertex1()->y())); + } + } + + inline void sample_curved_edge(const Points &points, const std::vector &segments, const edge_type& edge, std::vector &sampled_edge, coordinate_type max_dist) + { + point_type point = edge.cell()->contains_point() ? + retrieve_point(points, segments, *edge.cell()) : + retrieve_point(points, segments, *edge.twin()->cell()); + segment_type segment = edge.cell()->contains_point() ? + segments[edge.twin()->cell()->source_index()] : + segments[edge.cell()->source_index()]; + ::boost::polygon::voronoi_visual_utils::discretize(point, segment, max_dist, &sampled_edge); + } + +} /* namespace Internal */ } // namespace Voronoi + +BoundingBox get_extents(const Lines &lines); + +static inline void dump_voronoi_to_svg( + const char *path, + const Geometry::VoronoiDiagram &vd, + const Points &points, + const Lines &lines, + const Polygons &offset_curves = Polygons(), + const Lines &helper_lines = Lines(), + double scale = 0) +{ + BoundingBox bbox; + bbox.merge(get_extents(points)); + bbox.merge(get_extents(lines)); + bbox.merge(get_extents(offset_curves)); + bbox.merge(get_extents(helper_lines)); + bbox.min -= (0.01 * bbox.size().cast()).cast(); + bbox.max += (0.01 * bbox.size().cast()).cast(); + + if (scale == 0) + scale = +// 0.1 + 0.01 + * std::min(bbox.size().x(), bbox.size().y()); + else + scale /= SCALING_FACTOR; + + const std::string inputSegmentPointColor = "lightseagreen"; + const coord_t inputSegmentPointRadius = coord_t(0.09 * scale); + const std::string inputSegmentColor = "lightseagreen"; + const coord_t inputSegmentLineWidth = coord_t(0.03 * scale); + + const std::string voronoiPointColor = "black"; + const coord_t voronoiPointRadius = coord_t(0.06 * scale); + const std::string voronoiLineColorPrimary = "black"; + const std::string voronoiLineColorSecondary = "green"; + const std::string voronoiArcColor = "red"; + const coord_t voronoiLineWidth = coord_t(0.02 * scale); + + const std::string offsetCurveColor = "magenta"; + const coord_t offsetCurveLineWidth = coord_t(0.02 * scale); + + const std::string helperLineColor = "orange"; + const coord_t helperLineWidth = coord_t(0.04 * scale); + + const bool internalEdgesOnly = false; + const bool primaryEdgesOnly = false; + + ::Slic3r::SVG svg(path, bbox); + + // For clipping of half-lines to some reasonable value. + // The line will then be clipped by the SVG viewer anyway. + const double bbox_dim_max = double(std::max(bbox.size().x(), bbox.size().y())); + // For the discretization of the Voronoi parabolic segments. + const double discretization_step = 0.0002 * bbox_dim_max; + + // Make a copy of the input segments with the double type. + std::vector segments; + for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++ it) + segments.push_back(Voronoi::Internal::segment_type( + Voronoi::Internal::point_type(double(it->a(0)), double(it->a(1))), + Voronoi::Internal::point_type(double(it->b(0)), double(it->b(1))))); + + // Color exterior edges. + for (boost::polygon::voronoi_diagram::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) + if (!it->is_finite()) + Voronoi::Internal::color_exterior(&(*it)); + + // Draw the end points of the input polygon. + for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) { + svg.draw(it->a, inputSegmentPointColor, inputSegmentPointRadius); + svg.draw(it->b, inputSegmentPointColor, inputSegmentPointRadius); + } + // Draw the input polygon. + for (Lines::const_iterator it = lines.begin(); it != lines.end(); ++it) + svg.draw(Line(Point(coord_t(it->a(0)), coord_t(it->a(1))), Point(coord_t(it->b(0)), coord_t(it->b(1)))), inputSegmentColor, inputSegmentLineWidth); + +#if 1 + // Draw voronoi vertices. + for (boost::polygon::voronoi_diagram::const_vertex_iterator it = vd.vertices().begin(); it != vd.vertices().end(); ++it) + if (! internalEdgesOnly || it->color() != Voronoi::Internal::EXTERNAL_COLOR) + svg.draw(Point(coord_t(it->x()), coord_t(it->y())), voronoiPointColor, voronoiPointRadius); + + for (boost::polygon::voronoi_diagram::const_edge_iterator it = vd.edges().begin(); it != vd.edges().end(); ++it) { + if (primaryEdgesOnly && !it->is_primary()) + continue; + if (internalEdgesOnly && (it->color() == Voronoi::Internal::EXTERNAL_COLOR)) + continue; + std::vector samples; + std::string color = voronoiLineColorPrimary; + if (!it->is_finite()) { + Voronoi::Internal::clip_infinite_edge(points, segments, *it, bbox_dim_max, &samples); + if (! it->is_primary()) + color = voronoiLineColorSecondary; + } else { + // Store both points of the segment into samples. sample_curved_edge will split the initial line + // until the discretization_step is reached. + samples.push_back(Voronoi::Internal::point_type(it->vertex0()->x(), it->vertex0()->y())); + samples.push_back(Voronoi::Internal::point_type(it->vertex1()->x(), it->vertex1()->y())); + if (it->is_curved()) { + Voronoi::Internal::sample_curved_edge(points, segments, *it, samples, discretization_step); + color = voronoiArcColor; + } else if (! it->is_primary()) + color = voronoiLineColorSecondary; + } + for (std::size_t i = 0; i + 1 < samples.size(); ++i) + svg.draw(Line(Point(coord_t(samples[i].x()), coord_t(samples[i].y())), Point(coord_t(samples[i+1].x()), coord_t(samples[i+1].y()))), color, voronoiLineWidth); + } +#endif + + svg.draw_outline(offset_curves, offsetCurveColor, offsetCurveLineWidth); + svg.draw(helper_lines, helperLineColor, helperLineWidth); + + svg.Close(); +} + +} // namespace Slic3r diff --git a/src/libslic3r/libslic3r.h b/src/libslic3r/libslic3r.h index 814ee080719..e3816b87f9c 100644 --- a/src/libslic3r/libslic3r.h +++ b/src/libslic3r/libslic3r.h @@ -24,37 +24,37 @@ #if 1 // Saves around 32% RAM after slicing step, 6.7% after G-code export (tested on PrusaSlicer 2.2.0 final). -typedef int32_t coord_t; +using coord_t = int32_t; #else -//FIXME At least FillRectilinear2 requires coord_t to be 32bit. +//FIXME At least FillRectilinear2 and std::boost Voronoi require coord_t to be 32bit. typedef int64_t coord_t; #endif -typedef double coordf_t; +using coordf_t = double; //FIXME This epsilon value is used for many non-related purposes: // For a threshold of a squared Euclidean distance, // for a trheshold in a difference of radians, // for a threshold of a cross product of two non-normalized vectors etc. -#define EPSILON 1e-4 +static constexpr double EPSILON = 1e-4; // Scaling factor for a conversion from coord_t to coordf_t: 10e-6 // This scaling generates a following fixed point representation with for a 32bit integer: // 0..4294mm with 1nm resolution // int32_t fits an interval of (-2147.48mm, +2147.48mm) // with int64_t we don't have to worry anymore about the size of the int. -#define SCALING_FACTOR 0.000001 +static constexpr double SCALING_FACTOR = 0.000001; // RESOLUTION, SCALED_RESOLUTION: Used as an error threshold for a Douglas-Peucker polyline simplification algorithm. -#define RESOLUTION 0.0125 -#define SCALED_RESOLUTION (RESOLUTION / SCALING_FACTOR) -#define PI 3.141592653589793238 +static constexpr double RESOLUTION = 0.0125; +#define SCALED_RESOLUTION (RESOLUTION / SCALING_FACTOR) +static constexpr double PI = 3.141592653589793238; // When extruding a closed loop, the loop is interrupted and shortened a bit to reduce the seam. -#define LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER 0.15 +static constexpr double LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER = 0.15; // Maximum perimeter length for the loop to apply the small perimeter speed. -#define SMALL_PERIMETER_LENGTH (6.5 / SCALING_FACTOR) * 2 * PI -#define INSET_OVERLAP_TOLERANCE 0.4 +#define SMALL_PERIMETER_LENGTH ((6.5 / SCALING_FACTOR) * 2 * PI) +static constexpr double INSET_OVERLAP_TOLERANCE = 0.4; // 3mm ring around the top / bottom / bridging areas. //FIXME This is quite a lot. -#define EXTERNAL_INFILL_MARGIN 3. +static constexpr double EXTERNAL_INFILL_MARGIN = 3.; //FIXME Better to use an inline function with an explicit return type. //inline coord_t scale_(coordf_t v) { return coord_t(floor(v / SCALING_FACTOR + 0.5f)); } #define scale_(val) ((val) / SCALING_FACTOR) @@ -63,14 +63,6 @@ typedef double coordf_t; #define SLIC3R_DEBUG_OUT_PATH_PREFIX "out/" -#if defined(_MSC_VER) && _MSC_VER < 1900 -# define SLIC3R_CONSTEXPR -# define SLIC3R_NOEXCEPT -#else -#define SLIC3R_CONSTEXPR constexpr -#define SLIC3R_NOEXCEPT noexcept -#endif - inline std::string debug_out_path(const char *name, ...) { char buffer[2048]; @@ -81,22 +73,10 @@ inline std::string debug_out_path(const char *name, ...) return std::string(SLIC3R_DEBUG_OUT_PATH_PREFIX) + std::string(buffer); } -#ifdef _MSC_VER - // Visual Studio older than 2015 does not support the prinf type specifier %zu. Use %Iu instead. - #define PRINTF_ZU "%Iu" -#else - #define PRINTF_ZU "%zu" -#endif - #ifndef UNUSED #define UNUSED(x) (void)(x) #endif /* UNUSED */ -// Detect whether the compiler supports C++11 noexcept exception specifications. -#if defined(_MSC_VER) && _MSC_VER < 1900 - #define noexcept throw() -#endif - // Write slices as SVG images into out directory during the 2D processing of the slices. // #define SLIC3R_DEBUG_SLICE_PROCESSING diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index ac696199028..7e02c0fdd7e 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -89,8 +89,6 @@ set(SLIC3R_GUI_SOURCES GUI/GUI_ObjectSettings.hpp GUI/GUI_ObjectLayers.cpp GUI/GUI_ObjectLayers.hpp - GUI/LambdaObjectDialog.cpp - GUI/LambdaObjectDialog.hpp GUI/MeshUtils.cpp GUI/MeshUtils.hpp GUI/Tab.cpp diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index c28c9375269..5dc0d430f12 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1,6 +1,17 @@ #include +#if ENABLE_SMOOTH_NORMALS +#include +#include +#include +#endif // ENABLE_SMOOTH_NORMALS + #include "3DScene.hpp" +#if ENABLE_ENVIRONMENT_MAP +#include "GUI_App.hpp" +#include "Plater.hpp" +#include "AppConfig.hpp" +#endif // ENABLE_ENVIRONMENT_MAP #include "libslic3r/ExtrusionEntity.hpp" #include "libslic3r/ExtrusionEntityCollection.hpp" @@ -29,12 +40,19 @@ #include #ifdef HAS_GLSAFE -void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name) +void glAssertRecentCallImpl(const char* file_name, unsigned int line, const char* function_name) { +#if defined(NDEBUG) && ENABLE_OPENGL_ERROR_LOGGING + // In release mode, if OpenGL debugging was forced by ENABLE_OPENGL_ERROR_LOGGING, only show + // OpenGL errors if sufficiently high loglevel. + if (Slic3r::get_logging_level() < 5) + return; +#endif // ENABLE_OPENGL_ERROR_LOGGING + GLenum err = glGetError(); if (err == GL_NO_ERROR) return; - const char *sErr = 0; + const char* sErr = 0; switch (err) { case GL_INVALID_ENUM: sErr = "Invalid Enum"; break; case GL_INVALID_VALUE: sErr = "Invalid Value"; break; @@ -45,30 +63,114 @@ void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char case GL_OUT_OF_MEMORY: sErr = "Out Of Memory"; break; default: sErr = "Unknown"; break; } - BOOST_LOG_TRIVIAL(error) << "OpenGL error in " << file_name << ":" << line << ", function " << function_name << "() : " << (int)err << " - " << sErr; + BOOST_LOG_TRIVIAL(error) << "OpenGL error in " << file_name << ":" << line << ", function " << function_name << "() : " << (int)err << " - " << sErr; assert(false); } -#endif +#endif // HAS_GLSAFE namespace Slic3r { -void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh &mesh) +#if ENABLE_SMOOTH_NORMALS +static void smooth_normals_corner(TriangleMesh& mesh, std::vector& normals) +{ + mesh.repair(); + + using MapMatrixXfUnaligned = Eigen::Map>; + using MapMatrixXiUnaligned = Eigen::Map>; + + std::vector face_normals(mesh.stl.stats.number_of_facets); + for (uint32_t i = 0; i < mesh.stl.stats.number_of_facets; ++i) { + face_normals[i] = mesh.stl.facet_start[i].normal; + } + + Eigen::MatrixXd vertices = MapMatrixXfUnaligned(mesh.its.vertices.front().data(), + Eigen::Index(mesh.its.vertices.size()), 3).cast(); + Eigen::MatrixXi indices = MapMatrixXiUnaligned(mesh.its.indices.front().data(), + Eigen::Index(mesh.its.indices.size()), 3); + Eigen::MatrixXd in_normals = MapMatrixXfUnaligned(face_normals.front().data(), + Eigen::Index(face_normals.size()), 3).cast(); + Eigen::MatrixXd out_normals; + + igl::per_corner_normals(vertices, indices, in_normals, 1.0, out_normals); + + normals = std::vector(mesh.its.vertices.size()); + for (size_t i = 0; i < mesh.its.indices.size(); ++i) { + for (size_t j = 0; j < 3; ++j) { + normals[mesh.its.indices[i][j]] = out_normals.row(i * 3 + j).cast(); + } + } +} + +static void smooth_normals_vertex(TriangleMesh& mesh, std::vector& normals) +{ + mesh.repair(); + + using MapMatrixXfUnaligned = Eigen::Map>; + using MapMatrixXiUnaligned = Eigen::Map>; + + Eigen::MatrixXd vertices = MapMatrixXfUnaligned(mesh.its.vertices.front().data(), + Eigen::Index(mesh.its.vertices.size()), 3).cast(); + Eigen::MatrixXi indices = MapMatrixXiUnaligned(mesh.its.indices.front().data(), + Eigen::Index(mesh.its.indices.size()), 3); + Eigen::MatrixXd out_normals; + +// igl::per_vertex_normals(vertices, indices, igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_UNIFORM, out_normals); +// igl::per_vertex_normals(vertices, indices, igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_AREA, out_normals); + igl::per_vertex_normals(vertices, indices, igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_ANGLE, out_normals); +// igl::per_vertex_normals(vertices, indices, igl::PER_VERTEX_NORMALS_WEIGHTING_TYPE_DEFAULT, out_normals); + + normals = std::vector(mesh.its.vertices.size()); + for (size_t i = 0; i < static_cast(out_normals.rows()); ++i) { + normals[i] = out_normals.row(i).cast(); + } +} +#endif // ENABLE_SMOOTH_NORMALS + +#if ENABLE_SMOOTH_NORMALS +void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh& mesh, bool smooth_normals) +#else +void GLIndexedVertexArray::load_mesh_full_shading(const TriangleMesh& mesh) +#endif // ENABLE_SMOOTH_NORMALS { assert(triangle_indices.empty() && vertices_and_normals_interleaved_size == 0); assert(quad_indices.empty() && triangle_indices_size == 0); assert(vertices_and_normals_interleaved.size() % 6 == 0 && quad_indices_size == vertices_and_normals_interleaved.size()); - this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count()); +#if ENABLE_SMOOTH_NORMALS + if (smooth_normals) { + TriangleMesh new_mesh(mesh); + std::vector normals; + smooth_normals_corner(new_mesh, normals); +// smooth_normals_vertex(new_mesh, normals); + + this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 2 * new_mesh.its.vertices.size()); + for (size_t i = 0; i < new_mesh.its.vertices.size(); ++i) { + const stl_vertex& v = new_mesh.its.vertices[i]; + const stl_normal& n = normals[i]; + this->push_geometry(v(0), v(1), v(2), n(0), n(1), n(2)); + } + + for (size_t i = 0; i < new_mesh.its.indices.size(); ++i) { + const stl_triangle_vertex_indices& idx = new_mesh.its.indices[i]; + this->push_triangle(idx(0), idx(1), idx(2)); + } + } + else { +#endif // ENABLE_SMOOTH_NORMALS + this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count()); - unsigned int vertices_count = 0; - for (int i = 0; i < (int)mesh.stl.stats.number_of_facets; ++i) { - const stl_facet &facet = mesh.stl.facet_start[i]; - for (int j = 0; j < 3; ++j) - this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2)); + unsigned int vertices_count = 0; + for (int i = 0; i < (int)mesh.stl.stats.number_of_facets; ++i) { + const stl_facet& facet = mesh.stl.facet_start[i]; + for (int j = 0; j < 3; ++j) + this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2)); - this->push_triangle(vertices_count, vertices_count + 1, vertices_count + 2); - vertices_count += 3; + this->push_triangle(vertices_count, vertices_count + 1, vertices_count + 2); + vertices_count += 3; + } +#if ENABLE_SMOOTH_NORMALS } +#endif // ENABLE_SMOOTH_NORMALS } void GLIndexedVertexArray::finalize_geometry(bool opengl_initialized) @@ -457,7 +559,11 @@ int GLVolumeCollection::load_object_volume( this->volumes.emplace_back(new GLVolume(color)); GLVolume& v = *this->volumes.back(); v.set_color_from_model_volume(model_volume); +#if ENABLE_SMOOTH_NORMALS + v.indexed_vertex_array.load_mesh(mesh, true); +#else v.indexed_vertex_array.load_mesh(mesh); +#endif // ENABLE_SMOOTH_NORMALS v.indexed_vertex_array.finalize_geometry(opengl_initialized); v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx); if (model_volume->is_model_part()) @@ -499,8 +605,12 @@ void GLVolumeCollection::load_object_auxiliary( const ModelInstance& model_instance = *print_object->model_object()->instances[instance_idx.first]; this->volumes.emplace_back(new GLVolume((milestone == slaposPad) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); GLVolume& v = *this->volumes.back(); +#if ENABLE_SMOOTH_NORMALS + v.indexed_vertex_array.load_mesh(mesh, true); +#else v.indexed_vertex_array.load_mesh(mesh); - v.indexed_vertex_array.finalize_geometry(opengl_initialized); +#endif // ENABLE_SMOOTH_NORMALS + v.indexed_vertex_array.finalize_geometry(opengl_initialized); v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first); v.geometry_id = std::pair(timestamp, model_instance.id().id); // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. @@ -663,6 +773,10 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab GLint slope_normal_matrix_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "slope.volume_world_normal_matrix") : -1; GLint slope_z_range_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "slope.z_range") : -1; #endif // ENABLE_SLOPE_RENDERING + +#if ENABLE_ENVIRONMENT_MAP + GLint use_environment_tex_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "use_environment_tex") : -1; +#endif // ENABLE_ENVIRONMENT_MAP glcheck(); if (print_box_min_id != -1) @@ -682,6 +796,18 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab glsafe(::glUniform2fv(slope_z_range_id, 1, (const GLfloat*)m_slope.z_range.data())); #endif // ENABLE_SLOPE_RENDERING +#if ENABLE_ENVIRONMENT_MAP + unsigned int environment_texture_id = GUI::wxGetApp().plater()->get_environment_texture_id(); + bool use_environment_texture = current_program_id > 0 && environment_texture_id > 0 && GUI::wxGetApp().app_config->get("use_environment_map") == "1"; + + if (use_environment_tex_id != -1) + { + glsafe(::glUniform1i(use_environment_tex_id, use_environment_texture ? 1 : 0)); + if (use_environment_texture) + glsafe(::glBindTexture(GL_TEXTURE_2D, environment_texture_id)); + } +#endif // ENABLE_ENVIRONMENT_MAP + GLVolumeWithIdAndZList to_render = volumes_to_render(this->volumes, type, view_matrix, filter_func); for (GLVolumeWithIdAndZ& volume : to_render) { volume.first->set_render_color(); @@ -712,6 +838,11 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab #endif // ENABLE_SLOPE_RENDERING } +#if ENABLE_ENVIRONMENT_MAP + if (use_environment_tex_id != -1 && use_environment_texture) + glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); +#endif // ENABLE_ENVIRONMENT_MAP + glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)); @@ -724,7 +855,7 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab glsafe(::glDisable(GL_BLEND)); } -bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, ModelInstance::EPrintVolumeState* out_state) +bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state) { if (config == nullptr) return false; @@ -738,7 +869,7 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M // Allow the objects to protrude below the print bed print_volume.min(2) = -1e10; - ModelInstance::EPrintVolumeState state = ModelInstance::PVS_Inside; + ModelInstanceEPrintVolumeState state = ModelInstancePVS_Inside; bool contained_min_one = false; @@ -757,11 +888,11 @@ bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, M if (contained) contained_min_one = true; - if ((state == ModelInstance::PVS_Inside) && volume->is_outside) - state = ModelInstance::PVS_Fully_Outside; + if ((state == ModelInstancePVS_Inside) && volume->is_outside) + state = ModelInstancePVS_Fully_Outside; - if ((state == ModelInstance::PVS_Fully_Outside) && volume->is_outside && print_volume.intersects(bb)) - state = ModelInstance::PVS_Partly_Outside; + if ((state == ModelInstancePVS_Fully_Outside) && volume->is_outside && print_volume.intersects(bb)) + state = ModelInstancePVS_Partly_Outside; } if (out_state != nullptr) diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 07c5cd53e3d..f935f0fb478 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -6,24 +6,24 @@ #include "libslic3r/Line.hpp" #include "libslic3r/TriangleMesh.hpp" #include "libslic3r/Utils.hpp" -#include "libslic3r/Model.hpp" +#include "libslic3r/Geometry.hpp" #include -#ifndef NDEBUG -#define HAS_GLSAFE +#if ENABLE_OPENGL_ERROR_LOGGING || ! defined(NDEBUG) + #define HAS_GLSAFE #endif #ifdef HAS_GLSAFE -extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name); -inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } -#define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) -#define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) -#else -inline void glAssertRecentCall() { } -#define glsafe(cmd) cmd -#define glcheck() -#endif + extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name); + inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } + #define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) + #define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false) +#else // HAS_GLSAFE + inline void glAssertRecentCall() { } + #define glsafe(cmd) cmd + #define glcheck() +#endif // HAS_GLSAFE namespace Slic3r { namespace GUI { @@ -40,6 +40,9 @@ class ExtrusionMultiPath; class ExtrusionLoop; class ExtrusionEntity; class ExtrusionEntityCollection; +class ModelObject; +class ModelVolume; +enum ModelInstanceEPrintVolumeState : unsigned char; // A container for interleaved arrays of 3D vertices and normals, // possibly indexed by triangles and / or quads. @@ -122,8 +125,13 @@ class GLIndexedVertexArray { unsigned int triangle_indices_VBO_id{ 0 }; unsigned int quad_indices_VBO_id{ 0 }; - void load_mesh_full_shading(const TriangleMesh &mesh); +#if ENABLE_SMOOTH_NORMALS + void load_mesh_full_shading(const TriangleMesh& mesh, bool smooth_normals = false); + void load_mesh(const TriangleMesh& mesh, bool smooth_normals = false) { this->load_mesh_full_shading(mesh, smooth_normals); } +#else + void load_mesh_full_shading(const TriangleMesh& mesh); void load_mesh(const TriangleMesh& mesh) { this->load_mesh_full_shading(mesh); } +#endif // ENABLE_SMOOTH_NORMALS inline bool has_VBOs() const { return vertices_and_normals_interleaved_VBO_id != 0; } @@ -578,7 +586,7 @@ class GLVolumeCollection // returns true if all the volumes are completely contained in the print volume // returns the containment state in the given out_state, if non-null - bool check_outside_state(const DynamicPrintConfig* config, ModelInstance::EPrintVolumeState* out_state); + bool check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state); void reset_outside_state(); void update_colors_by_extruder(const DynamicPrintConfig* config); diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp index 0f603c2651a..93589e536e9 100644 --- a/src/slic3r/GUI/AppConfig.cpp +++ b/src/slic3r/GUI/AppConfig.cpp @@ -93,6 +93,14 @@ void AppConfig::set_defaults() if (get("use_free_camera").empty()) set("use_free_camera", "0"); +#if ENABLE_ENVIRONMENT_MAP + if (get("use_environment_map").empty()) + set("use_environment_map", "0"); +#endif // ENABLE_ENVIRONMENT_MAP + + if (get("use_inches").empty()) + set("use_inches", "0"); + // Remove legacy window positions/sizes erase("", "main_frame_maximized"); erase("", "main_frame_pos"); @@ -300,13 +308,13 @@ void AppConfig::set_mouse_device(const std::string& name, double translation_spe std::vector AppConfig::get_mouse_device_names() const { - static constexpr char *prefix = "mouse_device:"; - static constexpr size_t prefix_len = 13; // strlen(prefix); reports error C2131: expression did not evaluate to a constant on VS2019 - std::vector out; + static constexpr const char *prefix = "mouse_device:"; + static const size_t prefix_len = strlen(prefix); + std::vector out; for (const std::pair>& key_value_pair : m_storage) - if (boost::starts_with(key_value_pair.first, "mouse_device:") && key_value_pair.first.size() > prefix_len) + if (boost::starts_with(key_value_pair.first, prefix) && key_value_pair.first.size() > prefix_len) out.emplace_back(key_value_pair.first.substr(prefix_len)); - return out; + return out; } void AppConfig::update_config_dir(const std::string &dir) diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index cfd38354b66..8d50998c481 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -1,6 +1,7 @@ #include "BackgroundSlicingProcess.hpp" #include "GUI_App.hpp" #include "GUI.hpp" +#include "MainFrame.hpp" #include #include @@ -34,6 +35,7 @@ #include "RemovableDriveManager.hpp" #include "slic3r/Utils/Thread.hpp" +#include "slic3r/GUI/Plater.hpp" namespace Slic3r { diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.hpp b/src/slic3r/GUI/BackgroundSlicingProcess.hpp index efaea1d11dc..38e9e10755b 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.hpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.hpp @@ -5,22 +5,22 @@ #include #include -#include - #include -#include "libslic3r/Print.hpp" +#include "libslic3r/PrintBase.hpp" +#include "libslic3r/GCode/ThumbnailData.hpp" #include "libslic3r/Format/SL1.hpp" #include "slic3r/Utils/PrintHost.hpp" +namespace boost { namespace filesystem { class path; } } + namespace Slic3r { class DynamicPrintConfig; class GCodePreviewData; class Model; class SLAPrint; -class SL1Archive; class SlicingStatusEvent : public wxEvent { @@ -86,7 +86,7 @@ class BackgroundSlicingProcess // Apply config over the print. Returns false, if the new config values caused any of the already // processed steps to be invalidated, therefore the task will need to be restarted. - Print::ApplyStatus apply(const Model &model, const DynamicPrintConfig &config); + PrintBase::ApplyStatus apply(const Model &model, const DynamicPrintConfig &config); // After calling the apply() function, set_task() may be called to limit the task to be processed by process(). // This is useful for calculating SLA supports for a single object only. void set_task(const PrintBase::TaskParams ¶ms); diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index 4357a371a49..f08b1a37949 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -1,4 +1,6 @@ #include "BedShapeDialog.hpp" +#include "GUI_App.hpp" +#include "OptionsGroup.hpp" #include #include diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index bf12cc8934d..b583eca4a10 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -3,7 +3,7 @@ // The bed shape dialog. // The dialog opens from Print Settins tab->Bed Shape : Set... -#include "OptionsGroup.hpp" +#include "GUI_Utils.hpp" #include "2DBed.hpp" #include "I18N.hpp" @@ -13,6 +13,8 @@ namespace Slic3r { namespace GUI { +class ConfigOptionsGroup; + using ConfigOptionsGroupShp = std::shared_ptr; class BedShapePanel : public wxPanel { diff --git a/src/slic3r/GUI/BitmapCache.cpp b/src/slic3r/GUI/BitmapCache.cpp index 8627ef4cb69..74a4bc1c87f 100644 --- a/src/slic3r/GUI/BitmapCache.cpp +++ b/src/slic3r/GUI/BitmapCache.cpp @@ -281,16 +281,19 @@ wxBitmap* BitmapCache::load_svg(const std::string &bitmap_name, unsigned target_ auto it = m_map.find(folder + bitmap_key); if (it != m_map.end()) return it->second; - else { + // It's expensive to check if the bitmap exists every time, but otherwise: + // For the case, when application was started in Light mode and then switched to the Dark, + // we will never get a white bitmaps, if check m_map.find(bitmap_key) + // before boost::filesystem::exists(var(folder + bitmap_name + ".svg")) + if (!boost::filesystem::exists(var(folder + bitmap_name + ".svg"))) { + folder.clear(); + it = m_map.find(bitmap_key); if (it != m_map.end()) return it->second; } - if (!boost::filesystem::exists(Slic3r::var(folder + bitmap_name + ".svg"))) - folder.clear(); - else - bitmap_key = folder + bitmap_key; + bitmap_key = folder + bitmap_key; } else { diff --git a/src/slic3r/GUI/Camera.cpp b/src/slic3r/GUI/Camera.cpp index 7ae4a7a1b75..ac32767c4ba 100644 --- a/src/slic3r/GUI/Camera.cpp +++ b/src/slic3r/GUI/Camera.cpp @@ -425,9 +425,6 @@ double Camera::calc_zoom_to_bounding_box_factor(const BoundingBoxf3& box, double if ((dx <= 0.0) || (dy <= 0.0)) return -1.0f; - double med_x = 0.5 * (max_x + min_x); - double med_y = 0.5 * (max_y + min_y); - dx *= margin_factor; dy *= margin_factor; diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index d7f0a37b04f..a0df4c65989 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -310,7 +310,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) toggle_field("standby_temperature_delta", have_ooze_prevention); bool have_wipe_tower = config->opt_bool("wipe_tower"); - for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_bridging" }) + for (auto el : { "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", + "wipe_tower_bridging", "wipe_tower_no_sparse_layers", "single_extruder_multi_material_priming" }) toggle_field(el, have_wipe_tower); } diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index e242033d95a..60323976c89 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -27,7 +27,9 @@ #include "libslic3r/Utils.hpp" #include "GUI.hpp" +#include "GUI_App.hpp" #include "GUI_Utils.hpp" +#include "GUI_ObjectManipulation.hpp" #include "slic3r/Config/Snapshot.hpp" #include "slic3r/Utils/PresetUpdater.hpp" @@ -839,6 +841,10 @@ PageMode::PageMode(ConfigWizard *parent) append(radio_simple); append(radio_advanced); append(radio_expert); + + append_text("\n" + _L("The size of the object can be specified in inches")); + check_inch = new wxCheckBox(this, wxID_ANY, _L("Use inches")); + append(check_inch); } void PageMode::on_activate() @@ -849,6 +855,8 @@ void PageMode::on_activate() if (mode == "advanced") { radio_advanced->SetValue(true); } else if (mode == "expert") { radio_expert->SetValue(true); } else { radio_simple->SetValue(true); } + + check_inch->SetValue(wxGetApp().app_config->get("use_inches") == "1"); } void PageMode::serialize_mode(AppConfig *app_config) const @@ -865,6 +873,7 @@ void PageMode::serialize_mode(AppConfig *app_config) const return; app_config->set("view_mode", mode); + app_config->set("use_inches", check_inch->GetValue() ? "1" : "0"); } PageVendors::PageVendors(ConfigWizard *parent) @@ -2178,6 +2187,7 @@ bool ConfigWizard::run(RunReason reason, StartPage start_page) p->apply_config(app.app_config, app.preset_bundle, app.preset_updater); app.app_config->set_legacy_datadir(false); app.update_mode(); + app.obj_manipul()->update_ui_from_settings(); BOOST_LOG_TRIVIAL(info) << "ConfigWizard applied"; return true; } else { diff --git a/src/slic3r/GUI/ConfigWizard_private.hpp b/src/slic3r/GUI/ConfigWizard_private.hpp index 49993bfb1b2..c99c5952b90 100644 --- a/src/slic3r/GUI/ConfigWizard_private.hpp +++ b/src/slic3r/GUI/ConfigWizard_private.hpp @@ -24,6 +24,9 @@ #include "AppConfig.hpp" #include "PresetBundle.hpp" #include "BedShapeDialog.hpp" +#include "GUI.hpp" +#include "wxExtensions.hpp" + namespace fs = boost::filesystem; @@ -313,6 +316,8 @@ struct PageMode: ConfigWizardPage wxRadioButton *radio_advanced; wxRadioButton *radio_expert; + wxCheckBox *check_inch; + PageMode(ConfigWizard *parent); void serialize_mode(AppConfig *app_config) const; diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index 4c4c1aa8dd7..a4b65c1b826 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -2,6 +2,7 @@ #include "libslic3r/GCode/PreviewData.hpp" #include "GUI.hpp" #include "GUI_App.hpp" +#include "Plater.hpp" #include "I18N.hpp" #include "ExtruderSequenceDialog.hpp" #include "libslic3r/Print.hpp" @@ -32,6 +33,21 @@ namespace DoubleSlider { wxDEFINE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent); +static std::string gcode(Type type) +{ + const PrintConfig& config = GUI::wxGetApp().plater()->fff_print().config(); + switch (type) { + case ColorChange: + return config.color_change_gcode; + case PausePrint: + return config.pause_print_gcode; + case Template: + return config.template_custom_gcode; + default: + return ""; + } +} + Control::Control( wxWindow *parent, wxWindowID id, int lowerValue, @@ -283,18 +299,17 @@ double Control::get_double_value(const SelectedSlider& selection) return m_values[selection == ssLower ? m_lower_value : m_higher_value]; } -using t_custom_code = CustomGCode::Item; -CustomGCode::Info Control::GetTicksValues() const +Info Control::GetTicksValues() const { - CustomGCode::Info custom_gcode_per_print_z; - std::vector& values = custom_gcode_per_print_z.gcodes; + Info custom_gcode_per_print_z; + std::vector& values = custom_gcode_per_print_z.gcodes; const int val_size = m_values.size(); if (!m_values.empty()) for (const TickCode& tick : m_ticks.ticks) { if (tick.tick > val_size) break; - values.emplace_back(t_custom_code{m_values[tick.tick], tick.gcode, tick.extruder, tick.color}); + values.emplace_back(Item{m_values[tick.tick], tick.type, tick.extruder, tick.color, tick.extra}); } if (m_force_mode_apply) @@ -303,7 +318,7 @@ CustomGCode::Info Control::GetTicksValues() const return custom_gcode_per_print_z; } -void Control::SetTicksValues(const CustomGCode::Info& custom_gcode_per_print_z) +void Control::SetTicksValues(const Info& custom_gcode_per_print_z) { if (m_values.empty()) { @@ -314,14 +329,14 @@ void Control::SetTicksValues(const CustomGCode::Info& custom_gcode_per_print_z) const bool was_empty = m_ticks.empty(); m_ticks.ticks.clear(); - const std::vector& heights = custom_gcode_per_print_z.gcodes; + const std::vector& heights = custom_gcode_per_print_z.gcodes; for (auto h : heights) { auto it = std::lower_bound(m_values.begin(), m_values.end(), h.print_z - epsilon()); if (it == m_values.end()) continue; - m_ticks.ticks.emplace(TickCode{int(it-m_values.begin()), h.gcode, h.extruder, h.color}); + m_ticks.ticks.emplace(TickCode{int(it-m_values.begin()), h.type, h.extruder, h.color, h.extra}); } if (!was_empty && m_ticks.empty()) @@ -344,14 +359,14 @@ void Control::SetDrawMode(bool is_sla_print, bool is_sequential_print) void Control::SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder) { - m_mode = !is_one_extruder_printed_model ? t_mode::MultiExtruder : - only_extruder < 0 ? t_mode::SingleExtruder : - t_mode::MultiAsSingle; + m_mode = !is_one_extruder_printed_model ? MultiExtruder : + only_extruder < 0 ? SingleExtruder : + MultiAsSingle; if (!m_ticks.mode) m_ticks.mode = m_mode; m_only_extruder = only_extruder; - UseDefaultColors(m_mode == t_mode::SingleExtruder); + UseDefaultColors(m_mode == SingleExtruder); } void Control::SetExtruderColors( const std::vector& extruder_colors) @@ -652,11 +667,11 @@ void Control::draw_ticks(wxDC& dc) // if we have non-regular draw mode, all ticks should be marked with error icon if (m_draw_mode != dmRegular) icon_name = focused_tick ? "error_tick_f" : "error_tick"; - else if (tick.gcode == ColorChangeCode || tick.gcode == ToolChangeCode) { + else if (tick.type == ColorChange || tick.type == ToolChange) { if (m_ticks.is_conflict_tick(tick, m_mode, m_only_extruder, m_values[tick.tick])) icon_name = focused_tick ? "error_tick_f" : "error_tick"; } - else if (tick.gcode == PausePrintCode) + else if (tick.type == PausePrint) icon_name = focused_tick ? "pause_print_f" : "pause_print"; else icon_name = focused_tick ? "edit_gcode_f" : "edit_gcode"; @@ -681,7 +696,7 @@ std::string Control::get_color_for_tool_change_tick(std::set::const_it auto it_n = it; while (it_n != m_ticks.ticks.begin()) { --it_n; - if (it_n->gcode == ColorChangeCode && it_n->extruder == current_extruder) + if (it_n->type == ColorChange && it_n->extruder == current_extruder) return it_n->color; } @@ -695,13 +710,13 @@ std::string Control::get_color_for_color_change_tick(std::set::const_i bool is_tool_change = false; while (it_n != m_ticks.ticks.begin()) { --it_n; - if (it_n->gcode == ToolChangeCode) { + if (it_n->type == ToolChange) { is_tool_change = true; if (it_n->extruder == it->extruder) return it->color; break; } - if (it_n->gcode == ColorChangeCode && it_n->extruder == it->extruder) + if (it_n->type == ColorChange && it_n->extruder == it->extruder) return it->color; } if (!is_tool_change && it->extruder == def_extruder) @@ -739,28 +754,28 @@ void Control::draw_colored_band(wxDC& dc) wxRect main_band = get_colored_band_rect(); // don't color a band for MultiExtruder mode - if (m_ticks.empty() || m_mode == t_mode::MultiExtruder) + if (m_ticks.empty() || m_mode == MultiExtruder) { draw_band(dc, GetParent()->GetBackgroundColour(), main_band); return; } - const int default_color_idx = m_mode==t_mode::MultiAsSingle ? std::max(m_only_extruder - 1, 0) : 0; + const int default_color_idx = m_mode==MultiAsSingle ? std::max(m_only_extruder - 1, 0) : 0; draw_band(dc, wxColour(m_extruder_colors[default_color_idx]), main_band); std::set::const_iterator tick_it = m_ticks.ticks.begin(); while (tick_it != m_ticks.ticks.end()) { - if ( (m_mode == t_mode::SingleExtruder && tick_it->gcode == ColorChangeCode ) || - (m_mode == t_mode::MultiAsSingle && (tick_it->gcode == ToolChangeCode || tick_it->gcode == ColorChangeCode)) ) + if ( (m_mode == SingleExtruder && tick_it->type == ColorChange ) || + (m_mode == MultiAsSingle && (tick_it->type == ToolChange || tick_it->type == ColorChange)) ) { const wxCoord pos = get_position_from_value(tick_it->tick); is_horizontal() ? main_band.SetLeft(SLIDER_MARGIN + pos) : main_band.SetBottom(pos - 1); - const std::string clr_str = m_mode == t_mode::SingleExtruder ? tick_it->color : - tick_it->gcode == ToolChangeCode ? + const std::string clr_str = m_mode == SingleExtruder ? tick_it->color : + tick_it->type == ToolChange ? get_color_for_tool_change_tick(tick_it) : get_color_for_color_change_tick(tick_it); @@ -949,17 +964,17 @@ wxString Control::get_tooltip(int tick/*=-1*/) if (m_focus == fiNone) return ""; if (m_focus == fiOneLayerIcon) - return _(L("One layer mode")); + return _L("One layer mode"); if (m_focus == fiRevertIcon) - return _(L("Discard all custom changes")); + return _L("Discard all custom changes"); if (m_focus == fiCogIcon) - return m_mode == t_mode::MultiAsSingle ? - GUI::from_u8((boost::format(_utf8(L("Jump to height %s or " - "Set extruder sequence for the entire print"))) % " (Shift + G)\n").str()) : - _(L("Jump to height")) + " (Shift + G)"; + return m_mode == MultiAsSingle ? + GUI::from_u8((boost::format(_u8L("Jump to height %s or " + "Set extruder sequence for the entire print")) % " (Shift + G)\n").str()) : + _L("Jump to height") + " (Shift + G)"; if (m_focus == fiColorBand) - return m_mode != t_mode::SingleExtruder ? "" : - _(L("Edit current color - Right click the colored slider segment")); + return m_mode != SingleExtruder ? "" : + _L("Edit current color - Right click the colored slider segment"); if (m_draw_mode == dmSlaPrint) return ""; // no drawn ticks and no tooltips for them in SlaPrinting mode @@ -969,10 +984,10 @@ wxString Control::get_tooltip(int tick/*=-1*/) if (tick_code_it == m_ticks.ticks.end() && m_focus == fiActionIcon) // tick doesn't exist { // Show mode as a first string of tooltop - tooltip = " " + _(L("Print mode")) + ": "; - tooltip += (m_mode == t_mode::SingleExtruder ? CustomGCode::SingleExtruderMode : - m_mode == t_mode::MultiAsSingle ? CustomGCode::MultiAsSingleMode : - CustomGCode::MultiExtruderMode ); + tooltip = " " + _L("Print mode") + ": "; + tooltip += (m_mode == SingleExtruder ? SingleExtruderMode : + m_mode == MultiAsSingle ? MultiAsSingleMode : + MultiExtruderMode ); tooltip += "\n\n"; /* Note: just on OSX!!! @@ -982,68 +997,70 @@ wxString Control::get_tooltip(int tick/*=-1*/) * */ // Show list of actions with new tick - tooltip += ( m_mode == t_mode::MultiAsSingle ? - _(L("Add extruder change - Left click")) : - m_mode == t_mode::SingleExtruder ? - _(L("Add color change - Left click for predefined color or " - "Shift + Left click for custom color selection")) : - _(L("Add color change - Left click")) ) + " " + - _(L("or press \"+\" key")) + "\n" + ( - is_osx ? - _(L("Add another code - Ctrl + Left click")) : - _(L("Add another code - Right click")) ); + tooltip += ( m_mode == MultiAsSingle ? + _L("Add extruder change - Left click") : + m_mode == SingleExtruder ? + _L("Add color change - Left click for predefined color or " + "Shift + Left click for custom color selection") : + _L("Add color change - Left click") ) + " " + + _L("or press \"+\" key") + "\n" + ( + is_osx ? + _L("Add another code - Ctrl + Left click") : + _L("Add another code - Right click") ); } if (tick_code_it != m_ticks.ticks.end()) // tick exists { if (m_draw_mode == dmSequentialFffPrint) - return _(L("The sequential print is on.\n" + return _L("The sequential print is on.\n" "It's impossible to apply any custom G-code for objects printing sequentually.\n" - "This code won't be processed during G-code generation.")); - + "This code won't be processed during G-code generation."); + // Show custom Gcode as a first string of tooltop tooltip = " "; tooltip += - tick_code_it->gcode == ColorChangeCode ? - (m_mode == t_mode::SingleExtruder ? - format_wxstr(_L("Color change (\"%1%\")"), tick_code_it->gcode) : - format_wxstr(_L("Color change (\"%1%\") for Extruder %2%"), tick_code_it->gcode, tick_code_it->extruder)) : - tick_code_it->gcode == PausePrintCode ? - format_wxstr(_L("Pause print (\"%1%\")"), tick_code_it->gcode) : - tick_code_it->gcode == ToolChangeCode ? - format_wxstr(_L("Extruder (tool) is changed to Extruder \"%1%\""), tick_code_it->extruder) : - from_u8(tick_code_it->gcode); + tick_code_it->type == ColorChange ? + (m_mode == SingleExtruder ? + format_wxstr(_L("Color change (\"%1%\")"), gcode(ColorChange)) : + format_wxstr(_L("Color change (\"%1%\") for Extruder %2%"), gcode(ColorChange), tick_code_it->extruder)) : + tick_code_it->type == PausePrint ? + format_wxstr(_L("Pause print (\"%1%\")"), gcode(PausePrint)) : + tick_code_it->type == Template ? + format_wxstr(_L("Custom template (\"%1%\")"), gcode(Template)) : + tick_code_it->type == ToolChange ? + format_wxstr(_L("Extruder (tool) is changed to Extruder \"%1%\""), tick_code_it->extruder) : + from_u8(tick_code_it->extra);// tick_code_it->type == Custom // If tick is marked as a conflict (exclamation icon), // we should to explain why ConflictType conflict = m_ticks.is_conflict_tick(*tick_code_it, m_mode, m_only_extruder, m_values[tick]); if (conflict != ctNone) - tooltip += "\n\n" + _(L("Note")) + "! "; + tooltip += "\n\n" + _L("Note") + "! "; if (conflict == ctModeConflict) - tooltip += _(L("G-code associated to this tick mark is in a conflict with print mode.\n" - "Editing it will cause changes of Slider data.")); + tooltip += _L("G-code associated to this tick mark is in a conflict with print mode.\n" + "Editing it will cause changes of Slider data."); else if (conflict == ctMeaninglessColorChange) - tooltip += _(L("There is a color change for extruder that won't be used till the end of print job.\n" - "This code won't be processed during G-code generation.")); + tooltip += _L("There is a color change for extruder that won't be used till the end of print job.\n" + "This code won't be processed during G-code generation."); else if (conflict == ctMeaninglessToolChange) - tooltip += _(L("There is an extruder change set to the same extruder.\n" - "This code won't be processed during G-code generation.")); + tooltip += _L("There is an extruder change set to the same extruder.\n" + "This code won't be processed during G-code generation."); else if (conflict == ctRedundant) - tooltip += _(L("There is a color change for extruder that has not been used before.\n" - "Check your settings to avoid redundant color changes.")); + tooltip += _L("There is a color change for extruder that has not been used before.\n" + "Check your settings to avoid redundant color changes."); // Show list of actions with existing tick if (m_focus == fiActionIcon) - tooltip += "\n\n" + _(L("Delete tick mark - Left click or press \"-\" key")) + "\n" + ( + tooltip += "\n\n" + _L("Delete tick mark - Left click or press \"-\" key") + "\n" + ( is_osx ? - _(L("Edit tick mark - Ctrl + Left click")) : - _(L("Edit tick mark - Right click")) ); + _L("Edit tick mark - Ctrl + Left click") : + _L("Edit tick mark - Right click") ); } return tooltip; } -int Control::get_edited_tick_for_position(const wxPoint pos, const std::string& gcode /*= ColorChangeCode*/) +int Control::get_edited_tick_for_position(const wxPoint pos, Type type /*= ColorChange*/) { if (m_ticks.empty()) return -1; @@ -1053,7 +1070,7 @@ int Control::get_edited_tick_for_position(const wxPoint pos, const std::string& while (it != m_ticks.ticks.begin()) { --it; - if (it->gcode == gcode) + if (it->type == type) return it->tick; } @@ -1079,7 +1096,7 @@ void Control::OnMotion(wxMouseEvent& event) m_focus = fiRevertIcon; else if (is_point_in_rect(pos, m_rect_cog_icon)) m_focus = fiCogIcon; - else if (m_mode == t_mode::SingleExtruder && is_point_in_rect(pos, get_colored_band_rect()) && + else if (m_mode == SingleExtruder && is_point_in_rect(pos, get_colored_band_rect()) && get_edited_tick_for_position(pos) >= 0 ) m_focus = fiColorBand; else { @@ -1136,21 +1153,21 @@ void Control::append_change_extruder_menu_item(wxMenu* menu, bool switch_current const wxString item_name = wxString::Format(_(L("Extruder %d")), i) + (is_active_extruder ? " (" + _(L("active")) + ")" : ""); - if (m_mode == t_mode::MultiAsSingle) + if (m_mode == MultiAsSingle) append_menu_item(change_extruder_menu, wxID_ANY, item_name, "", - [this, i](wxCommandEvent&) { add_code_as_tick(ToolChangeCode, i); }, *icons[i-1], menu, + [this, i](wxCommandEvent&) { add_code_as_tick(ToolChange, i); }, *icons[i-1], menu, [is_active_extruder]() { return !is_active_extruder; }, GUI::wxGetApp().plater()); } - const wxString change_extruder_menu_name = m_mode == t_mode::MultiAsSingle ? - (switch_current_code ? _(L("Switch code to Change extruder")) : _(L("Change extruder")) ) : - _(L("Change extruder (N/A)")); + const wxString change_extruder_menu_name = m_mode == MultiAsSingle ? + (switch_current_code ? _L("Switch code to Change extruder") : _L("Change extruder") ) : + _L("Change extruder (N/A)"); wxMenuItem* change_extruder_menu_item = menu->AppendSubMenu(change_extruder_menu, change_extruder_menu_name, _(L("Use another extruder"))); change_extruder_menu_item->SetBitmap(create_scaled_bitmap(active_extruders[1] > 0 ? "edit_uni" : "change_extruder")); GUI::wxGetApp().plater()->Bind(wxEVT_UPDATE_UI, [this, change_extruder_menu_item](wxUpdateUIEvent& evt) { - enable_menu_item(evt, [this]() {return m_mode == t_mode::MultiAsSingle; }, change_extruder_menu_item, this); }, + enable_menu_item(evt, [this]() {return m_mode == MultiAsSingle; }, change_extruder_menu_item, this); }, change_extruder_menu_item->GetId()); } } @@ -1173,13 +1190,13 @@ void Control::append_add_color_change_menu_item(wxMenu* menu, bool switch_curren (is_used_extruder ? " (" + _(L("used")) + ")" : ""); append_menu_item(add_color_change_menu, wxID_ANY, item_name, "", - [this, i](wxCommandEvent&) { add_code_as_tick(ColorChangeCode, i); }, "", menu, + [this, i](wxCommandEvent&) { add_code_as_tick(ColorChange, i); }, "", menu, []() { return true; }, GUI::wxGetApp().plater()); } const wxString menu_name = switch_current_code ? - format_wxstr(_L("Switch code to Color change (%1%) for:"), ColorChangeCode) : - format_wxstr(_L("Add color change (%1%) for:"), ColorChangeCode); + format_wxstr(_L("Switch code to Color change (%1%) for:"), gcode(ColorChange)) : + format_wxstr(_L("Add color change (%1%) for:"), gcode(ColorChange)); wxMenuItem* add_color_change_menu_item = menu->AppendSubMenu(add_color_change_menu, menu_name, ""); add_color_change_menu_item->SetBitmap(create_scaled_bitmap("colorchange_add_m")); } @@ -1203,7 +1220,7 @@ void Control::OnLeftUp(wxMouseEvent& event) add_current_tick(); break; case maCogIconClick : - if (m_mode == t_mode::MultiAsSingle && m_draw_mode == dmRegular) + if (m_mode == MultiAsSingle && m_draw_mode == dmRegular) show_cog_icon_context_menu(); else jump_to_print_z(); @@ -1361,9 +1378,9 @@ void Control::OnRightDown(wxMouseEvent& event) m_mouse = m_ticks.ticks.find(TickCode{ tick }) == m_ticks.ticks.end() ? maAddMenu : maEditMenu; } - else if (m_mode == t_mode::SingleExtruder && !detect_selected_slider(pos) && is_point_in_rect(pos, get_colored_band_rect())) + else if (m_mode == SingleExtruder && !detect_selected_slider(pos) && is_point_in_rect(pos, get_colored_band_rect())) m_mouse = maForceColorEdit; - else if (m_mode == t_mode::MultiAsSingle && is_point_in_rect(pos, m_rect_cog_icon)) + else if (m_mode == MultiAsSingle && is_point_in_rect(pos, m_rect_cog_icon)) m_mouse = maCogIconMenu; } if (m_mouse != maNone || !detect_selected_slider(pos)) @@ -1384,11 +1401,11 @@ void Control::OnRightDown(wxMouseEvent& event) // Get active extruders for tick. // Means one current extruder for not existing tick OR -// 2 extruders - for existing tick (extruder before ToolChangeCode and extruder of current existing tick) +// 2 extruders - for existing tick (extruder before ToolChange and extruder of current existing tick) // Use those values to disable selection of active extruders std::array Control::get_active_extruders_for_tick(int tick) const { - int default_initial_extruder = m_mode == t_mode::MultiAsSingle ? std::max(1, m_only_extruder) : 1; + int default_initial_extruder = m_mode == MultiAsSingle ? std::max(1, m_only_extruder) : 1; std::array extruders = { default_initial_extruder, -1 }; if (m_ticks.empty()) return extruders; @@ -1400,7 +1417,7 @@ std::array Control::get_active_extruders_for_tick(int tick) const while (it != m_ticks.ticks.begin()) { --it; - if(it->gcode == ToolChangeCode) { + if(it->type == ToolChange) { extruders[0] = it->extruder; break; } @@ -1411,11 +1428,11 @@ std::array Control::get_active_extruders_for_tick(int tick) const // Get used extruders for tick. // Means all extruders(tools) which will be used during printing from current tick to the end -std::set TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extruder, double print_z, t_mode force_mode/* = t_mode::Undef*/) const +std::set TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extruder, double print_z, Mode force_mode/* = Undef*/) const { - t_mode e_mode = !force_mode ? mode : force_mode; + Mode e_mode = !force_mode ? mode : force_mode; - if (e_mode == t_mode::MultiExtruder) + if (e_mode == MultiExtruder) { // #ys_FIXME: get tool ordering from _correct_ place const ToolOrdering& tool_ordering = GUI::wxGetApp().plater()->fff_print().get_tool_ordering(); @@ -1436,15 +1453,15 @@ std::set TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extru return used_extruders; } - const int default_initial_extruder = e_mode == t_mode::MultiAsSingle ? std::max(only_extruder, 1) : 1; - if (ticks.empty() || e_mode == t_mode::SingleExtruder) + const int default_initial_extruder = e_mode == MultiAsSingle ? std::max(only_extruder, 1) : 1; + if (ticks.empty() || e_mode == SingleExtruder) return {default_initial_extruder}; std::set used_extruders; auto it_start = ticks.lower_bound(TickCode{tick}); auto it = it_start; - if (it == ticks.begin() && it->gcode == ToolChangeCode && + if (it == ticks.begin() && it->type == ToolChange && tick != it->tick ) // In case of switch of ToolChange to ColorChange, when tick exists, // we shouldn't change color for extruder, which will be deleted { @@ -1455,7 +1472,7 @@ std::set TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extru while (it != ticks.begin()) { --it; - if (it->gcode == ToolChangeCode && tick != it->tick) { + if (it->type == ToolChange && tick != it->tick) { used_extruders.emplace(it->extruder); break; } @@ -1465,7 +1482,7 @@ std::set TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extru used_extruders.emplace(default_initial_extruder); for (it = it_start; it != ticks.end(); ++it) - if (it->gcode == ToolChangeCode && tick != it->tick) + if (it->type == ToolChange && tick != it->tick) used_extruders.emplace(it->extruder); return used_extruders; @@ -1475,9 +1492,9 @@ void Control::show_add_context_menu() { wxMenu menu; - if (m_mode == t_mode::SingleExtruder) { - append_menu_item(&menu, wxID_ANY, _(L("Add color change")) + " (M600)", "", - [this](wxCommandEvent&) { add_code_as_tick(ColorChangeCode); }, "colorchange_add_m", &menu); + if (m_mode == SingleExtruder) { + append_menu_item(&menu, wxID_ANY, _L("Add color change") + " (" + gcode(ColorChange) + ")", "", + [this](wxCommandEvent&) { add_code_as_tick(ColorChange); }, "colorchange_add_m", &menu); UseDefaultColors(false); } @@ -1486,11 +1503,15 @@ void Control::show_add_context_menu() append_add_color_change_menu_item(&menu); } - append_menu_item(&menu, wxID_ANY, _(L("Add pause print")) + " (M601)", "", - [this](wxCommandEvent&) { add_code_as_tick(PausePrintCode); }, "pause_print", &menu); + append_menu_item(&menu, wxID_ANY, _L("Add pause print") + " (" + gcode(PausePrint) + ")", "", + [this](wxCommandEvent&) { add_code_as_tick(PausePrint); }, "pause_print", &menu); - append_menu_item(&menu, wxID_ANY, _(L("Add custom G-code")), "", - [this](wxCommandEvent&) { add_code_as_tick(""); }, "edit_gcode", &menu); + if (!gcode(Template).empty()) + append_menu_item(&menu, wxID_ANY, _L("Add custom template") + " (" + gcode(Template) + ")", "", + [this](wxCommandEvent&) { add_code_as_tick(Template); }, "edit_gcode", &menu); + + append_menu_item(&menu, wxID_ANY, _L("Add custom G-code"), "", + [this](wxCommandEvent&) { add_code_as_tick(Custom); }, "edit_gcode", &menu); GUI::wxGetApp().plater()->PopupMenu(&menu); } @@ -1501,23 +1522,23 @@ void Control::show_edit_context_menu() std::set::iterator it = m_ticks.ticks.find(TickCode{ m_selection == ssLower ? m_lower_value : m_higher_value }); - if (it->gcode == ToolChangeCode) { - if (m_mode == t_mode::MultiAsSingle) + if (it->type == ToolChange) { + if (m_mode == MultiAsSingle) append_change_extruder_menu_item(&menu); append_add_color_change_menu_item(&menu, true); } else - append_menu_item(&menu, wxID_ANY, it->gcode == ColorChangeCode ? _(L("Edit color")) : - it->gcode == PausePrintCode ? _(L("Edit pause print message")) : + append_menu_item(&menu, wxID_ANY, it->type == ColorChange ? _(L("Edit color")) : + it->type == PausePrint ? _(L("Edit pause print message")) : _(L("Edit custom G-code")), "", [this](wxCommandEvent&) { edit_tick(); }, "edit_uni", &menu); - if (it->gcode == ColorChangeCode && m_mode == t_mode::MultiAsSingle) + if (it->type == ColorChange && m_mode == MultiAsSingle) append_change_extruder_menu_item(&menu, true); - append_menu_item(&menu, wxID_ANY, it->gcode == ColorChangeCode ? _(L("Delete color change")) : - it->gcode == ToolChangeCode ? _(L("Delete tool change")) : - it->gcode == PausePrintCode ? _(L("Delete pause print")) : + append_menu_item(&menu, wxID_ANY, it->type == ColorChange ? _(L("Delete color change")) : + it->type == ToolChange ? _(L("Delete tool change")) : + it->type == PausePrint ? _(L("Delete pause print")) : _(L("Delete custom G-code")), "", [this](wxCommandEvent&) { delete_current_tick();}, "colorchange_del_f", &menu); @@ -1598,6 +1619,8 @@ static void upgrade_text_entry_dialog(wxTextEntryDialog* dlg, double min = -1.0, if (!textctrl) return; + textctrl->SetInsertionPointEnd(); + wxButton* btn_OK = static_cast(dlg->FindWindowById(wxID_OK)); btn_OK->Bind(wxEVT_UPDATE_UI, [textctrl, min, max](wxUpdateUIEvent& evt) { @@ -1664,13 +1687,13 @@ static double get_print_z_to_jump(double active_print_z, double min_z, double ma return dlg.GetValue().ToCDouble(&value) ? value : -1.0; } -void Control::add_code_as_tick(std::string code, int selected_extruder/* = -1*/) +void Control::add_code_as_tick(Type type, int selected_extruder/* = -1*/) { if (m_selection == ssUndef) return; const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; - if ( !check_ticks_changed_event(code) ) + if ( !check_ticks_changed_event(type) ) return; const int extruder = selected_extruder > 0 ? selected_extruder : std::max(1, m_only_extruder); @@ -1678,18 +1701,18 @@ void Control::add_code_as_tick(std::string code, int selected_extruder/* = -1*/) if ( it == m_ticks.ticks.end() ) { // try to add tick - if (!m_ticks.add_tick(tick, code, extruder, m_values[tick])) + if (!m_ticks.add_tick(tick, type, extruder, m_values[tick])) return; } - else if (code == ToolChangeCode || code == ColorChangeCode) { - // try to switch tick code to ToolChangeCode or ColorChangeCode accordingly - if (!m_ticks.switch_code_for_tick(it, code, extruder)) + else if (type == ToolChange || type == ColorChange) { + // try to switch tick code to ToolChange or ColorChange accordingly + if (!m_ticks.switch_code_for_tick(it, type, extruder)) return; } else return; - post_ticks_changed_event(code); + post_ticks_changed_event(type); } void Control::add_current_tick(bool call_from_keyboard /*= false*/) @@ -1700,16 +1723,16 @@ void Control::add_current_tick(bool call_from_keyboard /*= false*/) auto it = m_ticks.ticks.find(TickCode{ tick }); if (it != m_ticks.ticks.end() || // this tick is already exist - !check_ticks_changed_event(m_mode == t_mode::MultiAsSingle ? ToolChangeCode : ColorChangeCode)) + !check_ticks_changed_event(m_mode == MultiAsSingle ? ToolChange : ColorChange)) return; - if (m_mode == t_mode::SingleExtruder) - add_code_as_tick(ColorChangeCode); + if (m_mode == SingleExtruder) + add_code_as_tick(ColorChange); else { wxMenu menu; - if (m_mode == t_mode::MultiAsSingle) + if (m_mode == MultiAsSingle) append_change_extruder_menu_item(&menu); else append_add_color_change_menu_item(&menu); @@ -1742,12 +1765,12 @@ void Control::delete_current_tick() auto it = m_ticks.ticks.find(TickCode{ m_selection == ssLower ? m_lower_value : m_higher_value }); if (it == m_ticks.ticks.end() || - !check_ticks_changed_event(it->gcode)) + !check_ticks_changed_event(it->type)) return; - const std::string code = it->gcode; + Type type = it->type; m_ticks.ticks.erase(it); - post_ticks_changed_event(code); + post_ticks_changed_event(type); } void Control::edit_tick(int tick/* = -1*/) @@ -1757,12 +1780,12 @@ void Control::edit_tick(int tick/* = -1*/) const std::set::iterator it = m_ticks.ticks.find(TickCode{ tick }); if (it == m_ticks.ticks.end() || - !check_ticks_changed_event(it->gcode)) + !check_ticks_changed_event(it->type)) return; - const std::string code = it->gcode; + Type type = it->type; if (m_ticks.edit_tick(it, m_values[it->tick])) - post_ticks_changed_event(code); + post_ticks_changed_event(type); } // switch on/off one layer mode @@ -1817,7 +1840,7 @@ void Control::move_current_thumb_to_pos(wxPoint pos) void Control::edit_extruder_sequence() { - if (!check_ticks_changed_event(ToolChangeCode)) + if (!check_ticks_changed_event(ToolChange)) return; GUI::ExtruderSequenceDialog dlg(m_extruders_sequence); @@ -1825,7 +1848,7 @@ void Control::edit_extruder_sequence() return; m_extruders_sequence = dlg.GetValue(); - m_ticks.erase_all_ticks_with_code(ToolChangeCode); + m_ticks.erase_all_ticks_with_code(ToolChange); int tick = 0; double value = 0.0; @@ -1838,7 +1861,7 @@ void Control::edit_extruder_sequence() bool meaningless_tick = tick == 0.0 && cur_extruder == extruder; if (!meaningless_tick) - m_ticks.ticks.emplace(TickCode{tick, ToolChangeCode, cur_extruder + 1, m_extruder_colors[cur_extruder]}); + m_ticks.ticks.emplace(TickCode{tick, ToolChange,cur_extruder + 1, m_extruder_colors[cur_extruder]}); extruder++; if (extruder == extr_cnt) @@ -1857,7 +1880,7 @@ void Control::edit_extruder_sequence() tick += m_extruders_sequence.interval_by_layers; } - post_ticks_changed_event(ToolChangeCode); + post_ticks_changed_event(ToolChange); } void Control::jump_to_print_z() @@ -1876,64 +1899,64 @@ void Control::jump_to_print_z() SetHigherValue(tick_value); } -void Control::post_ticks_changed_event(const std::string& gcode /*= ""*/) +void Control::post_ticks_changed_event(Type type /*= Custom*/) { - m_force_mode_apply = (gcode.empty() || gcode == ColorChangeCode || gcode == ToolChangeCode); + m_force_mode_apply = type != ToolChange; wxPostEvent(this->GetParent(), wxCommandEvent(wxCUSTOMEVT_TICKSCHANGED)); } -bool Control::check_ticks_changed_event(const std::string& gcode) +bool Control::check_ticks_changed_event(Type type) { if ( m_ticks.mode == m_mode || - (gcode != ColorChangeCode && gcode != ToolChangeCode) || - (m_ticks.mode == t_mode::SingleExtruder && m_mode == t_mode::MultiAsSingle) || // All ColorChanges will be applied for 1st extruder - (m_ticks.mode == t_mode::MultiExtruder && m_mode == t_mode::MultiAsSingle) ) // Just mark ColorChanges for all unused extruders + (type != ColorChange && type != ToolChange) || + (m_ticks.mode == SingleExtruder && m_mode == MultiAsSingle) || // All ColorChanges will be applied for 1st extruder + (m_ticks.mode == MultiExtruder && m_mode == MultiAsSingle) ) // Just mark ColorChanges for all unused extruders return true; - if ((m_ticks.mode == t_mode::SingleExtruder && m_mode == t_mode::MultiExtruder ) || - (m_ticks.mode == t_mode::MultiExtruder && m_mode == t_mode::SingleExtruder) ) + if ((m_ticks.mode == SingleExtruder && m_mode == MultiExtruder ) || + (m_ticks.mode == MultiExtruder && m_mode == SingleExtruder) ) { - if (!m_ticks.has_tick_with_code(ColorChangeCode)) + if (!m_ticks.has_tick_with_code(ColorChange)) return true; - wxString message = (m_ticks.mode == t_mode::SingleExtruder ? - _(L("The last color change data was saved for a single extruder printing.")) : - _(L("The last color change data was saved for a multi extruder printing.")) + wxString message = (m_ticks.mode == SingleExtruder ? + _L("The last color change data was saved for a single extruder printing.") : + _L("The last color change data was saved for a multi extruder printing.") ) + "\n" + - _(L("Your current changes will delete all saved color changes.")) + "\n\n\t" + - _(L("Are you sure you want to continue?")); + _L("Your current changes will delete all saved color changes.") + "\n\n\t" + + _L("Are you sure you want to continue?"); - wxMessageDialog msg(this, message, _(L("Notice")), wxYES_NO); + wxMessageDialog msg(this, message, _L("Notice"), wxYES_NO); if (msg.ShowModal() == wxID_YES) { - m_ticks.erase_all_ticks_with_code(ColorChangeCode); - post_ticks_changed_event(ColorChangeCode); + m_ticks.erase_all_ticks_with_code(ColorChange); + post_ticks_changed_event(ColorChange); } return false; } - // m_ticks_mode == t_mode::MultiAsSingle - if( m_ticks.has_tick_with_code(ToolChangeCode) ) + // m_ticks_mode == MultiAsSingle + if( m_ticks.has_tick_with_code(ToolChange) ) { - wxString message = m_mode == t_mode::SingleExtruder ? ( - _(L("The last color change data was saved for a multi extruder printing.")) + "\n\n" + - _(L("Select YES if you want to delete all saved tool changes, \n" - "NO if you want all tool changes switch to color changes, \n" - "or CANCEL to leave it unchanged.")) + "\n\n\t" + - _(L("Do you want to delete all saved tool changes?")) - ) : ( // t_mode::MultiExtruder - _(L("The last color change data was saved for a multi extruder printing with tool changes for whole print.")) + "\n\n" + - _(L("Your current changes will delete all saved extruder (tool) changes.")) + "\n\n\t" + - _(L("Are you sure you want to continue?")) ) ; - - wxMessageDialog msg(this, message, _(L("Notice")), wxYES_NO | (m_mode == t_mode::SingleExtruder ? wxCANCEL : 0)); + wxString message = m_mode == SingleExtruder ? ( + _L("The last color change data was saved for a multi extruder printing.") + "\n\n" + + _L("Select YES if you want to delete all saved tool changes, \n" + "NO if you want all tool changes switch to color changes, \n" + "or CANCEL to leave it unchanged.") + "\n\n\t" + + _L("Do you want to delete all saved tool changes?") + ): ( // MultiExtruder + _L("The last color change data was saved for a multi extruder printing with tool changes for whole print.") + "\n\n" + + _L("Your current changes will delete all saved extruder (tool) changes.") + "\n\n\t" + + _L("Are you sure you want to continue?") ) ; + + wxMessageDialog msg(this, message, _L("Notice"), wxYES_NO | (m_mode == SingleExtruder ? wxCANCEL : 0)); const int answer = msg.ShowModal(); if (answer == wxID_YES) { - m_ticks.erase_all_ticks_with_code(ToolChangeCode); - post_ticks_changed_event(ToolChangeCode); + m_ticks.erase_all_ticks_with_code(ToolChange); + post_ticks_changed_event(ToolChange); } - else if (m_mode == t_mode::SingleExtruder && answer == wxID_NO) { - m_ticks.switch_code(ToolChangeCode, ColorChangeCode); - post_ticks_changed_event(ColorChangeCode); + else if (m_mode == SingleExtruder && answer == wxID_NO) { + m_ticks.switch_code(ToolChange, ColorChange); + post_ticks_changed_event(ColorChange); } return false; } @@ -1941,9 +1964,9 @@ bool Control::check_ticks_changed_event(const std::string& gcode) return true; } -std::string TickCodeInfo::get_color_for_tick(TickCode tick, const std::string& code, const int extruder) +std::string TickCodeInfo::get_color_for_tick(TickCode tick, Type type, const int extruder) { - if (mode == t_mode::SingleExtruder && code == ColorChangeCode && m_use_default_colors) + if (mode == SingleExtruder && type == ColorChange && m_use_default_colors) { const std::vector& colors = GCodePreviewData::ColorPrintColors(); if (ticks.empty()) @@ -1955,14 +1978,14 @@ std::string TickCodeInfo::get_color_for_tick(TickCode tick, const std::string& c std::string color = (*m_colors)[extruder - 1]; - if (code == ColorChangeCode) + if (type == ColorChange) { if (!ticks.empty()) { auto before_tick_it = std::lower_bound(ticks.begin(), ticks.end(), tick ); while (before_tick_it != ticks.begin()) { --before_tick_it; - if (before_tick_it->gcode == ColorChangeCode && before_tick_it->extruder == extruder) { + if (before_tick_it->type == ColorChange && before_tick_it->extruder == extruder) { color = before_tick_it->color; break; } @@ -1974,63 +1997,67 @@ std::string TickCodeInfo::get_color_for_tick(TickCode tick, const std::string& c return color; } -bool TickCodeInfo::add_tick(const int tick, std::string& code, const int extruder, double print_z) +bool TickCodeInfo::add_tick(const int tick, Type type, const int extruder, double print_z) { std::string color; - if (code.empty()) // custom Gcode + std::string extra; + if (type == Custom) // custom Gcode { - code = get_custom_code(custom_gcode, print_z); - if (code.empty()) + extra = get_custom_code(custom_gcode, print_z); + if (extra.empty()) return false; - custom_gcode = code; + custom_gcode = extra; } - else if (code == PausePrintCode) + else if (type == PausePrint) { - /* PausePrintCode doesn't need a color, so - * this field is used for save a short message shown on Printer display - * */ - color = get_pause_print_msg(pause_print_msg, print_z); - if (color.empty()) + extra = get_pause_print_msg(pause_print_msg, print_z); + if (extra.empty()) return false; - pause_print_msg = color; + pause_print_msg = extra; } else { - color = get_color_for_tick(TickCode{ tick }, code, extruder); + color = get_color_for_tick(TickCode{ tick }, type, extruder); if (color.empty()) return false; } - if (mode == t_mode::SingleExtruder) + if (mode == SingleExtruder) m_use_default_colors = true; - ticks.emplace(TickCode{ tick, code, extruder, color }); + ticks.emplace(TickCode{ tick, type, extruder, color, extra }); return true; } bool TickCodeInfo::edit_tick(std::set::iterator it, double print_z) { std::string edited_value; - if (it->gcode == ColorChangeCode) + if (it->type == ColorChange) edited_value = get_new_color(it->color); - else if (it->gcode == PausePrintCode) - edited_value = get_pause_print_msg(it->color, print_z); + else if (it->type == PausePrint) + edited_value = get_pause_print_msg(it->extra, print_z); else - edited_value = get_custom_code(it->gcode, print_z); + edited_value = get_custom_code(it->type == Template ? gcode(Template) : it->extra, print_z); if (edited_value.empty()) return false; TickCode changed_tick = *it; - if (it->gcode == ColorChangeCode || it->gcode == PausePrintCode) { + if (it->type == ColorChange) { if (it->color == edited_value) return false; changed_tick.color = edited_value; } - else { - if (it->gcode == edited_value) + else if (it->type == Template) { + if (gcode(Template) == edited_value) + return false; + changed_tick.extra = edited_value; + changed_tick.type = Custom; + } + else if (it->type == Custom || it->type == PausePrint) { + if (it->extra == edited_value) return false; - changed_tick.gcode = edited_value; + changed_tick.extra = edited_value; } ticks.erase(it); @@ -2039,13 +2066,13 @@ bool TickCodeInfo::edit_tick(std::set::iterator it, double print_z) return true; } -void TickCodeInfo::switch_code(const std::string& code_from, const std::string& code_to) +void TickCodeInfo::switch_code(Type type_from, Type type_to) { for (auto it{ ticks.begin() }, end{ ticks.end() }; it != end; ) - if (it->gcode == code_from) + if (it->type == type_from) { TickCode tick = *it; - tick.gcode = code_to; + tick.type = type_to; tick.extruder = 1; ticks.erase(it); it = ticks.emplace(tick).first; @@ -2054,14 +2081,14 @@ void TickCodeInfo::switch_code(const std::string& code_from, const std::string& ++it; } -bool TickCodeInfo::switch_code_for_tick(std::set::iterator it, const std::string& code_to, const int extruder) +bool TickCodeInfo::switch_code_for_tick(std::set::iterator it, Type type_to, const int extruder) { - const std::string color = get_color_for_tick(*it, code_to, extruder); + const std::string color = get_color_for_tick(*it, type_to, extruder); if (color.empty()) return false; - TickCode changed_tick = *it; - changed_tick.gcode = code_to; + TickCode changed_tick = *it; + changed_tick.type = type_to; changed_tick.extruder = extruder; changed_tick.color = color; @@ -2071,36 +2098,36 @@ bool TickCodeInfo::switch_code_for_tick(std::set::iterator it, const s return true; } -void TickCodeInfo::erase_all_ticks_with_code(const std::string& gcode) +void TickCodeInfo::erase_all_ticks_with_code(Type type) { for (auto it{ ticks.begin() }, end{ ticks.end() }; it != end; ) { - if (it->gcode == gcode) + if (it->type == type) it = ticks.erase(it); else ++it; } } -bool TickCodeInfo::has_tick_with_code(const std::string& gcode) +bool TickCodeInfo::has_tick_with_code(Type type) { for (const TickCode& tick : ticks) - if (tick.gcode == gcode) + if (tick.type == type) return true; return false; } -ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, t_mode out_mode, int only_extruder, double print_z) +ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, Mode out_mode, int only_extruder, double print_z) { - if ((tick.gcode == ColorChangeCode && ( - (mode == t_mode::SingleExtruder && out_mode == t_mode::MultiExtruder ) || - (mode == t_mode::MultiExtruder && out_mode == t_mode::SingleExtruder) )) || - (tick.gcode == ToolChangeCode && - (mode == t_mode::MultiAsSingle && out_mode != t_mode::MultiAsSingle)) ) + if ((tick.type == ColorChange && ( + (mode == SingleExtruder && out_mode == MultiExtruder ) || + (mode == MultiExtruder && out_mode == SingleExtruder) )) || + (tick.type == ToolChange && + (mode == MultiAsSingle && out_mode != MultiAsSingle)) ) return ctModeConflict; // check ColorChange tick - if (tick.gcode == ColorChangeCode) + if (tick.type == ColorChange) { // We should mark a tick as a "MeaninglessColorChange", // if it has a ColorChange for unused extruder from current print to end of the print @@ -2111,15 +2138,15 @@ ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, t_mode out_mod // We should mark a tick as a "Redundant", // if it has a ColorChange for extruder that has not been used before - if (mode == t_mode::MultiAsSingle && tick.extruder != std::max(only_extruder, 1) ) + if (mode == MultiAsSingle && tick.extruder != std::max(only_extruder, 1) ) { auto it = ticks.lower_bound( tick ); - if (it == ticks.begin() && it->gcode == ToolChangeCode && tick.extruder == it->extruder) + if (it == ticks.begin() && it->type == ToolChange && tick.extruder == it->extruder) return ctNone; while (it != ticks.begin()) { --it; - if (it->gcode == ToolChangeCode && tick.extruder == it->extruder) + if (it->type == ToolChange && tick.extruder == it->extruder) return ctNone; } @@ -2128,7 +2155,7 @@ ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, t_mode out_mod } // check ToolChange tick - if (mode == t_mode::MultiAsSingle && tick.gcode == ToolChangeCode) + if (mode == MultiAsSingle && tick.type == ToolChange) { // We should mark a tick as a "MeaninglessToolChange", // if it has a ToolChange to the same extruder @@ -2138,7 +2165,7 @@ ConflictType TickCodeInfo::is_conflict_tick(const TickCode& tick, t_mode out_mod while (it != ticks.begin()) { --it; - if (it->gcode == ToolChangeCode) + if (it->type == ToolChange) return tick.extruder == it->extruder ? ctMeaninglessToolChange : ctNone; } } diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index bf8f54d6c9c..e39db6fb438 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -17,12 +17,14 @@ class wxMenu; namespace Slic3r { +using namespace CustomGCode; + namespace DoubleSlider { /* For exporting GCode in GCodeWriter is used XYZF_NUM(val) = PRECISION(val, 3) for XYZ values. * So, let use same value as a permissible error for layer height. */ -static double epsilon() { return 0.0011;} +constexpr double epsilon() { return 0.0011; } // custom message the slider sends to its parent to notify a tick-change: wxDECLARE_EVENT(wxCUSTOMEVT_TICKSCHANGED, wxEvent); @@ -73,17 +75,16 @@ enum DrawMode dmSequentialFffPrint, }; -using t_mode = CustomGCode::Mode; - struct TickCode { bool operator<(const TickCode& other) const { return other.tick > this->tick; } bool operator>(const TickCode& other) const { return other.tick < this->tick; } int tick = 0; - std::string gcode = ColorChangeCode; + Type type = ColorChange; int extruder = 0; std::string color; + std::string extra; }; class TickCodeInfo @@ -97,27 +98,27 @@ class TickCodeInfo std::vector* m_colors {nullptr}; - std::string get_color_for_tick(TickCode tick, const std::string& code, const int extruder); + std::string get_color_for_tick(TickCode tick, Type type, const int extruder); public: std::set ticks {}; - t_mode mode = t_mode::Undef; + Mode mode = Undef; bool empty() const { return ticks.empty(); } void set_pause_print_msg(const std::string& message) { pause_print_msg = message; } - bool add_tick(const int tick, std::string& code, int extruder, double print_z); + bool add_tick(const int tick, Type type, int extruder, double print_z); bool edit_tick(std::set::iterator it, double print_z); - void switch_code(const std::string& code_from, const std::string& code_to); - bool switch_code_for_tick(std::set::iterator it, const std::string& code_to, const int extruder); - void erase_all_ticks_with_code(const std::string& gcode); + void switch_code(Type type_from, Type type_to); + bool switch_code_for_tick(std::set::iterator it, Type type_to, const int extruder); + void erase_all_ticks_with_code(Type type); - bool has_tick_with_code(const std::string& gcode); - ConflictType is_conflict_tick(const TickCode& tick, t_mode out_mode, int only_extruder, double print_z); + bool has_tick_with_code(Type type); + ConflictType is_conflict_tick(const TickCode& tick, Mode out_mode, int only_extruder, double print_z); // Get used extruders for tick. // Means all extruders(tools) which will be used during printing from current tick to the end - std::set get_used_extruders_for_tick(int tick, int only_extruder, double print_z, t_mode force_mode = t_mode::Undef) const; + std::set get_used_extruders_for_tick(int tick, int only_extruder, double print_z, Mode force_mode = Undef) const; void suppress_plus (bool suppress) { m_suppress_plus = suppress; } void suppress_minus(bool suppress) { m_suppress_minus = suppress; } @@ -205,13 +206,13 @@ class Control : public wxControl void SetSliderValues(const std::vector& values) { m_values = values; } void ChangeOneLayerLock(); - CustomGCode::Info GetTicksValues() const; - void SetTicksValues(const Slic3r::CustomGCode::Info &custom_gcode_per_print_z); + Info GetTicksValues() const; + void SetTicksValues(const Info &custom_gcode_per_print_z); void SetDrawMode(bool is_sla_print, bool is_sequential_print); - void SetManipulationMode(t_mode mode) { m_mode = mode; } - t_mode GetManipulationMode() const { return m_mode; } + void SetManipulationMode(Mode mode) { m_mode = mode; } + Mode GetManipulationMode() const { return m_mode; } void SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder); void SetExtruderColors(const std::vector& extruder_colors); @@ -235,7 +236,7 @@ class Control : public wxControl void OnRightDown(wxMouseEvent& event); void OnRightUp(wxMouseEvent& event); - void add_code_as_tick(std::string code, int selected_extruder = -1); + void add_code_as_tick(Type type, int selected_extruder = -1); // add default action for tick, when press "+" void add_current_tick(bool call_from_keyboard = false); // delete current tick, when press "-" @@ -293,7 +294,7 @@ class Control : public wxControl void get_size(int *w, int *h); double get_double_value(const SelectedSlider& selection); wxString get_tooltip(int tick = -1); - int get_edited_tick_for_position(wxPoint pos, const std::string& gcode = ColorChangeCode); + int get_edited_tick_for_position(wxPoint pos, Type type = ColorChange); std::string get_color_for_tool_change_tick(std::set::const_iterator it) const; std::string get_color_for_color_change_tick(std::set::const_iterator it) const; @@ -305,8 +306,8 @@ class Control : public wxControl // Use those values to disable selection of active extruders std::array get_active_extruders_for_tick(int tick) const; - void post_ticks_changed_event(const std::string& gcode = ""); - bool check_ticks_changed_event(const std::string& gcode); + void post_ticks_changed_event(Type type = Custom); + bool check_ticks_changed_event(Type type); void append_change_extruder_menu_item (wxMenu*, bool switch_current_code = false); void append_add_color_change_menu_item(wxMenu*, bool switch_current_code = false); @@ -338,7 +339,7 @@ class Control : public wxControl DrawMode m_draw_mode = dmRegular; - t_mode m_mode = t_mode::SingleExtruder; + Mode m_mode = SingleExtruder; int m_only_extruder = -1; MouseAction m_mouse = maNone; diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp index 59ea01314a6..3a06c3056ef 100644 --- a/src/slic3r/GUI/Field.cpp +++ b/src/slic3r/GUI/Field.cpp @@ -3,6 +3,8 @@ #include "I18N.hpp" #include "Field.hpp" #include "wxExtensions.hpp" +#include "Plater.hpp" +#include "MainFrame.hpp" #include "libslic3r/PrintConfig.hpp" @@ -92,6 +94,12 @@ void Field::PostInitialize() case '2': { tab_id = 1; break; } case '3': { tab_id = 2; break; } case '4': { tab_id = 3; break; } +#ifdef __APPLE__ + case 'f': +#else /* __APPLE__ */ + case WXK_CONTROL_F: +#endif /* __APPLE__ */ + case 'F': { wxGetApp().plater()->search(false); break; } default: break; } if (tab_id >= 0) @@ -300,6 +308,12 @@ void Field::msw_rescale(bool rescale_sidetext) } } +void Field::sys_color_changed() +{ + m_Undo_to_sys_btn->msw_rescale(); + m_Undo_btn->msw_rescale(); +} + template bool is_defined_input_value(wxWindow* win, const ConfigOptionType& type) { diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index f41e3c7b05a..484b2059f06 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -221,6 +221,7 @@ class Field { } virtual void msw_rescale(bool rescale_sidetext = false); + void sys_color_changed(); bool get_enter_pressed() const { return bEnterPressed; } void set_enter_pressed(bool pressed) { bEnterPressed = pressed; } diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 1bdc9534201..2d45277849b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -9,6 +9,7 @@ #include "libslic3r/GCode/ThumbnailData.hpp" #include "libslic3r/Geometry.hpp" #include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/Layer.hpp" #include "libslic3r/Utils.hpp" #include "libslic3r/Technologies.hpp" #include "libslic3r/Tesselate.hpp" @@ -22,6 +23,8 @@ #include "slic3r/GUI/OpenGLManager.hpp" #include "slic3r/GUI/3DBed.hpp" #include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/Plater.hpp" +#include "slic3r/GUI/MainFrame.hpp" #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" @@ -61,11 +64,6 @@ #include #include #include "DoubleSlider.hpp" -#if !ENABLE_CANVAS_TOOLTIP_USING_IMGUI -#if ENABLE_RENDER_STATISTICS -#include -#endif // ENABLE_RENDER_STATISTICS -#endif // !ENABLE_CANVAS_TOOLTIP_USING_IMGUI #include @@ -918,7 +916,7 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_items( const GLCanvas3D std::vector print_zs = canvas.get_current_print_zs(true); for (auto custom_code : custom_gcode_per_print_z) { - if (custom_code.gcode != ColorChangeCode) + if (custom_code.type != CustomGCode::ColorChange) continue; auto lower_b = std::lower_bound(print_zs.begin(), print_zs.end(), custom_code.print_z - Slic3r::DoubleSlider::epsilon()); @@ -991,7 +989,7 @@ void GLCanvas3D::LegendTexture::fill_color_print_legend_items( const GLCanvas3D int cnt = custom_gcode_per_print_z.size(); int color_change_idx = color_cnt - extruders_cnt; for (int i = cnt-1; i >= 0; --i) - if (custom_gcode_per_print_z[i].gcode == ColorChangeCode) { + if (custom_gcode_per_print_z[i].type == CustomGCode::ColorChange) { ::memcpy((void*)(colors.data() + color_pos), (const void*)(colors_in.data() + color_in_pos), 4 * sizeof(float)); color_pos += 4; color_in_pos -= 4; @@ -1377,7 +1375,6 @@ void GLCanvas3D::Labels::render(const std::vector& sorted_ } } -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI void GLCanvas3D::Tooltip::set_text(const std::string& text) { // If the mouse is inside an ImGUI dialog, then the tooltip is suppressed. @@ -1429,7 +1426,6 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas imgui.end(); ImGui::PopStyleVar(2); } -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI #if ENABLE_SLOPE_RENDERING void GLCanvas3D::Slope::render() const @@ -1538,9 +1534,8 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas) , m_retina_helper(nullptr) #endif , m_in_render(false) - , m_main_toolbar(GLToolbar::Normal, "Top") - , m_undoredo_toolbar(GLToolbar::Normal, "Top") - , m_collapse_toolbar(GLToolbar::Normal, "Top") + , m_main_toolbar(GLToolbar::Normal, "Main") + , m_undoredo_toolbar(GLToolbar::Normal, "Undo_Redo") , m_gizmos(*this) , m_use_clipping_planes(false) , m_sidebar_field("") @@ -1701,7 +1696,7 @@ void GLCanvas3D::reset_volumes() int GLCanvas3D::check_volumes_outside_state() const { - ModelInstance::EPrintVolumeState state; + ModelInstanceEPrintVolumeState state; m_volumes.check_outside_state(m_config, &state); return (int)state; } @@ -1918,11 +1913,6 @@ void GLCanvas3D::enable_undoredo_toolbar(bool enable) m_undoredo_toolbar.set_enabled(enable); } -void GLCanvas3D::enable_collapse_toolbar(bool enable) -{ - m_collapse_toolbar.set_enabled(enable); -} - void GLCanvas3D::enable_dynamic_background(bool enable) { m_dynamic_background_enabled = enable; @@ -1998,6 +1988,10 @@ void GLCanvas3D::render() return; } +#if ENABLE_ENVIRONMENT_MAP + wxGetApp().plater()->init_environment_texture(); +#endif // ENABLE_ENVIRONMENT_MAP + const Size& cnv_size = get_canvas_size(); // Probably due to different order of events on Linux/GTK2, when one switched from 3D scene // to preview, this was called before canvas had its final size. It reported zero width @@ -2093,7 +2087,6 @@ void GLCanvas3D::render() m_camera.debug_render(); #endif // ENABLE_CAMERA_STATISTICS -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI std::string tooltip; // Negative coordinate means out of the window, likely because the window was deactivated. @@ -2113,7 +2106,7 @@ void GLCanvas3D::render() tooltip = m_undoredo_toolbar.get_tooltip(); if (tooltip.empty()) - tooltip = m_collapse_toolbar.get_tooltip(); + tooltip = wxGetApp().plater()->get_collapse_toolbar().get_tooltip(); if (tooltip.empty()) tooltip = wxGetApp().plater()->get_view_toolbar().get_tooltip(); @@ -2123,7 +2116,6 @@ void GLCanvas3D::render() if (m_tooltip_enabled) m_tooltip.render(m_mouse.position, *this); -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog(*this); @@ -2135,30 +2127,6 @@ void GLCanvas3D::render() auto end_time = std::chrono::high_resolution_clock::now(); m_render_stats.last_frame = std::chrono::duration_cast(end_time - start_time).count(); #endif // ENABLE_RENDER_STATISTICS - -#if !ENABLE_CANVAS_TOOLTIP_USING_IMGUI - std::string tooltip = ""; - - if (tooltip.empty()) - tooltip = m_layers_editing.get_tooltip(*this); - - if (tooltip.empty()) - tooltip = m_gizmos.get_tooltip(); - - if (tooltip.empty()) - tooltip = m_main_toolbar.get_tooltip(); - - if (tooltip.empty()) - tooltip = m_undoredo_toolbar.get_tooltip(); - - if (tooltip.empty()) - tooltip = m_collapse_toolbar.get_tooltip(); - - if (tooltip.empty()) - tooltip = m_view_toolbar.get_tooltip(); - - set_tooltip(tooltip); -#endif // !ENABLE_CANVAS_TOOLTIP_USING_IMGUI } void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background) const @@ -2517,11 +2485,19 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re TriangleMesh mesh = print_object->get_mesh(slaposDrillHoles); assert(! mesh.empty()); mesh.transform(sla_print->sla_trafo(*m_model->objects[volume.object_idx()]).inverse()); +#if ENABLE_SMOOTH_NORMALS + volume.indexed_vertex_array.load_mesh(mesh, true); +#else volume.indexed_vertex_array.load_mesh(mesh); - } else { +#endif // ENABLE_SMOOTH_NORMALS + } else { // Reload the original volume. +#if ENABLE_SMOOTH_NORMALS + volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh(), true); +#else volume.indexed_vertex_array.load_mesh(m_model->objects[volume.object_idx()]->volumes[volume.volume_idx()]->mesh()); - } +#endif // ENABLE_SMOOTH_NORMALS + } volume.finalize_geometry(true); } //FIXME it is an ugly hack to write the timestamp into the "offsets" field to not have to add another member variable @@ -2611,15 +2587,15 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re // checks for geometry outside the print volume to render it accordingly if (!m_volumes.empty()) { - ModelInstance::EPrintVolumeState state; + ModelInstanceEPrintVolumeState state; const bool contained_min_one = m_volumes.check_outside_state(m_config, &state); - _set_warning_texture(WarningTexture::ObjectClashed, state == ModelInstance::PVS_Partly_Outside); - _set_warning_texture(WarningTexture::ObjectOutside, state == ModelInstance::PVS_Fully_Outside); + _set_warning_texture(WarningTexture::ObjectClashed, state == ModelInstancePVS_Partly_Outside); + _set_warning_texture(WarningTexture::ObjectOutside, state == ModelInstancePVS_Fully_Outside); post_event(Event(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, - contained_min_one && !m_model->objects.empty() && state != ModelInstance::PVS_Partly_Outside)); + contained_min_one && !m_model->objects.empty() && state != ModelInstancePVS_Partly_Outside)); } else { @@ -2828,9 +2804,7 @@ void GLCanvas3D::bind_event_handlers() m_canvas->Bind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this); m_canvas->Bind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this); m_canvas->Bind(wxEVT_PAINT, &GLCanvas3D::on_paint, this); -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI m_canvas->Bind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this); -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI } } @@ -2858,9 +2832,7 @@ void GLCanvas3D::unbind_event_handlers() m_canvas->Unbind(wxEVT_MIDDLE_DCLICK, &GLCanvas3D::on_mouse, this); m_canvas->Unbind(wxEVT_RIGHT_DCLICK, &GLCanvas3D::on_mouse, this); m_canvas->Unbind(wxEVT_PAINT, &GLCanvas3D::on_paint, this); -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI m_canvas->Unbind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this); -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI } } @@ -2876,8 +2848,8 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt) m_dirty |= m_main_toolbar.update_items_state(); m_dirty |= m_undoredo_toolbar.update_items_state(); - m_dirty |= m_collapse_toolbar.update_items_state(); m_dirty |= wxGetApp().plater()->get_view_toolbar().update_items_state(); + m_dirty |= wxGetApp().plater()->get_collapse_toolbar().update_items_state(); bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(wxGetApp().plater()->get_camera()); m_dirty |= mouse3d_controller_applied; @@ -3328,6 +3300,11 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) if (evt.MiddleIsDown()) return; + if (wxGetApp().imgui()->update_mouse_data(evt)) { + m_dirty = true; + return; + } + #if ENABLE_RETINA_GL const float scale = m_retina_helper->get_scale_factor(); evt.SetX(evt.GetX() * scale); @@ -3445,29 +3422,20 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) Point pos(evt.GetX(), evt.GetY()); ImGuiWrapper* imgui = wxGetApp().imgui(); -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI if (m_tooltip.is_in_imgui() && evt.LeftUp()) // ignore left up events coming from imgui windows and not processed by them m_mouse.ignore_left_up = true; m_tooltip.set_in_imgui(false); -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI if (imgui->update_mouse_data(evt)) { m_mouse.position = evt.Leaving() ? Vec2d(-1.0, -1.0) : pos.cast(); -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI m_tooltip.set_in_imgui(true); -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI render(); #ifdef SLIC3R_DEBUG_MOUSE_EVENTS printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str()); #endif /* SLIC3R_DEBUG_MOUSE_EVENTS */ // do not return if dragging or tooltip not empty to allow for tooltip update -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI if (!m_mouse.dragging && m_tooltip.is_empty()) return; -#else - if (!m_mouse.dragging && m_canvas->GetToolTipText().empty()) - return; -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI } #ifdef __WXMSW__ @@ -3504,7 +3472,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) return; } - if (m_collapse_toolbar.on_mouse(evt, *this)) + if (wxGetApp().plater()->get_collapse_toolbar().on_mouse(evt, *this)) { if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp()) mouse_up_cleanup(); @@ -3526,9 +3494,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) mouse_up_cleanup(); m_mouse.set_start_position_3D_as_invalid(); -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI m_mouse.position = pos.cast(); -#endif /// ENABLE_CANVAS_TOOLTIP_USING_IMGUI return; } @@ -3560,18 +3526,14 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (top_level_wnd && top_level_wnd->IsActive()) m_canvas->SetFocus(); m_mouse.position = pos.cast(); -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI m_tooltip_enabled = false; -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI // 1) forces a frame render to ensure that m_hover_volume_idxs is updated even when the user right clicks while // the context menu is shown, ensuring it to disappear if the mouse is outside any volume and to // change the volume hover state if any is under the mouse // 2) when switching between 3d view and preview the size of the canvas changes if the side panels are visible, // so forces a resize to avoid multiple renders with different sizes (seen as flickering) _refresh_if_shown_on_screen(); -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI m_tooltip_enabled = true; -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI } m_mouse.set_start_position_2D_as_invalid(); //#endif @@ -3885,14 +3847,12 @@ void GLCanvas3D::on_paint(wxPaintEvent& evt) this->render(); } -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI void GLCanvas3D::on_set_focus(wxFocusEvent& evt) { m_tooltip_enabled = false; _refresh_if_shown_on_screen(); m_tooltip_enabled = true; } -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI Size GLCanvas3D::get_canvas_size() const { @@ -3940,26 +3900,7 @@ void GLCanvas3D::reset_legend_texture() void GLCanvas3D::set_tooltip(const std::string& tooltip) const { if (m_canvas != nullptr) - { -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI m_tooltip.set_text(tooltip); -#else - wxString txt = wxString::FromUTF8(tooltip.data()); - if (m_canvas->GetToolTipText() != txt) - m_canvas->SetToolTip(txt); - -// wxToolTip* t = m_canvas->GetToolTip(); -// if (t != nullptr) -// { -// if (tooltip.empty()) -// m_canvas->UnsetToolTip(); -// else -// t->SetTip(wxString::FromUTF8(tooltip.data())); -// } -// else if (!tooltip.empty()) // Avoid "empty" tooltips => unset of the empty tooltip leads to application crash under OSX -// m_canvas->SetToolTip(wxString::FromUTF8(tooltip.data())); -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI - } } void GLCanvas3D::do_move(const std::string& snapshot_type) @@ -4245,7 +4186,7 @@ void GLCanvas3D::update_ui_from_settings() #endif // ENABLE_RETINA_GL bool enable_collapse = wxGetApp().app_config->get("show_collapse_button") == "1"; - enable_collapse_toolbar(enable_collapse); + wxGetApp().plater()->get_collapse_toolbar().set_enabled(enable_collapse); } @@ -4937,7 +4878,7 @@ bool GLCanvas3D::_init_main_toolbar() return false; item.name = "settings"; - item.icon_filename = "cog.svg"; + item.icon_filename = "cog_.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() + "4] - " + _u8L("Printer Settings Tab") ; @@ -5113,51 +5054,7 @@ bool GLCanvas3D::_init_view_toolbar() bool GLCanvas3D::_init_collapse_toolbar() { - if (!m_collapse_toolbar.is_enabled() && m_collapse_toolbar.get_items_count() > 0) - return true; - - BackgroundTexture::Metadata background_data; - background_data.filename = "toolbar_background.png"; - background_data.left = 16; - background_data.top = 16; - background_data.right = 16; - background_data.bottom = 16; - - if (!m_collapse_toolbar.init(background_data)) - { - // unable to init the toolbar texture, disable it - m_collapse_toolbar.set_enabled(false); - return true; - } - - m_collapse_toolbar.set_layout_type(GLToolbar::Layout::Vertical); - m_collapse_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right); - m_collapse_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); - m_collapse_toolbar.set_border(5.0f); - m_collapse_toolbar.set_separator_size(5); - m_collapse_toolbar.set_gap_size(2); - - GLToolbarItem::Data item; - - item.name = "collapse_sidebar"; - item.icon_filename = "collapse.svg"; - item.tooltip = wxGetApp().plater()->is_sidebar_collapsed() ? _utf8(L("Expand right panel")) : _utf8(L("Collapse right panel")); - item.sprite_id = 0; - item.left.action_callback = [this, item]() { - std::string new_tooltip = wxGetApp().plater()->is_sidebar_collapsed() ? - _utf8(L("Collapse right panel")) : _utf8(L("Expand right panel")); - - int id = m_collapse_toolbar.get_item_id("collapse_sidebar"); - m_collapse_toolbar.set_tooltip(id, new_tooltip); - set_tooltip(""); - - wxGetApp().plater()->collapse_sidebar(!wxGetApp().plater()->is_sidebar_collapsed()); - }; - - if (!m_collapse_toolbar.add_item(item)) - return false; - - return true; + return wxGetApp().plater()->init_collapse_toolbar(); } bool GLCanvas3D::_set_current() @@ -5485,19 +5382,21 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale() const float size = GLToolbar::Default_Icons_Size * scale; // Set current size for all top toolbars. It will be used for next calculations + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); #if ENABLE_RETINA_GL const float sc = m_retina_helper->get_scale_factor() * scale; m_main_toolbar.set_scale(sc); m_undoredo_toolbar.set_scale(sc); - m_collapse_toolbar.set_scale(sc); + collapse_toolbar.set_scale(sc); + size *= m_retina_helper->get_scale_factor(); #else m_main_toolbar.set_icons_size(size); m_undoredo_toolbar.set_icons_size(size); - m_collapse_toolbar.set_icons_size(size); + collapse_toolbar.set_icons_size(size); #endif // ENABLE_RETINA_GL - float top_tb_width = m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + m_collapse_toolbar.get_width(); - int items_cnt = m_main_toolbar.get_visible_items_cnt() + m_undoredo_toolbar.get_visible_items_cnt() + m_collapse_toolbar.get_visible_items_cnt(); + float top_tb_width = m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar.get_width(); + int items_cnt = m_main_toolbar.get_visible_items_cnt() + m_undoredo_toolbar.get_visible_items_cnt() + collapse_toolbar.get_visible_items_cnt(); float noitems_width = top_tb_width - size * items_cnt; // width of separators and borders in top toolbars // calculate scale needed for items in all top toolbars @@ -5510,11 +5409,13 @@ void GLCanvas3D::_check_and_update_toolbar_icon_scale() const // set minimum scale as a auto scale for the toolbars float new_scale = std::min(new_h_scale, new_v_scale); - if (fabs(new_scale - scale) > EPSILON) +#if ENABLE_RETINA_GL + new_scale /= m_retina_helper->get_scale_factor(); +#endif + if (fabs(new_scale - scale) > 0.01) // scale is changed by 1% and more wxGetApp().set_auto_toolbar_icon_scale(new_scale); } - void GLCanvas3D::_render_overlays() const { glsafe(::glDisable(GL_DEPTH_TEST)); @@ -5539,12 +5440,12 @@ void GLCanvas3D::_render_overlays() const const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(/*true*/); m_main_toolbar.set_scale(scale); m_undoredo_toolbar.set_scale(scale); - m_collapse_toolbar.set_scale(scale); + wxGetApp().plater()->get_collapse_toolbar().set_scale(scale); #else const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(/*true*/)); m_main_toolbar.set_icons_size(size); m_undoredo_toolbar.set_icons_size(size); - m_collapse_toolbar.set_icons_size(size); + wxGetApp().plater()->get_collapse_toolbar().set_icons_size(size); #endif // ENABLE_RETINA_GL _render_main_toolbar(); @@ -5648,7 +5549,8 @@ void GLCanvas3D::_render_main_toolbar() const float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - float collapse_toolbar_width = m_collapse_toolbar.is_enabled() ? m_collapse_toolbar.get_width() : 0.0f; + const GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); + float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width) * inv_zoom; m_main_toolbar.set_position(top, left); @@ -5664,7 +5566,8 @@ void GLCanvas3D::_render_undoredo_toolbar() const float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - float collapse_toolbar_width = m_collapse_toolbar.is_enabled() ? m_collapse_toolbar.get_width() : 0.0f; + const GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); + float collapse_toolbar_width = collapse_toolbar.is_enabled() ? collapse_toolbar.get_width() : 0.0f; float left = (m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width() + collapse_toolbar_width)) * inv_zoom; m_undoredo_toolbar.set_position(top, left); m_undoredo_toolbar.render(*this); @@ -5672,8 +5575,7 @@ void GLCanvas3D::_render_undoredo_toolbar() const void GLCanvas3D::_render_collapse_toolbar() const { - if (!m_collapse_toolbar.is_enabled()) - return; + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); Size cnv_size = get_canvas_size(); float inv_zoom = (float)wxGetApp().plater()->get_camera().get_inv_zoom(); @@ -5681,10 +5583,10 @@ void GLCanvas3D::_render_collapse_toolbar() const float band = m_layers_editing.is_enabled() ? (wxGetApp().imgui()->get_style_scaling() * LayersEditing::THICKNESS_BAR_WIDTH) : 0.0; float top = 0.5f * (float)cnv_size.get_height() * inv_zoom; - float left = (0.5f * (float)cnv_size.get_width() - (float)m_collapse_toolbar.get_width() - band) * inv_zoom; + float left = (0.5f * (float)cnv_size.get_width() - (float)collapse_toolbar.get_width() - band) * inv_zoom; - m_collapse_toolbar.set_position(top, left); - m_collapse_toolbar.render(*this); + collapse_toolbar.set_position(top, left); + collapse_toolbar.render(*this); } void GLCanvas3D::_render_view_toolbar() const @@ -6106,7 +6008,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c // For coloring by a color_print(M600), return a parsed color. bool color_by_color_print() const { return color_print_values!=nullptr; } const size_t color_print_color_idx_by_layer_idx(const size_t layer_idx) const { - const CustomGCode::Item value{layers[layer_idx]->print_z + EPSILON, "", 0, ""}; + const CustomGCode::Item value{layers[layer_idx]->print_z + EPSILON, CustomGCode::Custom, 0, ""}; auto it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value); return (it - color_print_values->begin()) % number_tools(); } @@ -6120,36 +6022,36 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c { return fabs(code.print_z - print_z) < EPSILON; }); if (it != color_print_values->end()) { - const std::string& code = it->gcode; + CustomGCode::Type type = it->type; // pause print or custom Gcode - if (code == PausePrintCode || - (code != ColorChangeCode && code != ToolChangeCode)) + if (type == CustomGCode::PausePrint || + (type != CustomGCode::ColorChange && type != CustomGCode::ToolChange)) return number_tools()-1; // last color item is a gray color for pause print or custom G-code // change tool (extruder) - if (code == ToolChangeCode) + if (type == CustomGCode::ToolChange) return get_color_idx_for_tool_change(it, extruder); // change color for current extruder - if (code == ColorChangeCode) { + if (type == CustomGCode::ColorChange) { int color_idx = get_color_idx_for_color_change(it, extruder); if (color_idx >= 0) return color_idx; } } - const CustomGCode::Item value{print_z + EPSILON, "", 0, ""}; + const CustomGCode::Item value{print_z + EPSILON, CustomGCode::Custom, 0, ""}; it = std::lower_bound(color_print_values->begin(), color_print_values->end(), value); while (it != color_print_values->begin()) { --it; // change color for current extruder - if (it->gcode == ColorChangeCode) { + if (it->type == CustomGCode::ColorChange) { int color_idx = get_color_idx_for_color_change(it, extruder); if (color_idx >= 0) return color_idx; } // change tool (extruder) - if (it->gcode == ToolChangeCode) + if (it->type == CustomGCode::ToolChange) return get_color_idx_for_tool_change(it, extruder); } @@ -6162,7 +6064,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c int shift = 0; while (it != color_print_values->begin()) { --it; - if (it->gcode == ColorChangeCode) + if (it->type == CustomGCode::ColorChange) shift++; } return extruders_cnt + shift; @@ -6177,7 +6079,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c auto it_n = it; while (it_n != color_print_values->begin()) { --it_n; - if (it_n->gcode == ColorChangeCode && it_n->extruder == current_extruder) + if (it_n->type == CustomGCode::ColorChange && it_n->extruder == current_extruder) return get_m600_color_idx(it_n); } @@ -6193,7 +6095,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c bool is_tool_change = false; while (it_n != color_print_values->begin()) { --it_n; - if (it_n->gcode == ToolChangeCode) { + if (it_n->type == CustomGCode::ToolChange) { is_tool_change = true; if (it_n->extruder == it->extruder || (it_n->extruder == 0 && it->extruder == extruder)) return get_m600_color_idx(it); @@ -6876,7 +6778,11 @@ void GLCanvas3D::_load_sla_shells() const TriangleMesh &mesh, const float color[4], bool outside_printer_detection_enabled) { m_volumes.volumes.emplace_back(new GLVolume(color)); GLVolume& v = *m_volumes.volumes.back(); +#if ENABLE_SMOOTH_NORMALS + v.indexed_vertex_array.load_mesh(mesh, true); +#else v.indexed_vertex_array.load_mesh(mesh); +#endif // ENABLE_SMOOTH_NORMALS v.indexed_vertex_array.finalize_geometry(this->m_initialized); v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled; v.composite_id.volume_id = volume_id; @@ -7214,9 +7120,10 @@ bool GLCanvas3D::_activate_search_toolbar_item() bool GLCanvas3D::_deactivate_collapse_toolbar_items() { - if (m_collapse_toolbar.is_item_pressed("print")) + GLToolbar& collapse_toolbar = wxGetApp().plater()->get_collapse_toolbar(); + if (collapse_toolbar.is_item_pressed("print")) { - m_collapse_toolbar.force_left_action(m_collapse_toolbar.get_item_id("print"), *this); + collapse_toolbar.force_left_action(collapse_toolbar.get_item_id("print"), *this); return true; } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index c6875165f01..c9433a10ea6 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -3,11 +3,8 @@ #include #include -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI #include -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI -#include "3DScene.hpp" #include "GLToolbar.hpp" #include "GLShader.hpp" #include "Event.hpp" @@ -17,6 +14,8 @@ #include "GLSelectionRectangle.hpp" #include "MeshUtils.hpp" +#include "libslic3r/Slicing.hpp" + #include #include @@ -35,13 +34,16 @@ class wxGLContext; namespace Slic3r { -class Bed3D; struct Camera; class BackgroundSlicingProcess; class GCodePreviewData; struct ThumbnailData; -struct SlicingParameters; -enum LayerHeightEditActionType : unsigned int; +class ModelObject; +class ModelInstance; +class PrintObject; +class Print; +class SLAPrint; +namespace CustomGCode { struct Item; } namespace GUI { @@ -387,7 +389,6 @@ class GLCanvas3D void render(const std::vector& sorted_instances) const; }; -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI class Tooltip { std::string m_text; @@ -403,7 +404,6 @@ class GLCanvas3D void set_in_imgui(bool b) { m_in_imgui = b; } bool is_in_imgui() const { return m_in_imgui; } }; -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI #if ENABLE_SLOPE_RENDERING class Slope @@ -452,7 +452,6 @@ class GLCanvas3D mutable GLGizmosManager m_gizmos; mutable GLToolbar m_main_toolbar; mutable GLToolbar m_undoredo_toolbar; - mutable GLToolbar m_collapse_toolbar; ClippingPlane m_clipping_planes[2]; mutable ClippingPlane m_camera_clipping_plane; bool m_use_clipping_planes; @@ -506,10 +505,8 @@ class GLCanvas3D int m_selected_extruder; Labels m_labels; -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI mutable Tooltip m_tooltip; mutable bool m_tooltip_enabled{ true }; -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI #if ENABLE_SLOPE_RENDERING Slope m_slope; #endif // ENABLE_SLOPE_RENDERING @@ -590,7 +587,6 @@ class GLCanvas3D void enable_selection(bool enable); void enable_main_toolbar(bool enable); void enable_undoredo_toolbar(bool enable); - void enable_collapse_toolbar(bool enable); void enable_dynamic_background(bool enable); void enable_labels(bool enable) { m_labels.enable(enable); } #if ENABLE_SLOPE_RENDERING @@ -641,9 +637,7 @@ class GLCanvas3D void on_timer(wxTimerEvent& evt); void on_mouse(wxMouseEvent& evt); void on_paint(wxPaintEvent& evt); -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI void on_set_focus(wxFocusEvent& evt); -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI Size get_canvas_size() const; Vec2d get_local_mouse_position() const; diff --git a/src/slic3r/GUI/GLSelectionRectangle.cpp b/src/slic3r/GUI/GLSelectionRectangle.cpp index d1d2b89f1d5..d7f3f7a3a80 100644 --- a/src/slic3r/GUI/GLSelectionRectangle.cpp +++ b/src/slic3r/GUI/GLSelectionRectangle.cpp @@ -3,6 +3,7 @@ #include "3DScene.hpp" #include "GLCanvas3D.hpp" #include "GUI_App.hpp" +#include "Plater.hpp" #include diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index c69327cfa42..4ab282b066b 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -6,6 +6,7 @@ #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/Plater.hpp" #include #include @@ -153,9 +154,6 @@ GLToolbar::GLToolbar(GLToolbar::EType type, const std::string& name) , m_name(name) , m_enabled(false) , m_icons_texture_dirty(true) -#if !ENABLE_CANVAS_TOOLTIP_USING_IMGUI - , m_tooltip("") -#endif // !ENABLE_CANVAS_TOOLTIP_USING_IMGUI , m_pressed_toggable_id(-1) { } @@ -359,7 +357,6 @@ int GLToolbar::get_item_id(const std::string& name) const return -1; } -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI std::string GLToolbar::get_tooltip() const { std::string tooltip; @@ -382,7 +379,6 @@ std::string GLToolbar::get_tooltip() const return tooltip; } -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI void GLToolbar::get_additional_tooltip(int item_id, std::string& text) { @@ -449,17 +445,11 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) // prevents loosing selection into the scene if mouse down was done inside the toolbar and mouse up was down outside it, // as when switching between views m_mouse_capture.reset(); -#if !ENABLE_CANVAS_TOOLTIP_USING_IMGUI - if (contains_mouse(mouse_pos, parent) == -1) - // mouse is outside the toolbar - m_tooltip.clear(); -#endif // !ENABLE_CANVAS_TOOLTIP_USING_IMGUI return true; } m_mouse_capture.reset(); } -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI if (evt.Moving()) update_hover_state(mouse_pos, parent); else if (evt.LeftUp()) @@ -500,31 +490,9 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) else return false; } -#else - if (evt.Moving()) - m_tooltip = update_hover_state(mouse_pos, parent); - else if (evt.LeftUp()) - m_mouse_capture.left = false; - else if (evt.MiddleUp()) - m_mouse_capture.middle = false; - else if (evt.RightUp()) - m_mouse_capture.right = false; - else if (evt.Dragging() && m_mouse_capture.any()) - // if the button down was done on this toolbar, prevent from dragging into the scene - processed = true; -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI int item_id = contains_mouse(mouse_pos, parent); -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI if (item_id != -1) -#else - if (item_id == -1) - { - // mouse is outside the toolbar - m_tooltip.clear(); - } - else -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI { // mouse inside toolbar if (evt.LeftDown() || evt.LeftDClick()) @@ -532,12 +500,8 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) m_mouse_capture.left = true; m_mouse_capture.parent = &parent; processed = true; -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI if ((item_id != -2) && !m_items[item_id]->is_separator() && !m_items[item_id]->is_disabled() && ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Left))) -#else - if ((item_id != -2) && !m_items[item_id]->is_separator() && ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Left))) -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI { // mouse is inside an icon do_action(GLToolbarItem::Left, item_id, parent, true); @@ -554,22 +518,14 @@ bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent) m_mouse_capture.right = true; m_mouse_capture.parent = &parent; processed = true; -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI if ((item_id != -2) && !m_items[item_id]->is_separator() && !m_items[item_id]->is_disabled() && ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Right))) -#else - if ((item_id != -2) && !m_items[item_id]->is_separator() && ((m_pressed_toggable_id == -1) || (m_items[item_id]->get_last_action_type() == GLToolbarItem::Right))) -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI { // mouse is inside an icon do_action(GLToolbarItem::Right, item_id, parent, true); parent.set_as_dirty(); } } -#if !ENABLE_CANVAS_TOOLTIP_USING_IMGUI - else if (evt.LeftUp()) - processed = true; -#endif // !ENABLE_CANVAS_TOOLTIP_USING_IMGUI } return processed; @@ -654,11 +610,7 @@ void GLToolbar::do_action(GLToolbarItem::EActionType type, int item_id, GLCanvas if ((0 <= item_id) && (item_id < (int)m_items.size())) { GLToolbarItem* item = m_items[item_id]; -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI if ((item != nullptr) && !item->is_separator() && !item->is_disabled() && (!check_hover || item->is_hovered())) -#else - if ((item != nullptr) && !item->is_separator() && (!check_hover || item->is_hovered())) -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI { if (((type == GLToolbarItem::Right) && item->is_right_toggable()) || ((type == GLToolbarItem::Left) && item->is_left_toggable())) @@ -712,7 +664,6 @@ void GLToolbar::do_action(GLToolbarItem::EActionType type, int item_id, GLCanvas } } -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI void GLToolbar::update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent) { if (!m_enabled) @@ -725,26 +676,8 @@ void GLToolbar::update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent) case Layout::Vertical: { update_hover_state_vertical(mouse_pos, parent); break; } } } -#else -std::string GLToolbar::update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent) -{ - if (!m_enabled) - return ""; - - switch (m_layout.type) - { - default: - case Layout::Horizontal: { return update_hover_state_horizontal(mouse_pos, parent); } - case Layout::Vertical: { return update_hover_state_vertical(mouse_pos, parent); } - } -} -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI void GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent) -#else -std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent) -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI { // NB: mouse_pos is already scaled appropriately @@ -765,10 +698,6 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC float left = m_layout.left + scaled_border; float top = m_layout.top - scaled_border; -#if !ENABLE_CANVAS_TOOLTIP_USING_IMGUI - std::string tooltip; -#endif // !ENABLE_CANVAS_TOOLTIP_USING_IMGUI - for (GLToolbarItem* item : m_items) { if (!item->is_visible()) @@ -783,18 +712,6 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC GLToolbarItem::EState state = item->get_state(); bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top); -#if !ENABLE_CANVAS_TOOLTIP_USING_IMGUI - if (inside) - { - tooltip = item->get_tooltip(); - if (!item->is_pressed()) - { - const std::string& additional_tooltip = item->get_additional_tooltip(); - if (!additional_tooltip.empty()) - tooltip += "\n" + additional_tooltip; - } - } -#endif // !ENABLE_CANVAS_TOOLTIP_USING_IMGUI switch (state) { @@ -838,7 +755,6 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC break; } -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI case GLToolbarItem::Disabled: { if (inside) @@ -863,29 +779,14 @@ std::string GLToolbar::update_hover_state_horizontal(const Vec2d& mouse_pos, GLC { break; } -#else - default: - case GLToolbarItem::Disabled: - { - break; - } -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI } left += icon_stride; } } - -#if !ENABLE_CANVAS_TOOLTIP_USING_IMGUI - return tooltip; -#endif // !ENABLE_CANVAS_TOOLTIP_USING_IMGUI } -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI void GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent) -#else -std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent) -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI { // NB: mouse_pos is already scaled appropriately @@ -905,10 +806,6 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan float left = m_layout.left + scaled_border; float top = m_layout.top - scaled_border; -#if !ENABLE_CANVAS_TOOLTIP_USING_IMGUI - std::string tooltip; -#endif // !ENABLE_CANVAS_TOOLTIP_USING_IMGUI - for (GLToolbarItem* item : m_items) { if (!item->is_visible()) @@ -923,18 +820,6 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan GLToolbarItem::EState state = item->get_state(); bool inside = (left <= (float)scaled_mouse_pos(0)) && ((float)scaled_mouse_pos(0) <= right) && (bottom <= (float)scaled_mouse_pos(1)) && ((float)scaled_mouse_pos(1) <= top); -#if !ENABLE_CANVAS_TOOLTIP_USING_IMGUI - if (inside) - { - tooltip = item->get_tooltip(); - if (!item->is_pressed()) - { - const std::string& additional_tooltip = item->get_additional_tooltip(); - if (!additional_tooltip.empty()) - tooltip += "\n" + additional_tooltip; - } - } -#endif // !ENABLE_CANVAS_TOOLTIP_USING_IMGUI switch (state) { @@ -978,7 +863,6 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan break; } -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI case GLToolbarItem::Disabled: { if (inside) @@ -1003,22 +887,11 @@ std::string GLToolbar::update_hover_state_vertical(const Vec2d& mouse_pos, GLCan { break; } -#else - default: - case GLToolbarItem::Disabled: - { - break; - } -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI } top -= icon_stride; } } - -#if !ENABLE_CANVAS_TOOLTIP_USING_IMGUI - return tooltip; -#endif // !ENABLE_CANVAS_TOOLTIP_USING_IMGUI } int GLToolbar::contains_mouse(const Vec2d& mouse_pos, const GLCanvas3D& parent) const @@ -1365,39 +1238,23 @@ bool GLToolbar::generate_icons_texture() const } std::vector> states; - if (m_name == "Top") + if (m_type == Normal) { -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI states.push_back({ 1, false }); // Normal states.push_back({ 0, false }); // Pressed states.push_back({ 2, false }); // Disabled states.push_back({ 0, false }); // Hover states.push_back({ 0, false }); // HoverPressed states.push_back({ 2, false }); // HoverDisabled -#else - states.push_back(std::make_pair(1, false)); - states.push_back(std::make_pair(0, false)); - states.push_back(std::make_pair(2, false)); - states.push_back(std::make_pair(0, false)); - states.push_back(std::make_pair(0, false)); -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI } - else if (m_name == "View") + else { -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI states.push_back({ 1, false }); // Normal states.push_back({ 1, true }); // Pressed states.push_back({ 1, false }); // Disabled states.push_back({ 0, false }); // Hover states.push_back({ 1, true }); // HoverPressed states.push_back({ 1, false }); // HoverDisabled -#else - states.push_back(std::make_pair(1, false)); - states.push_back(std::make_pair(1, true)); - states.push_back(std::make_pair(1, false)); - states.push_back(std::make_pair(0, false)); - states.push_back(std::make_pair(1, true)); -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI } unsigned int sprite_size_px = (unsigned int)(m_layout.icons_size * m_layout.scale); diff --git a/src/slic3r/GUI/GLToolbar.hpp b/src/slic3r/GUI/GLToolbar.hpp index 9911bb34a52..41c2735c9a3 100644 --- a/src/slic3r/GUI/GLToolbar.hpp +++ b/src/slic3r/GUI/GLToolbar.hpp @@ -61,9 +61,7 @@ class GLToolbarItem Disabled, Hover, HoverPressed, -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI HoverDisabled, -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI Num_States }; @@ -123,15 +121,9 @@ class GLToolbarItem void do_left_action() { m_last_action_type = Left; m_data.left.action_callback(); } void do_right_action() { m_last_action_type = Right; m_data.right.action_callback(); } -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI bool is_enabled() const { return (m_state != Disabled) && (m_state != HoverDisabled); } bool is_disabled() const { return (m_state == Disabled) || (m_state == HoverDisabled); } bool is_hovered() const { return (m_state == Hover) || (m_state == HoverPressed) || (m_state == HoverDisabled); } -#else - bool is_enabled() const { return m_state != Disabled; } - bool is_disabled() const { return m_state == Disabled; } - bool is_hovered() const { return (m_state == Hover) || (m_state == HoverPressed); } -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI bool is_pressed() const { return (m_state == Pressed) || (m_state == HoverPressed); } bool is_visible() const { return m_data.visible; } bool is_separator() const { return m_type == Separator; } @@ -262,9 +254,6 @@ class GLToolbar }; MouseCapture m_mouse_capture; -#if !ENABLE_CANVAS_TOOLTIP_USING_IMGUI - std::string m_tooltip; -#endif // !ENABLE_CANVAS_TOOLTIP_USING_IMGUI int m_pressed_toggable_id; public: @@ -310,11 +299,7 @@ class GLToolbar void force_left_action(int item_id, GLCanvas3D& parent) { do_action(GLToolbarItem::Left, item_id, parent, false); } void force_right_action(int item_id, GLCanvas3D& parent) { do_action(GLToolbarItem::Right, item_id, parent, false); } -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI std::string get_tooltip() const; -#else - const std::string& get_tooltip() const { return m_tooltip; } -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI void get_additional_tooltip(int item_id, std::string& text); void set_additional_tooltip(int item_id, const std::string& text); @@ -336,15 +321,9 @@ class GLToolbar float get_height_vertical() const; float get_main_size() const; void do_action(GLToolbarItem::EActionType type, int item_id, GLCanvas3D& parent, bool check_hover); -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI void update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent); void update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent); void update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent); -#else - std::string update_hover_state(const Vec2d& mouse_pos, GLCanvas3D& parent); - std::string update_hover_state_horizontal(const Vec2d& mouse_pos, GLCanvas3D& parent); - std::string update_hover_state_vertical(const Vec2d& mouse_pos, GLCanvas3D& parent); -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI // returns the id of the item under the given mouse position or -1 if none int contains_mouse(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; int contains_mouse_horizontal(const Vec2d& mouse_pos, const GLCanvas3D& parent) const; diff --git a/src/slic3r/GUI/GUI.cpp b/src/slic3r/GUI/GUI.cpp index 7deed0786be..c119112c2c2 100644 --- a/src/slic3r/GUI/GUI.cpp +++ b/src/slic3r/GUI/GUI.cpp @@ -1,13 +1,11 @@ #include "GUI.hpp" #include "GUI_App.hpp" #include "I18N.hpp" -#include "WipeTowerDialog.hpp" -#include #include -#include #include +#include #if __APPLE__ #import @@ -18,22 +16,16 @@ #include "boost/nowide/convert.hpp" #endif -#include - -#include "wxExtensions.hpp" -#include "GUI_Preview.hpp" #include "AboutDialog.hpp" -#include "AppConfig.hpp" -#include "ConfigWizard.hpp" -#include "PresetBundle.hpp" -#include "UpdateDialogs.hpp" +#include "MsgDialog.hpp" -#include "libslic3r/Utils.hpp" #include "libslic3r/Print.hpp" -#include "Tab.hpp" -#include "GUI_ObjectList.hpp" -namespace Slic3r { namespace GUI { +namespace Slic3r { + +class AppConfig; + +namespace GUI { #if __APPLE__ IOPMAssertionID assertionID; @@ -245,7 +237,7 @@ void show_error_id(int id, const std::string& message) void show_info(wxWindow* parent, const wxString& message, const wxString& title) { - wxMessageDialog msg_wingow(parent, message, wxString(SLIC3R_APP_NAME " - ") + (title.empty() ? _(L("Notice")) : title), wxOK | wxICON_INFORMATION); + wxMessageDialog msg_wingow(parent, message, wxString(SLIC3R_APP_NAME " - ") + (title.empty() ? _L("Notice") : title), wxOK | wxICON_INFORMATION); msg_wingow.ShowModal(); } @@ -257,7 +249,7 @@ void show_info(wxWindow* parent, const char* message, const char* title) void warning_catcher(wxWindow* parent, const wxString& message) { - wxMessageDialog msg(parent, message, _(L("Warning")), wxOK | wxICON_WARNING); + wxMessageDialog msg(parent, message, _L("Warning"), wxOK | wxICON_WARNING); msg.ShowModal(); } @@ -267,14 +259,14 @@ void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string return; wxCheckListBoxComboPopup* popup = new wxCheckListBoxComboPopup; - if (popup != nullptr) - { - // FIXME If the following line is removed, the combo box popup list will not react to mouse clicks. + if (popup != nullptr) { + // FIXME If the following line is removed, the combo box popup list will not react to mouse clicks. // On the other side, with this line the combo box popup cannot be closed by clicking on the combo button on Windows 10. comboCtrl->UseAltPopupWindow(); - comboCtrl->EnablePopupAnimation(false); - comboCtrl->SetPopupControl(popup); + // the following line messes up the popup size the first time it is shown on wxWidgets 3.1.3 +// comboCtrl->EnablePopupAnimation(false); + comboCtrl->SetPopupControl(popup); popup->SetStringValue(from_u8(text)); popup->Bind(wxEVT_CHECKLISTBOX, [popup](wxCommandEvent& evt) { popup->OnCheckListBox(evt); }); popup->Bind(wxEVT_LISTBOX, [popup](wxCommandEvent& evt) { popup->OnListBoxSelection(evt); }); @@ -284,13 +276,11 @@ void create_combochecklist(wxComboCtrl* comboCtrl, std::string text, std::string std::vector items_str; boost::split(items_str, items, boost::is_any_of("|"), boost::token_compress_off); - for (const std::string& item : items_str) - { + for (const std::string& item : items_str) { popup->Append(from_u8(item)); } - for (unsigned int i = 0; i < popup->GetCount(); ++i) - { + for (unsigned int i = 0; i < popup->GetCount(); ++i) { popup->Check(i, initial_value); } } @@ -301,10 +291,8 @@ int combochecklist_get_flags(wxComboCtrl* comboCtrl) int flags = 0; wxCheckListBoxComboPopup* popup = wxDynamicCast(comboCtrl->GetPopupControl(), wxCheckListBoxComboPopup); - if (popup != nullptr) - { - for (unsigned int i = 0; i < popup->GetCount(); ++i) - { + if (popup != nullptr) { + for (unsigned int i = 0; i < popup->GetCount(); ++i) { if (popup->IsChecked(i)) flags |= 1 << i; } diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 9a2a51e9ce1..a7b562bd752 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -36,6 +36,9 @@ #include "GUI_Utils.hpp" #include "AppConfig.hpp" #include "PresetBundle.hpp" +#include "3DScene.hpp" +#include "MainFrame.hpp" +#include "Plater.hpp" #include "../Utils/PresetUpdater.hpp" #include "../Utils/PrintHost.hpp" @@ -103,6 +106,7 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension) static std::string libslic3r_translate_callback(const char *s) { return wxGetTranslation(wxString(s, wxConvUTF8)).utf8_str().data(); } #ifdef WIN32 +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) static void register_win32_dpi_event() { enum { WM_DPICHANGED_ = 0x02e0 }; @@ -118,13 +122,12 @@ static void register_win32_dpi_event() return true; }); } +#endif // !wxVERSION_EQUAL_OR_GREATER_THAN static GUID GUID_DEVINTERFACE_HID = { 0x4D1E55B2, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 }; static void register_win32_device_notification_event() { - enum { WM_DPICHANGED_ = 0x02e0 }; - wxWindow::MSWRegisterMessageHandler(WM_DEVICECHANGE, [](wxWindow *win, WXUINT /* nMsg */, WXWPARAM wParam, WXLPARAM lParam) { // Some messages are sent to top level windows by default, some messages are sent to only registered windows, and we explictely register on MainFrame only. auto main_frame = dynamic_cast(win); @@ -323,6 +326,12 @@ void GUI_App::init_app_config() } } +void GUI_App::init_single_instance_checker(const std::string &name, const std::string &path) +{ + BOOST_LOG_TRIVIAL(debug) << "init wx instance checker " << name << " "<< path; + m_single_instance_checker = std::make_unique(boost::nowide::widen(name), boost::nowide::widen(path)); +} + bool GUI_App::OnInit() { try { @@ -401,7 +410,9 @@ bool GUI_App::on_init_inner() } #ifdef WIN32 +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) register_win32_dpi_event(); +#endif // !wxVERSION_EQUAL_OR_GREATER_THAN register_win32_device_notification_event(); #endif // WIN32 @@ -451,6 +462,10 @@ bool GUI_App::on_init_inner() preset_updater->slic3r_update_notify(); preset_updater->sync(preset_bundle); }); +#ifdef _WIN32 + //sets window property to mainframe so other instances can indentify it + OtherInstanceMessageHandler::init_windows_properties(mainframe, m_instance_hash_int); +#endif //WIN32 } }); @@ -570,6 +585,11 @@ void GUI_App::set_label_clr_sys(const wxColour& clr) { app_config->save(); } +wxSize GUI_App::get_min_size() const +{ + return wxSize(76*m_em_unit, 49 * m_em_unit); +} + float GUI_App::toolbar_icon_scale(const bool is_limited/* = false*/) const { #ifdef __APPLE__ @@ -603,7 +623,7 @@ void GUI_App::set_auto_toolbar_icon_scale(float scale) const const float icon_sc = m_em_unit * 0.1f; #endif // __APPLE__ - int int_val = std::min(int(scale / icon_sc * 100), 100); + long int_val = std::min(int(std::lround(scale / icon_sc * 100)), 100); std::string val = std::to_string(int_val); app_config->set("auto_toolbar_size", val); @@ -613,9 +633,9 @@ void GUI_App::recreate_GUI(const wxString& msg_name) { mainframe->shutdown(); - wxProgressDialog dlg(msg_name, msg_name); + wxProgressDialog dlg(msg_name, msg_name, 100, nullptr, wxPD_AUTO_HIDE); dlg.Pulse(); - dlg.Update(10, _(L("Recreating")) + dots); + dlg.Update(10, _L("Recreating") + dots); MainFrame *old_main_frame = mainframe; mainframe = new MainFrame(); @@ -625,17 +645,17 @@ void GUI_App::recreate_GUI(const wxString& msg_name) sidebar().obj_list()->init_objects(); SetTopWindow(mainframe); - dlg.Update(30, _(L("Recreating")) + dots); + dlg.Update(30, _L("Recreating") + dots); old_main_frame->Destroy(); // For this moment ConfigWizard is deleted, invalidate it. m_wizard = nullptr; - dlg.Update(80, _(L("Loading of current presets")) + dots); + dlg.Update(80, _L("Loading of current presets") + dots); m_printhost_job_queue.reset(new PrintHostJobQueue(mainframe->printhost_queue_dlg())); load_current_presets(); mainframe->Show(true); - dlg.Update(90, _(L("Loading of a mode view")) + dots); + dlg.Update(90, _L("Loading of a mode view") + dots); /* Temporary workaround for the correct behavior of the Scrolled sidebar panel: * change min hight of object list to the normal min value (15 * wxGetApp().em_unit()) * after first whole Mainframe updating/layouting @@ -1041,17 +1061,34 @@ void GUI_App::add_config_menu(wxMenuBar *menu) break; case ConfigMenuPreferences: { +#if ENABLE_LAYOUT_NO_RESTART + bool app_layout_changed = false; +#else bool recreate_app = false; +#endif // ENABLE_LAYOUT_NO_RESTART { // the dialog needs to be destroyed before the call to recreate_GUI() // or sometimes the application crashes into wxDialogBase() destructor // so we put it into an inner scope PreferencesDialog dlg(mainframe); dlg.ShowModal(); +#if ENABLE_LAYOUT_NO_RESTART + app_layout_changed = dlg.settings_layout_changed(); +#else recreate_app = dlg.settings_layout_changed(); +#endif // ENABLE_LAYOUT_NO_RESTART + } +#if ENABLE_LAYOUT_NO_RESTART + if (app_layout_changed) { + mainframe->GetSizer()->Hide((size_t)0); + mainframe->update_layout(); + mainframe->select_tab(0); + mainframe->GetSizer()->Show((size_t)0); } +#else if (recreate_app) recreate_GUI(_L("Changing of the settings layout") + dots); +#endif // ENABLE_LAYOUT_NO_RESTART break; } case ConfigMenuLanguage: @@ -1368,7 +1405,9 @@ void GUI_App::window_pos_restore(wxTopLevelWindow* window, const std::string &na return; } - window->SetSize(metrics->get_rect()); + const wxRect& rect = metrics->get_rect(); + window->SetPosition(rect.GetPosition()); + window->SetSize(rect.GetSize()); window->Maximize(metrics->get_maximized()); } diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 3afadf4e0e0..c2b257f458f 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -3,8 +3,7 @@ #include #include -#include "libslic3r/PrintConfig.hpp" -#include "MainFrame.hpp" +#include "Preset.hpp" #include "ImGuiWrapper.hpp" #include "ConfigWizard.hpp" #include "OpenGLManager.hpp" @@ -13,6 +12,7 @@ #include #include #include +#include #include #include @@ -29,11 +29,21 @@ class PresetBundle; class PresetUpdater; class ModelObject; class PrintHostJobQueue; - +class Model; namespace GUI{ class RemovableDriveManager; class OtherInstanceMessageHandler; +class MainFrame; +class Sidebar; +class ObjectManipulation; +class ObjectSettings; +class ObjectList; +class ObjectLayers; +class Plater; + + + enum FileType { FT_STL, @@ -106,6 +116,9 @@ class GUI_App : public wxApp std::unique_ptr m_printhost_job_queue; ConfigWizard* m_wizard; // Managed by wxWindow tree std::unique_ptr m_other_instance_message_handler; + std::unique_ptr m_single_instance_checker; + std::string m_instance_hash_string; + size_t m_instance_hash_int; public: bool OnInit() override; bool initialized() const { return m_initialized; } @@ -134,6 +147,7 @@ class GUI_App : public wxApp const wxFont& bold_font() { return m_bold_font; } const wxFont& normal_font() { return m_normal_font; } int em_unit() const { return m_em_unit; } + wxSize get_min_size() const; float toolbar_icon_scale(const bool is_limited = false) const; void set_auto_toolbar_icon_scale(float scale) const; @@ -194,6 +208,12 @@ class GUI_App : public wxApp RemovableDriveManager* removable_drive_manager() { return m_removable_drive_manager.get(); } OtherInstanceMessageHandler* other_instance_message_handler() { return m_other_instance_message_handler.get(); } + wxSingleInstanceChecker* single_instance_checker() {return m_single_instance_checker.get();} + + void init_single_instance_checker(const std::string &name, const std::string &path); + void set_instance_hash (const size_t hash) { m_instance_hash_int = hash; m_instance_hash_string = std::to_string(hash); } + std::string get_instance_hash_string () { return m_instance_hash_string; } + size_t get_instance_hash_int () { return m_instance_hash_int; } ImGuiWrapper* imgui() { return m_imgui.get(); } diff --git a/src/slic3r/GUI/GUI_ObjectLayers.cpp b/src/slic3r/GUI/GUI_ObjectLayers.cpp index 157e45ab499..b1a5512d4bb 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.cpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.cpp @@ -2,9 +2,11 @@ #include "GUI_ObjectList.hpp" #include "OptionsGroup.hpp" +#include "GUI_App.hpp" #include "PresetBundle.hpp" #include "libslic3r/Model.hpp" #include "GLCanvas3D.hpp" +#include "Plater.hpp" #include @@ -272,6 +274,33 @@ void ObjectLayers::msw_rescale() m_grid_sizer->Layout(); } +void ObjectLayers::sys_color_changed() +{ + m_bmp_delete.msw_rescale(); + m_bmp_add.msw_rescale(); + + m_grid_sizer->SetHGap(wxGetApp().em_unit()); + + // rescale edit-boxes + const int cells_cnt = m_grid_sizer->GetCols() * m_grid_sizer->GetEffectiveRowsCount(); + 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 + for (auto btn : btns) { + wxSizerItem* b_item = item->GetSizer()->GetItem(btn); + if (b_item->IsWindow()) { + auto button = dynamic_cast(b_item->GetWindow()); + if (button != nullptr) + button->msw_rescale(); + } + } + } + } + m_grid_sizer->Layout(); +} + void ObjectLayers::reset_selection() { m_selectable_range = { 0.0, 0.0 }; diff --git a/src/slic3r/GUI/GUI_ObjectLayers.hpp b/src/slic3r/GUI/GUI_ObjectLayers.hpp index 08b59491038..736b5844d65 100644 --- a/src/slic3r/GUI/GUI_ObjectLayers.hpp +++ b/src/slic3r/GUI/GUI_ObjectLayers.hpp @@ -94,6 +94,7 @@ class ObjectLayers : public OG_Settings void UpdateAndShow(const bool show) override; void msw_rescale(); + void sys_color_changed(); void reset_selection(); void set_selectable_range(const t_layer_height_range& range) { m_selectable_range = range; } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b2283ba4a22..f3ff264cedd 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -4,13 +4,13 @@ #include "GUI_ObjectLayers.hpp" #include "GUI_App.hpp" #include "I18N.hpp" +#include "Plater.hpp" #include "OptionsGroup.hpp" #include "PresetBundle.hpp" #include "Tab.hpp" #include "wxExtensions.hpp" #include "libslic3r/Model.hpp" -#include "LambdaObjectDialog.hpp" #include "GLCanvas3D.hpp" #include "Selection.hpp" @@ -94,6 +94,7 @@ ObjectList::ObjectList(wxWindow* parent) : // ptFFF CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers"); CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill"); + CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("infill"); // FIXME when the ironing icon is available CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support"); CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time"); CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel"); @@ -333,6 +334,34 @@ void ObjectList::get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxD vol_idx = type & itVolume ? m_objects_model->GetVolumeIdByItem(item) : -1; } +void ObjectList::get_selection_indexes(std::vector& obj_idxs, std::vector& vol_idxs) +{ + wxDataViewItemArray sels; + GetSelections(sels); + assert(!sels.IsEmpty()); + + if (m_objects_model->GetItemType(sels[0]) & itVolume) { + for (wxDataViewItem item : sels) { + obj_idxs.emplace_back(m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item))); + + assert(m_objects_model->GetItemType(item) & itVolume); + vol_idxs.emplace_back(m_objects_model->GetVolumeIdByItem(item)); + } + } + else { + for (wxDataViewItem item : sels) { + const ItemType type = m_objects_model->GetItemType(item); + assert(type & (itObject | itInstance | itInstanceRoot)); + + obj_idxs.emplace_back(type & itObject ? m_objects_model->GetIdByItem(item) : + m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item))); + } + } + + std::sort(obj_idxs.begin(), obj_idxs.end(), std::greater()); + obj_idxs.erase(std::unique(obj_idxs.begin(), obj_idxs.end()), obj_idxs.end()); +} + int ObjectList::get_mesh_errors_count(const int obj_idx, const int vol_idx /*= -1*/) const { if (obj_idx < 0) @@ -616,6 +645,7 @@ void ObjectList::msw_rescale_icons() // ptFFF CATEGORY_ICON[L("Layers and Perimeters")] = create_scaled_bitmap("layers"); CATEGORY_ICON[L("Infill")] = create_scaled_bitmap("infill"); + CATEGORY_ICON[L("Ironing")] = create_scaled_bitmap("infill"); // FIXME when the ironing icon is available CATEGORY_ICON[L("Support material")] = create_scaled_bitmap("support"); CATEGORY_ICON[L("Speed")] = create_scaled_bitmap("time"); CATEGORY_ICON[L("Extruders")] = create_scaled_bitmap("funnel"); @@ -667,33 +697,41 @@ void ObjectList::selection_changed() part_selection_changed(); } -void ObjectList::fill_layer_config_ranges_cache() +void ObjectList::copy_layers_to_clipboard() { wxDataViewItemArray sel_layers; GetSelections(sel_layers); - const int obj_idx = m_objects_model->GetObjectIdByItem(sel_layers[0]); + const int obj_idx = m_objects_model->GetObjectIdByItem(sel_layers.front()); if (obj_idx < 0 || (int)m_objects->size() <= obj_idx) return; const t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; - m_layer_config_ranges_cache.clear(); + t_layer_config_ranges& cache_ranges = m_clipboard.get_ranges_cache(); + + if (sel_layers.Count() == 1 && m_objects_model->GetItemType(sel_layers.front()) & itLayerRoot) + { + cache_ranges.clear(); + cache_ranges = ranges; + return; + } for (const auto layer_item : sel_layers) if (m_objects_model->GetItemType(layer_item) & itLayer) { auto range = m_objects_model->GetLayerRangeByItem(layer_item); auto it = ranges.find(range); if (it != ranges.end()) - m_layer_config_ranges_cache[it->first] = it->second; + cache_ranges[it->first] = it->second; } } void ObjectList::paste_layers_into_list() { const int obj_idx = m_objects_model->GetObjectIdByItem(GetSelection()); + t_layer_config_ranges& cache_ranges = m_clipboard.get_ranges_cache(); if (obj_idx < 0 || (int)m_objects->size() <= obj_idx || - m_layer_config_ranges_cache.empty() || printer_technology() == ptSLA) + cache_ranges.empty() || printer_technology() == ptSLA) return; const wxDataViewItem object_item = m_objects_model->GetItemById(obj_idx); @@ -704,7 +742,7 @@ void ObjectList::paste_layers_into_list() t_layer_config_ranges& ranges = object(obj_idx)->layer_config_ranges; // and create Layer item(s) according to the layer_config_ranges - for (const auto range : m_layer_config_ranges_cache) + for (const auto range : cache_ranges) ranges.emplace(range); layers_item = add_layer_root_item(object_item); @@ -717,6 +755,48 @@ void ObjectList::paste_layers_into_list() #endif //no __WXOSX__ } +void ObjectList::copy_settings_to_clipboard() +{ + wxDataViewItem item = GetSelection(); + assert(item.IsOk()); + if (m_objects_model->GetItemType(item) & itSettings) + item = m_objects_model->GetParent(item); + + DynamicPrintConfig& config_cache = m_clipboard.get_config_cache(); + config_cache = get_item_config(item); +} + +void ObjectList::paste_settings_into_list() +{ + wxDataViewItem item = GetSelection(); + assert(item.IsOk()); + if (m_objects_model->GetItemType(item) & itSettings) + item = m_objects_model->GetParent(item); + + ItemType item_type = m_objects_model->GetItemType(item); + if(!(item_type & (itObject | itVolume |itLayer))) + return; + + DynamicPrintConfig& config_cache = m_clipboard.get_config_cache(); + assert(!config_cache.empty()); + + auto keys = config_cache.keys(); + auto part_options = get_options(true); + + for (const std::string& opt_key: keys) { + if (item_type & (itVolume | itLayer) && + std::find(part_options.begin(), part_options.end(), opt_key) == part_options.end()) + continue; // we can't to add object specific options for the part's(itVolume | itLayer) config + + const ConfigOption* option = config_cache.option(opt_key); + if (option) + m_config->set_key_value(opt_key, option->clone()); + } + + // Add settings item for object/sub-object and show them + show_settings(add_settings_item(item, m_config)); +} + void ObjectList::paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes) { if ((obj_idx < 0) || ((int)m_objects->size() <= obj_idx)) @@ -956,20 +1036,46 @@ void ObjectList::extruder_editing() void ObjectList::copy() { - // if (m_selection_mode & smLayer) - // fill_layer_config_ranges_cache(); - // else { - // m_layer_config_ranges_cache.clear(); - wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); - // } + wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_COPY)); } void ObjectList::paste() { - // if (!m_layer_config_ranges_cache.empty()) - // paste_layers_into_list(); - // else - wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); + wxPostEvent((wxEvtHandler*)wxGetApp().plater()->canvas3D()->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_PASTE)); +} + +bool ObjectList::copy_to_clipboard() +{ + wxDataViewItemArray sels; + GetSelections(sels); + ItemType type = m_objects_model->GetItemType(sels.front()); + if (!(type & (itSettings | itLayer | itLayerRoot))) { + m_clipboard.reset(); + return false; + } + + if (type & itSettings) + copy_settings_to_clipboard(); + if (type & (itLayer | itLayerRoot)) + copy_layers_to_clipboard(); + + m_clipboard.set_type(type); + return true; +} + +bool ObjectList::paste_from_clipboard() +{ + if (!(m_clipboard.get_type() & (itSettings | itLayer | itLayerRoot))) { + m_clipboard.reset(); + return false; + } + + if (m_clipboard.get_type() & itSettings) + paste_settings_into_list(); + if (m_clipboard.get_type() & (itLayer | itLayerRoot)) + paste_layers_into_list(); + + return true; } void ObjectList::undo() @@ -1744,6 +1850,31 @@ void ObjectList::append_menu_item_scale_selection_to_fit_print_volume(wxMenu* me [](wxCommandEvent&) { wxGetApp().plater()->scale_selection_to_fit_print_volume(); }, "", menu); } +void ObjectList::append_menu_items_convert_unit(wxMenu* menu) +{ + append_menu_item(menu, wxID_ANY, _L("Convert from imperial unit"), _L("Convert from imperial unit"), + [](wxCommandEvent&) { wxGetApp().plater()->convert_unit(true); }, "", menu); + + append_menu_item(menu, wxID_ANY, _L("Convert to imperial unit"), _L("Convert to imperial unit"), + [](wxCommandEvent&) { wxGetApp().plater()->convert_unit(false); }, "", menu); +} + +void ObjectList::append_menu_item_merge_to_multipart_object(wxMenu* menu) +{ + menu->AppendSeparator(); + append_menu_item(menu, wxID_ANY, _L("Merge"), _L("Merge objects to the one multipart object"), + [this](wxCommandEvent&) { merge(true); }, "", menu, + [this]() { return this->can_merge_to_multipart_object(); }, wxGetApp().plater()); +} + +void ObjectList::append_menu_item_merge_to_single_object(wxMenu* menu) +{ + menu->AppendSeparator(); + append_menu_item(menu, wxID_ANY, _L("Merge"), _L("Merge objects to the one single object"), + [this](wxCommandEvent&) { merge(false); }, "", menu, + [this]() { return this->can_merge_to_single_object(); }, wxGetApp().plater()); +} + void ObjectList::create_object_popupmenu(wxMenu *menu) { #ifdef __WXOSX__ @@ -1751,12 +1882,17 @@ void ObjectList::create_object_popupmenu(wxMenu *menu) #endif // __WXOSX__ append_menu_item_reload_from_disk(menu); + append_menu_items_convert_unit(menu); append_menu_item_export_stl(menu); append_menu_item_fix_through_netfabb(menu); append_menu_item_scale_selection_to_fit_print_volume(menu); // Split object to parts append_menu_item_split(menu); +// menu->AppendSeparator(); + + // Merge multipart object to the single object +// append_menu_item_merge_to_single_object(menu); menu->AppendSeparator(); // Layers Editing for object @@ -1775,6 +1911,7 @@ void ObjectList::create_sla_object_popupmenu(wxMenu *menu) #endif // __WXOSX__ append_menu_item_reload_from_disk(menu); + append_menu_items_convert_unit(menu); append_menu_item_export_stl(menu); append_menu_item_fix_through_netfabb(menu); // rest of a object_sla_menu will be added later in: @@ -1788,6 +1925,7 @@ void ObjectList::create_part_popupmenu(wxMenu *menu) #endif // __WXOSX__ append_menu_item_reload_from_disk(menu); + append_menu_items_convert_unit(menu); append_menu_item_export_stl(menu); append_menu_item_fix_through_netfabb(menu); @@ -2226,8 +2364,9 @@ void ObjectList::del_layers_from_object(const int obj_idx) bool ObjectList::del_subobject_from_object(const int obj_idx, const int idx, const int type) { - if (obj_idx == 1000) - // Cannot delete a wipe tower. + assert(idx >= 0); + if (obj_idx == 1000 || idx<0) + // Cannot delete a wipe tower or volume with negative id return false; ModelObject* object = (*m_objects)[obj_idx]; @@ -2325,6 +2464,205 @@ void ObjectList::split() changed_object(obj_idx); } +void ObjectList::merge(bool to_multipart_object) +{ + // merge selected objects to the multipart object + if (to_multipart_object) + { + auto get_object_idxs = [this](std::vector& obj_idxs, wxDataViewItemArray& sels) + { + // check selections and split instances to the separated objects... + bool instance_selection = false; + for (wxDataViewItem item : sels) + if (m_objects_model->GetItemType(item) & itInstance) { + instance_selection = true; + break; + } + + if (!instance_selection) + { + for (wxDataViewItem item : sels) { + assert(m_objects_model->GetItemType(item) & itObject); + obj_idxs.emplace_back(m_objects_model->GetIdByItem(item)); + } + return; + } + + // map of obj_idx -> set of selected instance_idxs + std::map> sel_map; + std::set empty_set; + for (wxDataViewItem item : sels) { + if (m_objects_model->GetItemType(item) & itObject) + { + int obj_idx = m_objects_model->GetIdByItem(item); + int inst_cnt = (*m_objects)[obj_idx]->instances.size(); + if (inst_cnt == 1) + sel_map.emplace(obj_idx, empty_set); + else + for (int i = 0; i < inst_cnt; i++) + sel_map[obj_idx].emplace(i); + continue; + } + int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetTopParent(item)); + sel_map[obj_idx].emplace(m_objects_model->GetInstanceIdByItem(item)); + } + + // all objects, created from the instances will be added to the end of list + int new_objects_cnt = 0; // count of this new objects + + for (auto map_item : sel_map) + { + int obj_idx = map_item.first; + // object with just 1 instance + if (map_item.second.empty()) { + obj_idxs.emplace_back(obj_idx); + continue; + } + + // object with selected all instances + if ((*m_objects)[map_item.first]->instances.size() == map_item.second.size()) { + instances_to_separated_objects(obj_idx); + // first instance stay on its own place and another all add to the end of list : + obj_idxs.emplace_back(obj_idx); + new_objects_cnt += map_item.second.size() - 1; + continue; + } + + // object with selected some of instances + instances_to_separated_object(obj_idx, map_item.second); + + if (map_item.second.size() == 1) + new_objects_cnt += 1; + else {// we should split to separate instances last object + instances_to_separated_objects(m_objects->size() - 1); + // all instances will stay at the end of list : + new_objects_cnt += map_item.second.size(); + } + } + + // all instatnces are extracted to the separate objects and should be selected + m_prevent_list_events = true; + sels.Clear(); + for (int obj_idx : obj_idxs) + sels.Add(m_objects_model->GetItemById(obj_idx)); + int obj_cnt = m_objects->size(); + for (int obj_idx = obj_cnt - new_objects_cnt; obj_idx < obj_cnt; obj_idx++) { + sels.Add(m_objects_model->GetItemById(obj_idx)); + obj_idxs.emplace_back(obj_idx); + } + UnselectAll(); + SetSelections(sels); + assert(!sels.IsEmpty()); + m_prevent_list_events = false; + }; + + std::vector obj_idxs; + wxDataViewItemArray sels; + GetSelections(sels); + assert(!sels.IsEmpty()); + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Merge")); + + get_object_idxs(obj_idxs, sels); + + // resulted objects merge to the one + Model* model = (*m_objects)[0]->get_model(); + ModelObject* new_object = model->add_object(); + new_object->name = _u8L("Merged"); + DynamicPrintConfig* config = &new_object->config; + + for (int obj_idx : obj_idxs) + { + ModelObject* object = (*m_objects)[obj_idx]; + + const Geometry::Transformation& transformation = object->instances[0]->get_transformation(); + Vec3d scale = transformation.get_scaling_factor(); + Vec3d mirror = transformation.get_mirror(); + Vec3d rotation = transformation.get_rotation(); + + if (object->id() == (*m_objects)[obj_idxs.front()]->id()) + new_object->add_instance(); + Transform3d volume_offset_correction = new_object->instances[0]->get_transformation().get_matrix().inverse() * transformation.get_matrix(); + + // merge volumes + for (const ModelVolume* volume : object->volumes) { + ModelVolume* new_volume = new_object->add_volume(*volume); + + //set rotation + Vec3d vol_rot = new_volume->get_rotation() + rotation; + new_volume->set_rotation(vol_rot); + + // set scale + Vec3d vol_sc_fact = new_volume->get_scaling_factor().cwiseProduct(scale); + new_volume->set_scaling_factor(vol_sc_fact); + + // set mirror + Vec3d vol_mirror = new_volume->get_mirror().cwiseProduct(mirror); + new_volume->set_mirror(vol_mirror); + + // set offset + Vec3d vol_offset = volume_offset_correction* new_volume->get_offset(); + new_volume->set_offset(vol_offset); + } + + // merge settings + auto new_opt_keys = config->keys(); + const DynamicPrintConfig& from_config = object->config; + auto opt_keys = from_config.keys(); + + for (auto& opt_key : opt_keys) { + if (find(new_opt_keys.begin(), new_opt_keys.end(), opt_key) == new_opt_keys.end()) { + const ConfigOption* option = from_config.option(opt_key); + if (!option) { + // if current option doesn't exist in prints.get_edited_preset(), + // get it from default config values + option = DynamicPrintConfig::new_from_defaults_keys({ opt_key })->option(opt_key); + } + config->set_key_value(opt_key, option->clone()); + } + } + // save extruder value if it was set + if (object->volumes.size() == 1 && find(opt_keys.begin(), opt_keys.end(), "extruder") != opt_keys.end()) { + ModelVolume* volume = new_object->volumes.back(); + const ConfigOption* option = from_config.option("extruder"); + if (option) + volume->config.set_key_value("extruder", option->clone()); + } + + // merge layers + for (const auto& range : object->layer_config_ranges) + new_object->layer_config_ranges.emplace(range); + } + // remove selected objects + remove(); + + // Add new object(merged) to the object_list + add_object_to_list(m_objects->size() - 1); + select_item(m_objects_model->GetItemById(m_objects->size() - 1)); + update_selections_on_canvas(); + } + // merge all parts to the one single object + // all part's settings will be lost + else + { + wxDataViewItem item = GetSelection(); + if (!item) + return; + const int obj_idx = m_objects_model->GetIdByItem(item); + if (obj_idx == -1) + return; + + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _L("Merge all parts to the one single object")); + + ModelObject* model_object = (*m_objects)[obj_idx]; + model_object->merge(); + + m_objects_model->DeleteVolumeChildren(item); + + changed_object(obj_idx); + } +} + void ObjectList::layers_editing() { const Selection& selection = scene_selection(); @@ -2450,6 +2788,34 @@ bool ObjectList::can_split_instances() return selection.is_multiple_full_instance() || selection.is_single_full_instance(); } +bool ObjectList::can_merge_to_multipart_object() const +{ + if (printer_technology() == ptSLA) + return false; + + wxDataViewItemArray sels; + GetSelections(sels); + if (sels.IsEmpty()) + return false; + + // should be selected just objects + for (wxDataViewItem item : sels) + if (!(m_objects_model->GetItemType(item) & (itObject | itInstance))) + return false; + + return true; +} + +bool ObjectList::can_merge_to_single_object() const +{ + int obj_idx = get_selected_obj_idx(); + if (obj_idx < 0) + return false; + + // selected object should be multipart + return (*m_objects)[obj_idx]->volumes.size() > 1; +} + // NO_PARAMETERS function call means that changed object index will be determine from Selection() void ObjectList::changed_object(const int obj_idx/* = -1*/) const { @@ -3989,6 +4355,26 @@ void ObjectList::msw_rescale() Layout(); } +void ObjectList::sys_color_changed() +{ + // msw_rescale_icons() updates icons, so use it + msw_rescale_icons(); + + // update existing items with bitmaps + m_objects_model->Rescale(); + + // msw_rescale_menu updates just icons, so use it + for (MenuWithSeparators* menu : { &m_menu_object, + &m_menu_part, + &m_menu_sla_object, + &m_menu_instance, + &m_menu_layer, + &m_menu_default}) + msw_rescale_menu(menu); + + Layout(); +} + void ObjectList::ItemValueChanged(wxDataViewEvent &event) { if (event.GetColumn() == colName) @@ -4050,6 +4436,9 @@ void ObjectList::show_multi_selection_menu() return wxGetApp().plater()->can_reload_from_disk(); }, wxGetApp().plater()); + append_menu_items_convert_unit(menu); + append_menu_item_merge_to_multipart_object(menu); + wxGetApp().plater()->PopupMenu(menu); } diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 72e130737c6..aa5264b07d1 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -13,6 +13,8 @@ #include "wxExtensions.hpp" #include "ObjectDataViewModel.hpp" +#include "libslic3r/PrintConfig.hpp" + class wxBoxSizer; class wxBitmapComboBox; class wxMenuItem; @@ -81,10 +83,32 @@ class ObjectList : public wxDataViewCtrl smLayerRoot = 16, // used for undo/redo }; + struct Clipboard + { + void reset() { + m_type = itUndef; + m_layer_config_ranges_cache .clear(); + m_config_cache.clear(); + } + bool empty() const { return m_type == itUndef; } + ItemType get_type() const { return m_type; } + void set_type(ItemType type) { m_type = type; } + + t_layer_config_ranges& get_ranges_cache() { return m_layer_config_ranges_cache; } + DynamicPrintConfig& get_config_cache() { return m_config_cache; } + + private: + ItemType m_type {itUndef}; + t_layer_config_ranges m_layer_config_ranges_cache; + DynamicPrintConfig m_config_cache; + }; + private: SELECTION_MODE m_selection_mode {smUndef}; int m_selected_layers_range_idx; + Clipboard m_clipboard; + struct dragged_item_data { void init(const int obj_idx, const int subobj_idx, const ItemType type) { @@ -148,8 +172,6 @@ class ObjectList : public wxDataViewCtrl std::vector m_bmp_vector; - t_layer_config_ranges m_layer_config_ranges_cache; - int m_selected_object_id = -1; bool m_prevent_list_events = false; // We use this flag to avoid circular event handling Select() // happens to fire a wxEVT_LIST_ITEM_SELECTED on OSX, whose event handler @@ -210,6 +232,7 @@ class ObjectList : public wxDataViewCtrl // Get obj_idx and vol_idx values for the selected (by default) or an adjusted item void get_selected_item_indexes(int& obj_idx, int& vol_idx, const wxDataViewItem& item = wxDataViewItem(0)); + void get_selection_indexes(std::vector& obj_idxs, std::vector& vol_idxs); // Get count of errors in the mesh int get_mesh_errors_count(const int obj_idx, const int vol_idx = -1) const; /* Get list of errors in the mesh. Return value is a string, used for the tooltip @@ -229,6 +252,8 @@ class ObjectList : public wxDataViewCtrl void copy(); void paste(); + bool copy_to_clipboard(); + bool paste_from_clipboard(); void undo(); void redo(); @@ -252,6 +277,9 @@ class ObjectList : public wxDataViewCtrl void append_menu_item_change_extruder(wxMenu* menu); void append_menu_item_delete(wxMenu* menu); void append_menu_item_scale_selection_to_fit_print_volume(wxMenu* menu); + void append_menu_items_convert_unit(wxMenu* menu); + void append_menu_item_merge_to_multipart_object(wxMenu *menu); + void append_menu_item_merge_to_single_object(wxMenu *menu); void create_object_popupmenu(wxMenu *menu); void create_sla_object_popupmenu(wxMenu*menu); void create_part_popupmenu(wxMenu*menu); @@ -275,6 +303,7 @@ class ObjectList : public wxDataViewCtrl void del_layers_from_object(const int obj_idx); bool del_subobject_from_object(const int obj_idx, const int idx, const int type); void split(); + void merge(bool to_multipart_object); void layers_editing(); wxDataViewItem add_layer_root_item(const wxDataViewItem obj_item); @@ -285,6 +314,8 @@ class ObjectList : public wxDataViewCtrl bool is_splittable(); bool selected_instances_of_same_object(); bool can_split_instances(); + bool can_merge_to_multipart_object() const; + bool can_merge_to_single_object() const; wxPoint get_mouse_position_in_control() const { return wxGetMousePosition() - this->GetScreenPosition(); } wxBoxSizer* get_sizer() {return m_sizer;} @@ -378,12 +409,16 @@ class ObjectList : public wxDataViewCtrl void fix_through_netfabb(); void update_item_error_icon(const int obj_idx, int vol_idx) const ; - void fill_layer_config_ranges_cache(); + void copy_layers_to_clipboard(); void paste_layers_into_list(); + void copy_settings_to_clipboard(); + void paste_settings_into_list(); + bool clipboard_is_empty() const { return m_clipboard.empty(); } void paste_volumes_into_list(int obj_idx, const ModelVolumePtrs& volumes); void paste_objects_into_list(const std::vector& object_idxs); void msw_rescale(); + void sys_color_changed(); void update_after_undo_redo(); //update printable state for item from objects model diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.cpp b/src/slic3r/GUI/GUI_ObjectManipulation.cpp index 36293525ab6..2c35fc316d2 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.cpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.cpp @@ -2,12 +2,16 @@ #include "GUI_ObjectList.hpp" #include "I18N.hpp" +#include "GLCanvas3D.hpp" #include "OptionsGroup.hpp" +#include "GUI_App.hpp" #include "wxExtensions.hpp" #include "PresetBundle.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/Geometry.hpp" #include "Selection.hpp" +#include "Plater.hpp" +#include "MainFrame.hpp" #include #include "slic3r/Utils/FixModelByWin10.hpp" @@ -17,6 +21,8 @@ namespace Slic3r namespace GUI { +const double ObjectManipulation::in_to_mm = 25.4; +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. @@ -121,6 +127,8 @@ static void set_font_and_background_style(wxWindow* win, const wxFont& font) ObjectManipulation::ObjectManipulation(wxWindow* parent) : OG_Settings(parent, true) { + m_imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + m_manifold_warning_bmp = ScalableBitmap(parent, "exclamation"); // Load bitmaps to be used for the mirroring buttons: @@ -314,15 +322,15 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : }; // add Units - auto add_unit_text = [this, parent, editors_grid_sizer, height](std::string unit) + auto add_unit_text = [this, parent, editors_grid_sizer, height](std::string unit, wxStaticText** unit_text) { - wxStaticText* unit_text = new wxStaticText(parent, wxID_ANY, _(unit)); - set_font_and_background_style(unit_text, wxGetApp().normal_font()); + *unit_text = new wxStaticText(parent, wxID_ANY, _(unit)); + set_font_and_background_style(*unit_text, wxGetApp().normal_font()); // Unit text should be the same height as labels wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL); sizer->SetMinSize(wxSize(-1, height)); - sizer->Add(unit_text, 0, wxALIGN_CENTER_VERTICAL); + sizer->Add(*unit_text, 0, wxALIGN_CENTER_VERTICAL); editors_grid_sizer->Add(sizer); m_rescalable_sizers.push_back(sizer); @@ -330,7 +338,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) add_edit_boxes("position", axis_idx); - add_unit_text(L("mm")); + add_unit_text(m_imperial_units ? L("in") : L("mm"), &m_position_unit); // Add drop to bed button m_drop_to_bed_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed")); @@ -356,7 +364,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) add_edit_boxes("rotation", axis_idx); - add_unit_text("°"); + wxStaticText* rotation_unit{ nullptr }; + add_unit_text("°", &rotation_unit); // Add reset rotation button m_reset_rotation_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); @@ -390,7 +399,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) add_edit_boxes("scale", axis_idx); - add_unit_text("%"); + wxStaticText* scale_unit{ nullptr }; + add_unit_text("%", &scale_unit); // Add reset scale button m_reset_scale_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo")); @@ -405,11 +415,22 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) : for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) add_edit_boxes("size", axis_idx); - add_unit_text("mm"); + add_unit_text(m_imperial_units ? L("in") : L("mm"), &m_size_unit); editors_grid_sizer->AddStretchSpacer(1); m_main_grid_sizer->Add(editors_grid_sizer, 1, wxEXPAND); + m_check_inch = new wxCheckBox(parent, wxID_ANY, "Inches"); + m_check_inch->SetFont(wxGetApp().normal_font()); + + m_check_inch->SetValue(m_imperial_units); + m_check_inch->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) { + wxGetApp().app_config->set("use_inches", m_check_inch->GetValue() ? "1" : "0"); + wxGetApp().sidebar().update_ui_from_settings(); + }); + + m_main_grid_sizer->Add(m_check_inch, 1, wxEXPAND); + m_og->sizer->Clear(true); m_og->sizer->Add(m_main_grid_sizer, 1, wxEXPAND | wxALL, border); } @@ -452,6 +473,32 @@ void ObjectManipulation::UpdateAndShow(const bool show) OG_Settings::UpdateAndShow(show); } +void ObjectManipulation::update_ui_from_settings() +{ + if (m_imperial_units != (wxGetApp().app_config->get("use_inches") == "1")) { + m_imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + + auto update_unit_text = [](const wxString& new_unit_text, wxStaticText* widget) { + widget->SetLabel(new_unit_text); + if (wxOSX) set_font_and_background_style(widget, wxGetApp().normal_font()); + }; + update_unit_text(m_imperial_units ? _L("in") : _L("mm"), m_position_unit); + update_unit_text(m_imperial_units ? _L("in") : _L("mm"), m_size_unit); + + for (int i = 0; i < 3; ++i) { + auto update = [this, i](/*ManipulationEditorKey*/int key_id, const Vec3d& new_value) { + wxString new_text = double_to_string(m_imperial_units ? new_value(i) * mm_to_in : new_value(i), 2); + const int id = key_id * 3 + i; + if (id >= 0) m_editors[id]->set_value(new_text); + }; + update(0/*mePosition*/, m_new_position); + update(3/*meSize*/, m_new_size); + } + } + + m_check_inch->SetValue(m_imperial_units); +} + void ObjectManipulation::update_settings_value(const Selection& selection) { m_new_move_label_string = L("Position"); @@ -562,6 +609,8 @@ void ObjectManipulation::update_if_dirty() if (std::abs(cached_rounded(i) - new_rounded) > EPSILON) { cached_rounded(i) = new_rounded; const int id = key_id*3+i; + if (m_imperial_units && (key_id == mePosition || key_id == meSize)) + new_text = double_to_string(new_value(i)*mm_to_in, 2); if (id >= 0) m_editors[id]->set_value(new_text); } cached(i) = new_value(i); @@ -851,6 +900,9 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double if (!m_cache.is_valid()) return; + if (m_imperial_units && (opt_key == "position" || opt_key == "size")) + new_value *= in_to_mm; + if (opt_key == "position") change_position_value(axis, new_value); else if (opt_key == "rotation") @@ -929,9 +981,29 @@ void ObjectManipulation::msw_rescale() for (ManipulationEditor* editor : m_editors) editor->msw_rescale(); + // rescale "inches" checkbox + m_check_inch->SetMinSize(wxSize(-1, int(1.5f * m_check_inch->GetFont().GetPixelSize().y + 0.5f))); + get_og()->msw_rescale(); } +void ObjectManipulation::sys_color_changed() +{ + // btn...->msw_rescale() updates icon on button, so use it + m_mirror_bitmap_on.msw_rescale(); + m_mirror_bitmap_off.msw_rescale(); + m_mirror_bitmap_hidden.msw_rescale(); + m_reset_scale_button->msw_rescale(); + m_reset_rotation_button->msw_rescale(); + m_drop_to_bed_button->msw_rescale(); + m_lock_bnt->msw_rescale(); + + for (int id = 0; id < 3; ++id) + m_mirror_buttons[id].first->msw_rescale(); + + get_og()->sys_color_changed(); +} + static const char axes[] = { 'x', 'y', 'z' }; ManipulationEditor::ManipulationEditor(ObjectManipulation* parent, const std::string& opt_key, diff --git a/src/slic3r/GUI/GUI_ObjectManipulation.hpp b/src/slic3r/GUI/GUI_ObjectManipulation.hpp index e6f99ab2a13..560fbb400d0 100644 --- a/src/slic3r/GUI/GUI_ObjectManipulation.hpp +++ b/src/slic3r/GUI/GUI_ObjectManipulation.hpp @@ -4,12 +4,14 @@ #include #include "GUI_ObjectSettings.hpp" -#include "GLCanvas3D.hpp" +#include "libslic3r/Point.hpp" +#include class wxBitmapComboBox; class wxStaticText; class LockButton; class wxStaticBitmap; +class wxCheckBox; namespace Slic3r { namespace GUI { @@ -41,6 +43,11 @@ class ManipulationEditor : public wxTextCtrl class ObjectManipulation : public OG_Settings { +public: + static const double in_to_mm; + static const double mm_to_in; + +private: struct Cache { Vec3d position; @@ -76,6 +83,10 @@ class ObjectManipulation : public OG_Settings wxStaticText* m_scale_Label = nullptr; wxStaticText* m_rotate_Label = nullptr; + bool m_imperial_units { false }; + wxStaticText* m_position_unit { nullptr }; + wxStaticText* m_size_unit { nullptr }; + wxStaticText* m_item_name = nullptr; wxStaticText* m_empty_str = nullptr; @@ -84,6 +95,8 @@ class ObjectManipulation : public OG_Settings ScalableButton* m_reset_rotation_button = nullptr; ScalableButton* m_drop_to_bed_button = nullptr; + wxCheckBox* m_check_inch {nullptr}; + // Mirroring buttons and their current state enum MirrorButtonState { mbHidden, @@ -138,6 +151,7 @@ class ObjectManipulation : public OG_Settings void Show(const bool show) override; bool IsShown() override; void UpdateAndShow(const bool show) override; + void update_ui_from_settings(); void set_dirty() { m_dirty = true; } // Called from the App to update the UI if dirty. @@ -160,6 +174,7 @@ class ObjectManipulation : public OG_Settings void update_item_name(const wxString &item_name); void update_warning_icon_state(const wxString& tooltip); void msw_rescale(); + void sys_color_changed(); void on_change(const std::string& opt_key, int axis, double new_value); void set_focused_editor(ManipulationEditor* focused_editor) { #ifndef __APPLE__ diff --git a/src/slic3r/GUI/GUI_ObjectSettings.cpp b/src/slic3r/GUI/GUI_ObjectSettings.cpp index 1447e076887..ef78123a4cd 100644 --- a/src/slic3r/GUI/GUI_ObjectSettings.cpp +++ b/src/slic3r/GUI/GUI_ObjectSettings.cpp @@ -2,8 +2,10 @@ #include "GUI_ObjectList.hpp" #include "OptionsGroup.hpp" +#include "GUI_App.hpp" #include "wxExtensions.hpp" #include "PresetBundle.hpp" +#include "Plater.hpp" #include "libslic3r/Model.hpp" #include diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index 8cf524f7d8e..b4606ab7f0f 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -4,13 +4,13 @@ #include "GUI_App.hpp" #include "GUI.hpp" #include "I18N.hpp" -#include "AppConfig.hpp" #include "3DScene.hpp" #include "BackgroundSlicingProcess.hpp" #include "OpenGLManager.hpp" #include "GLCanvas3D.hpp" #include "PresetBundle.hpp" #include "DoubleSlider.hpp" +#include "Plater.hpp" #include #include @@ -68,7 +68,6 @@ bool View3D::init(wxWindow* parent, Model* model, DynamicPrintConfig* config, Ba m_canvas->enable_selection(true); m_canvas->enable_main_toolbar(true); m_canvas->enable_undoredo_toolbar(true); - m_canvas->enable_collapse_toolbar(true); m_canvas->enable_labels(true); #if ENABLE_SLOPE_RENDERING m_canvas->enable_slope(true); @@ -222,7 +221,6 @@ bool Preview::init(wxWindow* parent, Model* model) m_canvas->set_process(m_process); m_canvas->enable_legend_texture(true); m_canvas->enable_dynamic_background(true); - m_canvas->enable_collapse_toolbar(true); m_double_slider_sizer = new wxBoxSizer(wxHORIZONTAL); create_double_slider(); @@ -252,6 +250,7 @@ bool Preview::init(wxWindow* parent, Model* model) _(L("Internal infill")) + "|" + _(L("Solid infill")) + "|" + _(L("Top solid infill")) + "|" + + _(L("Ironing")) + "|" + _(L("Bridge infill")) + "|" + _(L("Gap fill")) + "|" + _(L("Skirt")) + "|" + @@ -580,10 +579,10 @@ void Preview::update_view_type(bool slice_completed) const wxString& choice = !wxGetApp().plater()->model().custom_gcode_per_print_z.gcodes.empty() /*&& (wxGetApp().extruders_edited_cnt()==1 || !slice_completed) */? - _(L("Color Print")) : + _L("Color Print") : config.option("wiping_volumes_matrix")->values.size() > 1 ? - _(L("Tool")) : - _(L("Feature type")); + _L("Tool") : + _L("Feature type"); int type = m_choice_view_type->FindString(choice); if (m_choice_view_type->GetSelection() != type) { @@ -592,6 +591,8 @@ void Preview::update_view_type(bool slice_completed) m_gcode_preview_data->extrusion.view_type = (GCodePreviewData::Extrusion::EViewType)type; m_preferred_color_mode = "feature"; } + + reload_print(); } void Preview::create_double_slider() @@ -620,8 +621,6 @@ void Preview::create_double_slider() m_schedule_background_process(); update_view_type(false); - - reload_print(); }); } diff --git a/src/slic3r/GUI/GUI_Preview.hpp b/src/slic3r/GUI/GUI_Preview.hpp index 9d7c6c03132..bbf2774b89b 100644 --- a/src/slic3r/GUI/GUI_Preview.hpp +++ b/src/slic3r/GUI/GUI_Preview.hpp @@ -2,10 +2,12 @@ #define slic3r_GUI_Preview_hpp_ #include + #include "libslic3r/Point.hpp" +#include "libslic3r/CustomGCode.hpp" #include -#include "libslic3r/Model.hpp" + class wxNotebook; class wxGLCanvas; diff --git a/src/slic3r/GUI/GUI_Utils.cpp b/src/slic3r/GUI/GUI_Utils.cpp index 5b23aeee04b..7db3d57ffcd 100644 --- a/src/slic3r/GUI/GUI_Utils.cpp +++ b/src/slic3r/GUI/GUI_Utils.cpp @@ -61,7 +61,9 @@ void on_window_geometry(wxTopLevelWindow *tlw, std::function callback) #endif } +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) wxDEFINE_EVENT(EVT_DPI_CHANGED_SLICER, DpiChangedEvent); +#endif // !wxVERSION_EQUAL_OR_GREATER_THAN #ifdef _WIN32 template typename F::FN winapi_get_function(const wchar_t *dll, const char *fn_name) { diff --git a/src/slic3r/GUI/GUI_Utils.hpp b/src/slic3r/GUI/GUI_Utils.hpp index 0d5249e2549..2737b3edbfe 100644 --- a/src/slic3r/GUI/GUI_Utils.hpp +++ b/src/slic3r/GUI/GUI_Utils.hpp @@ -24,6 +24,11 @@ class wxCheckBox; class wxTopLevelWindow; class wxRect; +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT +#define wxVERSION_EQUAL_OR_GREATER_THAN(major, minor, release) ((wxMAJOR_VERSION > major) || ((wxMAJOR_VERSION == major) && (wxMINOR_VERSION > minor)) || ((wxMAJOR_VERSION == major) && (wxMINOR_VERSION == minor) && (wxRELEASE_NUMBER >= release))) +#else +#define wxVERSION_EQUAL_OR_GREATER_THAN(major, minor, release) 0 +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT namespace Slic3r { namespace GUI { @@ -51,6 +56,7 @@ enum { DPI_DEFAULT = 96 }; int get_dpi_for_window(wxWindow *window); wxFont get_default_font_for_dpi(int dpi); +#if !wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) struct DpiChangedEvent : public wxEvent { int dpi; wxRect rect; @@ -66,6 +72,7 @@ struct DpiChangedEvent : public wxEvent { }; wxDECLARE_EVENT(EVT_DPI_CHANGED_SLICER, DpiChangedEvent); +#endif // !wxVERSION_EQUAL_OR_GREATER_THAN template class DPIAware : public P { @@ -86,11 +93,33 @@ template class DPIAware : public P this->SetFont(m_normal_font); #endif // initialize default width_unit according to the width of the one symbol ("m") of the currently active font of this window. +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + m_em_unit = std::max(10, 10.0f * m_scale_factor); +#else m_em_unit = std::max(10, this->GetTextExtent("m").x - 1); +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT // recalc_font(); - this->Bind(EVT_DPI_CHANGED_SLICER, [this](const DpiChangedEvent &evt) { +#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + this->Bind(wxEVT_DPI_CHANGED, [this](wxDPIChangedEvent& evt) { + m_scale_factor = (float)evt.GetNewDPI().x / (float)DPI_DEFAULT; + + m_new_font_point_size = get_default_font_for_dpi(evt.GetNewDPI().x).GetPointSize(); + + if (!m_can_rescale) + return; + +#if ENABLE_LAYOUT_NO_RESTART + if (m_force_rescale || is_new_scale_factor()) + rescale(wxRect()); +#else + if (is_new_scale_factor()) + rescale(wxRect()); +#endif // ENABLE_LAYOUT_NO_RESTART + }); +#else + this->Bind(EVT_DPI_CHANGED_SLICER, [this](const DpiChangedEvent& evt) { m_scale_factor = (float)evt.dpi / (float)DPI_DEFAULT; m_new_font_point_size = get_default_font_for_dpi(evt.dpi).GetPointSize(); @@ -98,9 +127,15 @@ template class DPIAware : public P if (!m_can_rescale) return; +#if ENABLE_LAYOUT_NO_RESTART + if (m_force_rescale || is_new_scale_factor()) + rescale(evt.rect); +#else if (is_new_scale_factor()) rescale(evt.rect); - }); +#endif // ENABLE_LAYOUT_NO_RESTART + }); +#endif // wxVERSION_EQUAL_OR_GREATER_THAN this->Bind(wxEVT_MOVE_START, [this](wxMoveEvent& event) { @@ -124,6 +159,12 @@ template class DPIAware : public P // set value to _true_ in purpose of possibility of a display dpi changing from System Settings m_can_rescale = true; }); + + this->Bind(wxEVT_SYS_COLOUR_CHANGED, [this](wxSysColourChangedEvent& event) + { + event.Skip(); + on_sys_color_changed(); + }); } virtual ~DPIAware() {} @@ -134,9 +175,13 @@ template class DPIAware : public P int em_unit() const { return m_em_unit; } // int font_size() const { return m_font_size; } const wxFont& normal_font() const { return m_normal_font; } +#if ENABLE_LAYOUT_NO_RESTART + void enable_force_rescale() { m_force_rescale = true; } +#endif // ENABLE_LAYOUT_NO_RESTART protected: virtual void on_dpi_changed(const wxRect &suggested_rect) = 0; + virtual void on_sys_color_changed() {}; private: float m_scale_factor; @@ -146,6 +191,9 @@ template class DPIAware : public P wxFont m_normal_font; float m_prev_scale_factor; bool m_can_rescale{ true }; +#if ENABLE_LAYOUT_NO_RESTART + bool m_force_rescale{ false }; +#endif // ENABLE_LAYOUT_NO_RESTART int m_new_font_point_size; @@ -185,17 +233,27 @@ template class DPIAware : public P { this->Freeze(); - // rescale fonts of all controls - scale_controls_fonts(this, m_new_font_point_size); - // rescale current window font - scale_win_font(this, m_new_font_point_size); - +#if ENABLE_LAYOUT_NO_RESTART && wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + if (m_force_rescale) { +#endif // ENABLE_LAYOUT_NO_RESTART + // rescale fonts of all controls + scale_controls_fonts(this, m_new_font_point_size); + // rescale current window font + scale_win_font(this, m_new_font_point_size); +#if ENABLE_LAYOUT_NO_RESTART && wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + m_force_rescale = false; + } +#endif // ENABLE_LAYOUT_NO_RESTART // set normal application font as a current window font m_normal_font = this->GetFont(); // update em_unit value for new window font +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + m_em_unit = std::max(10, 10.0f * m_scale_factor); +#else m_em_unit = std::max(10, this->GetTextExtent("m").x - 1); +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT // rescale missed controls sizes and images on_dpi_changed(suggested_rect); @@ -334,7 +392,7 @@ class WindowMetrics static WindowMetrics from_window(wxTopLevelWindow *window); static boost::optional deserialize(const std::string &str); - wxRect get_rect() const { return rect; } + const wxRect& get_rect() const { return rect; } bool get_maximized() const { return maximized; } void sanitize_for_display(const wxRect &screen_rect); diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp index 46abe8a9568..9bc34f99092 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.cpp @@ -4,14 +4,9 @@ #include #include "slic3r/GUI/GUI_App.hpp" -#include "slic3r/GUI/GLCanvas3D.hpp" - - // TODO: Display tooltips quicker on Linux - - namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp index 8a2b7197634..3ab58c2585e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoBase.hpp @@ -4,7 +4,6 @@ #include "libslic3r/Point.hpp" #include "slic3r/GUI/I18N.hpp" -#include "slic3r/GUI/Selection.hpp" #include @@ -31,9 +30,9 @@ static const float CONSTRAINED_COLOR[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; class ImGuiWrapper; class GLCanvas3D; -class ClippingPlane; enum class CommonGizmosDataID; class CommonGizmosDataPool; +class Selection; class GLGizmoBase { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp index 9382579ea3f..4c00f8f2fdd 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoCut.cpp @@ -12,6 +12,7 @@ #include #include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" namespace Slic3r { @@ -197,12 +198,20 @@ void GLGizmoCut::set_cut_z(double cut_z) const void GLGizmoCut::perform_cut(const Selection& selection) { - const auto instance_idx = selection.get_instance_idx(); - const auto object_idx = selection.get_object_idx(); + const int instance_idx = selection.get_instance_idx(); + const int object_idx = selection.get_object_idx(); wxCHECK_RET(instance_idx >= 0 && object_idx >= 0, "GLGizmoCut: Invalid object selection"); - wxGetApp().plater()->cut(object_idx, instance_idx, m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); + // m_cut_z is the distance from the bed. Subtract possible SLA elevation. + const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin()); + coordf_t object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z(); + + if (object_cut_z > 0.) + wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); + else { + // the object is SLA-elevated and the plane is under it. + } } double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp index b85fe57f0c5..3769e96605c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.cpp @@ -8,6 +8,7 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/PresetBundle.hpp" #include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/Plater.hpp" #include "libslic3r/Model.hpp" @@ -15,6 +16,7 @@ namespace Slic3r { namespace GUI { + GLGizmoFdmSupports::GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id) , m_quadric(nullptr) @@ -46,23 +48,40 @@ bool GLGizmoFdmSupports::on_init() m_desc["block"] = _L("Block supports"); m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": "; m_desc["remove"] = _L("Remove selection"); - m_desc["remove_all"] = _L("Remove all"); + m_desc["remove_all"] = _L("Remove all selection"); return true; } + +void GLGizmoFdmSupports::activate_internal_undo_redo_stack(bool activate) +{ + if (activate && ! m_internal_stack_active) { + Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned on")); + wxGetApp().plater()->enter_gizmos_stack(); + m_internal_stack_active = true; + } + if (! activate && m_internal_stack_active) { + wxGetApp().plater()->leave_gizmos_stack(); + Plater::TakeSnapshot(wxGetApp().plater(), _L("FDM gizmo turned off")); + m_internal_stack_active = false; + } +} + void GLGizmoFdmSupports::set_fdm_support_data(ModelObject* model_object, const Selection& selection) { - const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr; - if (! mo) + if (m_state != On) return; + const ModelObject* mo = m_c->selection_info() ? m_c->selection_info()->model_object() : nullptr; + if (mo && selection.is_from_single_instance() - && (mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size)) + && (m_schedule_update || mo->id() != m_old_mo_id || mo->volumes.size() != m_old_volumes_size)) { update_from_model_object(); m_old_mo_id = mo->id(); m_old_volumes_size = mo->volumes.size(); + m_schedule_update = false; } } @@ -76,6 +95,7 @@ void GLGizmoFdmSupports::on_render() const glsafe(::glEnable(GL_DEPTH_TEST)); render_triangles(selection); + m_c->object_clipper()->render_cut(); render_cursor_circle(); @@ -118,17 +138,19 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const mo->instances[selection.get_instance_idx()]->get_transformation().get_matrix() * mv->get_matrix(); + bool is_left_handed = trafo_matrix.matrix().determinant() < 0.; + if (is_left_handed) + glsafe(::glFrontFace(GL_CW)); + glsafe(::glPushMatrix()); glsafe(::glMultMatrixd(trafo_matrix.data())); - // Now render both enforcers and blockers. - for (int i=0; i<2; ++i) { - if (m_ivas[mesh_id][i].has_VBOs()) { - glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f)); - m_ivas[mesh_id][i].render(); - } - } + if (! m_setting_angle) + m_triangle_selectors[mesh_id]->render(m_imgui); + glsafe(::glPopMatrix()); + if (is_left_handed) + glsafe(::glFrontFace(GL_CCW)); } if (clipping_plane_active) glsafe(::glDisable(GL_CLIP_PLANE0)); @@ -182,15 +204,18 @@ void GLGizmoFdmSupports::render_cursor_circle() const void GLGizmoFdmSupports::update_model_object() const { + bool updated = false; ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { - ++idx; if (! mv->is_model_part()) continue; - for (int i=0; im_supported_facets.set_facet(i, m_selected_facets[idx][i]); + ++idx; + updated |= mv->m_supported_facets.set(*m_triangle_selectors[idx].get()); } + + if (updated) + m_parent.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS)); } @@ -199,14 +224,7 @@ void GLGizmoFdmSupports::update_from_model_object() wxBusyCursor wait; const ModelObject* mo = m_c->selection_info()->model_object(); - size_t num_of_volumes = 0; - for (const ModelVolume* mv : mo->volumes) - if (mv->is_model_part()) - ++num_of_volumes; - m_selected_facets.resize(num_of_volumes); - m_neighbors.resize(num_of_volumes); - m_ivas.clear(); - m_ivas.resize(num_of_volumes); + m_triangle_selectors.clear(); int volume_id = -1; for (const ModelVolume* mv : mo->volumes) { @@ -218,26 +236,8 @@ void GLGizmoFdmSupports::update_from_model_object() // This mesh does not account for the possible Z up SLA offset. const TriangleMesh* mesh = &mv->mesh(); - m_selected_facets[volume_id].assign(mesh->its.indices.size(), FacetSupportType::NONE); - - // Load current state from ModelVolume. - for (FacetSupportType type : {FacetSupportType::ENFORCER, FacetSupportType::BLOCKER}) { - const std::vector& list = mv->m_supported_facets.get_facets(type); - for (int i : list) - m_selected_facets[volume_id][i] = type; - } - update_vertex_buffers(mv, volume_id, true, true); - - m_neighbors[volume_id].resize(3 * mesh->its.indices.size()); - - // Prepare vector of vertex_index - facet_index pairs to quickly find adjacent facets - for (size_t i=0; iits.indices.size(); ++i) { - const stl_triangle_vertex_indices& ind = mesh->its.indices[i]; - m_neighbors[volume_id][3*i] = std::make_pair(ind(0), i); - m_neighbors[volume_id][3*i+1] = std::make_pair(ind(1), i); - m_neighbors[volume_id][3*i+2] = std::make_pair(ind(2), i); - } - std::sort(m_neighbors[volume_id].begin(), m_neighbors[volume_id].end()); + m_triangle_selectors.emplace_back(std::make_unique(*mesh)); + m_triangle_selectors.back()->deserialize(mv->m_supported_facets.get_data()); } } @@ -259,11 +259,6 @@ bool GLGizmoFdmSupports::is_mesh_point_clipped(const Vec3d& point) const } -bool operator<(const GLGizmoFdmSupports::NeighborData& a, const GLGizmoFdmSupports::NeighborData& b) { - return a.first < b.first; -} - - // Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event. // The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is // aware that the event was reacted to and stops trying to make different sense of it. If the gizmo @@ -298,6 +293,9 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous || action == SLAGizmoEventType::RightDown || (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)) { + if (m_triangle_selectors.empty()) + return false; + FacetSupportType new_state = FacetSupportType::NONE; if (! shift_down) { if (action == SLAGizmoEventType::Dragging) @@ -317,7 +315,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous const Transform3d& instance_trafo = mi->get_transformation().get_matrix(); std::vector>> hit_positions_and_facet_ids; - bool some_mesh_was_hit = false; + bool clipped_mesh_was_hit = false; Vec3f normal = Vec3f::Zero(); Vec3f hit = Vec3f::Zero(); @@ -325,7 +323,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous Vec3f closest_hit = Vec3f::Zero(); double closest_hit_squared_distance = std::numeric_limits::max(); size_t closest_facet = 0; - size_t closest_hit_mesh_id = size_t(-1); + int closest_hit_mesh_id = -1; // Transformations of individual meshes std::vector trafo_matrices; @@ -352,7 +350,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous { // In case this hit is clipped, skip it. if (is_mesh_point_clipped(hit.cast())) { - some_mesh_was_hit = true; + clipped_mesh_was_hit = true; continue; } @@ -366,112 +364,57 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } } } - // We now know where the ray hit, let's save it and cast another ray - if (closest_hit_mesh_id != size_t(-1)) // only if there is at least one hit - hit_positions_and_facet_ids[closest_hit_mesh_id].emplace_back(closest_hit, closest_facet); + bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None); + + // The mouse button click detection is enabled when there is a valid hit + // or when the user clicks the clipping plane. Missing the object entirely + // shall not capture the mouse. + if (closest_hit_mesh_id != -1 || clipped_mesh_was_hit) { + if (m_button_down == Button::None) + m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right); + } + + if (closest_hit_mesh_id == -1) { + // In case we have no valid hit, we can return. The event will + // be stopped in following two cases: + // 1. clicking the clipping plane + // 2. dragging while painting (to prevent scene rotations and moving the object) + return clipped_mesh_was_hit + || dragging_while_painting; + } - // Now propagate the hits + // Find respective mesh id. + // FIXME We need a separate TriangleSelector for each volume mesh. mesh_id = -1; + //const TriangleMesh* mesh = nullptr; for (const ModelVolume* mv : mo->volumes) { - if (! mv->is_model_part()) continue; - ++mesh_id; - bool update_both = false; - - const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; - - // Calculate how far can a point be from the line (in mesh coords). - // FIXME: The scaling of the mesh can be non-uniform. - const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor(); - const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; - const float limit = pow(m_cursor_radius/avg_scaling , 2.f); - - // For all hits on this mesh... - for (const std::pair& hit_and_facet : hit_positions_and_facet_ids[mesh_id]) { - some_mesh_was_hit = true; - const TriangleMesh* mesh = &mv->mesh(); - std::vector& neighbors = m_neighbors[mesh_id]; - - // Calculate direction from camera to the hit (in mesh coords): - Vec3f dir = ((trafo_matrix.inverse() * camera.get_position()).cast() - hit_and_facet.first).normalized(); - - // A lambda to calculate distance from the centerline: - auto squared_distance_from_line = [&hit_and_facet, &dir](const Vec3f point) -> float { - Vec3f diff = hit_and_facet.first - point; - return (diff - diff.dot(dir) * dir).squaredNorm(); - }; - - // A lambda to determine whether this facet is potentionally visible (still can be obscured) - auto faces_camera = [&dir](const ModelVolume* mv, const size_t& facet) -> bool { - return (mv->mesh().stl.facet_start[facet].normal.dot(dir) > 0.); - }; - // Now start with the facet the pointer points to and check all adjacent facets. neighbors vector stores - // pairs of vertex_idx - facet_idx and is sorted with respect to the former. Neighboring facet index can be - // quickly found by finding a vertex in the list and read the respective facet ids. - std::vector facets_to_select{hit_and_facet.second}; - NeighborData vertex = std::make_pair(0, 0); - std::vector visited(m_selected_facets[mesh_id].size(), false); // keep track of facets we already processed - size_t facet_idx = 0; // index into facets_to_select - auto it = neighbors.end(); - while (facet_idx < facets_to_select.size()) { - size_t facet = facets_to_select[facet_idx]; - if (! visited[facet]) { - // check all three vertices and in case they're close enough, find the remaining facets - // and add them to the list to be proccessed later - for (size_t i=0; i<3; ++i) { - vertex.first = mesh->its.indices[facet](i); // vertex index - float dist = squared_distance_from_line(mesh->its.vertices[vertex.first]); - if (dist < limit) { - it = std::lower_bound(neighbors.begin(), neighbors.end(), vertex); - while (it != neighbors.end() && it->first == vertex.first) { - if (it->second != facet && faces_camera(mv, it->second)) - facets_to_select.push_back(it->second); - ++it; - } - } - } - visited[facet] = true; - } - ++facet_idx; - } + if (mesh_id == closest_hit_mesh_id) { + //mesh = &mv->mesh(); + break; + } + } - // Now just select all facets that passed. - for (size_t next_facet : facets_to_select) { - FacetSupportType& facet = m_selected_facets[mesh_id][next_facet]; + const Transform3d& trafo_matrix = trafo_matrices[mesh_id]; - if (facet != new_state && facet != FacetSupportType::NONE) { - // this triangle is currently in the other VBA. - // Both VBAs need to be refreshed. - update_both = true; - } - facet = new_state; - } - } + // Calculate how far can a point be from the line (in mesh coords). + // FIXME: The scaling of the mesh can be non-uniform. + const Vec3d sf = Geometry::Transformation(trafo_matrix).get_scaling_factor(); + const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.; + const float limit = m_cursor_radius/avg_scaling; - update_vertex_buffers(mv, mesh_id, - new_state == FacetSupportType::ENFORCER || update_both, - new_state == FacetSupportType::BLOCKER || update_both - ); - } + // Calculate direction from camera to the hit (in mesh coords): + Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast(); + Vec3f dir = (closest_hit - camera_pos).normalized(); - if (some_mesh_was_hit) - { - if (m_button_down == Button::None) - m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right); - // Force rendering. In case the user is dragging, the queue can be - // flooded by wxEVT_MOVING event and rendering would be skipped. - m_parent.render(); - return true; - } - if (action == SLAGizmoEventType::Dragging && m_button_down != Button::None) { - // Same as above. We don't want the cursor to freeze when we - // leave the mesh while painting. - m_parent.render(); - return true; - } + assert(mesh_id < int(m_triangle_selectors.size())); + m_triangle_selectors[mesh_id]->select_patch(closest_hit, closest_facet, camera_pos, + dir, limit, new_state); + + return true; } if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp) @@ -482,6 +425,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous : (m_button_down == Button::Left ? _L("Add supports") : _L("Block supports")); + activate_internal_undo_redo_stack(true); Plater::TakeSnapshot(wxGetApp().plater(), action_name); update_model_object(); @@ -493,38 +437,8 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous } -void GLGizmoFdmSupports::update_vertex_buffers(const ModelVolume* mv, - int mesh_id, - bool update_enforcers, - bool update_blockers) -{ - const TriangleMesh* mesh = &mv->mesh(); - for (FacetSupportType type : {FacetSupportType::ENFORCER, FacetSupportType::BLOCKER}) { - if ((type == FacetSupportType::ENFORCER && ! update_enforcers) - || (type == FacetSupportType::BLOCKER && ! update_blockers)) - continue; - - GLIndexedVertexArray& iva = m_ivas[mesh_id][type==FacetSupportType::ENFORCER ? 0 : 1]; - iva.release_geometry(); - size_t triangle_cnt=0; - for (size_t facet_idx=0; facet_idxits.vertices[mesh->its.indices[facet_idx](i)].cast(), - MeshRaycaster::get_triangle_normal(mesh->its, facet_idx).cast()); - iva.push_triangle(3*triangle_cnt, 3*triangle_cnt+1, 3*triangle_cnt+2); - ++triangle_cnt; - } - if (! m_selected_facets[mesh_id].empty()) - iva.finalize_geometry(true); - } -} - - -void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwrite, bool block) +void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block) { float threshold = (M_PI/180.)*threshold_deg; const Selection& selection = m_parent.get_selection(); @@ -548,14 +462,16 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwr int idx = -1; for (const stl_facet& facet : mv->mesh().stl.facet_start) { ++idx; - if (facet.normal.dot(down) > dot_limit && (overwrite || m_selected_facets[mesh_id][idx] == FacetSupportType::NONE)) - m_selected_facets[mesh_id][idx] = block - ? FacetSupportType::BLOCKER - : FacetSupportType::ENFORCER; + if (facet.normal.dot(down) > dot_limit) + m_triangle_selectors[mesh_id]->set_facet(idx, + block + ? FacetSupportType::BLOCKER + : FacetSupportType::ENFORCER); } - update_vertex_buffers(mv, mesh_id, true, true); } + activate_internal_undo_redo_stack(true); + Plater::TakeSnapshot(wxGetApp().plater(), block ? _L("Block supports by angle") : _L("Add supports by angle")); update_model_object(); @@ -616,17 +532,17 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(); if (m_imgui->button(m_desc.at("remove_all"))) { + Plater::TakeSnapshot(wxGetApp().plater(), wxString(_L("Reset selection"))); ModelObject* mo = m_c->selection_info()->model_object(); int idx = -1; for (ModelVolume* mv : mo->volumes) { - ++idx; if (mv->is_model_part()) { - m_selected_facets[idx].assign(m_selected_facets[idx].size(), FacetSupportType::NONE); - mv->m_supported_facets.clear(); - update_vertex_buffers(mv, idx, true, true); - m_parent.set_as_dirty(); + ++idx; + m_triangle_selectors[idx]->reset(); } } + update_model_object(); + m_parent.set_as_dirty(); } const float max_tooltip_width = ImGui::GetFontSize() * 20.0f; @@ -682,12 +598,11 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l ImGui::SameLine(); if (m_imgui->slider_float("", &m_angle_threshold_deg, 0.f, 90.f, "%.f")) m_parent.set_slope_range({90.f - m_angle_threshold_deg, 90.f - m_angle_threshold_deg}); - m_imgui->checkbox(wxString("Overwrite already selected facets"), m_overwrite_selected); if (m_imgui->button("Enforce")) - select_facets_by_angle(m_angle_threshold_deg, m_overwrite_selected, false); + select_facets_by_angle(m_angle_threshold_deg, false); ImGui::SameLine(); if (m_imgui->button("Block")) - select_facets_by_angle(m_angle_threshold_deg, m_overwrite_selected, true); + select_facets_by_angle(m_angle_threshold_deg, true); ImGui::SameLine(); if (m_imgui->button("Cancel")) m_setting_angle = false; @@ -733,9 +648,7 @@ CommonGizmosDataID GLGizmoFdmSupports::on_get_requirements() const int(CommonGizmosDataID::SelectionInfo) | int(CommonGizmosDataID::InstancesHider) | int(CommonGizmosDataID::Raycaster) - | int(CommonGizmosDataID::HollowedMesh) - | int(CommonGizmosDataID::ObjectClipper) - | int(CommonGizmosDataID::SupportsClipper)); + | int(CommonGizmosDataID::ObjectClipper)); } @@ -745,12 +658,9 @@ void GLGizmoFdmSupports::on_set_state() return; if (m_state == On && m_old_state != On) { // the gizmo was just turned on - { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("FDM gizmo turned on"))); - } if (! m_parent.get_gizmos_manager().is_serializing()) { - wxGetApp().CallAfter([]() { - wxGetApp().plater()->enter_gizmos_stack(); + wxGetApp().CallAfter([this]() { + activate_internal_undo_redo_stack(true); }); } } @@ -760,15 +670,10 @@ void GLGizmoFdmSupports::on_set_state() m_setting_angle = false; m_parent.use_slope(false); } - - wxGetApp().plater()->leave_gizmos_stack(); - { - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("FDM gizmo turned off"))); - } + activate_internal_undo_redo_stack(false); m_old_mo_id = -1; - m_ivas.clear(); - m_neighbors.clear(); - m_selected_facets.clear(); + //m_iva.release_geometry(); + m_triangle_selectors.clear(); } m_old_state = m_state; } @@ -788,17 +693,167 @@ void GLGizmoFdmSupports::on_stop_dragging() -void GLGizmoFdmSupports::on_load(cereal::BinaryInputArchive& ar) +void GLGizmoFdmSupports::on_load(cereal::BinaryInputArchive&) { - update_from_model_object(); + // We should update the gizmo from current ModelObject, but it is not + // possible at this point. That would require having updated selection and + // common gizmos data, which is not done at this point. Instead, save + // a flag to do the update in set_fdm_support_data, which will be called + // soon after. + m_schedule_update = true; } -void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive& ar) const +void GLGizmoFdmSupports::on_save(cereal::BinaryOutputArchive&) const +{ + +} + + +void TriangleSelectorGUI::render(ImGuiWrapper* imgui) { + int enf_cnt = 0; + int blc_cnt = 0; + + m_iva_enforcers.release_geometry(); + m_iva_blockers.release_geometry(); + + for (const Triangle& tr : m_triangles) { + if (! tr.valid || tr.is_split() || tr.get_state() == FacetSupportType::NONE) + continue; + + GLIndexedVertexArray& va = tr.get_state() == FacetSupportType::ENFORCER + ? m_iva_enforcers + : m_iva_blockers; + int& cnt = tr.get_state() == FacetSupportType::ENFORCER + ? enf_cnt + : blc_cnt; + + for (int i=0; i<3; ++i) + va.push_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), + double(m_vertices[tr.verts_idxs[i]].v[1]), + double(m_vertices[tr.verts_idxs[i]].v[2]), + 0., 0., 1.); + va.push_triangle(cnt, + cnt+1, + cnt+2); + cnt += 3; + } + + m_iva_enforcers.finalize_geometry(true); + m_iva_blockers.finalize_geometry(true); + + if (m_iva_enforcers.has_VBOs()) { + ::glColor4f(0.f, 0.f, 1.f, 0.2f); + m_iva_enforcers.render(); + } + + + if (m_iva_blockers.has_VBOs()) { + ::glColor4f(1.f, 0.f, 0.f, 0.2f); + m_iva_blockers.render(); + } + +#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + if (imgui) + render_debug(imgui); + else + assert(false); // If you want debug output, pass ptr to ImGuiWrapper. +#endif +} + + + +#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG +void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui) +{ + imgui->begin(std::string("TriangleSelector dialog (DEV ONLY)"), + ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse); + static float edge_limit = 1.f; + imgui->text("Edge limit (mm): "); + imgui->slider_float("", &edge_limit, 0.1f, 8.f); + set_edge_limit(edge_limit); + imgui->checkbox("Show split triangles: ", m_show_triangles); + imgui->checkbox("Show invalid triangles: ", m_show_invalid); + + int valid_triangles = m_triangles.size() - m_invalid_triangles; + imgui->text("Valid triangles: " + std::to_string(valid_triangles) + + "/" + std::to_string(m_triangles.size())); + imgui->text("Vertices: " + std::to_string(m_vertices.size())); + if (imgui->button("Force garbage collection")) + garbage_collect(); + + if (imgui->button("Serialize - deserialize")) { + auto map = serialize(); + deserialize(map); + } + + imgui->end(); + + if (! m_show_triangles) + return; + + enum vtype { + ORIGINAL = 0, + SPLIT, + INVALID + }; + + for (auto& va : m_varrays) + va.release_geometry(); + + std::array cnts; + + ::glScalef(1.01f, 1.01f, 1.01f); + + for (int tr_id=0; tr_idpush_geometry(double(m_vertices[tr.verts_idxs[i]].v[0]), + double(m_vertices[tr.verts_idxs[i]].v[1]), + double(m_vertices[tr.verts_idxs[i]].v[2]), + 0., 0., 1.); + va->push_triangle(*cnt, + *cnt+1, + *cnt+2); + *cnt += 3; + } + + ::glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + for (vtype i : {ORIGINAL, SPLIT, INVALID}) { + GLIndexedVertexArray& va = m_varrays[i]; + va.finalize_geometry(true); + if (va.has_VBOs()) { + switch (i) { + case ORIGINAL : ::glColor3f(0.f, 0.f, 1.f); break; + case SPLIT : ::glColor3f(1.f, 0.f, 0.f); break; + case INVALID : ::glColor3f(1.f, 1.f, 0.f); break; + } + va.render(); + } + } + ::glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); } +#endif diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp index bed6d00a0ca..ce24ea8d28a 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFdmSupports.hpp @@ -5,9 +5,14 @@ #include "slic3r/GUI/3DScene.hpp" +#include "libslic3r/ObjectID.hpp" +#include "libslic3r/TriangleSelector.hpp" + #include + + namespace Slic3r { enum class FacetSupportType : int8_t; @@ -15,6 +20,32 @@ enum class FacetSupportType : int8_t; namespace GUI { enum class SLAGizmoEventType : unsigned char; +class ClippingPlane; + + + +class TriangleSelectorGUI : public TriangleSelector { +public: + explicit TriangleSelectorGUI(const TriangleMesh& mesh) + : TriangleSelector(mesh) {} + + // Render current selection. Transformation matrices are supposed + // to be already set. + void render(ImGuiWrapper* imgui = nullptr); + +#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG + void render_debug(ImGuiWrapper* imgui); + bool m_show_triangles{false}; + bool m_show_invalid{false}; +#endif + +private: + GLIndexedVertexArray m_iva_enforcers; + GLIndexedVertexArray m_iva_blockers; + std::array m_varrays; +}; + + class GLGizmoFdmSupports : public GLGizmoBase { @@ -25,29 +56,18 @@ class GLGizmoFdmSupports : public GLGizmoBase GLUquadricObj* m_quadric; float m_cursor_radius = 2.f; - static constexpr float CursorRadiusMin = 0.f; + static constexpr float CursorRadiusMin = 0.4f; // cannot be zero static constexpr float CursorRadiusMax = 8.f; static constexpr float CursorRadiusStep = 0.2f; - // For each model-part volume, store a list of statuses of - // individual facets (one of the enum values above). - std::vector> m_selected_facets; - - // Store two vertex buffer arrays (for enforcers/blockers) - // for each model-part volume. - std::vector> m_ivas; - - void update_vertex_buffers(const ModelVolume* mv, - int mesh_id, - bool update_enforcers, - bool update_blockers); + // For each model-part volume, store status and division of the triangles. + std::vector> m_triangle_selectors; public: GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id); ~GLGizmoFdmSupports() override; void set_fdm_support_data(ModelObject* model_object, const Selection& selection); bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down); - using NeighborData = std::pair; private: @@ -60,9 +80,9 @@ class GLGizmoFdmSupports : public GLGizmoBase void update_model_object() const; void update_from_model_object(); + void activate_internal_undo_redo_stack(bool activate); - void select_facets_by_angle(float threshold, bool overwrite, bool block); - bool m_overwrite_selected = false; + void select_facets_by_angle(float threshold, bool block); float m_angle_threshold_deg = 45.f; bool is_mesh_point_clipped(const Vec3d& point) const; @@ -70,6 +90,8 @@ class GLGizmoFdmSupports : public GLGizmoBase float m_clipping_plane_distance = 0.f; std::unique_ptr m_clipping_plane; bool m_setting_angle = false; + bool m_internal_stack_active = false; + bool m_schedule_update = false; // This map holds all translated description texts, so they can be easily referenced during layout calculations // etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect. @@ -84,8 +106,6 @@ class GLGizmoFdmSupports : public GLGizmoBase Button m_button_down = Button::None; EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state) - std::vector> m_neighbors; // pairs of vertex_index - facet_index for each mesh - protected: void on_set_state() override; void on_start_dragging() override; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp index 7f33916cd5a..c9e8b9d2bad 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoFlatten.cpp @@ -1,9 +1,10 @@ // Include GLGizmoBase.hpp before I18N.hpp as it includes some libigl code, which overrides our localization "L" macro. #include "GLGizmoFlatten.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" -#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" +#include "libslic3r/Model.hpp" + #include #include @@ -27,8 +28,7 @@ bool GLGizmoFlatten::on_init() void GLGizmoFlatten::on_set_state() { - if (m_state == On && is_plane_update_necessary()) - update_planes(); + } CommonGizmosDataID GLGizmoFlatten::on_get_requirements() const @@ -80,12 +80,8 @@ void GLGizmoFlatten::on_render() const else glsafe(::glColor4f(0.9f, 0.9f, 0.9f, 0.5f)); - ::glBegin(GL_POLYGON); - for (const Vec3d& vertex : m_planes[i].vertices) - { - ::glVertex3dv(vertex.data()); - } - glsafe(::glEnd()); + if (m_planes[i].vbo.has_VBOs()) + m_planes[i].vbo.render(); } glsafe(::glPopMatrix()); } @@ -112,12 +108,7 @@ void GLGizmoFlatten::on_render_for_picking() const for (int i = 0; i < (int)m_planes.size(); ++i) { glsafe(::glColor4fv(picking_color_component(i).data())); - ::glBegin(GL_POLYGON); - for (const Vec3d& vertex : m_planes[i].vertices) - { - ::glVertex3dv(vertex.data()); - } - glsafe(::glEnd()); + m_planes[i].vbo.render(); } glsafe(::glPopMatrix()); } @@ -181,7 +172,7 @@ void GLGizmoFlatten::update_planes() if (std::abs(this_normal(0) - (*normal_ptr)(0)) < 0.001 && std::abs(this_normal(1) - (*normal_ptr)(1)) < 0.001 && std::abs(this_normal(2) - (*normal_ptr)(2)) < 0.001) { stl_vertex* first_vertex = ch.stl.facet_start[facet_idx].vertex; for (int j=0; j<3; ++j) - m_planes.back().vertices.emplace_back((double)first_vertex[j](0), (double)first_vertex[j](1), (double)first_vertex[j](2)); + m_planes.back().vertices.emplace_back(first_vertex[j].cast()); facet_visited[facet_idx] = true; for (int j = 0; j < 3; ++ j) { @@ -193,15 +184,16 @@ void GLGizmoFlatten::update_planes() } m_planes.back().normal = normal_ptr->cast(); + Pointf3s& verts = m_planes.back().vertices; // Now we'll transform all the points into world coordinates, so that the areas, angles and distances // make real sense. - m_planes.back().vertices = transform(m_planes.back().vertices, inst_matrix); + verts = transform(verts, inst_matrix); // if this is a just a very small triangle, remove it to speed up further calculations (it would be rejected later anyway): - if (m_planes.back().vertices.size() == 3 && - ((m_planes.back().vertices[0] - m_planes.back().vertices[1]).norm() < minimal_side - || (m_planes.back().vertices[0] - m_planes.back().vertices[2]).norm() < minimal_side - || (m_planes.back().vertices[1] - m_planes.back().vertices[2]).norm() < minimal_side)) + if (verts.size() == 3 && + ((verts[0] - verts[1]).norm() < minimal_side + || (verts[0] - verts[2]).norm() < minimal_side + || (verts[1] - verts[2]).norm() < minimal_side)) m_planes.pop_back(); } @@ -334,6 +326,21 @@ void GLGizmoFlatten::update_planes() m_first_instance_mirror = mo->instances.front()->get_mirror(); m_old_model_object = mo; + // And finally create respective VBOs. The polygon is convex with + // the vertices in order, so triangulation is trivial. + for (auto& plane : m_planes) { + plane.vbo.reserve(plane.vertices.size()); + for (const auto& vert : plane.vertices) + plane.vbo.push_geometry(vert, plane.normal); + for (size_t i=1; i vertices; + std::vector vertices; // should be in fact local in update_planes() + GLIndexedVertexArray vbo; Vec3d normal; float area; }; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp index 8d4616da37c..658db64cab5 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp @@ -11,6 +11,8 @@ #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/PresetBundle.hpp" +#include "libslic3r/Model.hpp" + namespace Slic3r { namespace GUI { @@ -57,7 +59,7 @@ void GLGizmoHollow::set_sla_support_data(ModelObject*, const Selection&) return; const ModelObject* mo = m_c->selection_info()->model_object(); - if (mo) { + if (m_state == On && mo) { if (m_old_mo_id != mo->id()) { reload_cache(); m_old_mo_id = mo->id(); @@ -808,11 +810,6 @@ void GLGizmoHollow::on_set_state() if (m_state == m_old_state) return; - if (m_state == On && m_old_state != On) { // the gizmo was just turned on - // we'll now reload support points: - if (m_c->selection_info()->model_object()) - reload_cache(); - } if (m_state == Off && m_old_state != Off) // the gizmo was just turned Off m_parent.post_event(SimpleEvent(EVT_GLCANVAS_FORCE_UPDATE)); m_old_state = m_state; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp index 93e577743a6..5d34f04d6f8 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp @@ -5,12 +5,17 @@ #include "slic3r/GUI/GLSelectionRectangle.hpp" #include +#include #include #include namespace Slic3r { + +class ConfigOption; +class ConfigOptionDef; + namespace GUI { enum class SLAGizmoEventType : unsigned char; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp index 80cc16ba207..fa2069d0ab3 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.cpp @@ -26,7 +26,6 @@ std::string GLGizmoScale3D::get_tooltip() const bool single_instance = selection.is_single_full_instance(); bool single_volume = selection.is_single_modifier() || selection.is_single_volume(); - bool single_selection = single_instance || single_volume; Vec3f scale = 100.0f * Vec3f::Ones(); if (single_instance) diff --git a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp index 71f79327998..0d8f3f7fa79 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoScale.hpp @@ -3,6 +3,8 @@ #include "GLGizmoBase.hpp" +#include "libslic3r/BoundingBox.hpp" + namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp index 261738d4448..908fe27b113 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.cpp @@ -3,6 +3,7 @@ #include "slic3r/GUI/GLCanvas3D.hpp" #include "slic3r/GUI/Camera.hpp" #include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" +#include "slic3r/GUI/MainFrame.hpp" #include @@ -66,10 +67,11 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S ModelObject* mo = m_c->selection_info()->model_object(); - if (mo && mo->id() != m_old_mo_id) { + if (m_state == On && mo && mo->id() != m_old_mo_id) { disable_editing_mode(); reload_cache(); m_old_mo_id = mo->id(); + m_c->instances_hider()->show_supports(true); } // If we triggered autogeneration before, check backend and fetch results if they are there @@ -883,24 +885,23 @@ CommonGizmosDataID GLGizmoSlaSupports::on_get_requirements() const void GLGizmoSlaSupports::on_set_state() { - const ModelObject* mo = m_c->selection_info()->model_object(); - if (m_state == m_old_state) return; if (m_state == On && m_old_state != On) { // the gizmo was just turned on - Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned on"))); - - // we'll now reload support points: - if (mo) - reload_cache(); - - // Set default head diameter from config. - const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; - m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; + // This function can be called from undo/redo, when selection (and hence + // common gizmos data are not yet deserialized. The CallAfter should put + // this off until after the update is done. + wxGetApp().CallAfter([this]() { + Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned on"))); + + // Set default head diameter from config. + const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config; + m_new_point_head_diameter = static_cast(cfg.option("support_head_front_diameter"))->value; + }); } if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off - bool will_ask = mo && m_editing_mode && unsaved_changes(); + bool will_ask = m_editing_mode && unsaved_changes(); if (will_ask) { wxGetApp().CallAfter([this]() { // Following is called through CallAfter, because otherwise there was a problem @@ -920,7 +921,7 @@ void GLGizmoSlaSupports::on_set_state() disable_editing_mode(); // so it is not active next time the gizmo opens Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("SLA gizmo turned off"))); m_normal_cache.clear(); - + m_old_mo_id = -1; } } m_old_state = m_state; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp index b6cad8f9aec..f9cf2f9352c 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp @@ -4,13 +4,17 @@ #include "GLGizmoBase.hpp" #include "slic3r/GUI/GLSelectionRectangle.hpp" -#include "libslic3r/SLA/Common.hpp" +#include "libslic3r/SLA/SupportPoint.hpp" +#include "libslic3r/ObjectID.hpp" #include #include namespace Slic3r { + +class ConfigOption; + namespace GUI { enum class SLAGizmoEventType : unsigned char; diff --git a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp index 32a6de42b9a..28f317c2696 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosCommon.cpp @@ -6,6 +6,9 @@ #include "libslic3r/SLAPrint.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/Camera.hpp" +#include "slic3r/GUI/Plater.hpp" + +#include "slic3r/GUI/PresetBundle.hpp" #include @@ -169,7 +172,8 @@ void InstancesHider::show_supports(bool show) { void HollowedMesh::on_update() { const ModelObject* mo = get_pool()->selection_info()->model_object(); - if (! mo) + bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; + if (! mo || ! is_sla) return; const GLCanvas3D* canvas = get_pool()->get_canvas(); @@ -342,15 +346,11 @@ void ObjectClipper::render_cut() const clipper->set_plane(*m_clp); clipper->set_transformation(trafo); - if (! clipper->get_triangles().empty()) { - ::glPushMatrix(); - ::glColor3f(1.0f, 0.37f, 0.0f); - ::glBegin(GL_TRIANGLES); - for (const Vec3f& point : clipper->get_triangles()) - ::glVertex3f(point(0), point(1), point(2)); - ::glEnd(); - ::glPopMatrix(); - } + ::glPushMatrix(); + ::glColor3f(1.0f, 0.37f, 0.0f); + clipper->render_cut(); + ::glPopMatrix(); + ++clipper_id; } } @@ -379,7 +379,8 @@ void ObjectClipper::set_position(double pos, bool keep_normal) void SupportsClipper::on_update() { const ModelObject* mo = get_pool()->selection_info()->model_object(); - if (! mo) + bool is_sla = wxGetApp().preset_bundle->printers.get_selected_preset().printer_technology() == ptSLA; + if (! mo || ! is_sla) return; const GLCanvas3D* canvas = get_pool()->get_canvas(); @@ -447,6 +448,7 @@ void SupportsClipper::render_cut() const // Get transformation of supports Geometry::Transformation supports_trafo = trafo; + supports_trafo.set_scaling_factor(Vec3d::Ones()); supports_trafo.set_offset(Vec3d(trafo.get_offset()(0), trafo.get_offset()(1), sel_info->get_sla_shift())); supports_trafo.set_rotation(Vec3d(0., 0., trafo.get_rotation()(2))); // I don't know why, but following seems to be correct. @@ -457,15 +459,10 @@ void SupportsClipper::render_cut() const m_clipper->set_plane(*ocl->get_clipping_plane()); m_clipper->set_transformation(supports_trafo); - if (! m_clipper->get_triangles().empty()) { - ::glPushMatrix(); - ::glColor3f(1.0f, 0.f, 0.37f); - ::glBegin(GL_TRIANGLES); - for (const Vec3f& point : m_clipper->get_triangles()) - ::glVertex3f(point(0), point(1), point(2)); - ::glEnd(); - ::glPopMatrix(); - } + ::glPushMatrix(); + ::glColor3f(1.0f, 0.f, 0.37f); + m_clipper->render_cut(); + ::glPopMatrix(); } diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index 8ff6c121fca..511c68735ca 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -6,6 +6,7 @@ #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_ObjectManipulation.hpp" #include "slic3r/GUI/PresetBundle.hpp" +#include "slic3r/GUI/Plater.hpp" #include "slic3r/Utils/UndoRedo.hpp" #include "slic3r/GUI/Gizmos/GLGizmoMove.hpp" @@ -17,6 +18,8 @@ #include "slic3r/GUI/Gizmos/GLGizmoCut.hpp" #include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp" +#include "libslic3r/Model.hpp" + #include namespace Slic3r { @@ -479,22 +482,9 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) int selected_object_idx = selection.get_object_idx(); bool processed = false; -#if !ENABLE_CANVAS_TOOLTIP_USING_IMGUI - // mouse anywhere - if (!evt.Dragging() && !evt.Leaving() && !evt.Entering() && (m_mouse_capture.parent != nullptr)) - { - if (m_mouse_capture.any() && (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())) - // prevents loosing selection into the scene if mouse down was done inside the toolbar and mouse up was down outside it - processed = true; - - m_mouse_capture.reset(); - } -#endif // !ENABLE_CANVAS_TOOLTIP_USING_IMGUI - // mouse anywhere if (evt.Moving()) m_tooltip = update_hover_state(mouse_pos); -#if ENABLE_CANVAS_TOOLTIP_USING_IMGUI else if (evt.LeftUp()) { if (m_mouse_capture.left) @@ -551,11 +541,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) // else // return false; } -#if ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX else if (evt.Dragging() && !is_dragging()) -#else - else if (evt.Dragging()) -#endif // ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX { if (m_mouse_capture.any()) // if the button down was done on this toolbar, prevent from dragging into the scene @@ -563,25 +549,6 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) // else // return false; } -#else - else if (evt.LeftUp()) - m_mouse_capture.left = false; - else if (evt.MiddleUp()) - m_mouse_capture.middle = false; - else if (evt.RightUp()) - { - m_mouse_capture.right = false; - if (pending_right_up) - { - pending_right_up = false; - processed = true; - } - } - else if (evt.Dragging() && m_mouse_capture.any()) - // if the button down was done on this toolbar, prevent from dragging into the scene - processed = true; -#endif // ENABLE_CANVAS_TOOLTIP_USING_IMGUI -#if ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX else if (evt.Dragging() && is_dragging()) { if (!m_parent.get_wxglcanvas()->HasCapture()) @@ -628,7 +595,6 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) m_parent.set_as_dirty(); processed = true; } -#endif // ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX if (get_gizmo_idx_from_mouse(mouse_pos) == Undefined) { @@ -680,77 +646,6 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) m_parent.set_as_dirty(); processed = true; } -#if !ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX - else if (evt.Dragging() && is_dragging()) - { - if (!m_parent.get_wxglcanvas()->HasCapture()) - m_parent.get_wxglcanvas()->CaptureMouse(); - - m_parent.set_mouse_as_dragging(); - update(m_parent.mouse_ray(pos), pos); - - switch (m_current) - { - case Move: - { - // Apply new temporary offset - selection.translate(get_displacement()); - wxGetApp().obj_manipul()->set_dirty(); - break; - } - case Scale: - { - // Apply new temporary scale factors - TransformationType transformation_type(TransformationType::Local_Absolute_Joint); - if (evt.AltDown()) - transformation_type.set_independent(); - selection.scale(get_scale(), transformation_type); - if (evt.ControlDown()) - selection.translate(get_scale_offset(), true); - wxGetApp().obj_manipul()->set_dirty(); - break; - } - case Rotate: - { - // Apply new temporary rotations - TransformationType transformation_type(TransformationType::World_Relative_Joint); - if (evt.AltDown()) - transformation_type.set_independent(); - selection.rotate(get_rotation(), transformation_type); - wxGetApp().obj_manipul()->set_dirty(); - break; - } - default: - break; - } - - m_parent.set_as_dirty(); - processed = true; - } -#endif // !ENABLE_GIZMO_TOOLBAR_DRAGGING_FIX -#if !ENABLE_CANVAS_TOOLTIP_USING_IMGUI - else if (evt.LeftUp() && is_dragging()) - { - switch (m_current) { - case Move : m_parent.do_move(L("Gizmo-Move")); break; - case Scale : m_parent.do_scale(L("Gizmo-Scale")); break; - case Rotate : m_parent.do_rotate(L("Gizmo-Rotate")); break; - default : break; - } - - stop_dragging(); - update_data(); - - wxGetApp().obj_manipul()->set_dirty(); - // Let the plater know that the dragging finished, so a delayed refresh - // of the scene with the background processing data should be performed. - m_parent.post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED)); - // updates camera target constraints - m_parent.refresh_camera_scene_box(); - - processed = true; - } -#endif // !ENABLE_CANVAS_TOOLTIP_USING_IMGUI else if (evt.LeftUp() && (m_current == SlaSupports || m_current == Hollow || m_current == FdmSupports) && !m_parent.is_mouse_dragging()) { // in case SLA/FDM gizmo is selected, we just pass the LeftUp event and stop processing - neither @@ -794,10 +689,6 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt) m_mouse_capture.right = true; m_mouse_capture.parent = &m_parent; } -#if !ENABLE_CANVAS_TOOLTIP_USING_IMGUI - else if (evt.LeftUp()) - processed = true; -#endif // !ENABLE_CANVAS_TOOLTIP_USING_IMGUI } return processed; @@ -1213,9 +1104,16 @@ void GLGizmosManager::activate_gizmo(EType type) } m_current = type; - m_common_gizmos_data->update(get_current() - ? get_current()->get_requirements() - : CommonGizmosDataID(0)); + + // Updating common data should be left to the update_data function, which + // is always called after this one. activate_gizmo can be called by undo/redo, + // when selection is not yet deserialized, so the common data would update + // incorrectly (or crash if relying on unempty selection). Undo/redo stack + // will also call update_data, after selection is restored. + + //m_common_gizmos_data->update(get_current() + // ? get_current()->get_requirements() + // : CommonGizmosDataID(0)); if (type != Undefined) m_gizmos[type]->set_state(GLGizmoBase::On); diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp index a7fb8212627..4ad46a2a92e 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.hpp @@ -6,6 +6,8 @@ #include "slic3r/GUI/Gizmos/GLGizmoBase.hpp" #include "slic3r/GUI/Gizmos/GLGizmosCommon.hpp" +#include "libslic3r/ObjectID.hpp" + #include namespace Slic3r { @@ -139,11 +141,6 @@ class GLGizmosManager : public Slic3r::ObjectBase EType new_current = m_current; m_current = old_current; - // Update common data. They should be updated when activate_gizmo is - // called, so it can be used in on_set_state which is called from there. - if (new_current != Undefined) - m_common_gizmos_data->update(m_gizmos[new_current]->get_requirements()); - // activate_gizmo call sets m_current and calls set_state for the gizmo // it does nothing in case the gizmo is already activated // it can safely be called for Undefined gizmo diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index 755d8418069..88dd02ccb7a 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -176,6 +176,9 @@ bool ImGuiWrapper::update_mouse_data(wxMouseEvent& evt) io.MouseDown[0] = evt.LeftIsDown(); io.MouseDown[1] = evt.RightIsDown(); io.MouseDown[2] = evt.MiddleIsDown(); + float wheel_delta = static_cast(evt.GetWheelDelta()); + if (wheel_delta != 0.0f) + io.MouseWheel = static_cast(evt.GetWheelRotation()) / wheel_delta; unsigned buttons = (evt.LeftIsDown() ? 1 : 0) | (evt.RightIsDown() ? 2 : 0) | (evt.MiddleIsDown() ? 4 : 0); m_mouse_buttons = buttons; @@ -747,7 +750,6 @@ void ImGuiWrapper::search_list(const ImVec2& size_, bool (*items_getter)(int, co // add checkboxes for show/hide Categories and Groups text(_L("Use for search")+":"); check_box(_L("Category"), view_params.category); - check_box(_L("Group"), view_params.group); if (is_localized) check_box(_L("Search in English"), view_params.english); } @@ -806,14 +808,6 @@ static const ImWchar ranges_keyboard_shortcuts[] = std::vector ImGuiWrapper::load_svg(const std::string& bitmap_name, unsigned target_width, unsigned target_height) { -#ifdef __APPLE__ - // Note: win->GetContentScaleFactor() is not used anymore here because it tends to - // return bogus results quite often (such as 1.0 on Retina or even 0.0). - // We're using the max scaling factor across all screens because it's very likely to be good enough. - double scale = mac_max_scaling_factor(); -#else - double scale = 1.0; -#endif std::vector empty_vector; #ifdef __WXMSW__ @@ -828,8 +822,6 @@ std::vector ImGuiWrapper::load_svg(const std::string& bitmap_name if (image == nullptr) return empty_vector; - target_height != 0 ? target_height *= scale : target_width *= scale; - float svg_scale = target_height != 0 ? (float)target_height / image->height : target_width != 0 ? (float)target_width / image->width : 1; diff --git a/src/slic3r/GUI/InstanceCheck.cpp b/src/slic3r/GUI/InstanceCheck.cpp index e408ce1183d..d5ecbcd0fc9 100644 --- a/src/slic3r/GUI/InstanceCheck.cpp +++ b/src/slic3r/GUI/InstanceCheck.cpp @@ -1,13 +1,25 @@ #include "GUI_App.hpp" #include "InstanceCheck.hpp" +#include "Plater.hpp" + +#ifdef _WIN32 + #include "MainFrame.hpp" +#endif + +#include "libslic3r/Utils.hpp" +#include "libslic3r/Config.hpp" #include "boost/nowide/convert.hpp" #include #include - +#include #include #include +#ifdef _WIN32 +#include +#endif //WIN32 + #if __linux__ #include /* Pull in all of D-Bus headers. */ #endif //__linux__ @@ -20,218 +32,225 @@ namespace instance_check_internal bool should_send; std::string cl_string; }; - static CommandLineAnalysis process_command_line(int argc, char** argv) //d:\3dmodels\Klapka\Klapka.3mf + static CommandLineAnalysis process_command_line(int argc, char** argv) { CommandLineAnalysis ret { false }; if (argc < 2) return ret; - ret.cl_string = argv[0]; - for (size_t i = 1; i < argc; i++) { - std::string token = argv[i]; - if (token == "--single-instance") { + ret.cl_string = escape_string_cstyle(argv[0]); + for (size_t i = 1; i < argc; ++i) { + const std::string token = argv[i]; + if (token == "--single-instance" || token == "--single-instance=1") { ret.should_send = true; } else { - ret.cl_string += " "; - ret.cl_string += token; + ret.cl_string += " : "; + ret.cl_string += escape_string_cstyle(token); } - } + } BOOST_LOG_TRIVIAL(debug) << "single instance: "<< ret.should_send << ". other params: " << ret.cl_string; return ret; } -} //namespace instance_check_internal -#if _WIN32 + + +#ifdef _WIN32 -namespace instance_check_internal -{ static HWND l_prusa_slicer_hwnd; static BOOL CALLBACK EnumWindowsProc(_In_ HWND hwnd, _In_ LPARAM lParam) { //checks for other instances of prusaslicer, if found brings it to front and return false to stop enumeration and quit this instance //search is done by classname(wxWindowNR is wxwidgets thing, so probably not unique) and name in window upper panel //other option would be do a mutex and check for its existence - TCHAR wndText[1000]; - TCHAR className[1000]; + //BOOST_LOG_TRIVIAL(error) << "ewp: version: " << l_version_wstring; + TCHAR wndText[1000]; + TCHAR className[1000]; GetClassName(hwnd, className, 1000); GetWindowText(hwnd, wndText, 1000); std::wstring classNameString(className); std::wstring wndTextString(wndText); if (wndTextString.find(L"PrusaSlicer") != std::wstring::npos && classNameString == L"wxWindowNR") { - l_prusa_slicer_hwnd = hwnd; - ShowWindow(hwnd, SW_SHOWMAXIMIZED); - SetForegroundWindow(hwnd); - return false; + //check if other instances has same instance hash + //if not it is not same version(binary) as this version + HANDLE handle = GetProp(hwnd, L"Instance_Hash_Minor"); + size_t other_instance_hash = PtrToUint(handle); + size_t other_instance_hash_major; + handle = GetProp(hwnd, L"Instance_Hash_Major"); + other_instance_hash_major = PtrToUint(handle); + other_instance_hash_major = other_instance_hash_major << 32; + other_instance_hash += other_instance_hash_major; + size_t my_instance_hash = GUI::wxGetApp().get_instance_hash_int(); + if(my_instance_hash == other_instance_hash) + { + BOOST_LOG_TRIVIAL(debug) << "win enum - found correct instance"; + l_prusa_slicer_hwnd = hwnd; + ShowWindow(hwnd, SW_SHOWMAXIMIZED); + SetForegroundWindow(hwnd); + return false; + } + BOOST_LOG_TRIVIAL(debug) << "win enum - found wrong instance"; } return true; } - static void send_message(const HWND hwnd) + static bool send_message(const std::string& message, const std::string &version) { - LPWSTR command_line_args = GetCommandLine(); - //Create a COPYDATASTRUCT to send the information - //cbData represents the size of the information we want to send. - //lpData represents the information we want to send. - //dwData is an ID defined by us(this is a type of ID different than WM_COPYDATA). - COPYDATASTRUCT data_to_send = { 0 }; - data_to_send.dwData = 1; - data_to_send.cbData = sizeof(TCHAR) * (wcslen(command_line_args) + 1); - data_to_send.lpData = command_line_args; - - SendMessage(hwnd, WM_COPYDATA, 0, (LPARAM)&data_to_send); - } -} //namespace instance_check_internal - -bool instance_check(int argc, char** argv, bool app_config_single_instance) -{ - instance_check_internal::CommandLineAnalysis cla = instance_check_internal::process_command_line(argc, argv); - if (cla.should_send || app_config_single_instance) { - // Call EnumWidnows with own callback. cons: Based on text in the name of the window and class name which is generic. - if (!EnumWindows(instance_check_internal::EnumWindowsProc, 0)) { - BOOST_LOG_TRIVIAL(info) << "instance check: Another instance found. This instance will terminate."; - instance_check_internal::send_message(instance_check_internal::l_prusa_slicer_hwnd); - return true; + if (!EnumWindows(EnumWindowsProc, 0)) { + std::wstring wstr = boost::nowide::widen(message); + //LPWSTR command_line_args = wstr.c_str();//GetCommandLine(); + LPWSTR command_line_args = new wchar_t[wstr.size() + 1]; + copy(wstr.begin(), wstr.end(), command_line_args); + command_line_args[wstr.size()] = 0; + //Create a COPYDATASTRUCT to send the information + //cbData represents the size of the information we want to send. + //lpData represents the information we want to send. + //dwData is an ID defined by us(this is a type of ID different than WM_COPYDATA). + COPYDATASTRUCT data_to_send = { 0 }; + data_to_send.dwData = 1; + data_to_send.cbData = sizeof(TCHAR) * (wcslen(command_line_args) + 1); + data_to_send.lpData = command_line_args; + + SendMessage(l_prusa_slicer_hwnd, WM_COPYDATA, 0, (LPARAM)&data_to_send); + return true; } + return false; } - BOOST_LOG_TRIVIAL(info) << "instance check: Another instance not found or single-instance not set."; - return false; -} -#elif defined(__APPLE__) +#else -namespace instance_check_internal -{ - static int get_lock() + static bool get_lock(const std::string& name, const std::string& path) { - struct flock fl; - int fdlock; + std::string dest_dir = path + name; + BOOST_LOG_TRIVIAL(debug) <<"full lock path: "<< dest_dir; + struct flock fl; + int fdlock; fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 1; - - if ((fdlock = open("/tmp/prusaslicer.lock", O_WRONLY | O_CREAT, 0666)) == -1) - return 0; + if ((fdlock = open(dest_dir.c_str(), O_WRONLY | O_CREAT, 0666)) == -1) + return true; if (fcntl(fdlock, F_SETLK, &fl) == -1) - return 0; + return true; - return 1; + return false; } -} //namespace instance_check_internal -bool instance_check(int argc, char** argv, bool app_config_single_instance) -{ - instance_check_internal::CommandLineAnalysis cla = instance_check_internal::process_command_line(argc, argv); - if (!instance_check_internal::get_lock() && (cla.should_send || app_config_single_instance)) { - BOOST_LOG_TRIVIAL(info) << "instance check: Another instance found. This instance will terminate."; - send_message_mac(cla.cl_string); - return true; +#endif //WIN32 +#if defined(__APPLE__) + + static bool send_message(const std::string &message_text, const std::string &version) + { + //std::string v(version); + //std::replace(v.begin(), v.end(), '.', '-'); + //if (!instance_check_internal::get_lock(v)) + { + send_message_mac(message_text, version); + return true; + } + return false; } - BOOST_LOG_TRIVIAL(info) << "instance check: Another instance not found or single-instance not set."; - return false; -} #elif defined(__linux__) -namespace instance_check_internal -{ - static int get_lock() + static bool send_message(const std::string &message_text, const std::string &version) { - struct flock fl; - int fdlock; - fl.l_type = F_WRLCK; - fl.l_whence = SEEK_SET; - fl.l_start = 0; - fl.l_len = 1; - - if ((fdlock = open("/tmp/prusaslicer.lock", O_WRONLY | O_CREAT, 0666)) == -1) - return 0; - - if (fcntl(fdlock, F_SETLK, &fl) == -1) - return 0; + /*std::string v(version); + std::replace(v.begin(), v.end(), '.', '-'); + if (!instance_check_internal::get_lock(v))*/ + /*auto checker = new wxSingleInstanceChecker; + if ( !checker->IsAnotherRunning() ) */ + { + DBusMessage* msg; + DBusMessageIter args; + DBusConnection* conn; + DBusError err; + dbus_uint32_t serial = 0; + const char* sigval = message_text.c_str(); + //std::string interface_name = "com.prusa3d.prusaslicer.InstanceCheck"; + std::string interface_name = "com.prusa3d.prusaslicer.InstanceCheck.Object" + version; + std::string method_name = "AnotherInstace"; + //std::string object_name = "/com/prusa3d/prusaslicer/InstanceCheck"; + std::string object_name = "/com/prusa3d/prusaslicer/InstanceCheck/Object" + version; + + + // initialise the error value + dbus_error_init(&err); + + // connect to bus, and check for errors (use SESSION bus everywhere!) + conn = dbus_bus_get(DBUS_BUS_SESSION, &err); + if (dbus_error_is_set(&err)) { + BOOST_LOG_TRIVIAL(error) << "DBus Connection Error. Message to another instance wont be send."; + BOOST_LOG_TRIVIAL(error) << "DBus Connection Error: " << err.message; + dbus_error_free(&err); + return true; + } + if (NULL == conn) { + BOOST_LOG_TRIVIAL(error) << "DBus Connection is NULL. Message to another instance wont be send."; + return true; + } - return 1; - } + //some sources do request interface ownership before constructing msg but i think its wrong. - static void send_message(std::string message_text) - { - DBusMessage* msg; - DBusMessageIter args; - DBusConnection* conn; - DBusError err; - dbus_uint32_t serial = 0; - const char* sigval = message_text.c_str(); - std::string interface_name = "com.prusa3d.prusaslicer.InstanceCheck"; - std::string method_name = "AnotherInstace"; - std::string object_name = "/com/prusa3d/prusaslicer/InstanceCheck"; - - - // initialise the error value - dbus_error_init(&err); + //create new method call message + msg = dbus_message_new_method_call(interface_name.c_str(), object_name.c_str(), interface_name.c_str(), method_name.c_str()); + if (NULL == msg) { + BOOST_LOG_TRIVIAL(error) << "DBus Message is NULL. Message to another instance wont be send."; + dbus_connection_unref(conn); + return true; + } + //the AnotherInstace method is not sending reply. + dbus_message_set_no_reply(msg, TRUE); + + //append arguments to message + if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &sigval, DBUS_TYPE_INVALID)) { + BOOST_LOG_TRIVIAL(error) << "Ran out of memory while constructing args for DBus message. Message to another instance wont be send."; + dbus_message_unref(msg); + dbus_connection_unref(conn); + return true; + } - // connect to bus, and check for errors (use SESSION bus everywhere!) - conn = dbus_bus_get(DBUS_BUS_SESSION, &err); - if (dbus_error_is_set(&err)) { - BOOST_LOG_TRIVIAL(error) << "DBus Connection Error. Message to another instance wont be send."; - BOOST_LOG_TRIVIAL(error) << "DBus Connection Error: "<< err.message; - dbus_error_free(&err); - return; - } - if (NULL == conn) { - BOOST_LOG_TRIVIAL(error) << "DBus Connection is NULL. Message to another instance wont be send."; - return; - } + // send the message and flush the connection + if (!dbus_connection_send(conn, msg, &serial)) { + BOOST_LOG_TRIVIAL(error) << "Ran out of memory while sending DBus message."; + dbus_message_unref(msg); + dbus_connection_unref(conn); + return true; + } + dbus_connection_flush(conn); - //some sources do request interface ownership before constructing msg but i think its wrong. + BOOST_LOG_TRIVIAL(trace) << "DBus message sent."; - //create new method call message - msg = dbus_message_new_method_call(interface_name.c_str(), object_name.c_str(), interface_name.c_str(), method_name.c_str()); - if (NULL == msg) { - BOOST_LOG_TRIVIAL(error) << "DBus Message is NULL. Message to another instance wont be send."; - dbus_connection_unref(conn); - return; - } - //the AnotherInstace method is not sending reply. - dbus_message_set_no_reply(msg, TRUE); - - //append arguments to message - if (!dbus_message_append_args(msg, DBUS_TYPE_STRING, &sigval, DBUS_TYPE_INVALID)) { - BOOST_LOG_TRIVIAL(error) << "Ran out of memory while constructing args for DBus message. Message to another instance wont be send."; - dbus_message_unref(msg); - dbus_connection_unref(conn); - return; + // free the message and close the connection + dbus_message_unref(msg); + dbus_connection_unref(conn); + return true; } - - // send the message and flush the connection - if (!dbus_connection_send(conn, msg, &serial)) { - BOOST_LOG_TRIVIAL(error) << "Ran out of memory while sending DBus message."; - dbus_message_unref(msg); - dbus_connection_unref(conn); - return; - } - dbus_connection_flush(conn); - - BOOST_LOG_TRIVIAL(trace) << "DBus message sent."; - - // free the message and close the connection - dbus_message_unref(msg); - dbus_connection_unref(conn); + return false; } + +#endif //__APPLE__/__linux__ } //namespace instance_check_internal bool instance_check(int argc, char** argv, bool app_config_single_instance) -{ +{ + std::size_t hashed_path = std::hash{}(boost::filesystem::system_complete(argv[0]).string()); + std::string lock_name = std::to_string(hashed_path); + GUI::wxGetApp().set_instance_hash(hashed_path); + BOOST_LOG_TRIVIAL(debug) <<"full path: "<< lock_name; instance_check_internal::CommandLineAnalysis cla = instance_check_internal::process_command_line(argc, argv); - if (!instance_check_internal::get_lock() && (cla.should_send || app_config_single_instance)) { +#ifdef _WIN32 + GUI::wxGetApp().init_single_instance_checker(lock_name + ".lock", data_dir() + "/cache/"); + if ((cla.should_send || app_config_single_instance) && GUI::wxGetApp().single_instance_checker()->IsAnotherRunning()) { +#else // mac & linx + if (instance_check_internal::get_lock(lock_name + ".lock", data_dir() + "/cache/") && (cla.should_send || app_config_single_instance)) { +#endif + instance_check_internal::send_message(cla.cl_string, lock_name); BOOST_LOG_TRIVIAL(info) << "instance check: Another instance found. This instance will terminate."; - instance_check_internal::send_message(cla.cl_string); return true; } BOOST_LOG_TRIVIAL(info) << "instance check: Another instance not found or single-instance not set."; return false; } -#endif //_WIN32/__APPLE__/__linux__ - - namespace GUI { @@ -248,23 +267,24 @@ void OtherInstanceMessageHandler::init(wxEvtHandler* callback_evt_handler) m_initialized = true; m_callback_evt_handler = callback_evt_handler; -#if _WIN32 - //create_listener_window(); -#endif //_WIN32 - #if defined(__APPLE__) - this->register_for_messages(); + this->register_for_messages(wxGetApp().get_instance_hash_string()); #endif //__APPLE__ #ifdef BACKGROUND_MESSAGE_LISTENER m_thread = boost::thread((boost::bind(&OtherInstanceMessageHandler::listen, this))); #endif //BACKGROUND_MESSAGE_LISTENER } -void OtherInstanceMessageHandler::shutdown() +void OtherInstanceMessageHandler::shutdown(MainFrame* main_frame) { BOOST_LOG_TRIVIAL(debug) << "message handler shutdown()."; assert(m_initialized); if (m_initialized) { +#ifdef _WIN32 + HWND hwnd = main_frame->GetHandle(); + RemoveProp(hwnd, L"Instance_Hash_Minor"); + RemoveProp(hwnd, L"Instance_Hash_Major"); +#endif //_WIN32 #if __APPLE__ //delete macos implementation this->unregister_for_messages(); @@ -283,16 +303,51 @@ void OtherInstanceMessageHandler::shutdown() m_stop = false; } #endif //BACKGROUND_MESSAGE_LISTENER - m_initialized = false; + m_callback_evt_handler = nullptr; + m_initialized = false; } } +#ifdef _WIN32 +void OtherInstanceMessageHandler::init_windows_properties(MainFrame* main_frame, size_t instance_hash) +{ + size_t minor_hash = instance_hash & 0xFFFFFFFF; + size_t major_hash = (instance_hash & 0xFFFFFFFF00000000) >> 32; + HWND hwnd = main_frame->GetHandle(); + HANDLE handle_minor = UIntToPtr(minor_hash); + HANDLE handle_major = UIntToPtr(major_hash); + SetProp(hwnd, L"Instance_Hash_Minor", handle_minor); + SetProp(hwnd, L"Instance_Hash_Major", handle_major); + //BOOST_LOG_TRIVIAL(debug) << "window properties initialized " << instance_hash << " (" << minor_hash << " & "<< major_hash; +} + +#if 0 + +void OtherInstanceMessageHandler::print_window_info(HWND hwnd) +{ + std::wstring instance_hash = boost::nowide::widen(wxGetApp().get_instance_hash_string()); + TCHAR wndText[1000]; + TCHAR className[1000]; + GetClassName(hwnd, className, 1000); + GetWindowText(hwnd, wndText, 1000); + std::wstring classNameString(className); + std::wstring wndTextString(wndText); + HANDLE handle = GetProp(hwnd, L"Instance_Hash_Minor"); + size_t result = PtrToUint(handle); + handle = GetProp(hwnd, L"Instance_Hash_Major"); + size_t r2 = PtrToUint(handle); + r2 = (r2 << 32); + result += r2; + BOOST_LOG_TRIVIAL(info) << "window info: " << result; +} +#endif //0 +#endif //WIN32 namespace MessageHandlerInternal { // returns ::path to possible model or empty ::path if input string is not existing path - static boost::filesystem::path get_path(const std::string possible_path) + static boost::filesystem::path get_path(std::string possible_path) { - BOOST_LOG_TRIVIAL(debug) << "message part: " << possible_path; + BOOST_LOG_TRIVIAL(debug) << "message part:" << possible_path; if (possible_path.empty() || possible_path.size() < 3) { BOOST_LOG_TRIVIAL(debug) << "empty"; @@ -312,9 +367,9 @@ namespace MessageHandlerInternal } } //namespace MessageHandlerInternal -void OtherInstanceMessageHandler::handle_message(const std::string message) { +void OtherInstanceMessageHandler::handle_message(const std::string& message) { std::vector paths; - auto next_space = message.find(' '); + auto next_space = message.find(" : "); size_t last_space = 0; int counter = 0; @@ -323,17 +378,17 @@ void OtherInstanceMessageHandler::handle_message(const std::string message) { while (next_space != std::string::npos) { if (counter != 0) { - const std::string possible_path = message.substr(last_space, next_space - last_space); - boost::filesystem::path p = MessageHandlerInternal::get_path(possible_path); + std::string possible_path = message.substr(last_space, next_space - last_space); + boost::filesystem::path p = MessageHandlerInternal::get_path(std::move(possible_path)); if(!p.string().empty()) paths.emplace_back(p); } - last_space = next_space; - next_space = message.find(' ', last_space + 1); + last_space = next_space + 3; + next_space = message.find(" : ", last_space); counter++; } if (counter != 0 ) { - boost::filesystem::path p = MessageHandlerInternal::get_path(message.substr(last_space + 1)); + boost::filesystem::path p = MessageHandlerInternal::get_path(message.substr(last_space)); if (!p.string().empty()) paths.emplace_back(p); } @@ -401,13 +456,12 @@ namespace MessageHandlerDBusInternal { const char* interface_name = dbus_message_get_interface(message); const char* member_name = dbus_message_get_member(message); - + std::string our_interface = "com.prusa3d.prusaslicer.InstanceCheck.Object" + wxGetApp().get_instance_hash_string(); BOOST_LOG_TRIVIAL(trace) << "DBus message received: interface: " << interface_name << ", member: " << member_name; - if (0 == strcmp("org.freedesktop.DBus.Introspectable", interface_name) && 0 == strcmp("Introspect", member_name)) { respond_to_introspect(connection, message); return DBUS_HANDLER_RESULT_HANDLED; - } else if (0 == strcmp("com.prusa3d.prusaslicer.InstanceCheck", interface_name) && 0 == strcmp("AnotherInstace", member_name)) { + } else if (0 == strcmp(our_interface.c_str(), interface_name) && 0 == strcmp("AnotherInstace", member_name)) { handle_method_another_instance(connection, message); return DBUS_HANDLER_RESULT_HANDLED; } @@ -421,9 +475,11 @@ void OtherInstanceMessageHandler::listen() DBusError err; int name_req_val; DBusObjectPathVTable vtable; - std::string interface_name = "com.prusa3d.prusaslicer.InstanceCheck"; - std::string object_name = "/com/prusa3d/prusaslicer/InstanceCheck"; + std::string instance_hash = wxGetApp().get_instance_hash_string(); + std::string interface_name = "com.prusa3d.prusaslicer.InstanceCheck.Object" + instance_hash; + std::string object_name = "/com/prusa3d/prusaslicer/InstanceCheck/Object" + instance_hash; + //BOOST_LOG_TRIVIAL(debug) << "init dbus listen " << interface_name << " " << object_name; dbus_error_init(&err); // connect to the bus and check for errors (use SESSION bus everywhere!) @@ -469,7 +525,7 @@ void OtherInstanceMessageHandler::listen() return; } - BOOST_LOG_TRIVIAL(trace) << "Dbus object registered. Starting listening for messages."; + BOOST_LOG_TRIVIAL(trace) << "Dbus object "<< object_name <<" registered. Starting listening for messages."; for (;;) { // Wait for 1 second diff --git a/src/slic3r/GUI/InstanceCheck.hpp b/src/slic3r/GUI/InstanceCheck.hpp index 4207296a050..781cb139bee 100644 --- a/src/slic3r/GUI/InstanceCheck.hpp +++ b/src/slic3r/GUI/InstanceCheck.hpp @@ -11,10 +11,11 @@ #include +#if __linux__ #include #include #include - +#endif // __linux__ namespace Slic3r { @@ -26,11 +27,13 @@ bool instance_check(int argc, char** argv, bool app_config_single_instance); #if __APPLE__ // apple implementation of inner functions of instance_check // in InstanceCheckMac.mm -void send_message_mac(const std::string msg); +void send_message_mac(const std::string& msg, const std::string& version); #endif //__APPLE__ namespace GUI { +class MainFrame; + #if __linux__ #define BACKGROUND_MESSAGE_LISTENER #endif // __linux__ @@ -52,7 +55,7 @@ class OtherInstanceMessageHandler // inits listening, on each platform different. On linux starts background thread void init(wxEvtHandler* callback_evt_handler); // stops listening, on linux stops the background thread - void shutdown(); + void shutdown(MainFrame* main_frame); //finds paths to models in message(= command line arguments, first should be prusaSlicer executable) //and sends them to plater via LoadFromOtherInstanceEvent @@ -60,7 +63,10 @@ class OtherInstanceMessageHandler // win32 - anybody who has hwnd can send message. // mac - anybody who posts notification with name:@"OtherPrusaSlicerTerminating" // linux - instrospectable on dbus - void handle_message(const std::string message); + void handle_message(const std::string& message); +#ifdef _WIN32 + static void init_windows_properties(MainFrame* main_frame, size_t instance_hash); +#endif //WIN32 private: bool m_initialized { false }; wxEvtHandler* m_callback_evt_handler { nullptr }; @@ -79,7 +85,7 @@ class OtherInstanceMessageHandler #if __APPLE__ //implemented at InstanceCheckMac.mm - void register_for_messages(); + void register_for_messages(const std::string &version_hash); void unregister_for_messages(); // Opaque pointer to RemovableDriveManagerMM void* m_impl_osx; diff --git a/src/slic3r/GUI/InstanceCheckMac.h b/src/slic3r/GUI/InstanceCheckMac.h index 9931bb67961..ed9d17418f5 100644 --- a/src/slic3r/GUI/InstanceCheckMac.h +++ b/src/slic3r/GUI/InstanceCheckMac.h @@ -3,6 +3,6 @@ @interface OtherInstanceMessageHandlerMac : NSObject -(instancetype) init; --(void) add_observer; +-(void) add_observer:(NSString *)version; -(void) message_update:(NSNotification *)note; @end diff --git a/src/slic3r/GUI/InstanceCheckMac.mm b/src/slic3r/GUI/InstanceCheckMac.mm index cbc833c79c5..67bf64980b9 100644 --- a/src/slic3r/GUI/InstanceCheckMac.mm +++ b/src/slic3r/GUI/InstanceCheckMac.mm @@ -9,10 +9,12 @@ -(instancetype) init self = [super init]; return self; } --(void)add_observer +-(void)add_observer:(NSString *)version_hash { - NSLog(@"adding observer"); - [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(message_update:) name:@"OtherPrusaSlicerInstanceMessage" object:nil suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately]; + //NSLog(@"adding observer"); + //NSString *nsver = @"OtherPrusaSlicerInstanceMessage" + version_hash; + NSString *nsver = [NSString stringWithFormat: @"%@%@", @"OtherPrusaSlicerInstanceMessage", version_hash]; + [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(message_update:) name:nsver object:nil suspensionBehavior:NSNotificationSuspensionBehaviorDeliverImmediately]; } -(void)message_update:(NSNotification *)msg @@ -36,19 +38,22 @@ -(void)message_update:(NSNotification *)msg namespace Slic3r { -void send_message_mac(const std::string msg) +void send_message_mac(const std::string &msg, const std::string &version) { NSString *nsmsg = [NSString stringWithCString:msg.c_str() encoding:[NSString defaultCStringEncoding]]; - //NSLog(@"sending msg %@", nsmsg); - [[NSDistributedNotificationCenter defaultCenter] postNotificationName:@"OtherPrusaSlicerInstanceMessage" object:nil userInfo:[NSDictionary dictionaryWithObject:nsmsg forKey:@"data"] deliverImmediately:YES]; + //NSString *nsver = @"OtherPrusaSlicerInstanceMessage" + [NSString stringWithCString:version.c_str() encoding:[NSString defaultCStringEncoding]]; + NSString *nsver = [NSString stringWithCString:version.c_str() encoding:[NSString defaultCStringEncoding]]; + NSString *notifname = [NSString stringWithFormat: @"%@%@", @"OtherPrusaSlicerInstanceMessage", nsver]; + [[NSDistributedNotificationCenter defaultCenter] postNotificationName:notifname object:nil userInfo:[NSDictionary dictionaryWithObject:nsmsg forKey:@"data"] deliverImmediately:YES]; } namespace GUI { -void OtherInstanceMessageHandler::register_for_messages() +void OtherInstanceMessageHandler::register_for_messages(const std::string &version_hash) { m_impl_osx = [[OtherInstanceMessageHandlerMac alloc] init]; if(m_impl_osx) { - [m_impl_osx add_observer]; + NSString *nsver = [NSString stringWithCString:version_hash.c_str() encoding:[NSString defaultCStringEncoding]]; + [m_impl_osx add_observer:nsver]; } } @@ -59,7 +64,7 @@ void send_message_mac(const std::string msg) [m_impl_osx release]; m_impl_osx = nullptr; } else { - NSLog(@"unreegister not required"); + NSLog(@"warning: unregister instance InstanceCheck notifications not required"); } } }//namespace GUI diff --git a/src/slic3r/GUI/Jobs/ArrangeJob.cpp b/src/slic3r/GUI/Jobs/ArrangeJob.cpp index 00b9fb654d0..41fd717da86 100644 --- a/src/slic3r/GUI/Jobs/ArrangeJob.cpp +++ b/src/slic3r/GUI/Jobs/ArrangeJob.cpp @@ -1,6 +1,7 @@ #include "ArrangeJob.hpp" #include "libslic3r/MTUtils.hpp" +#include "libslic3r/Model.hpp" #include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/GLCanvas3D.hpp" diff --git a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp index 4cc34e71c22..c847c84b48b 100644 --- a/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp +++ b/src/slic3r/GUI/Jobs/RotoptimizeJob.cpp @@ -3,6 +3,7 @@ #include "libslic3r/MTUtils.hpp" #include "libslic3r/SLA/Rotfinder.hpp" #include "libslic3r/MinAreaBoundingBox.hpp" +#include "libslic3r/Model.hpp" #include "slic3r/GUI/Plater.hpp" diff --git a/src/slic3r/GUI/Jobs/SLAImportJob.cpp b/src/slic3r/GUI/Jobs/SLAImportJob.cpp index e791bf94e15..4e9f08ff239 100644 --- a/src/slic3r/GUI/Jobs/SLAImportJob.cpp +++ b/src/slic3r/GUI/Jobs/SLAImportJob.cpp @@ -8,6 +8,8 @@ #include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/Utils/SLAImport.hpp" +#include "libslic3r/Model.hpp" + #include #include #include diff --git a/src/slic3r/GUI/KBShortcutsDialog.cpp b/src/slic3r/GUI/KBShortcutsDialog.cpp index 9f31d23d87a..556b610e91e 100644 --- a/src/slic3r/GUI/KBShortcutsDialog.cpp +++ b/src/slic3r/GUI/KBShortcutsDialog.cpp @@ -25,15 +25,6 @@ #include #endif // BOOK_TYPE -#if ENABLE_SCROLLABLE -static wxSize get_screen_size(wxWindow* window) -{ - const auto idx = wxDisplay::GetFromWindow(window); - wxDisplay display(idx != wxNOT_FOUND ? idx : 0u); - return display.GetClientArea().GetSize(); -} -#endif // ENABLE_SCROLLABLE - namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/LambdaObjectDialog.cpp b/src/slic3r/GUI/LambdaObjectDialog.cpp deleted file mode 100644 index 63c8d329c50..00000000000 --- a/src/slic3r/GUI/LambdaObjectDialog.cpp +++ /dev/null @@ -1,200 +0,0 @@ -#include "LambdaObjectDialog.hpp" - -#include -#include -#include "OptionsGroup.hpp" -#include "I18N.hpp" - -namespace Slic3r -{ -namespace GUI -{ - -LambdaObjectDialog::LambdaObjectDialog(wxWindow* parent, - const wxString type_name): - m_type_name(type_name) -{ - Create(parent, wxID_ANY, _(L("Lambda Object")), - parent->GetScreenPosition(), wxDefaultSize, - wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER); - - // instead of double dim[3] = { 1.0, 1.0, 1.0 }; - object_parameters.dim[0] = 1.0; - object_parameters.dim[1] = 1.0; - object_parameters.dim[2] = 1.0; - - sizer = new wxBoxSizer(wxVERTICAL); - - // modificator options - if (m_type_name == wxEmptyString) { - m_modificator_options_book = new wxChoicebook( this, wxID_ANY, wxDefaultPosition, - wxDefaultSize, wxCHB_TOP); - sizer->Add(m_modificator_options_book, 1, wxEXPAND | wxALL, 10); - } - else { - m_panel = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize); - sizer->Add(m_panel, 1, wxEXPAND | wxALL, 10); - } - - ConfigOptionDef def; - def.width = 70; - auto optgroup = init_modificator_options_page(_(L("Box"))); - if (optgroup) { - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - int opt_id = opt_key == "l" ? 0 : - opt_key == "w" ? 1 : - opt_key == "h" ? 2 : -1; - if (opt_id < 0) return; - object_parameters.dim[opt_id] = boost::any_cast(value); - }; - - def.type = coFloat; - def.set_default_value(new ConfigOptionFloat{ 1.0 }); - def.label = L("Length"); - Option option(def, "l"); - optgroup->append_single_option_line(option); - - def.label = L("Width"); - option = Option(def, "w"); - optgroup->append_single_option_line(option); - - def.label = L("Height"); - option = Option(def, "h"); - optgroup->append_single_option_line(option); - } - - optgroup = init_modificator_options_page(_(L("Cylinder"))); - if (optgroup) { - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - int val = boost::any_cast(value); - if (opt_key == "cyl_r") - object_parameters.cyl_r = val; - else if (opt_key == "cyl_h") - object_parameters.cyl_h = val; - else return; - }; - - def.type = coInt; - def.set_default_value(new ConfigOptionInt{ 1 }); - def.label = L("Radius"); - auto option = Option(def, "cyl_r"); - optgroup->append_single_option_line(option); - - def.label = L("Height"); - option = Option(def, "cyl_h"); - optgroup->append_single_option_line(option); - } - - optgroup = init_modificator_options_page(_(L("Sphere"))); - if (optgroup) { - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - if (opt_key == "sph_rho") - object_parameters.sph_rho = boost::any_cast(value); - else return; - }; - - def.type = coFloat; - def.set_default_value(new ConfigOptionFloat{ 1.0 }); - def.label = L("Rho"); - auto option = Option(def, "sph_rho"); - optgroup->append_single_option_line(option); - } - - optgroup = init_modificator_options_page(_(L("Slab"))); - if (optgroup) { - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - double val = boost::any_cast(value); - if (opt_key == "slab_z") - object_parameters.slab_z = val; - else if (opt_key == "slab_h") - object_parameters.slab_h = val; - else return; - }; - - def.type = coFloat; - def.set_default_value(new ConfigOptionFloat{ 1.0 }); - def.label = L("Height"); - auto option = Option(def, "slab_h"); - optgroup->append_single_option_line(option); - - def.label = L("Initial Z"); - option = Option(def, "slab_z"); - optgroup->append_single_option_line(option); - } - - Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent e) - { - auto page_idx = m_modificator_options_book->GetSelection(); - if (page_idx < 0) return; - switch (page_idx) - { - case 0: - object_parameters.type = LambdaTypeBox; - break; - case 1: - object_parameters.type = LambdaTypeCylinder; - break; - case 2: - object_parameters.type = LambdaTypeSphere; - break; - case 3: - object_parameters.type = LambdaTypeSlab; - break; - default: - break; - } - })); - - const auto button_sizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL); - - wxButton* btn_OK = static_cast(FindWindowById(wxID_OK, this)); - btn_OK->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { - // validate user input - if (!CanClose())return; - EndModal(wxID_OK); - Destroy(); - }); - - wxButton* btn_CANCEL = static_cast(FindWindowById(wxID_CANCEL, this)); - btn_CANCEL->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { - // validate user input - if (!CanClose())return; - EndModal(wxID_CANCEL); - Destroy(); - }); - - sizer->Add(button_sizer, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10); - - SetSizer(sizer); - sizer->Fit(this); - sizer->SetSizeHints(this); -} - -// Called from the constructor. -// Create a panel for a rectangular / circular / custom bed shape. -ConfigOptionsGroupShp LambdaObjectDialog::init_modificator_options_page(const wxString& title) -{ - if (!m_type_name.IsEmpty() && m_type_name != title) - return nullptr; - - auto panel = m_type_name.IsEmpty() ? new wxPanel(m_modificator_options_book) : m_panel; - - ConfigOptionsGroupShp optgroup; - optgroup = std::make_shared(panel, _(L("Add")) + " " +title + " " +dots); - optgroup->label_width = 100; - - m_optgroups.push_back(optgroup); - - if (m_type_name.IsEmpty()) { - panel->SetSizerAndFit(optgroup->sizer); - m_modificator_options_book->AddPage(panel, title); - } - else - panel->SetSizer(optgroup->sizer); - - return optgroup; -} - - -} //namespace GUI -} //namespace Slic3r diff --git a/src/slic3r/GUI/LambdaObjectDialog.hpp b/src/slic3r/GUI/LambdaObjectDialog.hpp deleted file mode 100644 index 5bc2d19a53f..00000000000 --- a/src/slic3r/GUI/LambdaObjectDialog.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef slic3r_LambdaObjectDialog_hpp_ -#define slic3r_LambdaObjectDialog_hpp_ - -#include -#include - -#include -#include -#include - -class wxPanel; - -namespace Slic3r -{ -namespace GUI -{ -enum LambdaTypeIDs{ - LambdaTypeBox, - LambdaTypeCylinder, - LambdaTypeSphere, - LambdaTypeSlab -}; - -struct OBJECT_PARAMETERS -{ - LambdaTypeIDs type = LambdaTypeBox; - double dim[3];// = { 1.0, 1.0, 1.0 }; - int cyl_r = 1; - int cyl_h = 1; - double sph_rho = 1.0; - double slab_h = 1.0; - double slab_z = 0.0; -}; -class ConfigOptionsGroup; -using ConfigOptionsGroupShp = std::shared_ptr; -class LambdaObjectDialog : public wxDialog -{ - wxChoicebook* m_modificator_options_book = nullptr; - std::vector m_optgroups; - wxString m_type_name; - wxPanel* m_panel = nullptr; -public: - LambdaObjectDialog(wxWindow* parent, - const wxString type_name = wxEmptyString); - ~LambdaObjectDialog() {} - - bool CanClose() { return true; } // ??? - OBJECT_PARAMETERS& ObjectParameters() { return object_parameters; } - - ConfigOptionsGroupShp init_modificator_options_page(const wxString& title); - - // Note whether the window was already closed, so a pending update is not executed. - bool m_already_closed = false; - OBJECT_PARAMETERS object_parameters; - wxBoxSizer* sizer = nullptr; -}; -} //namespace GUI -} //namespace Slic3r -#endif //slic3r_LambdaObjectDialog_hpp_ diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 73b78d3df26..521dcab804c 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -29,6 +29,7 @@ #include "InstanceCheck.hpp" #include "I18N.hpp" #include "GLCanvas3D.hpp" +#include "Plater.hpp" #include #include "GUI_App.hpp" @@ -41,10 +42,44 @@ namespace Slic3r { namespace GUI { +#if ENABLE_LAYOUT_NO_RESTART +enum class ERescaleTarget +{ + Mainframe, + SettingsDialog +}; + +static void rescale_dialog_after_dpi_change(MainFrame& mainframe, SettingsDialog& dialog, ERescaleTarget target) +{ + int mainframe_dpi = get_dpi_for_window(&mainframe); + int dialog_dpi = get_dpi_for_window(&dialog); + if (mainframe_dpi != dialog_dpi) { + if (target == ERescaleTarget::SettingsDialog) { + dialog.enable_force_rescale(); +#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + dialog.GetEventHandler()->AddPendingEvent(wxDPIChangedEvent(wxSize(mainframe_dpi, mainframe_dpi), wxSize(dialog_dpi, dialog_dpi))); +#else + dialog.GetEventHandler()->AddPendingEvent(DpiChangedEvent(EVT_DPI_CHANGED_SLICER, dialog_dpi, dialog.GetRect())); +#endif // wxVERSION_EQUAL_OR_GREATER_THAN + } else { +#if wxVERSION_EQUAL_OR_GREATER_THAN(3,1,3) + mainframe.GetEventHandler()->AddPendingEvent(wxDPIChangedEvent(wxSize(dialog_dpi, dialog_dpi), wxSize(mainframe_dpi, mainframe_dpi))); +#else + mainframe.enable_force_rescale(); + mainframe.GetEventHandler()->AddPendingEvent(DpiChangedEvent(EVT_DPI_CHANGED_SLICER, mainframe_dpi, mainframe.GetRect())); +#endif // wxVERSION_EQUAL_OR_GREATER_THAN + } + } +} +#endif // ENABLE_LAYOUT_NO_RESTART + MainFrame::MainFrame() : DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_STYLE, "mainframe"), m_printhost_queue_dlg(new PrintHostQueueDialog(this)) , m_recent_projects(9) +#if ENABLE_LAYOUT_NO_RESTART + , m_settings_dialog(this) +#endif // ENABLE_LAYOUT_NO_RESTART { // Fonts were created by the DPIFrame constructor for the monitor, on which the window opened. wxGetApp().update_fonts(this); @@ -71,7 +106,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S m_statusbar->embed(this); m_statusbar->set_status_text(_(L("Version")) + " " + SLIC3R_VERSION + - _(L(" - Remember to check for updates at http://github.com/prusa3d/PrusaSlicer/releases"))); + _(L(" - Remember to check for updates at https://github.com/prusa3d/PrusaSlicer/releases"))); /* Load default preset bitmaps before a tabpanel initialization, * but after filling of an em_unit value @@ -89,8 +124,33 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S m_loaded = true; +#if !ENABLE_LAYOUT_NO_RESTART +#ifdef __APPLE__ + // Using SetMinSize() on Mac messes up the window position in some cases + // cf. https://groups.google.com/forum/#!topic/wx-users/yUKPBBfXWO0 + // So, if we haven't possibility to set MinSize() for the MainFrame, + // set the MinSize() as a half of regular for the m_plater and m_tabpanel, when settings layout is in slNew mode + // Otherwise, MainFrame will be maximized by height + if (slNew) { + wxSize size = wxGetApp().get_min_size(); + size.SetHeight(int(0.5*size.GetHeight())); + m_plater->SetMinSize(size); + m_tabpanel->SetMinSize(size); + } +#endif +#endif // !ENABLE_LAYOUT_NO_RESTART + // initialize layout - auto sizer = new wxBoxSizer(wxVERTICAL); + m_main_sizer = new wxBoxSizer(wxVERTICAL); + wxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(m_main_sizer, 1, wxEXPAND); +#if ENABLE_LAYOUT_NO_RESTART + SetSizer(sizer); + // initialize layout from config + update_layout(); + sizer->SetSizeHints(this); + Fit(); +#else if (m_plater && m_layout != slOld) sizer->Add(m_plater, 1, wxEXPAND); @@ -100,8 +160,9 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S sizer->SetSizeHints(this); SetSizer(sizer); Fit(); +#endif // !ENABLE_LAYOUT_NO_RESTART - const wxSize min_size = wxSize(76*wxGetApp().em_unit(), 49*wxGetApp().em_unit()); + const wxSize min_size = wxGetApp().get_min_size(); //wxSize(76*wxGetApp().em_unit(), 49*wxGetApp().em_unit()); #ifdef __APPLE__ // Using SetMinSize() on Mac messes up the window position in some cases // cf. https://groups.google.com/forum/#!topic/wx-users/yUKPBBfXWO0 @@ -191,6 +252,12 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S }); wxGetApp().persist_window_geometry(this, true); +#if ENABLE_LAYOUT_NO_RESTART + wxGetApp().persist_window_geometry(&m_settings_dialog, true); +#else + if (m_settings_dialog != nullptr) + wxGetApp().persist_window_geometry(m_settings_dialog, true); +#endif // ENABLE_LAYOUT_NO_RESTART update_ui_from_settings(); // FIXME (?) @@ -198,6 +265,123 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S m_plater->show_action_buttons(true); } +#if ENABLE_LAYOUT_NO_RESTART +void MainFrame::update_layout() +{ + auto restore_to_creation = [this]() { + auto clean_sizer = [](wxSizer* sizer) { + while (!sizer->GetChildren().IsEmpty()) { + sizer->Detach(0); + } + }; + + // On Linux m_plater needs to be removed from m_tabpanel before to reparent it + int plater_page_id = m_tabpanel->FindPage(m_plater); + if (plater_page_id != wxNOT_FOUND) + m_tabpanel->RemovePage(plater_page_id); + + if (m_plater->GetParent() != this) + m_plater->Reparent(this); + + if (m_tabpanel->GetParent() != this) + m_tabpanel->Reparent(this); + + plater_page_id = (m_plater_page != nullptr) ? m_tabpanel->FindPage(m_plater_page) : wxNOT_FOUND; + if (plater_page_id != wxNOT_FOUND) { + m_tabpanel->DeletePage(plater_page_id); + m_plater_page = nullptr; + } + + if (m_layout == ESettingsLayout::Dlg) + rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::Mainframe); + + clean_sizer(m_main_sizer); + clean_sizer(m_settings_dialog.GetSizer()); + + if (m_settings_dialog.IsShown()) + m_settings_dialog.Close(); + + m_tabpanel->Hide(); + m_plater->Hide(); + + Layout(); + }; + + ESettingsLayout layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? ESettingsLayout::Old : + wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? ESettingsLayout::New : + wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? ESettingsLayout::Dlg : ESettingsLayout::Old; + + if (m_layout == layout) + return; + + wxBusyCursor busy; + + Freeze(); + + // Remove old settings + if (m_layout != ESettingsLayout::Unknown) + restore_to_creation(); + + m_layout = layout; + + // From the very beginning the Print settings should be selected + m_last_selected_tab = m_layout == ESettingsLayout::Dlg ? 0 : 1; + + // Set new settings + switch (m_layout) + { + case ESettingsLayout::Old: + { + m_plater->Reparent(m_tabpanel); + m_tabpanel->InsertPage(0, m_plater, _L("Plater")); + m_main_sizer->Add(m_tabpanel, 1, wxEXPAND); + m_plater->Show(); + m_tabpanel->Show(); + break; + } + case ESettingsLayout::New: + { + m_main_sizer->Add(m_plater, 1, wxEXPAND); + m_tabpanel->Hide(); + m_main_sizer->Add(m_tabpanel, 1, wxEXPAND); + m_plater_page = new wxPanel(m_tabpanel); + m_tabpanel->InsertPage(0, m_plater_page, _L("Plater")); // empty panel just for Plater tab */ + m_plater->Show(); + break; + } + case ESettingsLayout::Dlg: + { + m_main_sizer->Add(m_plater, 1, wxEXPAND); + m_tabpanel->Reparent(&m_settings_dialog); + m_settings_dialog.GetSizer()->Add(m_tabpanel, 1, wxEXPAND); + + rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::SettingsDialog); + + m_tabpanel->Show(); + m_plater->Show(); + break; + } + } + +//#ifdef __APPLE__ +// // Using SetMinSize() on Mac messes up the window position in some cases +// // cf. https://groups.google.com/forum/#!topic/wx-users/yUKPBBfXWO0 +// // So, if we haven't possibility to set MinSize() for the MainFrame, +// // set the MinSize() as a half of regular for the m_plater and m_tabpanel, when settings layout is in slNew mode +// // Otherwise, MainFrame will be maximized by height +// if (m_layout == ESettingsLayout::New) { +// wxSize size = wxGetApp().get_min_size(); +// size.SetHeight(int(0.5 * size.GetHeight())); +// m_plater->SetMinSize(size); +// m_tabpanel->SetMinSize(size); +// } +//#endif + + Layout(); + Thaw(); +} +#endif // ENABLE_LAYOUT_NO_RESTART + // Called when closing the application and when switching the application language. void MainFrame::shutdown() { @@ -230,8 +414,20 @@ void MainFrame::shutdown() // In addition, there were some crashes due to the Paint events sent to already destructed windows. this->Show(false); - if (m_settings_dialog) +#if ENABLE_LAYOUT_NO_RESTART + if (m_settings_dialog.IsShown()) + // call Close() to trigger call to lambda defined into GUI_App::persist_window_geometry() + m_settings_dialog.Close(); +#else + if (m_settings_dialog != nullptr) + { + if (m_settings_dialog->IsShown()) + // call Close() to trigger call to lambda defined into GUI_App::persist_window_geometry() + m_settings_dialog->Close(); + m_settings_dialog->Destroy(); + } +#endif // ENABLE_LAYOUT_NO_RESTART // Stop the background thread (Windows and Linux). // Disconnect from a 3DConnextion driver (OSX). @@ -242,7 +438,7 @@ void MainFrame::shutdown() // Stop the background thread of the removable drive manager, so that no new updates will be sent to the Plater. wxGetApp().removable_drive_manager()->shutdown(); //stop listening for messages from other instances - wxGetApp().other_instance_message_handler()->shutdown(); + wxGetApp().other_instance_message_handler()->shutdown(this); // Save the slic3r.ini.Usually the ini file is saved from "on idle" callback, // but in rare cases it may not have been called yet. wxGetApp().app_config->save(); @@ -290,9 +486,19 @@ void MainFrame::update_title() void MainFrame::init_tabpanel() { +#if ENABLE_LAYOUT_NO_RESTART + // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10 + // with multiple high resolution displays connected. + m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); +#ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList + m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font()); +#endif + m_tabpanel->Hide(); + m_settings_dialog.set_tabpanel(m_tabpanel); +#else m_layout = wxGetApp().app_config->get("old_settings_layout_mode") == "1" ? slOld : - wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? slNew : - wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? slDlg : slOld; + wxGetApp().app_config->get("new_settings_layout_mode") == "1" ? slNew : + wxGetApp().app_config->get("dlg_settings_layout_mode") == "1" ? slDlg : slOld; // From the very beginning the Print settings should be selected m_last_selected_tab = m_layout == slDlg ? 0 : 1; @@ -309,24 +515,31 @@ void MainFrame::init_tabpanel() m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font()); #endif } +#endif // ENABLE_LAYOUT_NO_RESTART m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxEvent&) { - auto panel = m_tabpanel->GetCurrentPage(); + wxWindow* panel = m_tabpanel->GetCurrentPage(); + Tab* tab = dynamic_cast(panel); - if (panel == nullptr) + // There shouldn't be a case, when we try to select a tab, which doesn't support a printer technology + if (panel == nullptr || (tab != nullptr && !tab->supports_printer_technology(m_plater->printer_technology()))) return; auto& tabs_list = wxGetApp().tabs_list; - if (find(tabs_list.begin(), tabs_list.end(), panel) != tabs_list.end()) { + if (tab && std::find(tabs_list.begin(), tabs_list.end(), tab) != tabs_list.end()) { // On GTK, the wxEVT_NOTEBOOK_PAGE_CHANGED event is triggered // before the MainFrame is fully set up. - static_cast(panel)->OnActivate(); + tab->OnActivate(); m_last_selected_tab = m_tabpanel->GetSelection(); } else - select_tab(0); + select_tab(0); // select Plater }); +#if ENABLE_LAYOUT_NO_RESTART + m_plater = new Plater(this, this); + m_plater->Hide(); +#else if (m_layout == slOld) { m_plater = new Plater(m_tabpanel, this); m_tabpanel->AddPage(m_plater, _L("Plater")); @@ -336,6 +549,7 @@ void MainFrame::init_tabpanel() if (m_layout == slNew) m_tabpanel->AddPage(new wxPanel(m_tabpanel), _L("Plater")); // empty panel just for Plater tab } +#endif // ENABLE_LAYOUT_NO_RESTART wxGetApp().plater_ = m_plater; wxGetApp().obj_list()->create_popup_menus(); @@ -477,6 +691,18 @@ bool MainFrame::can_slice() const bool MainFrame::can_change_view() const { +#if ENABLE_LAYOUT_NO_RESTART + switch (m_layout) + { + default: { return false; } + case ESettingsLayout::New: { return m_plater->IsShown(); } + case ESettingsLayout::Dlg: { return true; } + case ESettingsLayout::Old: { + int page_id = m_tabpanel->GetSelection(); + return page_id != wxNOT_FOUND && dynamic_cast(m_tabpanel->GetPage((size_t)page_id)) != nullptr; + } + } +#else if (m_layout == slNew) return m_plater->IsShown(); if (m_layout == slDlg) @@ -484,6 +710,7 @@ bool MainFrame::can_change_view() const // slOld layout mode int page_id = m_tabpanel->GetSelection(); return page_id != wxNOT_FOUND && dynamic_cast(m_tabpanel->GetPage((size_t)page_id)) != nullptr; +#endif // ENABLE_LAYOUT_NO_RESTART } bool MainFrame::can_select() const @@ -511,9 +738,13 @@ bool MainFrame::can_reslice() const return (m_plater != nullptr) && !m_plater->model().objects.empty(); } -void MainFrame::on_dpi_changed(const wxRect &suggested_rect) +void MainFrame::on_dpi_changed(const wxRect& suggested_rect) { +#if ENABLE_WX_3_1_3_DPI_CHANGED_EVENT + wxGetApp().update_fonts(this); +#else wxGetApp().update_fonts(); +#endif // ENABLE_WX_3_1_3_DPI_CHANGED_EVENT this->SetFont(this->normal_font()); /* Load default preset bitmaps before a tabpanel initialization, @@ -525,8 +756,13 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect) wxGetApp().plater()->msw_rescale(); // update Tabs - for (auto tab : wxGetApp().tabs_list) - tab->msw_rescale(); +#if ENABLE_LAYOUT_NO_RESTART + if (m_layout != ESettingsLayout::Dlg) // Do not update tabs if the Settings are in the separated dialog +#else + if (m_layout != slDlg) // Update tabs later, from the SettingsDialog, when the Settings are in the separated dialog +#endif // ENABLE_LAYOUT_NO_RESTART + for (auto tab : wxGetApp().tabs_list) + tab->msw_rescale(); wxMenuBar* menu_bar = this->GetMenuBar(); for (size_t id = 0; id < menu_bar->GetMenuCount(); id++) @@ -534,7 +770,7 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect) // Workarounds for correct Window rendering after rescale - /* Even if Window is maximized during moving, + /* Even if Window is maximized during moving, * first of all we should imitate Window resizing. So: * 1. cancel maximization, if it was set * 2. imitate resizing @@ -552,6 +788,33 @@ void MainFrame::on_dpi_changed(const wxRect &suggested_rect) this->SetSize(sz); this->Maximize(is_maximized); + +#if ENABLE_LAYOUT_NO_RESTART + if (m_layout == ESettingsLayout::Dlg) + rescale_dialog_after_dpi_change(*this, m_settings_dialog, ERescaleTarget::SettingsDialog); +#endif // ENABLE_LAYOUT_NO_RESTART +} + +void MainFrame::on_sys_color_changed() +{ + wxBusyCursor wait; + + // update label colors in respect to the system mode + wxGetApp().init_label_colours(); + + wxGetApp().preset_bundle->load_default_preset_bitmaps(); + + // update Plater + wxGetApp().plater()->sys_color_changed(); + + // update Tabs + for (auto tab : wxGetApp().tabs_list) + tab->sys_color_changed(); + + // msw_rescale_menu updates just icons, so use it + wxMenuBar* menu_bar = this->GetMenuBar(); + for (size_t id = 0; id < menu_bar->GetMenuCount(); id++) + msw_rescale_menu(menu_bar->GetMenu(id)); } void MainFrame::init_menubar() @@ -623,6 +886,10 @@ void MainFrame::init_menubar() [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "import_plater", nullptr, [this](){return m_plater != nullptr; }, this); + append_menu_item(import_menu, wxID_ANY, _L("Import STL (imperial units)"), _L("Load an model saved with imperial units"), + [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(true); }, "import_plater", nullptr, + [this](){return m_plater != nullptr; }, this); + append_menu_item(import_menu, wxID_ANY, _(L("Import SL1 archive")) + dots, _(L("Load an SL1 output archive")), [this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr, [this](){return m_plater != nullptr; }, this); @@ -642,7 +909,7 @@ void MainFrame::init_menubar() wxMenu* export_menu = new wxMenu(); wxMenuItem* item_export_gcode = append_menu_item(export_menu, wxID_ANY, _(L("Export &G-code")) + dots +"\tCtrl+G", _(L("Export current plate as G-code")), - [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(); }, "export_gcode", nullptr, + [this](wxCommandEvent&) { if (m_plater) m_plater->export_gcode(false); }, "export_gcode", nullptr, [this](){return can_export_gcode(); }, this); m_changeable_menu_items.push_back(item_export_gcode); wxMenuItem* item_send_gcode = append_menu_item(export_menu, wxID_ANY, _(L("S&end G-code")) + dots +"\tCtrl+Shift+G", _(L("Send to print current plate as G-code")), @@ -874,7 +1141,7 @@ void MainFrame::init_menubar() append_menu_item(helpMenu, wxID_ANY, _(L("Prusa 3D &Drivers")), _(L("Open the Prusa3D drivers download page in your browser")), [this](wxCommandEvent&) { wxGetApp().open_web_page_localized("https://www.prusa3d.com/downloads"); }); append_menu_item(helpMenu, wxID_ANY, _(L("Software &Releases")), _(L("Open the software releases page in your browser")), - [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://github.com/prusa3d/PrusaSlicer/releases"); }); + [this](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/PrusaSlicer/releases"); }); //# my $versioncheck = $self->_append_menu_item($helpMenu, "Check for &Updates...", "Check for new Slic3r versions", sub{ //# wxTheApp->check_version(1); //# }); @@ -891,7 +1158,7 @@ void MainFrame::init_menubar() append_menu_item(helpMenu, wxID_ANY, _(L("Show &Configuration Folder")), _(L("Show user configuration folder (datadir)")), [this](wxCommandEvent&) { Slic3r::GUI::desktop_open_datadir_folder(); }); append_menu_item(helpMenu, wxID_ANY, _(L("Report an I&ssue")), wxString::Format(_(L("Report an issue on %s")), SLIC3R_APP_NAME), - [this](wxCommandEvent&) { wxLaunchDefaultBrowser("http://github.com/prusa3d/slic3r/issues/new"); }); + [this](wxCommandEvent&) { wxLaunchDefaultBrowser("https://github.com/prusa3d/slic3r/issues/new"); }); append_menu_item(helpMenu, wxID_ANY, wxString::Format(_(L("&About %s")), SLIC3R_APP_NAME), _(L("Show about dialog")), [this](wxCommandEvent&) { Slic3r::GUI::about(); }); helpMenu->AppendSeparator(); @@ -1036,10 +1303,10 @@ void MainFrame::quick_slice(const int qs) } // show processbar dialog - m_progress_dialog = new wxProgressDialog(_(L("Slicing")) + dots, - // TRN "Processing input_file_basename" - from_u8((boost::format(_utf8(L("Processing %s"))) % (input_file_basename + dots)).str()), - 100, this, 4); + m_progress_dialog = new wxProgressDialog(_L("Slicing") + dots, + // TRN "Processing input_file_basename" + from_u8((boost::format(_utf8(L("Processing %s"))) % (input_file_basename + dots)).str()), + 100, nullptr, wxPD_AUTO_HIDE); m_progress_dialog->Pulse(); { // my @warnings = (); @@ -1260,15 +1527,43 @@ void MainFrame::load_config(const DynamicPrintConfig& config) void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) { + bool tabpanel_was_hidden = false; +#if ENABLE_LAYOUT_NO_RESTART + if (m_layout == ESettingsLayout::Dlg) { +#else if (m_layout == slDlg) { +#endif // ENABLE_LAYOUT_NO_RESTART if (tab==0) { +#if ENABLE_LAYOUT_NO_RESTART + if (m_settings_dialog.IsShown()) + this->SetFocus(); +#else if (m_settings_dialog->IsShown()) this->SetFocus(); +#endif // ENABLE_LAYOUT_NO_RESTART // plater should be focused for correct navigation inside search window if (m_plater->canvas3D()->is_search_pressed()) m_plater->SetFocus(); return; } +#if ENABLE_LAYOUT_NO_RESTART + // Show/Activate Settings Dialog +#ifdef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList + if (m_settings_dialog.IsShown()) + m_settings_dialog.Hide(); + + m_tabpanel->Show(); + m_settings_dialog.Show(); +#else + if (m_settings_dialog.IsShown()) + m_settings_dialog.SetFocus(); + else { + tabpanel_was_hidden = true; + m_tabpanel->Show(); + m_settings_dialog.Show(); + } +#endif +#else // Show/Activate Settings Dialog if (m_settings_dialog->IsShown()) #ifdef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList @@ -1278,10 +1573,18 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) else #endif m_settings_dialog->Show(); +#endif // ENABLE_LAYOUT_NO_RESTART } +#if ENABLE_LAYOUT_NO_RESTART + else if (m_layout == ESettingsLayout::New) { + m_main_sizer->Show(m_plater, tab == 0); + tabpanel_was_hidden = !m_main_sizer->IsShown(m_tabpanel); + m_main_sizer->Show(m_tabpanel, tab != 0); +#else else if (m_layout == slNew) { m_plater->Show(tab == 0); m_tabpanel->Show(tab != 0); +#endif // ENABLE_LAYOUT_NO_RESTART // plater should be focused for correct navigation inside search window if (tab == 0 && m_plater->canvas3D()->is_search_pressed()) @@ -1289,8 +1592,20 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) Layout(); } - // when tab == -1, it means we should to show the last selected tab + // When we run application in ESettingsLayout::New or ESettingsLayout::Dlg mode, tabpanel is hidden from the very beginning + // and as a result Tab::update_changed_tree_ui() function couldn't update m_is_nonsys_values values, + // which are used for update TreeCtrl and "revert_buttons". + // So, force the call of this function for Tabs, if tab panel was hidden + if (tabpanel_was_hidden) + for (auto tab : wxGetApp().tabs_list) + tab->update_changed_tree_ui(); + + // when tab == -1, it means we should show the last selected tab +#if ENABLE_LAYOUT_NO_RESTART + m_tabpanel->SetSelection(tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == ESettingsLayout::Dlg && tab != 0) ? tab - 1 : tab); +#else m_tabpanel->SetSelection(tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == slDlg && tab != 0) ? tab-1 : tab); +#endif // ENABLE_LAYOUT_NO_RESTART } // Set a camera direction, zoom to all objects. @@ -1401,13 +1716,12 @@ std::string MainFrame::get_dir_name(const wxString &full_name) const // ---------------------------------------------------------------------------- SettingsDialog::SettingsDialog(MainFrame* mainframe) -: DPIDialog(nullptr, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _L("Settings")), +: DPIDialog(mainframe, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _L("Settings"), wxDefaultPosition, wxDefaultSize, + wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX, "settings_dialog"), m_main_frame(mainframe) { this->SetFont(wxGetApp().normal_font()); - - wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); - this->SetBackgroundColour(bgr_clr); + this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); // Load the icon either from the exe, or from the ico file. #if _WIN32 @@ -1420,9 +1734,10 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) SetIcon(wxIcon(var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); #endif // _WIN32 +#if !ENABLE_LAYOUT_NO_RESTART // wxNB_NOPAGETHEME: Disable Windows Vista theme for the Notebook background. The theme performance is terrible on Windows 10 // with multiple high resolution displays connected. - m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); + m_tabpanel = new wxNotebook(this, wxID_ANY, wxDefaultPosition, wxGetApp().get_min_size(), wxNB_TOP | wxTAB_TRAVERSAL | wxNB_NOPAGETHEME); #ifndef __WXOSX__ // Don't call SetFont under OSX to avoid name cutting in ObjectList m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font()); #endif @@ -1434,14 +1749,55 @@ SettingsDialog::SettingsDialog(MainFrame* mainframe) case '2': { m_main_frame->select_tab(1); break; } case '3': { m_main_frame->select_tab(2); break; } case '4': { m_main_frame->select_tab(3); break; } +#ifdef __APPLE__ + case 'f': +#else /* __APPLE__ */ + case WXK_CONTROL_F: +#endif /* __APPLE__ */ + case 'F': { m_main_frame->plater()->search(false); break; } default:break; } } }); +#endif // !ENABLE_LAYOUT_NO_RESTART + +#if ENABLE_LAYOUT_NO_RESTART + this->Bind(wxEVT_SHOW, [this](wxShowEvent& evt) { + + auto key_up_handker = [this](wxKeyEvent& evt) { + if ((evt.GetModifiers() & wxMOD_CONTROL) != 0) { + switch (evt.GetKeyCode()) { + case '1': { m_main_frame->select_tab(0); break; } + case '2': { m_main_frame->select_tab(1); break; } + case '3': { m_main_frame->select_tab(2); break; } + case '4': { m_main_frame->select_tab(3); break; } +#ifdef __APPLE__ + case 'f': +#else /* __APPLE__ */ + case WXK_CONTROL_F: +#endif /* __APPLE__ */ + case 'F': { m_main_frame->plater()->search(false); break; } + default:break; + } + } + }; + + if (evt.IsShown()) { + if (m_tabpanel != nullptr) + m_tabpanel->Bind(wxEVT_KEY_UP, key_up_handker); + } + else { + if (m_tabpanel != nullptr) + m_tabpanel->Unbind(wxEVT_KEY_UP, key_up_handker); + } + }); +#endif // ENABLE_LAYOUT_NO_RESTART // initialize layout auto sizer = new wxBoxSizer(wxVERTICAL); +#if !ENABLE_LAYOUT_NO_RESTART sizer->Add(m_tabpanel, 1, wxEXPAND); +#endif // !ENABLE_LAYOUT_NO_RESTART sizer->SetSizeHints(this); SetSizer(sizer); Fit(); @@ -1463,6 +1819,10 @@ void SettingsDialog::on_dpi_changed(const wxRect& suggested_rect) const int& em = em_unit(); const wxSize& size = wxSize(85 * em, 50 * em); + // update Tabs + for (auto tab : wxGetApp().tabs_list) + tab->msw_rescale(); + SetMinSize(size); Fit(); Refresh(); diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 219f6831947..4514b8f50f1 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -12,7 +12,6 @@ #include #include "GUI_Utils.hpp" -#include "Plater.hpp" #include "Event.hpp" class wxNotebook; @@ -27,6 +26,8 @@ namespace GUI class Tab; class PrintHostQueueDialog; +class Plater; +class MainFrame; enum QuickSlice { @@ -50,11 +51,15 @@ struct PresetTab { class SettingsDialog : public DPIDialog { wxNotebook* m_tabpanel { nullptr }; - MainFrame* m_main_frame {nullptr }; + MainFrame* m_main_frame { nullptr }; public: SettingsDialog(MainFrame* mainframe); ~SettingsDialog() {} +#if ENABLE_LAYOUT_NO_RESTART + void set_tabpanel(wxNotebook* tabpanel) { m_tabpanel = tabpanel; } +#else wxNotebook* get_tabpanel() { return m_tabpanel; } +#endif // ENABLE_LAYOUT_NO_RESTART protected: void on_dpi_changed(const wxRect& suggested_rect) override; @@ -71,6 +76,7 @@ class MainFrame : public DPIFrame wxMenuItem* m_menu_item_repeat { nullptr }; // doesn't used now #endif wxMenuItem* m_menu_item_reslice_now { nullptr }; + wxSizer* m_main_sizer{ nullptr }; PrintHostQueueDialog *m_printhost_queue_dlg; @@ -113,19 +119,36 @@ class MainFrame : public DPIFrame wxFileHistory m_recent_projects; +#if ENABLE_LAYOUT_NO_RESTART + enum class ESettingsLayout + { + Unknown, + Old, + New, + Dlg, + }; + + ESettingsLayout m_layout{ ESettingsLayout::Unknown }; +#else enum SettingsLayout { slOld = 0, slNew, slDlg, } m_layout; +#endif // ENABLE_LAYOUT_NO_RESTART protected: virtual void on_dpi_changed(const wxRect &suggested_rect); + virtual void on_sys_color_changed() override; public: MainFrame(); ~MainFrame() = default; +#if ENABLE_LAYOUT_NO_RESTART + void update_layout(); +#endif // ENABLE_LAYOUT_NO_RESTART + // Called when closing the application and when switching the application language. void shutdown(); @@ -167,7 +190,12 @@ class MainFrame : public DPIFrame Plater* m_plater { nullptr }; wxNotebook* m_tabpanel { nullptr }; +#if ENABLE_LAYOUT_NO_RESTART + SettingsDialog m_settings_dialog; + wxWindow* m_plater_page{ nullptr }; +#else SettingsDialog* m_settings_dialog { nullptr }; +#endif // ENABLE_LAYOUT_NO_RESTART wxProgressDialog* m_progress_dialog { nullptr }; std::shared_ptr m_statusbar; diff --git a/src/slic3r/GUI/MeshUtils.cpp b/src/slic3r/GUI/MeshUtils.cpp index 6c4f5e49d54..581f50a8824 100644 --- a/src/slic3r/GUI/MeshUtils.cpp +++ b/src/slic3r/GUI/MeshUtils.cpp @@ -27,7 +27,6 @@ void MeshClipper::set_mesh(const TriangleMesh& mesh) m_mesh = &mesh; m_triangles_valid = false; m_triangles2d.resize(0); - m_triangles3d.resize(0); m_tms.reset(nullptr); } } @@ -40,18 +39,18 @@ void MeshClipper::set_transformation(const Geometry::Transformation& trafo) m_trafo = trafo; m_triangles_valid = false; m_triangles2d.resize(0); - m_triangles3d.resize(0); } } -const std::vector& MeshClipper::get_triangles() +void MeshClipper::render_cut() { if (! m_triangles_valid) recalculate_triangles(); - return m_triangles3d; + if (m_vertex_array.has_VBOs()) + m_vertex_array.render(); } @@ -67,39 +66,43 @@ void MeshClipper::recalculate_triangles() const Vec3f& scaling = m_trafo.get_scaling_factor().cast(); // Calculate clipping plane normal in mesh coordinates. Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast(); - Vec3f up (up_noscale(0)*scaling(0), up_noscale(1)*scaling(1), up_noscale(2)*scaling(2)); + Vec3d up (up_noscale(0)*scaling(0), up_noscale(1)*scaling(1), up_noscale(2)*scaling(2)); // Calculate distance from mesh origin to the clipping plane (in mesh coordinates). 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); + m_tms->set_up_direction(up.cast()); m_tms->slice(std::vector{height_mesh}, SlicingMode::Regular, 0.f, &list_of_expolys, [](){}); m_triangles2d = triangulate_expolygons_2f(list_of_expolys[0], m_trafo.get_matrix().matrix().determinant() < 0.); // Rotate the cut into world coords: - Eigen::Quaternionf q; - q.setFromTwoVectors(Vec3f::UnitZ(), up); - Transform3f tr = Transform3f::Identity(); + Eigen::Quaterniond q; + q.setFromTwoVectors(Vec3d::UnitZ(), up); + Transform3d tr = Transform3d::Identity(); tr.rotate(q); - tr = m_trafo.get_matrix().cast() * tr; - - m_triangles3d.clear(); - m_triangles3d.reserve(m_triangles2d.size()); - for (const Vec2f& pt : m_triangles2d) { - m_triangles3d.push_back(Vec3f(pt(0), pt(1), height_mesh+0.001f)); - m_triangles3d.back() = tr * m_triangles3d.back(); - } + tr = m_trafo.get_matrix() * tr; + + // to avoid z-fighting + height_mesh += 0.001f; + + m_vertex_array.release_geometry(); + for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) { + m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up); + m_vertex_array.push_geometry(tr * Vec3d((*(it+1))(0), (*(it+1))(1), height_mesh), up); + m_vertex_array.push_geometry(tr * Vec3d((*(it+2))(0), (*(it+2))(1), height_mesh), up); + size_t idx = it - m_triangles2d.cbegin(); + m_vertex_array.push_triangle(idx, idx+1, idx+2); + } + m_vertex_array.finalize_geometry(true); m_triangles_valid = true; } -Vec3f MeshRaycaster::get_triangle_normal(const indexed_triangle_set& its, size_t facet_idx) +Vec3f MeshRaycaster::get_triangle_normal(size_t facet_idx) const { - Vec3f a(its.vertices[its.indices[facet_idx](1)] - its.vertices[its.indices[facet_idx](0)]); - Vec3f b(its.vertices[its.indices[facet_idx](2)] - its.vertices[its.indices[facet_idx](0)]); - return Vec3f(a.cross(b)).normalized(); + return m_normals[facet_idx]; } void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, @@ -218,12 +221,9 @@ Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const int idx = 0; Vec3d closest_point; m_emesh.squared_distance(point.cast(), idx, closest_point); - if (normal) { - auto indices = m_emesh.F().row(idx); - Vec3d a(m_emesh.V().row(indices(1)) - m_emesh.V().row(indices(0))); - Vec3d b(m_emesh.V().row(indices(2)) - m_emesh.V().row(indices(0))); - *normal = Vec3f(a.cross(b).cast()); - } + if (normal) + *normal = m_normals[idx]; + return closest_point.cast(); } diff --git a/src/slic3r/GUI/MeshUtils.hpp b/src/slic3r/GUI/MeshUtils.hpp index 92f444f55ba..2758577a256 100644 --- a/src/slic3r/GUI/MeshUtils.hpp +++ b/src/slic3r/GUI/MeshUtils.hpp @@ -6,7 +6,7 @@ #include "libslic3r/SLA/EigenMesh3D.hpp" #include "admesh/stl.h" - +#include "slic3r/GUI/3DScene.hpp" #include @@ -82,9 +82,9 @@ class MeshClipper { // into world coordinates. void set_transformation(const Geometry::Transformation& trafo); - // Return the triangulated cut. The points are returned directly - // in world coordinates. - const std::vector& get_triangles(); + // Render the triangulated cut. Transformation matrices should + // be set in world coords. + void render_cut(); private: void recalculate_triangles(); @@ -93,7 +93,7 @@ class MeshClipper { const TriangleMesh* m_mesh = nullptr; ClippingPlane m_plane; std::vector m_triangles2d; - std::vector m_triangles3d; + GLIndexedVertexArray m_vertex_array; bool m_triangles_valid = false; std::unique_ptr m_tms; }; @@ -104,11 +104,15 @@ class MeshClipper { // whether certain points are visible or obscured by the mesh etc. class MeshRaycaster { public: - // The class makes a copy of the mesh as EigenMesh3D. - // The pointer can be invalidated after constructor returns. + // The class references extern TriangleMesh, which must stay alive + // during MeshRaycaster existence. MeshRaycaster(const TriangleMesh& mesh) : m_emesh(mesh) - {} + { + m_normals.reserve(mesh.stl.facet_start.size()); + for (const stl_facet& facet : mesh.stl.facet_start) + m_normals.push_back(facet.normal); + } void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera, Vec3d& point, Vec3d& direction) const; @@ -140,10 +144,11 @@ class MeshRaycaster { Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const; - static Vec3f get_triangle_normal(const indexed_triangle_set& its, size_t facet_idx); + Vec3f get_triangle_normal(size_t facet_idx) const; private: sla::EigenMesh3D m_emesh; + std::vector m_normals; }; diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp index 8f80f6847c9..baa9356b695 100644 --- a/src/slic3r/GUI/Mouse3DController.cpp +++ b/src/slic3r/GUI/Mouse3DController.cpp @@ -6,6 +6,7 @@ #include "PresetBundle.hpp" #include "AppConfig.hpp" #include "GLCanvas3D.hpp" +#include "Plater.hpp" #include diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index d9b9af016f9..336475d2e02 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -138,7 +138,7 @@ void ObjectDataViewModelNode::update_settings_digest_bitmaps() std::map& categories_icon = Slic3r::GUI::wxGetApp().obj_list()->CATEGORY_ICON; std::string scaled_bitmap_name = m_name.ToUTF8().data(); - scaled_bitmap_name += "-em" + std::to_string(Slic3r::GUI::wxGetApp().em_unit()); + scaled_bitmap_name += "-em" + std::to_string(wxGetApp().em_unit()) + (wxGetApp().dark_mode() ? "-dm" : ""); wxBitmap *bmp = m_bitmap_cache->find(scaled_bitmap_name); if (bmp == nullptr) { diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 5b2b5703b74..819c214a85c 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -1,5 +1,7 @@ #include "OptionsGroup.hpp" #include "ConfigExceptions.hpp" +#include "Plater.hpp" +#include "GUI_App.hpp" #include #include @@ -100,6 +102,36 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co return field; } +OptionsGroup::OptionsGroup( wxWindow* _parent, const wxString& title, + bool is_tab_opt /* = false */, + column_t extra_clmn /* = nullptr */) : + m_parent(_parent), title(title), + m_show_modified_btns(is_tab_opt), + staticbox(title!=""), extra_column(extra_clmn) +{ + if (staticbox) { + stb = new wxStaticBox(_parent, wxID_ANY, _(title)); + if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT); + stb->SetFont(wxOSX ? wxGetApp().normal_font() : wxGetApp().bold_font()); + } else + stb = nullptr; + sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); + auto num_columns = 1U; + if (label_width != 0) num_columns++; + if (extra_column != nullptr) num_columns++; + m_grid_sizer = new wxFlexGridSizer(0, num_columns, 1,0); + static_cast(m_grid_sizer)->SetFlexibleDirection(wxBOTH/*wxHORIZONTAL*/); + static_cast(m_grid_sizer)->AddGrowableCol(label_width == 0 ? 0 : !extra_column ? 1 : 2 ); +#if 0//#ifdef __WXGTK__ + m_panel = new wxPanel( _parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); + sizer->Fit(m_panel); + sizer->Add(m_panel, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5); +#else + sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5); +#endif /* __WXGTK__ */ + +} + void OptionsGroup::add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& field) { if (!m_show_modified_btns) { @@ -568,6 +600,18 @@ void ConfigOptionsGroup::msw_rescale() } } +void ConfigOptionsGroup::sys_color_changed() +{ + // update bitmaps for near label widgets (like "Set uniform scale" button on settings panel) + if (rescale_near_label_widget) + for (auto near_label_widget : m_near_label_widget_ptrs) + rescale_near_label_widget(near_label_widget); + + // update undo buttons : rescale bitmaps + for (const auto& field : m_fields) + field.second->sys_color_changed(); +} + boost::any ConfigOptionsGroup::config_value(const std::string& opt_key, int opt_index, bool deserialize) { if (deserialize) { diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index 09df9ea6530..2e6f9aa0f47 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -11,7 +11,6 @@ #include "libslic3r/PrintConfig.hpp" #include "Field.hpp" -#include "GUI_App.hpp" #include "I18N.hpp" // Translate the ifdef @@ -171,32 +170,7 @@ class OptionsGroup { } OptionsGroup( wxWindow* _parent, const wxString& title, bool is_tab_opt = false, - column_t extra_clmn = nullptr) : - m_parent(_parent), title(title), - m_show_modified_btns(is_tab_opt), - staticbox(title!=""), extra_column(extra_clmn) { - if (staticbox) { - stb = new wxStaticBox(_parent, wxID_ANY, _(title)); - if (!wxOSX) stb->SetBackgroundStyle(wxBG_STYLE_PAINT); - stb->SetFont(wxOSX ? wxGetApp().normal_font() : wxGetApp().bold_font()); - } else - stb = nullptr; - sizer = (staticbox ? new wxStaticBoxSizer(stb, wxVERTICAL) : new wxBoxSizer(wxVERTICAL)); - auto num_columns = 1U; - if (label_width != 0) num_columns++; - if (extra_column != nullptr) num_columns++; - m_grid_sizer = new wxFlexGridSizer(0, num_columns, 1,0); - static_cast(m_grid_sizer)->SetFlexibleDirection(wxBOTH/*wxHORIZONTAL*/); - static_cast(m_grid_sizer)->AddGrowableCol(label_width == 0 ? 0 : !extra_column ? 1 : 2 ); -#if 0//#ifdef __WXGTK__ - m_panel = new wxPanel( _parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); - sizer->Fit(m_panel); - sizer->Add(m_panel, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5); -#else - sizer->Add(m_grid_sizer, 0, wxEXPAND | wxALL, wxOSX||!staticbox ? 0: 5); -#endif /* __WXGTK__ */ - - } + column_t extra_clmn = nullptr); wxGridSizer* get_grid_sizer() { return m_grid_sizer; } @@ -280,6 +254,7 @@ class ConfigOptionsGroup: public OptionsGroup { void Show(const bool show); bool update_visibility(ConfigOptionMode mode); void msw_rescale(); + void sys_color_changed(); boost::any config_value(const std::string& opt_key, int opt_index, bool deserialize); // return option value from config boost::any get_config_value(const DynamicPrintConfig& config, const std::string& opt_key, int opt_index = -1); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ca91967e128..761f574e152 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -771,7 +771,9 @@ Sidebar::Sidebar(Plater *parent) p->scrolled->SetScrollbars(0, 100, 1, 2); SetFont(wxGetApp().normal_font()); +#ifndef __APPLE__ SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); +#endif // Sizer in the scrolled area auto *scrolled_sizer = new wxBoxSizer(wxVERTICAL); @@ -927,7 +929,7 @@ Sidebar::Sidebar(Plater *parent) { const bool export_gcode_after_slicing = wxGetKeyState(WXK_SHIFT); if (export_gcode_after_slicing) - p->plater->export_gcode(); + p->plater->export_gcode(true); else p->plater->reslice(); p->plater->select_view_3D("Preview"); @@ -1090,6 +1092,34 @@ void Sidebar::msw_rescale() p->scrolled->Layout(); } +void Sidebar::sys_color_changed() +{ + // Update preset comboboxes in respect to the system color ... + // combo->msw_rescale() updates icon on button, so use it + for (PresetComboBox* combo : std::vector{ p->combo_print, + p->combo_sla_print, + p->combo_sla_material, + p->combo_printer }) + combo->msw_rescale(); + for (PresetComboBox* combo : p->combos_filament) + combo->msw_rescale(); + + // ... then refill them and set min size to correct layout of the sidebar + update_all_preset_comboboxes(); + + p->object_list->sys_color_changed(); + p->object_manipulation->sys_color_changed(); +// p->object_settings->msw_rescale(); + p->object_layers->sys_color_changed(); + + // btn...->msw_rescale() updates icon on button, so use it + p->btn_send_gcode->msw_rescale(); + p->btn_remove_device->msw_rescale(); + p->btn_export_gcode_removable->msw_rescale(); + + p->scrolled->Layout(); +} + void Sidebar::search() { p->searcher.search(); @@ -1168,12 +1198,15 @@ void Sidebar::show_info_sizer() return; } + bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + double koef = imperial_units ? ObjectManipulation::mm_to_in : 1.0f; + auto size = model_object->bounding_box().size(); - p->object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f",size(0), size(1), size(2))); + p->object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f",size(0)*koef, size(1)*koef, size(2)*koef)); p->object_info->info_materials->SetLabel(wxString::Format("%d", static_cast(model_object->materials_count()))); const auto& stats = model_object->get_object_stl_stats();//model_object->volumes.front()->mesh.stl.stats; - p->object_info->info_volume->SetLabel(wxString::Format("%.2f", stats.volume)); + p->object_info->info_volume->SetLabel(wxString::Format("%.2f", stats.volume*pow(koef,3))); p->object_info->info_facets->SetLabel(wxString::Format(_L("%d (%d shells)"), static_cast(model_object->facets_count()), stats.number_of_parts)); int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed + @@ -1251,18 +1284,24 @@ void Sidebar::update_sliced_info_sizer() const PrintStatistics& ps = p->plater->fff_print().print_statistics(); const bool is_wipe_tower = ps.total_wipe_tower_filament > 0; - wxString new_label = _L("Used Filament (m)"); + bool imperial_units = wxGetApp().app_config->get("use_inches") == "1"; + double koef = imperial_units ? ObjectManipulation::in_to_mm : 1000.0; + + wxString new_label = imperial_units ? _L("Used Filament (in)") : _L("Used Filament (m)"); if (is_wipe_tower) 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, - (ps.total_used_filament - ps.total_wipe_tower_filament) / 1000, - ps.total_wipe_tower_filament / 1000) : - wxString::Format("%.2f", ps.total_used_filament / 1000); + 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); p->sliced_info->SetTextAndShow(siFilament_m, info_text, new_label); - p->sliced_info->SetTextAndShow(siFilament_mm3, wxString::Format("%.2f", ps.total_extruded_volume)); + koef = imperial_units ? pow(ObjectManipulation::mm_to_in, 3) : 1.0f; + new_label = imperial_units ? _L("Used Filament (in³)") : _L("Used Filament (mm³)"); + info_text = wxString::Format("%.2f", imperial_units ? ps.total_extruded_volume * koef : ps.total_extruded_volume); + p->sliced_info->SetTextAndShow(siFilament_mm3, info_text, new_label); p->sliced_info->SetTextAndShow(siFilament_g, ps.total_weight == 0.0 ? "N/A" : wxString::Format("%.2f", ps.total_weight)); new_label = _L("Cost"); @@ -1285,22 +1324,22 @@ void Sidebar::update_sliced_info_sizer() wxString str_color = _L("Color"); wxString str_pause = _L("Pause"); - auto fill_labels = [str_color, str_pause](const std::vector>& times, + auto fill_labels = [str_color, str_pause](const std::vector>& times, wxString& new_label, wxString& info_text) { int color_change_count = 0; for (auto time : times) - if (time.first == cgtColorChange) + if (time.first == CustomGCode::ColorChange) color_change_count++; for (int i = (int)times.size() - 1; i >= 0; --i) { - if (i == 0 || times[i - 1].first == cgtPausePrint) + if (i == 0 || times[i - 1].first == CustomGCode::PausePrint) new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count); - else if (times[i - 1].first == cgtColorChange) + else if (times[i - 1].first == CustomGCode::ColorChange) new_label += format_wxstr("\n - %1%%2%", str_color + " ", color_change_count--); - if (i != (int)times.size() - 1 && times[i].first == cgtPausePrint) + if (i != (int)times.size() - 1 && times[i].first == CustomGCode::PausePrint) new_label += format_wxstr(" -> %1%", str_pause); info_text += format_wxstr("\n%1%", times[i].second); @@ -1412,6 +1451,13 @@ void Sidebar::collapse(bool collapse) } +void Sidebar::update_ui_from_settings() +{ + p->object_manipulation->update_ui_from_settings(); + show_info_sizer(); + update_sliced_info_sizer(); +} + std::vector& Sidebar::combos_filament() { return p->combos_filament; @@ -1537,9 +1583,13 @@ struct Plater::priv Sidebar *sidebar; Bed3D bed; Camera camera; +#if ENABLE_ENVIRONMENT_MAP + GLTexture environment_texture; +#endif // ENABLE_ENVIRONMENT_MAP Mouse3DController mouse3d_controller; View3D* view3D; GLToolbar view_toolbar; + GLToolbar collapse_toolbar; Preview *preview; BackgroundSlicingProcess background_process; @@ -1634,6 +1684,7 @@ struct Plater::priv void reset_canvas_volumes(); bool init_view_toolbar(); + bool init_collapse_toolbar(); void reset_all_gizmos(); void update_ui_from_settings(); @@ -1643,7 +1694,7 @@ struct Plater::priv BoundingBoxf bed_shape_bb() const; BoundingBox scaled_bed_shape_bb() const; - std::vector load_files(const std::vector& input_files, bool load_model, bool load_config); + std::vector load_files(const std::vector& input_files, bool load_model, bool load_config, bool used_inches = false); std::vector load_model_objects(const ModelObjectPtrs &model_objects); wxString get_export_file(GUI::FileType file_type); @@ -1829,6 +1880,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) , m_ui_jobs(this) , delayed_scene_refresh(false) , view_toolbar(GLToolbar::Radio, "View") + , collapse_toolbar(GLToolbar::Normal, "Collapse") , m_project_filename(wxEmptyString) { this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font()); @@ -2014,27 +2066,27 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) this->q->Bind(EVT_INSTANCE_GO_TO_FRONT, [this](InstanceGoToFrontEvent &) { BOOST_LOG_TRIVIAL(debug) << "prusaslicer window going forward"; //this code maximize app window on Fedora - wxGetApp().mainframe->Iconize(false); - if (wxGetApp().mainframe->IsMaximized()) - wxGetApp().mainframe->Maximize(true); - else - wxGetApp().mainframe->Maximize(false); - //this code (without code above) maximize window on Ubuntu - wxGetApp().mainframe->Restore(); - wxGetApp().GetTopWindow()->SetFocus(); // focus on my window - wxGetApp().GetTopWindow()->Raise(); // bring window to front - wxGetApp().GetTopWindow()->Show(true); // show the window + { + wxGetApp().mainframe->Iconize(false); + if (wxGetApp().mainframe->IsMaximized()) + wxGetApp().mainframe->Maximize(true); + else + wxGetApp().mainframe->Maximize(false); + } + //this code maximize window on Ubuntu + { + wxGetApp().mainframe->Restore(); + wxGetApp().GetTopWindow()->SetFocus(); // focus on my window + wxGetApp().GetTopWindow()->Raise(); // bring window to front + wxGetApp().GetTopWindow()->Show(true); // show the window + } }); wxGetApp().other_instance_message_handler()->init(this->q); - // collapse sidebar according to saved value bool is_collapsed = wxGetApp().app_config->get("collapsed_sidebar") == "1"; sidebar->collapse(is_collapsed); - // Update an enable of the collapse_toolbar: if sidebar is collapsed, then collapse_toolbar should be visible - if (is_collapsed) - wxGetApp().app_config->set("show_collapse_button", "1"); } Plater::priv::~priv() @@ -2107,6 +2159,8 @@ void Plater::priv::update_ui_from_settings() view3D->get_canvas3d()->update_ui_from_settings(); preview->get_canvas3d()->update_ui_from_settings(); + + sidebar->update_ui_from_settings(); } // Called after the print technology was changed. @@ -2139,7 +2193,7 @@ BoundingBox Plater::priv::scaled_bed_shape_bb() const return bed_shape.bounding_box(); } -std::vector Plater::priv::load_files(const std::vector& input_files, bool load_model, bool load_config) +std::vector Plater::priv::load_files(const std::vector& input_files, bool load_model, bool load_config, bool imperial_units/* = false*/) { if (input_files.empty()) { return std::vector(); } @@ -2156,17 +2210,17 @@ std::vector Plater::priv::load_files(const std::vector& input_ } const auto loading = _L("Loading") + dots; - wxProgressDialog dlg(loading, loading); + wxProgressDialog dlg(loading, "", 100, q, wxPD_AUTO_HIDE); dlg.Pulse(); auto *new_model = (!load_model || one_by_one) ? nullptr : new Slic3r::Model(); std::vector obj_idxs; - for (size_t i = 0; i < input_files.size(); i++) { + for (size_t i = 0; i < input_files.size(); ++i) { const auto &path = input_files[i]; const auto filename = path.filename(); - const auto dlg_info = format_wxstr(_L("Processing input file %s"), from_path(filename)) + "\n"; - dlg.Update(100 * i / input_files.size(), dlg_info); + const auto dlg_info = _L("Loading file") + ": " + from_path(filename); + dlg.Update(static_cast(100.0f * static_cast(i) / static_cast(input_files.size())), dlg_info); const bool type_3mf = std::regex_match(path.string(), pattern_3mf); const bool type_zip_amf = !type_3mf && std::regex_match(path.string(), pattern_zip_amf); @@ -2236,6 +2290,23 @@ std::vector Plater::priv::load_files(const std::vector& input_ { // The model should now be initialized + auto convert_from_imperial_units = [](Model& model) { + model.convert_from_imperial_units(); + wxGetApp().app_config->set("use_inches", "1"); + wxGetApp().sidebar().update_ui_from_settings(); + }; + + if (imperial_units) + convert_from_imperial_units(model); + else if (model.looks_like_imperial_units()) { + wxMessageDialog msg_dlg(q, format_wxstr(_L( + "Some object(s) in file %s looks like saved in inches.\n" + "Should I consider them as a saved in inches and convert them?"), from_path(filename)) + "\n", + _L("Saved in inches object detected"), wxICON_WARNING | wxYES | wxNO); + if (msg_dlg.ShowModal() == wxID_YES) + convert_from_imperial_units(model); + } + if (! is_project_file) { if (model.looks_like_multipart_object()) { wxMessageDialog msg_dlg(q, _L( @@ -2557,7 +2628,7 @@ void Plater::priv::object_list_changed() { const bool export_in_progress = this->background_process.is_export_scheduled(); // || ! send_gcode_file.empty()); // XXX: is this right? - const bool model_fits = view3D->check_volumes_outside_state() == ModelInstance::PVS_Inside; + const bool model_fits = view3D->check_volumes_outside_state() == ModelInstancePVS_Inside; sidebar->enable_buttons(!model.objects.empty() && !export_in_progress && model_fits); } @@ -3249,7 +3320,7 @@ void Plater::priv::set_current_panel(wxPanel* panel) // see: Plater::priv::object_list_changed() // FIXME: it may be better to have a single function making this check and let it be called wherever needed bool export_in_progress = this->background_process.is_export_scheduled(); - bool model_fits = view3D->check_volumes_outside_state() != ModelInstance::PVS_Partly_Outside; + bool model_fits = view3D->check_volumes_outside_state() != ModelInstancePVS_Partly_Outside; if (!model.objects.empty() && !export_in_progress && model_fits) this->q->reslice(); // keeps current gcode preview, if any @@ -3342,6 +3413,27 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt) // Update the SLA preview. Only called if not RELOAD_SLA_SUPPORT_POINTS, as the block above will refresh the preview anyways. this->preview->reload_print(); } + + if (evt.status.flags & (PrintBase::SlicingStatus::UPDATE_PRINT_STEP_WARNINGS | PrintBase::SlicingStatus::UPDATE_PRINT_OBJECT_STEP_WARNINGS)) { + // Update notification center with warnings of object_id and its warning_step. + ObjectID object_id = evt.status.warning_object_id; + int warning_step = evt.status.warning_step; + PrintStateBase::StateWithWarnings state; + if (evt.status.flags & PrintBase::SlicingStatus::UPDATE_PRINT_STEP_WARNINGS) { + state = this->printer_technology == ptFFF ? + this->fff_print.step_state_with_warnings(static_cast(warning_step)) : + this->sla_print.step_state_with_warnings(static_cast(warning_step)); + } else if (this->printer_technology == ptFFF) { + const PrintObject *print_object = this->fff_print.get_object(object_id); + if (print_object) + state = print_object->step_state_with_warnings(static_cast(warning_step)); + } else { + const SLAPrintObject *print_object = this->sla_print.get_object(object_id); + if (print_object) + state = print_object->step_state_with_warnings(static_cast(warning_step)); + } + // Now process state.warnings. + } } void Plater::priv::on_slicing_completed(wxCommandEvent &) @@ -3358,7 +3450,7 @@ void Plater::priv::on_slicing_completed(wxCommandEvent &) break; default: break; } -} +} void Plater::priv::on_process_completed(wxCommandEvent &evt) { @@ -3418,7 +3510,10 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) show_action_buttons(true); } else if (this->writing_to_removable_device || wxGetApp().get_mode() == comSimple) + { + wxGetApp().removable_drive_manager()->set_exporting_finished(true); show_action_buttons(false); + } this->writing_to_removable_device = false; } @@ -3685,6 +3780,9 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ menu->AppendSeparator(); + // "Scale to print volume" makes a sense just for whole object + sidebar->obj_list()->append_menu_item_scale_selection_to_fit_print_volume(menu); + q->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { const Selection& selection = get_selection(); int instance_idx = selection.get_instance_idx(); @@ -3697,10 +3795,9 @@ bool Plater::priv::init_common_menu(wxMenu* menu, const bool is_part/* = false*/ }, menu_item_printable->GetId()); } + sidebar->obj_list()->append_menu_items_convert_unit(menu); sidebar->obj_list()->append_menu_item_fix_through_netfabb(menu); - sidebar->obj_list()->append_menu_item_scale_selection_to_fit_print_volume(menu); - wxMenu* mirror_menu = new wxMenu(); if (mirror_menu == nullptr) return false; @@ -3849,6 +3946,51 @@ bool Plater::priv::init_view_toolbar() return true; } +bool Plater::priv::init_collapse_toolbar() +{ + if (collapse_toolbar.get_items_count() > 0) + // already initialized + return true; + + BackgroundTexture::Metadata background_data; + background_data.filename = "toolbar_background.png"; + background_data.left = 16; + background_data.top = 16; + background_data.right = 16; + background_data.bottom = 16; + + if (!collapse_toolbar.init(background_data)) + return false; + + collapse_toolbar.set_layout_type(GLToolbar::Layout::Vertical); + collapse_toolbar.set_horizontal_orientation(GLToolbar::Layout::HO_Right); + collapse_toolbar.set_vertical_orientation(GLToolbar::Layout::VO_Top); + collapse_toolbar.set_border(5.0f); + collapse_toolbar.set_separator_size(5); + collapse_toolbar.set_gap_size(2); + + GLToolbarItem::Data item; + + item.name = "collapse_sidebar"; + item.icon_filename = "collapse.svg"; + item.tooltip = wxGetApp().plater()->is_sidebar_collapsed() ? _utf8(L("Expand right panel")) : _utf8(L("Collapse right panel")); + item.sprite_id = 0; + item.left.action_callback = [this, item]() { + std::string new_tooltip = wxGetApp().plater()->is_sidebar_collapsed() ? + _utf8(L("Collapse right panel")) : _utf8(L("Expand right panel")); + + int id = collapse_toolbar.get_item_id("collapse_sidebar"); + collapse_toolbar.set_tooltip(id, new_tooltip); + + wxGetApp().plater()->collapse_sidebar(!wxGetApp().plater()->is_sidebar_collapsed()); + }; + + if (!collapse_toolbar.add_item(item)) + return false; + + return true; +} + bool Plater::priv::can_set_instance_to_object() const { const int obj_idx = get_selected_object_idx(); @@ -4274,7 +4416,7 @@ void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& lab // Plater / Public Plater::Plater(wxWindow *parent, MainFrame *main_frame) - : wxPanel(parent) + : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxGetApp().get_min_size()) , p(new priv(this, main_frame)) { // Initialization performed in the private c-tor @@ -4326,7 +4468,7 @@ void Plater::load_project(const wxString& filename) p->set_project_filename(filename); } -void Plater::add_model() +void Plater::add_model(bool imperial_units/* = false*/) { wxArrayString input_files; wxGetApp().import_model(this, input_files); @@ -4354,7 +4496,7 @@ void Plater::add_model() } Plater::TakeSnapshot snapshot(this, snapshot_label); - load_files(paths, true, false); + load_files(paths, true, false, imperial_units); } void Plater::import_sl1_archive() @@ -4375,16 +4517,16 @@ void Plater::extract_config_from_project() load_files(input_paths, false, true); } -std::vector Plater::load_files(const std::vector& input_files, bool load_model, bool load_config) { return p->load_files(input_files, load_model, load_config); } +std::vector Plater::load_files(const std::vector& input_files, bool load_model, bool load_config, bool imperial_units /*= false*/) { return p->load_files(input_files, load_model, load_config, imperial_units); } // To be called when providing a list of files to the GUI slic3r on command line. -std::vector Plater::load_files(const std::vector& input_files, bool load_model, bool load_config) +std::vector Plater::load_files(const std::vector& input_files, bool load_model, bool load_config, bool imperial_units /*= false*/) { std::vector paths; paths.reserve(input_files.size()); for (const std::string& path : input_files) paths.emplace_back(path); - return p->load_files(paths, load_model, load_config); + return p->load_files(paths, load_model, load_config, imperial_units); } void Plater::update() { p->update(); } @@ -4526,6 +4668,37 @@ void Plater::scale_selection_to_fit_print_volume() p->scale_selection_to_fit_print_volume(); } +void Plater::convert_unit(bool from_imperial_unit) +{ + std::vector obj_idxs, volume_idxs; + wxGetApp().obj_list()->get_selection_indexes(obj_idxs, volume_idxs); + if (obj_idxs.empty() && volume_idxs.empty()) + return; + + TakeSnapshot snapshot(this, from_imperial_unit ? _L("Convert from imperial units") : _L("Convert to imperial units")); + wxBusyCursor wait; + + ModelObjectPtrs objects; + for (int obj_idx : obj_idxs) { + ModelObject *object = p->model.objects[obj_idx]; + object->convert_units(objects, from_imperial_unit, volume_idxs); + remove(obj_idx); + } + p->load_model_objects(objects); + + Selection& selection = p->view3D->get_canvas3d()->get_selection(); + size_t last_obj_idx = p->model.objects.size() - 1; + + if (volume_idxs.empty()) { + for (size_t i = 0; i < objects.size(); ++i) + selection.add_object((unsigned int)(last_obj_idx - i), i == 0); + } + else { + for (int vol_idx : volume_idxs) + selection.add_volume(last_obj_idx, vol_idx, 0, false); + } +} + void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower) { wxCHECK_RET(obj_idx < p->model.objects.size(), "obj_idx out of bounds"); @@ -4695,25 +4868,33 @@ void Plater::export_stl(bool extended, bool selection_only) ? Transform3d::Identity() : object->model_object()->instances[instance_idx]->get_transformation().get_matrix(); + TriangleMesh inst_mesh; + if (has_pad_mesh) { TriangleMesh inst_pad_mesh = pad_mesh; inst_pad_mesh.transform(inst_transform, is_left_handed); - mesh.merge(inst_pad_mesh); + inst_mesh.merge(inst_pad_mesh); } if (has_supports_mesh) { TriangleMesh inst_supports_mesh = supports_mesh; inst_supports_mesh.transform(inst_transform, is_left_handed); - mesh.merge(inst_supports_mesh); + inst_mesh.merge(inst_supports_mesh); } TriangleMesh inst_object_mesh = object->get_mesh_to_print(); inst_object_mesh.transform(mesh_trafo_inv); inst_object_mesh.transform(inst_transform, is_left_handed); - mesh.merge(inst_object_mesh); + inst_mesh.merge(inst_object_mesh); + + // ensure that the instance lays on the bed + inst_mesh.translate(0.0f, 0.0f, -inst_mesh.bounding_box().min[2]); + + // merge instance with global mesh + mesh.merge(inst_mesh); if (one_inst_only) break; @@ -5184,7 +5365,7 @@ std::vector Plater::get_colors_for_color_print() const colors.reserve(colors.size() + p->model.custom_gcode_per_print_z.gcodes.size()); for (const CustomGCode::Item& code : p->model.custom_gcode_per_print_z.gcodes) - if (code.gcode == ColorChangeCode) + if (code.type == CustomGCode::ColorChange) colors.emplace_back(code.color); return colors; @@ -5352,7 +5533,10 @@ void Plater::show_action_buttons(const bool ready_to_slice) const { p->show_acti void Plater::copy_selection_to_clipboard() { - if (can_copy_to_clipboard()) + // At first try to copy selected values to the ObjectList's clipboard + // to check if Settings or Layers are selected in the list + // and then copy to 3DCanvas's clipboard if not + if (can_copy_to_clipboard() && !p->sidebar->obj_list()->copy_to_clipboard()) p->view3D->get_canvas3d()->get_selection().copy_to_clipboard(); } @@ -5362,7 +5546,12 @@ void Plater::paste_from_clipboard() return; Plater::TakeSnapshot snapshot(this, _L("Paste From Clipboard")); - p->view3D->get_canvas3d()->get_selection().paste_from_clipboard(); + + // At first try to paste values from the ObjectList's clipboard + // to check if Settings or Layers were copied + // and then paste from the 3DCanvas's clipboard if not + if (!p->sidebar->obj_list()->paste_from_clipboard()) + p->view3D->get_canvas3d()->get_selection().paste_from_clipboard(); } void Plater::search(bool plater_is_active) @@ -5403,11 +5592,27 @@ void Plater::msw_rescale() GetParent()->Layout(); } +void Plater::sys_color_changed() +{ + p->sidebar->sys_color_changed(); + + // msw_rescale_menu updates just icons, so use it + p->msw_rescale_object_menu(); + + Layout(); + GetParent()->Layout(); +} + bool Plater::init_view_toolbar() { return p->init_view_toolbar(); } +bool Plater::init_collapse_toolbar() +{ + return p->init_collapse_toolbar(); +} + const Camera& Plater::get_camera() const { return p->camera; @@ -5418,6 +5623,19 @@ Camera& Plater::get_camera() return p->camera; } +#if ENABLE_ENVIRONMENT_MAP +void Plater::init_environment_texture() +{ + if (p->environment_texture.get_id() == 0) + p->environment_texture.load_from_file(resources_dir() + "/icons/Pmetal_001.png", false, GLTexture::SingleThreaded, false); +} + +unsigned int Plater::get_environment_texture_id() const +{ + return p->environment_texture.get_id(); +} +#endif // ENABLE_ENVIRONMENT_MAP + const Bed3D& Plater::get_bed() const { return p->bed; @@ -5438,6 +5656,16 @@ GLToolbar& Plater::get_view_toolbar() return p->view_toolbar; } +const GLToolbar& Plater::get_collapse_toolbar() const +{ + return p->collapse_toolbar; +} + +GLToolbar& Plater::get_collapse_toolbar() +{ + return p->collapse_toolbar; +} + const Mouse3DController& Plater::get_mouse3d_controller() const { return p->mouse3d_controller; @@ -5463,7 +5691,7 @@ bool Plater::can_paste_from_clipboard() const const Selection& selection = p->view3D->get_canvas3d()->get_selection(); const Selection::Clipboard& clipboard = selection.get_clipboard(); - if (clipboard.is_empty()) + if (clipboard.is_empty() && p->sidebar->obj_list()->clipboard_is_empty()) return false; if ((wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA) && !clipboard.is_sla_compliant()) diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 74d4f798ba8..a08b19fa354 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -25,10 +25,13 @@ namespace Slic3r { class Model; class ModelObject; +class ModelInstance; class Print; class SLAPrint; enum SLAPrintObjectStep : unsigned int; +using ModelInstancePtrs = std::vector; + namespace UndoRedo { class Stack; struct Snapshot; @@ -105,6 +108,7 @@ class Sidebar : public wxPanel void update_mode_sizer() const; void update_reslice_btn_tooltip() const; void msw_rescale(); + void sys_color_changed(); void search(); void jump_to_option(size_t selected); @@ -133,6 +137,7 @@ class Sidebar : public wxPanel bool is_collapsed(); void collapse(bool collapse); void update_searcher(); + void update_ui_from_settings(); std::vector& combos_filament(); Search::OptionsSearcher& get_searcher(); @@ -165,13 +170,13 @@ class Plater: public wxPanel void new_project(); void load_project(); void load_project(const wxString& filename); - void add_model(); + void add_model(bool imperial_units = false); void import_sl1_archive(); void extract_config_from_project(); - std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true); + std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true, bool imperial_units = false); // To be called when providing a list of files to the GUI slic3r on command line. - std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true); + std::vector load_files(const std::vector& input_files, bool load_model = true, bool load_config = true, bool imperial_units = false); void update(); void stop_jobs(); @@ -211,10 +216,11 @@ class Plater: public wxPanel void set_number_of_copies(/*size_t num*/); bool is_selection_empty() const; void scale_selection_to_fit_print_volume(); + void convert_unit(bool from_imperial_unit); void cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); - void export_gcode(bool prefer_removable = true); + void export_gcode(bool prefer_removable); void export_stl(bool extended = false, bool selection_only = false); void export_amf(); void export_3mf(const boost::filesystem::path& output_path = boost::filesystem::path()); @@ -306,18 +312,28 @@ class Plater: public wxPanel bool can_reload_from_disk() const; void msw_rescale(); + void sys_color_changed(); bool init_view_toolbar(); + bool init_collapse_toolbar(); const Camera& get_camera() const; Camera& get_camera(); +#if ENABLE_ENVIRONMENT_MAP + void init_environment_texture(); + unsigned int get_environment_texture_id() const; +#endif // ENABLE_ENVIRONMENT_MAP + const Bed3D& get_bed() const; Bed3D& get_bed(); const GLToolbar& get_view_toolbar() const; GLToolbar& get_view_toolbar(); + const GLToolbar& get_collapse_toolbar() const; + GLToolbar& get_collapse_toolbar(); + const Mouse3DController& get_mouse3d_controller() const; Mouse3DController& get_mouse3d_controller(); diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index 9e462d4bf15..02e4a899d28 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -1,6 +1,7 @@ #include "Preferences.hpp" #include "AppConfig.hpp" #include "OptionsGroup.hpp" +#include "GUI_App.hpp" #include "I18N.hpp" namespace Slic3r { @@ -102,7 +103,11 @@ void PreferencesDialog::build() def.label = L("Single Instance"); def.type = coBool; - def.tooltip = L("If this is enabled, when staring PrusaSlicer and another instance is running, that instance will be reactivated instead."); +#if __APPLE__ + def.tooltip = L("On OSX there is always only one instance of app running by default. However it is allowed to run multiple instances of same app from the command line. In such case this settings will allow only one instance."); +#else + def.tooltip = L("If this is enabled, when staring PrusaSlicer and another instance of same PrusaSlicer is running, that instance will be reactivated instead."); +#endif def.set_default_value(new ConfigOptionBool{ app_config->has("single_instance") ? app_config->get("single_instance") == "1" : false }); option = Option(def, "single_instance"); m_optgroup_general->append_single_option_line(option); @@ -116,7 +121,16 @@ void PreferencesDialog::build() option = Option (def, "use_retina_opengl"); m_optgroup_general->append_single_option_line(option); #endif - +/* // ysFIXME THis part is temporary commented + // The using of inches is implemented just for object's size and position + + def.label = L("Use inches instead of millimeters"); + def.type = coBool; + def.tooltip = L("Use inches instead of millimeters for the object's size"); + def.set_default_value(new ConfigOptionBool{ app_config->get("use_inches") == "1" }); + option = Option(def, "use_inches"); + m_optgroup_general->append_single_option_line(option); +*/ m_optgroup_camera = std::make_shared(this, _(L("Camera"))); m_optgroup_camera->label_width = 40; m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) { @@ -147,7 +161,7 @@ void PreferencesDialog::build() } }; - def.label = L("Show the button for the collapse sidebar"); + def.label = L("Show sidebar collapse/expand button"); def.type = coBool; def.tooltip = L("If enabled, the button for the collapse sidebar will be appeared in top right corner of the 3D Scene"); def.set_default_value(new ConfigOptionBool{ app_config->get("show_collapse_button") == "1" }); @@ -166,10 +180,28 @@ void PreferencesDialog::build() create_settings_mode_widget(); +#if ENABLE_ENVIRONMENT_MAP + m_optgroup_render = std::make_shared(this, _(L("Render"))); + m_optgroup_render->label_width = 40; + m_optgroup_render->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + m_values[opt_key] = boost::any_cast(value) ? "1" : "0"; + }; + + def.label = L("Use environment map"); + def.type = coBool; + def.tooltip = L("If enabled, renders object using the environment map."); + def.set_default_value(new ConfigOptionBool{ app_config->get("use_environment_map") == "1" }); + option = Option(def, "use_environment_map"); + m_optgroup_render->append_single_option_line(option); +#endif // ENABLE_ENVIRONMENT_MAP + auto sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(m_optgroup_general->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); sizer->Add(m_optgroup_camera->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); sizer->Add(m_optgroup_gui->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); +#if ENABLE_ENVIRONMENT_MAP + sizer->Add(m_optgroup_render->sizer, 0, wxEXPAND | wxBOTTOM | wxLEFT | wxRIGHT, 10); +#endif // ENABLE_ENVIRONMENT_MAP SetFont(wxGetApp().normal_font()); @@ -202,6 +234,7 @@ void PreferencesDialog::accept() } } +#if !ENABLE_LAYOUT_NO_RESTART if (m_settings_layout_changed) { // the dialog needs to be destroyed before the call to recreate_gui() // or sometimes the application crashes into wxDialogBase() destructor @@ -223,6 +256,7 @@ void PreferencesDialog::accept() return; } } +#endif // !ENABLE_LAYOUT_NO_RESTART for (std::map::iterator it = m_values.begin(); it != m_values.end(); ++it) app_config->set(it->first, it->second); @@ -319,9 +353,9 @@ void PreferencesDialog::create_icon_size_slider() void PreferencesDialog::create_settings_mode_widget() { - wxString choices[] = { _L("Old regular layout with tab bar"), - _L("New layout without the tab bar on the platter"), - _L("Settings will be shown in non-modal dialog") }; + wxString choices[] = { _L("Old regular layout with the tab bar"), + _L("New layout without the tab bar on the plater"), + _L("Settings will be shown in the non-modal dialog") }; auto app_config = get_app_config(); int selection = app_config->get("old_settings_layout_mode") == "1" ? 0 : @@ -351,4 +385,4 @@ void PreferencesDialog::create_settings_mode_widget() } // GUI -} // Slic3r \ No newline at end of file +} // Slic3r diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index d90f01e2b2d..f61c4d932df 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -20,6 +20,9 @@ class PreferencesDialog : public DPIDialog std::shared_ptr m_optgroup_general; std::shared_ptr m_optgroup_camera; std::shared_ptr m_optgroup_gui; +#if ENABLE_ENVIRONMENT_MAP + std::shared_ptr m_optgroup_render; +#endif // ENABLE_ENVIRONMENT_MAP wxSizer* m_icon_size_sizer; wxRadioBox* m_layout_mode_box; bool isOSX {false}; diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 2681d3f3580..126a6ada073 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -461,6 +461,7 @@ const std::vector& Preset::printer_options() "use_firmware_retraction", "use_volumetric_e", "variable_layer_height", "host_type", "print_host", "printhost_apikey", "printhost_cafile", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", + "color_change_gcode", "pause_print_gcode", "template_custom_gcode", "between_objects_gcode", "printer_vendor", "printer_model", "printer_variant", "printer_notes", "cooling_tube_retraction", "cooling_tube_length", "high_current_on_filament_swap", "parking_pos_retraction", "extra_loading_move", "max_print_height", "default_print_profile", "inherits", @@ -962,7 +963,7 @@ void PresetCollection::load_bitmap_add(const std::string &file_name) const Preset* PresetCollection::get_selected_preset_parent() const { - if (this->get_selected_idx() == -1) + if (this->get_selected_idx() == size_t(-1)) // This preset collection has no preset activated yet. Only the get_edited_preset() is valid. return nullptr; diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index 22fa09f6cc6..24afeb526ea 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -296,9 +296,8 @@ std::string PresetHints::top_bottom_shell_thickness_explanation(const PresetBund double bottom_solid_min_thickness = print_config.opt_float("bottom_solid_min_thickness"); double layer_height = print_config.opt_float("layer_height"); bool variable_layer_height = printer_config.opt_bool("variable_layer_height"); - //FIXME the following lines take into account the 1st extruder only. + //FIXME the following line takes into account the 1st extruder only. double min_layer_height = variable_layer_height ? Slicing::min_layer_height_from_nozzle(printer_config, 1) : layer_height; - double max_layer_height = variable_layer_height ? Slicing::max_layer_height_from_nozzle(printer_config, 1) : layer_height; if (layer_height <= 0.f) { out += _utf8(L("Top / bottom shell thickness hint: Not available due to invalid layer height.")); diff --git a/src/slic3r/GUI/RemovableDriveManager.cpp b/src/slic3r/GUI/RemovableDriveManager.cpp index d67ac4a22fb..d865fe34768 100644 --- a/src/slic3r/GUI/RemovableDriveManager.cpp +++ b/src/slic3r/GUI/RemovableDriveManager.cpp @@ -393,6 +393,7 @@ bool RemovableDriveManager::set_and_verify_last_save_path(const std::string &pat #endif // REMOVABLE_DRIVE_MANAGER_OS_CALLBACKS m_last_save_path = this->get_removable_drive_from_path(path); + m_exporting_finished = false; return ! m_last_save_path.empty(); } @@ -407,6 +408,7 @@ RemovableDriveManager::RemovableDrivesStatus RemovableDriveManager::status() } if (! out.has_eject) m_last_save_path.clear(); + out.has_eject = out.has_eject && m_exporting_finished; return out; } diff --git a/src/slic3r/GUI/RemovableDriveManager.hpp b/src/slic3r/GUI/RemovableDriveManager.hpp index e1a8d6faf18..26ee12e40cb 100644 --- a/src/slic3r/GUI/RemovableDriveManager.hpp +++ b/src/slic3r/GUI/RemovableDriveManager.hpp @@ -83,7 +83,7 @@ class RemovableDriveManager // Public to be accessible from RemovableDriveManagerMM::on_device_unmount OSX notification handler. // It would be better to make this method private and friend to RemovableDriveManagerMM, but RemovableDriveManagerMM is an ObjectiveC class. void update(); - + void set_exporting_finished(bool b) { m_exporting_finished = b; } #ifdef _WIN32 // Called by Win32 Volume arrived / detached callback. void volumes_changed(); @@ -121,7 +121,9 @@ class RemovableDriveManager std::vector::const_iterator find_last_save_path_drive_data() const; // Set with set_and_verify_last_save_path() to a removable drive path to be ejected. std::string m_last_save_path; - + // Verifies that exporting was finished so drive can be ejected. + // Set false by set_and_verify_last_save_path() that is called just before exporting. + bool m_exporting_finished; #if __APPLE__ void register_window_osx(); void unregister_window_osx(); diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index b929b37d0a5..012af342a80 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -6,8 +6,11 @@ #include #include +#include "wx/dataview.h" + #include "libslic3r/PrintConfig.hpp" #include "GUI_App.hpp" +#include "Plater.hpp" #include "Tab.hpp" #include "PresetBundle.hpp" @@ -25,19 +28,11 @@ using GUI::into_u8; namespace Search { -static const std::vector& NameByType() -{ - static std::vector data; - if (data.empty()) { - data.assign(Preset::TYPE_COUNT, std::wstring()); - data[Preset::TYPE_PRINT ] = _L("Print" ).ToStdWstring(); - data[Preset::TYPE_FILAMENT ] = _L("Filament" ).ToStdWstring(); - data[Preset::TYPE_SLA_MATERIAL ] = _L("Material" ).ToStdWstring(); - data[Preset::TYPE_SLA_PRINT ] = _L("Print" ).ToStdWstring(); - data[Preset::TYPE_PRINTER ] = _L("Printer" ).ToStdWstring(); - }; - return data; -} +// Does our wxWidgets version support markup? +// https://github.com/prusa3d/PrusaSlicer/issues/4282#issuecomment-634676371 +#if wxUSE_MARKUP && wxCHECK_VERSION(3, 1, 1) + #define SEARCH_SUPPORTS_MARKUP +#endif static char marker_by_type(Preset::Type type, PrinterTechnology pt) { @@ -199,10 +194,8 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) const std::wstring *prev = nullptr; for (const std::wstring * const s : { view_params.category ? &opt.category_local : nullptr, - view_params.group ? &opt.group_local : nullptr, - &opt.label_local }) + &opt.group_local, &opt.label_local }) if (s != nullptr && (prev == nullptr || *prev != *s)) { -// if (! out.empty()) if (out.size()>2) out += sep; out += *s; @@ -218,10 +211,8 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) const std::wstring*prev = nullptr; for (const std::wstring * const s : { view_params.category ? &opt.category : nullptr, - view_params.group ? &opt.group : nullptr, - &opt.label }) + &opt.group, &opt.label }) if (s != nullptr && (prev == nullptr || *prev != *s)) { -// if (! out.empty()) if (out.size()>2) out += sep; out += *s; @@ -272,9 +263,15 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) label += L" [" + std::to_wstring(score) + L"]";// add score value std::string label_u8 = into_u8(label); std::string label_plain = label_u8; - boost::erase_all(label_plain, std::string(1, char(ImGui::ColorMarkerStart))); - boost::erase_all(label_plain, std::string(1, char(ImGui::ColorMarkerEnd))); - found.emplace_back(FoundOption{ label_plain, label_u8, boost::nowide::narrow(get_tooltip(opt)), i, score }); + +#ifdef SEARCH_SUPPORTS_MARKUP + boost::replace_all(label_plain, std::string(1, char(ImGui::ColorMarkerStart)), ""); + boost::replace_all(label_plain, std::string(1, char(ImGui::ColorMarkerEnd)), ""); +#else + boost::erase_all(label_plain, std::string(1, char(ImGui::ColorMarkerStart))); + boost::erase_all(label_plain, std::string(1, char(ImGui::ColorMarkerEnd))); +#endif + found.emplace_back(FoundOption{ label_plain, label_u8, boost::nowide::narrow(get_tooltip(opt)), i, score }); } } @@ -415,6 +412,14 @@ void SearchComboPopup::OnKeyDown(wxKeyEvent& event) // SearchDialog //------------------------------------------ +static const std::map icon_idxs = { + {ImGui::PrintIconMarker , 0}, + {ImGui::PrinterIconMarker , 1}, + {ImGui::PrinterSlaIconMarker, 2}, + {ImGui::FilamentIconMarker , 3}, + {ImGui::MaterialIconMarker , 4}, +}; + SearchDialog::SearchDialog(OptionsSearcher* searcher) : GUI::DPIDialog(NULL, wxID_ANY, _L("Search"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER), searcher(searcher) @@ -425,24 +430,38 @@ SearchDialog::SearchDialog(OptionsSearcher* searcher) default_string = _L("Type here to search"); int border = 10; + int em = em_unit(); + + search_line = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxTE_PROCESS_ENTER); + + search_list = new wxDataViewCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(em * 40, em * 30), wxDV_NO_HEADER | wxDV_SINGLE | wxBORDER_SIMPLE); + search_list_model = new SearchListModel(this); + search_list->AssociateModel(search_list_model); - search_line = new wxTextCtrl(this, wxID_ANY, "", wxDefaultPosition, wxDefaultSize); + search_list->AppendBitmapColumn("", SearchListModel::colIcon); - // wxWANTS_CHARS style is neede for process Enter key press - search_list = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxSize(em_unit() * 40, em_unit() * 30), 0, NULL, wxWANTS_CHARS); + wxDataViewTextRenderer* const markupRenderer = new wxDataViewTextRenderer(); + +#ifdef SEARCH_SUPPORTS_MARKUP + markupRenderer->EnableMarkup(); +#endif + + search_list->AppendColumn(new wxDataViewColumn("", markupRenderer, SearchListModel::colMarkedText, wxCOL_WIDTH_AUTOSIZE, wxALIGN_LEFT)); + + search_list->GetColumn(SearchListModel::colIcon )->SetWidth(3 * em_unit()); + search_list->GetColumn(SearchListModel::colMarkedText)->SetWidth(40 * em_unit()); wxBoxSizer* check_sizer = new wxBoxSizer(wxHORIZONTAL); check_category = new wxCheckBox(this, wxID_ANY, _L("Category")); - check_group = new wxCheckBox(this, wxID_ANY, _L("Group")); if (GUI::wxGetApp().is_localized()) check_english = new wxCheckBox(this, wxID_ANY, _L("Search in English")); wxStdDialogButtonSizer* cancel_btn = this->CreateStdDialogButtonSizer(wxCANCEL); + check_sizer->Add(new wxStaticText(this, wxID_ANY, _L("Use for search") + ":"), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border); check_sizer->Add(check_category, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border); - check_sizer->Add(check_group, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border); - if (GUI::wxGetApp().is_localized()) + if (check_english) check_sizer->Add(check_english, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border); check_sizer->AddStretchSpacer(border); check_sizer->Add(cancel_btn, 0, wxALIGN_CENTER_VERTICAL); @@ -458,16 +477,26 @@ SearchDialog::SearchDialog(OptionsSearcher* searcher) // process wxEVT_KEY_DOWN to navigate inside search_list, if ArrowUp/Down was pressed search_line->Bind(wxEVT_KEY_DOWN,&SearchDialog::OnKeyDown, this); - search_list->Bind(wxEVT_MOTION, &SearchDialog::OnMouseMove, this); - search_list->Bind(wxEVT_LEFT_UP, &SearchDialog::OnMouseClick, this); - search_list->Bind(wxEVT_KEY_DOWN,&SearchDialog::OnKeyDown, this); + search_list->Bind(wxEVT_DATAVIEW_SELECTION_CHANGED, &SearchDialog::OnSelect, this); + search_list->Bind(wxEVT_DATAVIEW_ITEM_ACTIVATED, &SearchDialog::OnActivate, this); +#ifdef __WXMSW__ + search_list->GetMainWindow()->Bind(wxEVT_MOTION, &SearchDialog::OnMotion, this); + search_list->GetMainWindow()->Bind(wxEVT_LEFT_DOWN, &SearchDialog::OnLeftDown, this); +#endif //__WXMSW__ + + // Under OSX mouse and key states didn't fill after wxEVT_DATAVIEW_SELECTION_CHANGED call + // As a result, we can't to identify what kind of actions was done + // So, under OSX is used OnKeyDown function to navigate inside the list +#ifdef __APPLE__ + search_list->Bind(wxEVT_KEY_DOWN, &SearchDialog::OnKeyDown, this); +#endif - if (GUI::wxGetApp().is_localized()) - check_english ->Bind(wxEVT_CHECKBOX, &SearchDialog::OnCheck, this); check_category->Bind(wxEVT_CHECKBOX, &SearchDialog::OnCheck, this); - check_group ->Bind(wxEVT_CHECKBOX, &SearchDialog::OnCheck, this); + if (check_english) + check_english ->Bind(wxEVT_CHECKBOX, &SearchDialog::OnCheck, this); - this->Bind(wxEVT_LISTBOX, &SearchDialog::OnSelect, this); +// Bind(wxEVT_MOTION, &SearchDialog::OnMotion, this); + Bind(wxEVT_LEFT_DOWN, &SearchDialog::OnLeftDown, this); SetSizer(topSizer); topSizer->SetSizeHints(this); @@ -484,19 +513,19 @@ void SearchDialog::Popup(wxPoint position /*= wxDefaultPosition*/) const OptionViewParameters& params = searcher->view_params; check_category->SetValue(params.category); - check_group->SetValue(params.group); - check_english->SetValue(params.english); + if (check_english) + check_english->SetValue(params.english); this->SetPosition(position); this->ShowModal(); } -void SearchDialog::ProcessSelection(int selection) +void SearchDialog::ProcessSelection(wxDataViewItem selection) { - if (selection < 0) + if (!selection.IsOk()) return; - GUI::wxGetApp().sidebar().jump_to_option(selection); + GUI::wxGetApp().sidebar().jump_to_option(search_list_model->GetRow(selection)); this->EndModal(wxID_CLOSE); } @@ -521,39 +550,6 @@ void SearchDialog::OnLeftUpInTextCtrl(wxEvent& event) event.Skip(); } -void SearchDialog::OnMouseMove(wxMouseEvent& event) -{ - wxPoint pt = wxGetMousePosition() - search_list->GetScreenPosition(); - int selection = search_list->HitTest(pt); - search_list->Select(selection); -} - -void SearchDialog::OnMouseClick(wxMouseEvent&) -{ - int selection = search_list->GetSelection(); - search_list->SetSelection(wxNOT_FOUND); - - wxCommandEvent event(wxEVT_LISTBOX, search_list->GetId()); - event.SetInt(selection); - event.SetEventObject(search_list); - ProcessEvent(event); -} - -void SearchDialog::OnSelect(wxCommandEvent& event) -{ - int selection = event.GetSelection(); - ProcessSelection(selection); -} - -void SearchDialog::update_list() -{ - search_list->Clear(); - - const std::vector& filters = searcher->found_options(); - for (const FoundOption& item : filters) - search_list->Append(from_u8(item.label).Remove(0, 1)); -} - void SearchDialog::OnKeyDown(wxKeyEvent& event) { int key = event.GetKeyCode(); @@ -561,17 +557,23 @@ void SearchDialog::OnKeyDown(wxKeyEvent& event) // change selected item in the list if (key == WXK_UP || key == WXK_DOWN) { - int selection = search_list->GetSelection(); - - if (key == WXK_UP && selection > 0) - selection--; - if (key == WXK_DOWN && selection < int(search_list->GetCount() - 1)) - selection++; - - search_list->Select(selection); - // This function could be called from search_line, // So, for the next correct navigation, set focus on the search_list search_list->SetFocus(); + + auto item = search_list->GetSelection(); + + if (item.IsOk()) { + unsigned selection = search_list_model->GetRow(item); + + if (key == WXK_UP && selection > 0) + selection--; + if (key == WXK_DOWN && selection < unsigned(search_list_model->GetCount() - 1)) + selection++; + + prevent_list_events = true; + search_list->Select(search_list_model->GetItem(selection)); + prevent_list_events = false; + } } // process "Enter" pressed else if (key == WXK_NUMPAD_ENTER || key == WXK_RETURN) @@ -580,21 +582,85 @@ void SearchDialog::OnKeyDown(wxKeyEvent& event) event.Skip(); // !Needed to have EVT_CHAR generated as well } +void SearchDialog::OnActivate(wxDataViewEvent& event) +{ + ProcessSelection(event.GetItem()); +} + +void SearchDialog::OnSelect(wxDataViewEvent& event) +{ + // To avoid selection update from Select() under osx + if (prevent_list_events) + return; + + // Under OSX mouse and key states didn't fill after wxEVT_DATAVIEW_SELECTION_CHANGED call + // As a result, we can't to identify what kind of actions was done + // So, under OSX is used OnKeyDown function to navigate inside the list +#ifndef __APPLE__ + // wxEVT_DATAVIEW_SELECTION_CHANGED is processed, when selection is changed after mouse click or press the Up/Down arrows + // But this two cases should be processed in different way: + // Up/Down arrows -> leave it as it is (just a navigation) + // LeftMouseClick -> call the ProcessSelection function + if (wxGetMouseState().LeftIsDown()) +#endif //__APPLE__ + ProcessSelection(search_list->GetSelection()); +} + +void SearchDialog::update_list() +{ + // Under OSX model->Clear invoke wxEVT_DATAVIEW_SELECTION_CHANGED, so + // set prevent_list_events to true already here + prevent_list_events = true; + search_list_model->Clear(); + + const std::vector& filters = searcher->found_options(); + for (const FoundOption& item : filters) + search_list_model->Prepend(item.label); + + // select first item, if search_list + if (search_list_model->GetCount() > 0) + search_list->Select(search_list_model->GetItem(0)); + prevent_list_events = false; +} + void SearchDialog::OnCheck(wxCommandEvent& event) { OptionViewParameters& params = searcher->view_params; - params.english = check_english->GetValue(); + if (check_english) + params.english = check_english->GetValue(); params.category = check_category->GetValue(); - params.group = check_group->GetValue(); searcher->search(); update_list(); } +void SearchDialog::OnMotion(wxMouseEvent& event) +{ + wxDataViewItem item; + wxDataViewColumn* col; + wxWindow* win = this; +#ifdef __WXMSW__ + win = search_list; +#endif + search_list->HitTest(wxGetMousePosition() - win->GetScreenPosition(), item, col); + search_list->Select(item); + + event.Skip(); +} + +void SearchDialog::OnLeftDown(wxMouseEvent& event) +{ + ProcessSelection(search_list->GetSelection()); +} + void SearchDialog::on_dpi_changed(const wxRect& suggested_rect) { const int& em = em_unit(); + search_list_model->msw_rescale(); + search_list->GetColumn(SearchListModel::colIcon )->SetWidth(3 * em); + search_list->GetColumn(SearchListModel::colMarkedText)->SetWidth(45 * em); + msw_buttons_rescale(this, em, { wxID_CANCEL }); const wxSize& size = wxSize(40 * em, 30 * em); @@ -604,6 +670,73 @@ void SearchDialog::on_dpi_changed(const wxRect& suggested_rect) Refresh(); } +void SearchDialog::on_sys_color_changed() +{ + // msw_rescale updates just icons, so use it + search_list_model->msw_rescale(); + + Refresh(); +} + +// ---------------------------------------------------------------------------- +// SearchListModel +// ---------------------------------------------------------------------------- + +SearchListModel::SearchListModel(wxWindow* parent) : wxDataViewVirtualListModel(0) +{ + int icon_id = 0; + for (const std::string& icon : { "cog", "printer", "sla_printer", "spool", "resin" }) + m_icon[icon_id++] = ScalableBitmap(parent, icon); +} + +void SearchListModel::Clear() +{ + m_values.clear(); + Reset(0); +} + +void SearchListModel::Prepend(const std::string& label) +{ + const char icon_c = label.at(0); + int icon_idx = icon_idxs.at(icon_c); + wxString str = from_u8(label).Remove(0, 1); + + m_values.emplace_back(str, icon_idx); + + RowPrepended(); +} + +void SearchListModel::msw_rescale() +{ + for (ScalableBitmap& bmp : m_icon) + bmp.msw_rescale(); +} + +wxString SearchListModel::GetColumnType(unsigned int col) const +{ + if (col == colIcon) + return "wxBitmap"; + return "string"; +} + +void SearchListModel::GetValueByRow(wxVariant& variant, + unsigned int row, unsigned int col) const +{ + switch (col) + { + case colIcon: + variant << m_icon[m_values[row].second].bmp(); + break; + case colMarkedText: + variant = m_values[row].first; + break; + case colMax: + wxFAIL_MSG("invalid column"); + default: + break; + } +} + } diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index 291633a2bc7..9701e68088f 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -67,7 +67,6 @@ struct FoundOption { struct OptionViewParameters { bool category {false}; - bool group {true }; bool english {false}; int hovered_id {0}; @@ -158,48 +157,87 @@ class SearchComboPopup : public wxListBox, public wxComboPopup wxString m_input_string; }; - //------------------------------------------ // SearchDialog //------------------------------------------ - +class SearchListModel; class SearchDialog : public GUI::DPIDialog { wxString search_str; wxString default_string; - wxTextCtrl* search_line { nullptr }; - wxListBox* search_list { nullptr }; - wxCheckBox* check_category { nullptr }; - wxCheckBox* check_group { nullptr }; - wxCheckBox* check_english { nullptr }; + bool prevent_list_events {false}; - OptionsSearcher* searcher; + wxTextCtrl* search_line { nullptr }; + wxDataViewCtrl* search_list { nullptr }; + SearchListModel* search_list_model { nullptr }; + wxCheckBox* check_category { nullptr }; + wxCheckBox* check_english { nullptr }; - void update_list(); + OptionsSearcher* searcher { nullptr }; void OnInputText(wxCommandEvent& event); void OnLeftUpInTextCtrl(wxEvent& event); - - void OnMouseMove(wxMouseEvent& event); - void OnMouseClick(wxMouseEvent& event); - void OnSelect(wxCommandEvent& event); void OnKeyDown(wxKeyEvent& event); + void OnActivate(wxDataViewEvent& event); + void OnSelect(wxDataViewEvent& event); + void OnCheck(wxCommandEvent& event); + void OnMotion(wxMouseEvent& event); + void OnLeftDown(wxMouseEvent& event); + + void update_list(); public: SearchDialog(OptionsSearcher* searcher); ~SearchDialog() {} void Popup(wxPoint position = wxDefaultPosition); - void ProcessSelection(int selection); + void ProcessSelection(wxDataViewItem selection); protected: void on_dpi_changed(const wxRect& suggested_rect) override; + virtual void on_sys_color_changed() override; +}; + + +// ---------------------------------------------------------------------------- +// SearchListModel +// ---------------------------------------------------------------------------- + +class SearchListModel : public wxDataViewVirtualListModel +{ + std::vector> m_values; + ScalableBitmap m_icon[5]; + +public: + enum { + colIcon, + colMarkedText, + colMax + }; + + SearchListModel(wxWindow* parent); + + // helper methods to change the model + + void Clear(); + void Prepend(const std::string& text); + void msw_rescale(); + + // implementation of base class virtuals to define model + + virtual unsigned int GetColumnCount() const override { return colMax; } + virtual wxString GetColumnType(unsigned int col) const override; + virtual void GetValueByRow(wxVariant& variant, unsigned int row, unsigned int col) const override; + virtual bool GetAttrByRow(unsigned int row, unsigned int col, wxDataViewItemAttr& attr) const override { return true; } + virtual bool SetValueByRow(const wxVariant& variant, unsigned int row, unsigned int col) override { return false; } }; + + } // Search namespace } diff --git a/src/slic3r/GUI/Selection.cpp b/src/slic3r/GUI/Selection.cpp index 87f8b60ede2..e9250fe9e41 100644 --- a/src/slic3r/GUI/Selection.cpp +++ b/src/slic3r/GUI/Selection.cpp @@ -1,14 +1,17 @@ #include "libslic3r/libslic3r.h" #include "Selection.hpp" +#include "3DScene.hpp" #include "GLCanvas3D.hpp" #include "GUI_App.hpp" #include "GUI.hpp" #include "GUI_ObjectManipulation.hpp" #include "GUI_ObjectList.hpp" #include "Gizmos/GLGizmoBase.hpp" -#include "3DScene.hpp" #include "Camera.hpp" +#include "Plater.hpp" + +#include "libslic3r/Model.hpp" #include @@ -54,7 +57,7 @@ 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; @@ -69,6 +72,35 @@ bool Selection::Clipboard::is_sla_compliant() const return true; } +Selection::Clipboard::Clipboard() +{ + m_model.reset(new Model); +} + +void Selection::Clipboard::reset() { + m_model->clear_objects(); +} + +bool Selection::Clipboard::is_empty() const +{ + return m_model->objects.empty(); +} + +ModelObject* Selection::Clipboard::add_object() +{ + return m_model->add_object(); +} + +ModelObject* Selection::Clipboard::get_object(unsigned int id) +{ + return (id < (unsigned int)m_model->objects.size()) ? m_model->objects[id] : nullptr; +} + +const ModelObjectPtrs& Selection::Clipboard::get_objects() const +{ + return m_model->objects; +} + Selection::Selection() : m_volumes(nullptr) , m_model(nullptr) @@ -76,9 +108,11 @@ Selection::Selection() , m_mode(Instance) , m_type(Empty) , m_valid(false) - , m_curved_arrow(16) , m_scale_factor(1.0f) { + m_arrow.reset(new GLArrow); + m_curved_arrow.reset(new GLCurvedArrow(16)); + this->set_bounding_boxes_dirty(); #if ENABLE_RENDER_SELECTION_CENTER m_quadric = ::gluNewQuadric(); @@ -104,15 +138,15 @@ void Selection::set_volumes(GLVolumePtrs* volumes) // Init shall be called from the OpenGL render function, so that the OpenGL context is initialized! bool Selection::init() { - if (!m_arrow.init()) + if (!m_arrow->init()) return false; - m_arrow.set_scale(5.0 * Vec3d::Ones()); + m_arrow->set_scale(5.0 * Vec3d::Ones()); - if (!m_curved_arrow.init()) + if (!m_curved_arrow->init()) return false; - m_curved_arrow.set_scale(5.0 * Vec3d::Ones()); + m_curved_arrow->set_scale(5.0 * Vec3d::Ones()); return true; } @@ -1561,20 +1595,21 @@ void Selection::update_type() } else { + unsigned int sla_volumes_count = 0; + // Note: sla_volumes_count is a count of the selected sla_volumes per object instead of per instance, like a model_volumes_count is + for (unsigned int i : m_list) { + if ((*m_volumes)[i]->volume_idx() < 0) + ++sla_volumes_count; + } + if (m_cache.content.size() == 1) // single object { const ModelObject* model_object = m_model->objects[m_cache.content.begin()->first]; unsigned int model_volumes_count = (unsigned int)model_object->volumes.size(); - unsigned int sla_volumes_count = 0; - for (unsigned int i : m_list) - { - if ((*m_volumes)[i]->volume_idx() < 0) - ++sla_volumes_count; - } - unsigned int volumes_count = model_volumes_count + sla_volumes_count; + unsigned int instances_count = (unsigned int)model_object->instances.size(); unsigned int selected_instances_count = (unsigned int)m_cache.content.begin()->second.size(); - if (volumes_count * instances_count == (unsigned int)m_list.size()) + if (model_volumes_count * instances_count + sla_volumes_count == (unsigned int)m_list.size()) { m_type = SingleFullObject; // ensures the correct mode is selected @@ -1582,7 +1617,7 @@ void Selection::update_type() } else if (selected_instances_count == 1) { - if (volumes_count == (unsigned int)m_list.size()) + if (model_volumes_count + sla_volumes_count == (unsigned int)m_list.size()) { m_type = SingleFullInstance; // ensures the correct mode is selected @@ -1605,7 +1640,7 @@ void Selection::update_type() requires_disable = true; } } - else if ((selected_instances_count > 1) && (selected_instances_count * volumes_count == (unsigned int)m_list.size())) + else if ((selected_instances_count > 1) && (selected_instances_count * model_volumes_count + sla_volumes_count == (unsigned int)m_list.size())) { m_type = MultipleFullInstance; // ensures the correct mode is selected @@ -1622,7 +1657,7 @@ void Selection::update_type() unsigned int instances_count = (unsigned int)model_object->instances.size(); sels_cntr += volumes_count * instances_count; } - if (sels_cntr == (unsigned int)m_list.size()) + if (sels_cntr + sla_volumes_count == (unsigned int)m_list.size()) { m_type = MultipleFullObject; // ensures the correct mode is selected @@ -2048,29 +2083,29 @@ void Selection::render_sidebar_layers_hints(const std::string& sidebar_field) co void Selection::render_sidebar_position_hint(Axis axis) const { - m_arrow.set_color(AXES_COLOR[axis], 3); - m_arrow.render(); + m_arrow->set_color(AXES_COLOR[axis], 3); + m_arrow->render(); } void Selection::render_sidebar_rotation_hint(Axis axis) const { - m_curved_arrow.set_color(AXES_COLOR[axis], 3); - m_curved_arrow.render(); + m_curved_arrow->set_color(AXES_COLOR[axis], 3); + m_curved_arrow->render(); glsafe(::glRotated(180.0, 0.0, 0.0, 1.0)); - m_curved_arrow.render(); + m_curved_arrow->render(); } void Selection::render_sidebar_scale_hint(Axis axis) const { - m_arrow.set_color(((requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling()) ? UNIFORM_SCALE_COLOR : AXES_COLOR[axis]), 3); + m_arrow->set_color(((requires_uniform_scale() || wxGetApp().obj_manipul()->get_uniform_scaling()) ? UNIFORM_SCALE_COLOR : AXES_COLOR[axis]), 3); glsafe(::glTranslated(0.0, 5.0, 0.0)); - m_arrow.render(); + m_arrow->render(); glsafe(::glTranslated(0.0, -10.0, 0.0)); glsafe(::glRotated(180.0, 0.0, 0.0, 1.0)); - m_arrow.render(); + m_arrow->render(); } void Selection::render_sidebar_size_hint(Axis axis, double length) const diff --git a/src/slic3r/GUI/Selection.hpp b/src/slic3r/GUI/Selection.hpp index c27b4cc29d1..7a929926c4b 100644 --- a/src/slic3r/GUI/Selection.hpp +++ b/src/slic3r/GUI/Selection.hpp @@ -3,7 +3,7 @@ #include #include "libslic3r/Geometry.hpp" -#include "3DScene.hpp" + #if ENABLE_RENDER_SELECTION_CENTER class GLUquadric; @@ -11,7 +11,19 @@ typedef class GLUquadric GLUquadricObj; #endif // ENABLE_RENDER_SELECTION_CENTER namespace Slic3r { + class Shader; +class Model; +class ModelObject; +class GLVolume; +class GLArrow; +class GLCurvedArrow; +class DynamicPrintConfig; + +using GLVolumePtrs = std::vector; +using ModelObjectPtrs = std::vector; + + namespace GUI { class TransformationType { @@ -145,18 +157,23 @@ class Selection class Clipboard { - Model m_model; + // Model is stored through a pointer to avoid including heavy Model.hpp. + // It is created in constructor. + std::unique_ptr m_model; + Selection::EMode m_mode; public: - void reset() { m_model.clear_objects(); } - bool is_empty() const { return m_model.objects.empty(); } + Clipboard(); + + void reset(); + bool is_empty() const; bool is_sla_compliant() const; - ModelObject* add_object() { return m_model.add_object(); } - ModelObject* get_object(unsigned int id) { return (id < (unsigned int)m_model.objects.size()) ? m_model.objects[id] : nullptr; } - const ModelObjectPtrs& get_objects() const { return m_model.objects; } + ModelObject* add_object(); + ModelObject* get_object(unsigned int id); + const ModelObjectPtrs& get_objects() const; Selection::EMode get_mode() const { return m_mode; } void set_mode(Selection::EMode mode) { m_mode = mode; } @@ -200,8 +217,11 @@ class Selection #if ENABLE_RENDER_SELECTION_CENTER GLUquadricObj* m_quadric; #endif // ENABLE_RENDER_SELECTION_CENTER - mutable GLArrow m_arrow; - mutable GLCurvedArrow m_curved_arrow; + + // Arrows are saved through pointers to avoid including 3DScene.hpp. + // It also allows mutability. + std::unique_ptr m_arrow; + std::unique_ptr m_curved_arrow; mutable float m_scale_factor; diff --git a/src/slic3r/GUI/SysInfoDialog.cpp b/src/slic3r/GUI/SysInfoDialog.cpp index 9e7d1bad6cc..3bd0fcf9f78 100644 --- a/src/slic3r/GUI/SysInfoDialog.cpp +++ b/src/slic3r/GUI/SysInfoDialog.cpp @@ -3,9 +3,12 @@ #include "3DScene.hpp" #include "GUI.hpp" #include "../Utils/UndoRedo.hpp" +#include "Plater.hpp" #include +#include + #include #include #include "GUI_App.hpp" @@ -145,11 +148,11 @@ SysInfoDialog::SysInfoDialog() "" "" "", bgr_clr_str, text_clr_str, text_clr_str, - get_mem_info(true) + "
" + wxGetApp().get_gl_info(true, true)); + get_mem_info(true) + "
" + wxGetApp().get_gl_info(true, true) + "
Eigen vectorization supported: " + Eigen::SimdInstructionSetsInUse()); m_opengl_info_html->SetPage(text); main_sizer->Add(m_opengl_info_html, 1, wxEXPAND | wxBOTTOM, 15); } - + wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK); m_btn_copy_to_clipboard = new wxButton(this, wxID_ANY, _(L("Copy to Clipboard")), wxDefaultPosition, wxDefaultSize); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 8d6c0bcc15d..a328261d9c8 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -32,6 +32,8 @@ #include "GUI_App.hpp" #include "GUI_ObjectList.hpp" #include "ConfigWizard.hpp" +#include "Plater.hpp" +#include "MainFrame.hpp" #include "format.hpp" namespace Slic3r { @@ -53,7 +55,7 @@ void Tab::Highlighter::init(BlinkingBitmap* bmp) if (!bmp) return; - timer.Start(100, false); + timer.Start(300, false); bbmp = bmp; bbmp->activate(); @@ -74,7 +76,7 @@ void Tab::Highlighter::blink() return; bbmp->blink(); - if ((++blink_counter) == 29) + if ((++blink_counter) == 11) invalidate(); } @@ -190,15 +192,13 @@ void Tab::create_preset_tab() add_scaled_button(panel, &m_search_btn, "search"); m_search_btn->SetToolTip(format_wxstr(_L("Click to start a search or use %1% shortcut"), "Ctrl+F")); - // Determine the theme color of OS (dark or light) - auto luma = wxGetApp().get_colour_approx_luma(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); // Bitmaps to be shown on the "Revert to system" aka "Lock to system" button next to each input field. - add_scaled_bitmap(this, m_bmp_value_lock , luma >= 128 ? "lock_closed" : "lock_closed_white"); + add_scaled_bitmap(this, m_bmp_value_lock , "lock_closed"); add_scaled_bitmap(this, m_bmp_value_unlock, "lock_open"); m_bmp_non_system = &m_bmp_white_bullet; // Bitmaps to be shown on the "Undo user changes" button next to each input field. add_scaled_bitmap(this, m_bmp_value_revert, "undo"); - add_scaled_bitmap(this, m_bmp_white_bullet, luma >= 128 ? "dot" : "dot_white"); + add_scaled_bitmap(this, m_bmp_white_bullet, "dot"); fill_icon_descriptions(); set_tooltips_text(); @@ -590,6 +590,18 @@ void TabPrinter::msw_rescale() Layout(); } +void TabPrinter::sys_color_changed() +{ + Tab::sys_color_changed(); + + // update missed options_groups + const std::vector& pages = m_printer_technology == ptFFF ? m_pages_sla : m_pages_fff; + for (auto page : pages) + page->sys_color_changed(); + + Layout(); +} + void TabSLAMaterial::init_options_list() { if (!m_options_list.empty()) @@ -869,6 +881,40 @@ void Tab::msw_rescale() Layout(); } +void Tab::sys_color_changed() +{ + update_tab_ui(); + + // update buttons and cached bitmaps + for (const auto btn : m_scaled_buttons) + btn->msw_rescale(); + for (const auto bmp : m_scaled_bitmaps) + bmp->msw_rescale(); +// for (ScalableBitmap& bmp : m_mode_bitmap_cache) +// bmp.msw_rescale(); + + // update icons for tree_ctrl + for (ScalableBitmap& bmp : m_scaled_icons_list) + bmp.msw_rescale(); + // recreate and set new ImageList for tree_ctrl + m_icons->RemoveAll(); + m_icons = new wxImageList(m_scaled_icons_list.front().bmp().GetWidth(), m_scaled_icons_list.front().bmp().GetHeight()); + for (ScalableBitmap& bmp : m_scaled_icons_list) + m_icons->Add(bmp.bmp()); + m_treectrl->AssignImageList(m_icons); + + // Colors for ui "decoration" + m_sys_label_clr = wxGetApp().get_label_clr_sys(); + m_modified_label_clr = wxGetApp().get_label_clr_modified(); + update_labels_colour(); + + // update options_groups + for (auto page : m_pages) + page->sys_color_changed(); + + Layout(); +} + Field* Tab::get_field(const t_config_option_key& opt_key, int opt_index/* = -1*/) const { Field* field = nullptr; @@ -2173,6 +2219,21 @@ void TabPrinter::build_fff() option.opt.height = gcode_field_height;//150; optgroup->append_single_option_line(option); + optgroup = page->new_optgroup(L("Color Change G-code"), 0); + option = optgroup->get_option("color_change_gcode"); + option.opt.height = gcode_field_height;//150; + optgroup->append_single_option_line(option); + + optgroup = page->new_optgroup(L("Pause Print G-code"), 0); + option = optgroup->get_option("pause_print_gcode"); + option.opt.height = gcode_field_height;//150; + optgroup->append_single_option_line(option); + + optgroup = page->new_optgroup(L("Template Custom G-code"), 0); + option = optgroup->get_option("template_custom_gcode"); + option.opt.height = gcode_field_height;//150; + optgroup->append_single_option_line(option); + page = add_options_page(L("Notes"), "note.png"); optgroup = page->new_optgroup(L("Notes"), 0); option = optgroup->get_option("printer_notes"); @@ -3254,28 +3315,28 @@ void Tab::save_preset(std::string name /*= ""*/, bool detach) wxGetApp().plater()->force_filament_colors_update(); { - // Profile compatiblity is updated first when the profile is saved. - // Update profile selection combo boxes at the depending tabs to reflect modifications in profile compatibility. - std::vector dependent; - switch (m_type) { - case Preset::TYPE_PRINT: - dependent = { Preset::TYPE_FILAMENT }; - break; - case Preset::TYPE_SLA_PRINT: - dependent = { Preset::TYPE_SLA_MATERIAL }; - break; - case Preset::TYPE_PRINTER: + // Profile compatiblity is updated first when the profile is saved. + // Update profile selection combo boxes at the depending tabs to reflect modifications in profile compatibility. + std::vector dependent; + switch (m_type) { + case Preset::TYPE_PRINT: + dependent = { Preset::TYPE_FILAMENT }; + break; + case Preset::TYPE_SLA_PRINT: + dependent = { Preset::TYPE_SLA_MATERIAL }; + break; + case Preset::TYPE_PRINTER: if (static_cast(this)->m_printer_technology == ptFFF) dependent = { Preset::TYPE_PRINT, Preset::TYPE_FILAMENT }; else dependent = { Preset::TYPE_SLA_PRINT, Preset::TYPE_SLA_MATERIAL }; - break; + break; default: - break; - } - for (Preset::Type preset_type : dependent) - wxGetApp().get_tab(preset_type)->update_tab_ui(); - } + break; + } + for (Preset::Type preset_type : dependent) + wxGetApp().get_tab(preset_type)->update_tab_ui(); + } } // Called for a currently selected preset. @@ -3539,6 +3600,18 @@ void Tab::set_tooltips_text() "Click to reset current value to the last saved preset.")); } +Page::Page(wxWindow* parent, const wxString& title, const int iconID, const std::vector& mode_bmp_cache) : + m_parent(parent), + m_title(title), + m_iconID(iconID), + m_mode_bitmap_cache(mode_bmp_cache) +{ + Create(m_parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); + m_vsizer = new wxBoxSizer(wxVERTICAL); + m_item_color = &wxGetApp().get_label_clr_default(); + SetSizer(m_vsizer); +} + void Page::reload_config() { for (auto group : m_optgroups) @@ -3560,6 +3633,12 @@ void Page::msw_rescale() group->msw_rescale(); } +void Page::sys_color_changed() +{ + for (auto group : m_optgroups) + group->sys_color_changed(); +} + Field* Page::get_field(const t_config_option_key& opt_key, int opt_index /*= -1*/) const { Field* field = nullptr; diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index a13d13f2dbd..5805809bfb7 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -33,11 +33,12 @@ #include "Event.hpp" #include "wxExtensions.hpp" #include "ConfigManipulation.hpp" +#include "Preset.hpp" +#include "OptionsGroup.hpp" namespace Slic3r { namespace GUI { - // Single Tab page containing a{ vsizer } of{ optgroups } // package Slic3r::GUI::Tab::Page; using ConfigOptionsGroupShp = std::shared_ptr; @@ -49,17 +50,8 @@ class Page : public wxScrolledWindow wxBoxSizer* m_vsizer; bool m_show = true; public: - Page(wxWindow* parent, const wxString& title, const int iconID, const std::vector& mode_bmp_cache) : - m_parent(parent), - m_title(title), - m_iconID(iconID), - m_mode_bitmap_cache(mode_bmp_cache) - { - Create(m_parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL); - m_vsizer = new wxBoxSizer(wxVERTICAL); - m_item_color = &wxGetApp().get_label_clr_default(); - SetSizer(m_vsizer); - } + Page(wxWindow* parent, const wxString& title, const int iconID, + const std::vector& mode_bmp_cache); ~Page() {} bool m_is_modified_values{ false }; @@ -81,6 +73,7 @@ class Page : public wxScrolledWindow void reload_config(); void update_visibility(ConfigOptionMode mode); void msw_rescale(); + void sys_color_changed(); Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const; bool set_value(const t_config_option_key& opt_key, const boost::any& value); ConfigOptionsGroupShp new_optgroup(const wxString& title, int noncommon_label_width = -1); @@ -318,6 +311,7 @@ class Tab: public wxPanel void update_mode(); void update_visibility(); virtual void msw_rescale(); + virtual void sys_color_changed(); Field* get_field(const t_config_option_key& opt_key, int opt_index = -1) const; Field* get_field(const t_config_option_key &opt_key, Page** selected_page, int opt_index = -1); bool set_value(const t_config_option_key& opt_key, const boost::any& value); @@ -436,6 +430,7 @@ class TabPrinter : public Tab void on_preset_loaded() override; void init_options_list() override; void msw_rescale() override; + void sys_color_changed() override; bool supports_printer_technology(const PrinterTechnology /* tech */) override { return true; } wxSizer* create_bed_shape_widget(wxWindow* parent); diff --git a/src/slic3r/GUI/fts_fuzzy_match.h b/src/slic3r/GUI/fts_fuzzy_match.h index e692232cc17..34b2bbc5a7f 100644 --- a/src/slic3r/GUI/fts_fuzzy_match.h +++ b/src/slic3r/GUI/fts_fuzzy_match.h @@ -120,7 +120,7 @@ namespace fts { char *end = Slic3r::fold_to_ascii(*str, tmp); char *c = tmp; for (const wchar_t* d = pattern; c != end && *d != 0 && wchar_t(std::tolower(*c)) == std::tolower(*d); ++c, ++d); - if (c == end) { + if (c == end) { folded_match = true; num_matched = end - tmp; } diff --git a/src/slic3r/GUI/wxExtensions.cpp b/src/slic3r/GUI/wxExtensions.cpp index dd56f2acdd8..ad9f0a121e0 100644 --- a/src/slic3r/GUI/wxExtensions.cpp +++ b/src/slic3r/GUI/wxExtensions.cpp @@ -13,6 +13,7 @@ #include "GUI_ObjectList.hpp" #include "I18N.hpp" #include "GUI_Utils.hpp" +#include "Plater.hpp" #include "../Utils/MacDarkMode.hpp" #ifndef __WXGTK__// msw_menuitem_bitmaps is used for MSW and OSX diff --git a/src/slic3r/Utils/FixModelByWin10.cpp b/src/slic3r/Utils/FixModelByWin10.cpp index 0de526432b0..8de4991d85c 100644 --- a/src/slic3r/Utils/FixModelByWin10.cpp +++ b/src/slic3r/Utils/FixModelByWin10.cpp @@ -337,8 +337,8 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx) // Open a progress dialog. wxProgressDialog progress_dialog( - _(L("Model fixing")), - _(L("Exporting model...")), + _L("Model fixing"), + _L("Exporting model") + "...", 100, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT); // Executing the calculation in a background thread, so that the COM context could be created with its own threading model. // (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context). diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index b86775c88c3..c32613c468b 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -25,6 +25,7 @@ #include "slic3r/GUI/UpdateDialogs.hpp" #include "slic3r/GUI/ConfigWizard.hpp" #include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/format.hpp" #include "slic3r/Utils/Http.hpp" #include "slic3r/Config/Version.hpp" diff --git a/src/slic3r/Utils/SLAImport.hpp b/src/slic3r/Utils/SLAImport.hpp index a819bd7e766..73995014f41 100644 --- a/src/slic3r/Utils/SLAImport.hpp +++ b/src/slic3r/Utils/SLAImport.hpp @@ -4,7 +4,6 @@ #include #include -#include #include namespace Slic3r { diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index 75a9c313723..c69e722af38 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -19,5 +19,9 @@ add_executable(${_TEST_NAME}_tests target_link_libraries(${_TEST_NAME}_tests test_common libslic3r) set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests") +if (WIN32) + prusaslicer_copy_dlls(${_TEST_NAME}_tests) +endif() + # catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ") add_test(${_TEST_NAME}_tests ${_TEST_NAME}_tests ${CATCH_EXTRA_ARGS}) diff --git a/tests/fff_print/test_print.cpp b/tests/fff_print/test_print.cpp index 484e6018efb..3250443ed0b 100644 --- a/tests/fff_print/test_print.cpp +++ b/tests/fff_print/test_print.cpp @@ -2,6 +2,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Print.hpp" +#include "libslic3r/Layer.hpp" #include "test_data.hpp" diff --git a/tests/fff_print/test_printobject.cpp b/tests/fff_print/test_printobject.cpp index e7c441e8c98..84df95201ab 100644 --- a/tests/fff_print/test_printobject.cpp +++ b/tests/fff_print/test_printobject.cpp @@ -2,6 +2,7 @@ #include "libslic3r/libslic3r.h" #include "libslic3r/Print.hpp" +#include "libslic3r/Layer.hpp" #include "test_data.hpp" diff --git a/tests/fff_print/test_support_material.cpp b/tests/fff_print/test_support_material.cpp index 0a38d138e66..663b715a984 100644 --- a/tests/fff_print/test_support_material.cpp +++ b/tests/fff_print/test_support_material.cpp @@ -1,6 +1,7 @@ #include #include "libslic3r/GCodeReader.hpp" +#include "libslic3r/Layer.hpp" #include "test_data.hpp" // get access to init_print, etc diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index b41dbf8baff..5a1e8f18b7c 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -3,6 +3,7 @@ get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests.cpp test_3mf.cpp + test_aabbindirect.cpp test_clipper_offset.cpp test_clipper_utils.cpp test_config.cpp @@ -11,10 +12,11 @@ add_executable(${_TEST_NAME}_tests test_placeholder_parser.cpp test_polygon.cpp test_stl.cpp - test_meshsimplify.cpp - test_meshboolean.cpp - test_marchingsquares.cpp - test_timeutils.cpp + test_meshsimplify.cpp + test_meshboolean.cpp + test_marchingsquares.cpp + test_timeutils.cpp + test_voronoi.cpp ) if (TARGET OpenVDB::openvdb) diff --git a/tests/libslic3r/test_aabbindirect.cpp b/tests/libslic3r/test_aabbindirect.cpp new file mode 100644 index 00000000000..c0792a94332 --- /dev/null +++ b/tests/libslic3r/test_aabbindirect.cpp @@ -0,0 +1,61 @@ +#include +#include + +#include +#include + +using namespace Slic3r; + +TEST_CASE("Building a tree over a box, ray caster and closest query", "[AABBIndirect]") +{ + TriangleMesh tmesh = make_cube(1., 1., 1.); + tmesh.repair(); + + auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(tmesh.its.vertices, tmesh.its.indices); + REQUIRE(! tree.empty()); + + igl::Hit hit; + bool intersected = AABBTreeIndirect::intersect_ray_first_hit( + tmesh.its.vertices, tmesh.its.indices, + tree, + Vec3d(0.5, 0.5, -5.), + Vec3d(0., 0., 1.), + hit); + + REQUIRE(intersected); + REQUIRE(hit.t == Approx(5.)); + + std::vector hits; + bool intersected2 = AABBTreeIndirect::intersect_ray_all_hits( + tmesh.its.vertices, tmesh.its.indices, + tree, + Vec3d(0.3, 0.5, -5.), + Vec3d(0., 0., 1.), + hits); + REQUIRE(intersected2); + REQUIRE(hits.size() == 2); + REQUIRE(hits.front().t == Approx(5.)); + REQUIRE(hits.back().t == Approx(6.)); + + size_t hit_idx; + Vec3d closest_point; + double squared_distance = AABBTreeIndirect::squared_distance_to_indexed_triangle_set( + tmesh.its.vertices, tmesh.its.indices, + tree, + Vec3d(0.3, 0.5, -5.), + hit_idx, closest_point); + REQUIRE(squared_distance == Approx(5. * 5.)); + REQUIRE(closest_point.x() == Approx(0.3)); + REQUIRE(closest_point.y() == Approx(0.5)); + REQUIRE(closest_point.z() == Approx(0.)); + + squared_distance = AABBTreeIndirect::squared_distance_to_indexed_triangle_set( + tmesh.its.vertices, tmesh.its.indices, + tree, + Vec3d(0.3, 0.5, 5.), + hit_idx, closest_point); + REQUIRE(squared_distance == Approx(4. * 4.)); + REQUIRE(closest_point.x() == Approx(0.3)); + REQUIRE(closest_point.y() == Approx(0.5)); + REQUIRE(closest_point.z() == Approx(1.)); +} diff --git a/tests/libslic3r/test_voronoi.cpp b/tests/libslic3r/test_voronoi.cpp new file mode 100644 index 00000000000..ba318e4fd62 --- /dev/null +++ b/tests/libslic3r/test_voronoi.cpp @@ -0,0 +1,1397 @@ +#include +#include + +#include +#include +#include +#include + +#include + +#include + +// #define VORONOI_DEBUG_OUT + +#ifdef VORONOI_DEBUG_OUT +#include +#endif + +using boost::polygon::voronoi_builder; +using boost::polygon::voronoi_diagram; + +using namespace Slic3r; + +using VD = Geometry::VoronoiDiagram; + +// https://svn.boost.org/trac10/ticket/12067 +// This bug seems to be confirmed. +// Vojtech supposes that there may be no Voronoi edges produced for +// the 1st and last sweep line positions. +TEST_CASE("Voronoi missing edges - points 12067", "[Voronoi]") +{ + Points pts { + { -10, -20 }, + { 10, -20 }, + { 5, 0 }, + { 10, 20 }, + { -10, 20 }, + { -5, 0 } + }; + + // Construction of the Voronoi Diagram. + VD vd; + construct_voronoi(pts.begin(), pts.end(), &vd); + +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-pts.svg").c_str(), + vd, pts, Lines()); +#endif + +// REQUIRE(closest_point.z() == Approx(1.)); +} + +// https://svn.boost.org/trac10/ticket/12707 +// This issue is confirmed, there are no self intersections in the polygon. +// A minimal test case is created at the end of this test, +// a new issue opened with the minimal test case: +// https://github.com/boostorg/polygon/issues/43 +TEST_CASE("Voronoi missing edges - Alessandro gapfill 12707", "[Voronoi]") +{ + Lines lines0 { + { { 42127548, 699996}, { 42127548, 10135750 } }, + { { 42127548, 10135750}, { 50487352, 10135750 } }, + { { 50487352, 10135750}, { 50487352, 699995 } }, + { { 50487352, 699995}, { 51187348, 0 } }, + { { 51187348, 0}, { 64325952, 0 } }, + { { 64325952, 0}, { 64325952, 699996 } }, + { { 64325952, 699996}, { 51187348, 699996 } }, + { { 51187348, 699996}, { 51187348, 10835701 } }, + { { 51187348, 10835701}, { 41427552, 10835701 } }, + { { 41427552, 10835701}, { 41427552, 699996 } }, + { { 41427552, 699996}, { 28664848, 699996 } }, + { { 28664848, 699996}, { 28664848, 10835701 } }, + { { 28664848, 10835701}, { 19280052, 10835701 } }, + { { 19280052, 10835701}, { 27964852, 699996 } }, + { { 27964852, 699996}, { 28664848, 0 } }, + { { 28664848, 0}, { 41427551, 0 } }, + { { 41427551, 0}, { 42127548, 699996 } } + }; + + Lines lines1 { + { { 42127548, 699996}, { 42127548, 10135750 } }, + { { 42127548, 10135750}, { 50487352, 10135750 } }, + { { 50487352, 10135750}, { 50487352, 699995 } }, + { { 50487352, 699995}, { 51187348, 0 } }, + { { 51187348, 0}, { 51187348, 10835701 } }, + { { 51187348, 10835701}, { 41427552, 10835701 } }, + { { 41427552, 10835701}, { 41427552, 699996 } }, + { { 41427552, 699996}, { 28664848, 699996 } }, + { { 28664848, 699996}, { 28664848, 10835701 } }, + { { 28664848, 10835701}, { 19280052, 10835701 } }, + { { 19280052, 10835701}, { 27964852, 699996 } }, + { { 27964852, 699996}, { 28664848, 0 } }, + { { 28664848, 0}, { 41427551, 0 } }, + { { 41427551, 0}, { 42127548, 699996 } } + }; + + Lines lines2 { + { { 42127548, 699996}, { 42127548, 10135750 } }, + { { 42127548, 10135750}, { 50487352, 10135750 } }, + { { 50487352, 10135750}, { 50487352, 699995 } }, + { { 50487352, 699995}, { 51187348, 0 } }, + { { 51187348, 0}, { 51187348, 10835701 } }, + { { 51187348, 10835701}, { 41427552, 10835701 } }, + { { 41427552, 10835701}, { 41427552, 699996 } }, + { { 41427552, 699996}, { 28664848, 699996 } }, + { { 28664848, 699996}, { 28664848, 10835701 } }, + { { 28664848, 10835701}, { 19280052, 10835701 } }, + { { 19280052, 10835701}, { 28664848, 0 } }, + { { 28664848, 0}, { 41427551, 0 } }, + { { 41427551, 0}, { 42127548, 699996 } } + }; + + Lines lines3 { + { { 42127548, 699996}, { 42127548, 10135750 } }, + { { 42127548, 10135750}, { 50487352, 10135750 } }, + { { 50487352, 10135750}, { 50487352, 699995 } }, + { { 50487352, 699995}, { 51187348, 0 } }, + { { 51187348, 0}, { 51187348, 10835701 } }, + { { 51187348, 10835701}, { 41427552, 10835701 } }, + { { 41427552, 10835701}, { 41427552, 699996 } }, + { { 41427552, 699996}, { 41427551, 0 } }, + { { 41427551, 0}, { 42127548, 699996 } } + }; + + Lines lines4 { + { { 42127548, 699996}, { 42127548, 10135750 } }, + { { 42127548, 10135750}, { 50487352, 10135750 } }, + { { 50487352, 10135750}, { 50487352, 699995 } }, + { { 50487352, 699995}, { 51187348, 0 } }, + { { 51187348, 0}, { 51187348, 10835701 } }, + { { 51187348, 10835701}, { 41427552, 10835701 } }, + { { 41427552, 10835701}, { 41427551, 0 } }, + { { 41427551, 0}, { 42127548, 699996 } } + }; + + Lines lines = to_lines(Polygon { + { 0, 10000000}, + { 700000, 1}, // it has to be 1, higher number, zero or -1 work. + { 700000, 9000000}, + { 9100000, 9000000}, + { 9100000, 0}, + {10000000, 10000000} + }); + + Polygon poly; + std::mt19937 gen; + std::uniform_int_distribution dist(-100, 100); + for (size_t i = 0; i < lines.size(); ++ i) { + Line &l1 = lines[i]; + Line &l2 = lines[(i + 1) % lines.size()]; + REQUIRE(l1.b.x() == l2.a.x()); + REQUIRE(l1.b.y() == l2.a.y()); +#if 0 + // Wiggle the points a bit to find out whether this fixes the voronoi diagram for this particular polygon. + l1.b.x() = (l2.a.x() += dist(gen)); + l1.b.y() = (l2.a.y() += dist(gen)); +#endif + poly.points.emplace_back(l1.a); + } + + REQUIRE(intersecting_edges({ poly }).empty()); + + VD vd; + construct_voronoi(lines.begin(), lines.end(), &vd); + +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-lines.svg").c_str(), + vd, Points(), lines); +#endif +} + +// https://svn.boost.org/trac10/ticket/12903 +// division by zero reported, but this issue is most likely a non-issue, as it produces an infinity for the interval of validity +// of the floating point calculation, therefore forcing a recalculation with extended accuracy. +TEST_CASE("Voronoi division by zero 12903", "[Voronoi]") +{ + Points pts { { 1, 1 }, { 3, 1 }, { 1, 3 }, { 3, 3 }, + { -1, 1 }, { 1, -1 }, { 5, 1 }, { 3, -1 }, + { -1, 3 }, { 1, 5 }, { 5, 3 }, { 3, 5 } }; + { + auto pts2 { pts }; + std::sort(pts2.begin(), pts2.end(), [](auto &l, auto &r) { return (l.x() == r.x()) ? l.y() < r.y() : l.x() < r.x(); }); + // No point removed -> no duplicate. + REQUIRE(std::unique(pts2.begin(), pts2.end()) == pts2.end()); + } + + VD vd; + construct_voronoi(pts.begin(), pts.end(), &vd); + +#ifdef VORONOI_DEBUG_OUT + // Scale the voronoi vertices and input points, so that the dump_voronoi_to_svg will display them correctly. + for (auto &pt : vd.vertices()) { + const_cast(pt.x()) = scale_(pt.x()); + const_cast(pt.y()) = scale_(pt.y()); + } + for (auto &pt : pts) + pt = Point::new_scale(pt.x(), pt.y()); + dump_voronoi_to_svg(debug_out_path("voronoi-div-by-zero.svg").c_str(), vd, pts, Lines()); +#endif +} + +// https://svn.boost.org/trac10/ticket/12139 +// Funny sample from a dental industry? +// Vojtech confirms this test fails and rightly so, because the input data contain self intersections. +// This test is suppressed. +TEST_CASE("Voronoi NaN coordinates 12139", "[Voronoi][!hide][!mayfail]") +{ + Lines lines = { + { { 260500,1564400 }, { 261040,1562960 } }, + { { 261040,1562960 }, { 260840,1561780 } }, + { { 260840,1561780 }, { 262620,1561480 } }, + { { 262620,1561480 }, { 263160,1561220 } }, + { { 263160,1561220 }, { 264100,1563259 } }, + { { 264100,1563259 }, { 262380,1566980 } }, + { { 262380,1566980 }, { 260500,1564400 } }, + { { 137520,1851640 }, { 132160,1851100 } }, + { { 132160,1851100 }, { 126460,1848779 } }, + { { 126460,1848779 }, { 123960,1847320 } }, + { { 123960,1847320 }, { 120960,1844559 } }, + { { 120960,1844559 }, { 119640,1843040 } }, + { { 119640,1843040 }, { 118320,1840900 } }, + { { 118320,1840900 }, { 117920,1838120 } }, + { { 117920,1838120 }, { 118219,1833340 } }, + { { 118219,1833340 }, { 116180,1835000 } }, + { { 116180,1835000 }, { 115999,1834820 } }, + { { 115999,1834820 }, { 114240,1836340 } }, + { { 114240,1836340 }, { 112719,1837260 } }, + { { 112719,1837260 }, { 109460,1838239 } }, + { { 109460,1838239 }, { 103639,1837480 } }, + { { 103639,1837480 }, { 99819,1835460 } }, + { { 99819,1835460 }, { 96320,1834260 } }, + { { 96320,1834260 }, { 95339,1834260 } }, + { { 95339,1834260 }, { 93660,1833720 } }, + { { 93660,1833720 }, { 90719,1833300 } }, + { { 90719,1833300 }, { 87860,1831660 } }, + { { 87860,1831660 }, { 84580,1830499 } }, + { { 84580,1830499 }, { 79780,1827419 } }, + { { 79780,1827419 }, { 76020,1824280 } }, + { { 76020,1824280 }, { 73680,1821180 } }, + { { 73680,1821180 }, { 72560,1818960 } }, + { { 72560,1818960 }, { 71699,1817719 } }, + { { 71699,1817719 }, { 70280,1814260 } }, + { { 70280,1814260 }, { 69460,1811060 } }, + { { 69460,1811060 }, { 69659,1807320 } }, + { { 69659,1807320 }, { 69640,1803300 } }, + { { 69640,1803300 }, { 69360,1799780 } }, + { { 69360,1799780 }, { 69320,1796720 } }, + { { 69320,1796720 }, { 69640,1793980 } }, + { { 69640,1793980 }, { 70160,1791780 } }, + { { 70160,1791780 }, { 72460,1784879 } }, + { { 72460,1784879 }, { 74420,1780780 } }, + { { 74420,1780780 }, { 76500,1772899 } }, + { { 76500,1772899 }, { 76760,1769359 } }, + { { 76760,1769359 }, { 76480,1766259 } }, + { { 76480,1766259 }, { 76839,1760360 } }, + { { 76839,1760360 }, { 77539,1756680 } }, + { { 77539,1756680 }, { 80540,1748140 } }, + { { 80540,1748140 }, { 84200,1742619 } }, + { { 84200,1742619 }, { 90900,1735220 } }, + { { 90900,1735220 }, { 94159,1732679 } }, + { { 94159,1732679 }, { 101259,1729559 } }, + { { 101259,1729559 }, { 107299,1727939 } }, + { { 107299,1727939 }, { 110979,1727919 } }, + { { 110979,1727919 }, { 113499,1727240 } }, + { { 113499,1727240 }, { 113619,1727359 } }, + { { 113619,1727359 }, { 114280,1727280 } }, + { { 114280,1727280 }, { 131440,1732560 } }, + { { 131440,1732560 }, { 118140,1727119 } }, + { { 118140,1727119 }, { 117120,1723759 } }, + { { 117120,1723759 }, { 113840,1720660 } }, + { { 113840,1720660 }, { 111399,1716760 } }, + { { 111399,1716760 }, { 109700,1712979 } }, + { { 109700,1712979 }, { 108879,1708400 } }, + { { 108879,1708400 }, { 108060,1696360 } }, + { { 108060,1696360 }, { 110040,1687760 } }, + { { 110040,1687760 }, { 112140,1682480 } }, + { { 112140,1682480 }, { 112540,1681780 } }, + { { 112540,1681780 }, { 115260,1678320 } }, + { { 115260,1678320 }, { 118720,1675320 } }, + { { 118720,1675320 }, { 126100,1670980 } }, + { { 126100,1670980 }, { 132400,1668080 } }, + { { 132400,1668080 }, { 136700,1667440 } }, + { { 136700,1667440 }, { 142440,1667159 } }, + { { 142440,1667159 }, { 143340,1666720 } }, + { { 143340,1666720 }, { 138679,1661319 } }, + { { 138679,1661319 }, { 137240,1657480 } }, + { { 137240,1657480 }, { 136760,1650739 } }, + { { 136760,1650739 }, { 136780,1647339 } }, + { { 136780,1647339 }, { 135940,1644280 } }, + { { 135940,1644280 }, { 136000,1640820 } }, + { { 136000,1640820 }, { 135480,1638020 } }, + { { 135480,1638020 }, { 137060,1634220 } }, + { { 137060,1634220 }, { 136320,1631340 } }, + { { 136320,1631340 }, { 134620,1629700 } }, + { { 134620,1629700 }, { 132460,1628199 } }, + { { 132460,1628199 }, { 132299,1627860 } }, + { { 132299,1627860 }, { 138360,1618020 } }, + { { 138360,1618020 }, { 142440,1611859 } }, + { { 142440,1611859 }, { 143180,1611299 } }, + { { 143180,1611299 }, { 144000,1611259 } }, + { { 144000,1611259 }, { 145960,1612540 } }, + { { 145960,1612540 }, { 146720,1613700 } }, + { { 146720,1613700 }, { 147700,1613539 } }, + { { 147700,1613539 }, { 148520,1614039 } }, + { { 148520,1614039 }, { 149840,1613740 } }, + { { 149840,1613740 }, { 150620,1614079 } }, + { { 150620,1614079 }, { 154760,1612740 } }, + { { 154760,1612740 }, { 159000,1608420 } }, + { { 159000,1608420 }, { 161120,1606780 } }, + { { 161120,1606780 }, { 164060,1605139 } }, + { { 164060,1605139 }, { 168079,1603620 } }, + { { 168079,1603620 }, { 170240,1603400 } }, + { { 170240,1603400 }, { 172400,1603499 } }, + { { 172400,1603499 }, { 194440,1613740 } }, + { { 194440,1613740 }, { 195880,1616460 } }, + { { 195880,1616460 }, { 197060,1618140 } }, + { { 197060,1618140 }, { 198039,1617860 } }, + { { 198039,1617860 }, { 198739,1618900 } }, + { { 198739,1618900 }, { 200259,1619200 } }, + { { 200259,1619200 }, { 201940,1618920 } }, + { { 201940,1618920 }, { 201700,1617139 } }, + { { 201700,1617139 }, { 203860,1618179 } }, + { { 203860,1618179 }, { 203500,1617540 } }, + { { 203500,1617540 }, { 205000,1616579 } }, + { { 205000,1616579 }, { 206780,1615020 } }, + { { 206780,1615020 }, { 210159,1614059 } }, + { { 210159,1614059 }, { 217080,1611080 } }, + { { 217080,1611080 }, { 219200,1611579 } }, + { { 219200,1611579 }, { 223219,1610980 } }, + { { 223219,1610980 }, { 224580,1610540 } }, + { { 224580,1610540 }, { 227460,1611440 } }, + { { 227460,1611440 }, { 229359,1611859 } }, + { { 229359,1611859 }, { 230620,1612580 } }, + { { 230620,1612580 }, { 232340,1614460 } }, + { { 232340,1614460 }, { 232419,1617040 } }, + { { 232419,1617040 }, { 231740,1619480 } }, + { { 231740,1619480 }, { 231880,1624899 } }, + { { 231880,1624899 }, { 231540,1625820 } }, + { { 231540,1625820 }, { 231700,1627079 } }, + { { 231700,1627079 }, { 231320,1628239 } }, + { { 231320,1628239 }, { 231420,1636080 } }, + { { 231420,1636080 }, { 231099,1637200 } }, + { { 231099,1637200 }, { 228660,1643280 } }, + { { 228660,1643280 }, { 227699,1644960 } }, + { { 227699,1644960 }, { 226080,1651140 } }, + { { 226080,1651140 }, { 225259,1653420 } }, + { { 225259,1653420 }, { 225159,1655399 } }, + { { 225159,1655399 }, { 223760,1659260 } }, + { { 223760,1659260 }, { 219860,1666360 } }, + { { 219860,1666360 }, { 219180,1667220 } }, + { { 219180,1667220 }, { 212580,1673680 } }, + { { 212580,1673680 }, { 207880,1676460 } }, + { { 207880,1676460 }, { 205560,1677560 } }, + { { 205560,1677560 }, { 199700,1678920 } }, + { { 199700,1678920 }, { 195280,1679420 } }, + { { 195280,1679420 }, { 193939,1679879 } }, + { { 193939,1679879 }, { 188780,1679440 } }, + { { 188780,1679440 }, { 188100,1679639 } }, + { { 188100,1679639 }, { 186680,1679339 } }, + { { 186680,1679339 }, { 184760,1679619 } }, + { { 184760,1679619 }, { 183520,1681440 } }, + { { 183520,1681440 }, { 183860,1682200 } }, + { { 183860,1682200 }, { 186620,1686120 } }, + { { 186620,1686120 }, { 190380,1688380 } }, + { { 190380,1688380 }, { 192780,1690739 } }, + { { 192780,1690739 }, { 195860,1694839 } }, + { { 195860,1694839 }, { 196620,1696539 } }, + { { 196620,1696539 }, { 197540,1701819 } }, + { { 197540,1701819 }, { 198939,1705699 } }, + { { 198939,1705699 }, { 198979,1711819 } }, + { { 198979,1711819 }, { 198240,1716900 } }, + { { 198240,1716900 }, { 197440,1720139 } }, + { { 197440,1720139 }, { 195340,1724639 } }, + { { 195340,1724639 }, { 194040,1726140 } }, + { { 194040,1726140 }, { 192559,1728239 } }, + { { 192559,1728239 }, { 187780,1732339 } }, + { { 187780,1732339 }, { 182519,1735520 } }, + { { 182519,1735520 }, { 181239,1736140 } }, + { { 181239,1736140 }, { 177340,1737619 } }, + { { 177340,1737619 }, { 175439,1738140 } }, + { { 175439,1738140 }, { 171380,1738880 } }, + { { 171380,1738880 }, { 167860,1739059 } }, + { { 167860,1739059 }, { 166040,1738920 } }, + { { 166040,1738920 }, { 163680,1738539 } }, + { { 163680,1738539 }, { 157660,1736859 } }, + { { 157660,1736859 }, { 154900,1735460 } }, + { { 154900,1735460 }, { 151420,1735159 } }, + { { 151420,1735159 }, { 142100,1736160 } }, + { { 142100,1736160 }, { 140880,1735920 } }, + { { 140880,1735920 }, { 142820,1736859 } }, + { { 142820,1736859 }, { 144080,1737240 } }, + { { 144080,1737240 }, { 144280,1737460 } }, + { { 144280,1737460 }, { 144239,1738120 } }, + { { 144239,1738120 }, { 144980,1739420 } }, + { { 144980,1739420 }, { 146340,1741039 } }, + { { 146340,1741039 }, { 147160,1741720 } }, + { { 147160,1741720 }, { 154260,1745800 } }, + { { 154260,1745800 }, { 156560,1746879 } }, + { { 156560,1746879 }, { 165180,1752679 } }, + { { 165180,1752679 }, { 168240,1755860 } }, + { { 168240,1755860 }, { 170940,1759260 } }, + { { 170940,1759260 }, { 173440,1762079 } }, + { { 173440,1762079 }, { 174540,1764079 } }, + { { 174540,1764079 }, { 176479,1766640 } }, + { { 176479,1766640 }, { 178900,1768960 } }, + { { 178900,1768960 }, { 180819,1772780 } }, + { { 180819,1772780 }, { 181479,1776859 } }, + { { 181479,1776859 }, { 181660,1788499 } }, + { { 181660,1788499 }, { 181460,1791740 } }, + { { 181460,1791740 }, { 181160,1792840 } }, + { { 181160,1792840 }, { 179580,1797180 } }, + { { 179580,1797180 }, { 174620,1808960 } }, + { { 174620,1808960 }, { 174100,1809839 } }, + { { 174100,1809839 }, { 171660,1812419 } }, + { { 171660,1812419 }, { 169639,1813840 } }, + { { 169639,1813840 }, { 168880,1814720 } }, + { { 168880,1814720 }, { 168960,1815980 } }, + { { 168960,1815980 }, { 169979,1819160 } }, + { { 169979,1819160 }, { 170080,1820159 } }, + { { 170080,1820159 }, { 168280,1830540 } }, + { { 168280,1830540 }, { 167580,1832200 } }, + { { 167580,1832200 }, { 165679,1835720 } }, + { { 165679,1835720 }, { 164720,1836819 } }, + { { 164720,1836819 }, { 161840,1841740 } }, + { { 161840,1841740 }, { 159880,1843519 } }, + { { 159880,1843519 }, { 158959,1844120 } }, + { { 158959,1844120 }, { 154960,1847500 } }, + { { 154960,1847500 }, { 152140,1848580 } }, + { { 152140,1848580 }, { 150440,1849520 } }, + { { 150440,1849520 }, { 144940,1850980 } }, + { { 144940,1850980 }, { 138340,1851700 } }, + { { 138340,1851700 }, { 137520,1851640 } }, + { { 606940,1873860 }, { 602860,1872460 } }, + { { 602860,1872460 }, { 600680,1871539 } }, + { { 600680,1871539 }, { 599300,1870640 } }, + { { 599300,1870640 }, { 598120,1869579 } }, + { { 598120,1869579 }, { 594680,1867180 } }, + { { 594680,1867180 }, { 589680,1861460 } }, + { { 589680,1861460 }, { 586300,1855020 } }, + { { 586300,1855020 }, { 584700,1848060 } }, + { { 584700,1848060 }, { 585199,1843499 } }, + { { 585199,1843499 }, { 584000,1842079 } }, + { { 584000,1842079 }, { 582900,1841480 } }, + { { 582900,1841480 }, { 581020,1839899 } }, + { { 581020,1839899 }, { 579440,1838040 } }, + { { 579440,1838040 }, { 577840,1834299 } }, + { { 577840,1834299 }, { 576160,1831859 } }, + { { 576160,1831859 }, { 574540,1828499 } }, + { { 574540,1828499 }, { 572140,1822860 } }, + { { 572140,1822860 }, { 570180,1815219 } }, + { { 570180,1815219 }, { 570080,1812280 } }, + { { 570080,1812280 }, { 570340,1808300 } }, + { { 570340,1808300 }, { 570160,1807119 } }, + { { 570160,1807119 }, { 570140,1804039 } }, + { { 570140,1804039 }, { 571640,1796660 } }, + { { 571640,1796660 }, { 571740,1794680 } }, + { { 571740,1794680 }, { 572279,1794039 } }, + { { 572279,1794039 }, { 575480,1788300 } }, + { { 575480,1788300 }, { 576379,1787419 } }, + { { 576379,1787419 }, { 577020,1786120 } }, + { { 577020,1786120 }, { 578000,1785100 } }, + { { 578000,1785100 }, { 579960,1783720 } }, + { { 579960,1783720 }, { 581420,1782079 } }, + { { 581420,1782079 }, { 585480,1778440 } }, + { { 585480,1778440 }, { 586680,1777079 } }, + { { 586680,1777079 }, { 590520,1774639 } }, + { { 590520,1774639 }, { 592440,1773199 } }, + { { 592440,1773199 }, { 595160,1772260 } }, + { { 595160,1772260 }, { 598079,1770920 } }, + { { 598079,1770920 }, { 601420,1769019 } }, + { { 601420,1769019 }, { 606400,1767280 } }, + { { 606400,1767280 }, { 607320,1766620 } }, + { { 607320,1766620 }, { 605760,1766460 } }, + { { 605760,1766460 }, { 604420,1766780 } }, + { { 604420,1766780 }, { 601660,1766579 } }, + { { 601660,1766579 }, { 597160,1766980 } }, + { { 597160,1766980 }, { 591420,1766720 } }, + { { 591420,1766720 }, { 585360,1765460 } }, + { { 585360,1765460 }, { 578540,1763680 } }, + { { 578540,1763680 }, { 574020,1761599 } }, + { { 574020,1761599 }, { 572520,1760560 } }, + { { 572520,1760560 }, { 570959,1759000 } }, + { { 570959,1759000 }, { 566580,1755620 } }, + { { 566580,1755620 }, { 563820,1752000 } }, + { { 563820,1752000 }, { 563140,1751380 } }, + { { 563140,1751380 }, { 560800,1747899 } }, + { { 560800,1747899 }, { 558640,1742280 } }, + { { 558640,1742280 }, { 557860,1741620 } }, + { { 557860,1741620 }, { 555820,1739099 } }, + { { 555820,1739099 }, { 553920,1737540 } }, + { { 553920,1737540 }, { 551900,1735179 } }, + { { 551900,1735179 }, { 551180,1733880 } }, + { { 551180,1733880 }, { 549540,1729559 } }, + { { 549540,1729559 }, { 548860,1720720 } }, + { { 548860,1720720 }, { 549080,1719099 } }, + { { 549080,1719099 }, { 548200,1714700 } }, + { { 548200,1714700 }, { 547560,1713860 } }, + { { 547560,1713860 }, { 544500,1711259 } }, + { { 544500,1711259 }, { 543939,1709780 } }, + { { 543939,1709780 }, { 544520,1705439 } }, + { { 544520,1705439 }, { 543520,1701519 } }, + { { 543520,1701519 }, { 543920,1699319 } }, + { { 543920,1699319 }, { 546360,1697440 } }, + { { 546360,1697440 }, { 546680,1695419 } }, + { { 546680,1695419 }, { 545600,1694180 } }, + { { 545600,1694180 }, { 543220,1692000 } }, + { { 543220,1692000 }, { 538260,1685139 } }, + { { 538260,1685139 }, { 537540,1683000 } }, + { { 537540,1683000 }, { 537020,1682220 } }, + { { 537020,1682220 }, { 535560,1675940 } }, + { { 535560,1675940 }, { 535940,1671220 } }, + { { 535940,1671220 }, { 536320,1669379 } }, + { { 536320,1669379 }, { 535420,1666400 } }, + { { 535420,1666400 }, { 533540,1664460 } }, + { { 533540,1664460 }, { 530720,1662860 } }, + { { 530720,1662860 }, { 529240,1662260 } }, + { { 529240,1662260 }, { 528780,1659160 } }, + { { 528780,1659160 }, { 528820,1653560 } }, + { { 528820,1653560 }, { 529779,1650900 } }, + { { 529779,1650900 }, { 536760,1640840 } }, + { { 536760,1640840 }, { 540360,1636120 } }, + { { 540360,1636120 }, { 541160,1635380 } }, + { { 541160,1635380 }, { 544719,1629480 } }, + { { 544719,1629480 }, { 545319,1626140 } }, + { { 545319,1626140 }, { 543560,1623740 } }, + { { 543560,1623740 }, { 539880,1620739 } }, + { { 539880,1620739 }, { 533400,1617300 } }, + { { 533400,1617300 }, { 527840,1613020 } }, + { { 527840,1613020 }, { 525200,1611579 } }, + { { 525200,1611579 }, { 524360,1610800 } }, + { { 524360,1610800 }, { 517320,1605739 } }, + { { 517320,1605739 }, { 516240,1604240 } }, + { { 516240,1604240 }, { 515220,1602000 } }, + { { 515220,1602000 }, { 514079,1594240 } }, + { { 514079,1594240 }, { 513740,1581460 } }, + { { 513740,1581460 }, { 514660,1577359 } }, + { { 514660,1577359 }, { 514660,1576380 } }, + { { 514660,1576380 }, { 514199,1575380 } }, + { { 514199,1575380 }, { 514680,1572860 } }, + { { 514680,1572860 }, { 513440,1573940 } }, + { { 513440,1573940 }, { 512399,1575580 } }, + { { 512399,1575580 }, { 511620,1576220 } }, + { { 511620,1576220 }, { 507840,1581880 } }, + { { 507840,1581880 }, { 504600,1584579 } }, + { { 504600,1584579 }, { 502440,1584599 } }, + { { 502440,1584599 }, { 499060,1584059 } }, + { { 499060,1584059 }, { 498019,1581960 } }, + { { 498019,1581960 }, { 497819,1581240 } }, + { { 497819,1581240 }, { 498019,1576039 } }, + { { 498019,1576039 }, { 497539,1574740 } }, + { { 497539,1574740 }, { 495459,1574460 } }, + { { 495459,1574460 }, { 492320,1575600 } }, + { { 492320,1575600 }, { 491040,1576360 } }, + { { 491040,1576360 }, { 490080,1575640 } }, + { { 490080,1575640 }, { 490020,1575040 } }, + { { 490020,1575040 }, { 490220,1574400 } }, + { { 490220,1574400 }, { 490819,1573440 } }, + { { 490819,1573440 }, { 492680,1568259 } }, + { { 492680,1568259 }, { 492920,1566799 } }, + { { 492920,1566799 }, { 495760,1563660 } }, + { { 495760,1563660 }, { 496100,1562139 } }, + { { 496100,1562139 }, { 497879,1560240 } }, + { { 497879,1560240 }, { 497059,1558020 } }, + { { 497059,1558020 }, { 495620,1557399 } }, + { { 495620,1557399 }, { 494800,1556839 } }, + { { 494800,1556839 }, { 493500,1555479 } }, + { { 493500,1555479 }, { 491860,1554100 } }, + { { 491860,1554100 }, { 487840,1552139 } }, + { { 487840,1552139 }, { 485900,1551720 } }, + { { 485900,1551720 }, { 483639,1555439 } }, + { { 483639,1555439 }, { 482080,1556480 } }, + { { 482080,1556480 }, { 480200,1556259 } }, + { { 480200,1556259 }, { 478519,1556259 } }, + { { 478519,1556259 }, { 474020,1554019 } }, + { { 474020,1554019 }, { 472660,1551539 } }, + { { 472660,1551539 }, { 471260,1549899 } }, + { { 471260,1549899 }, { 470459,1548020 } }, + { { 470459,1548020 }, { 469920,1545479 } }, + { { 469920,1545479 }, { 469079,1542939 } }, + { { 469079,1542939 }, { 469120,1541799 } }, + { { 469120,1541799 }, { 465840,1537139 } }, + { { 465840,1537139 }, { 463360,1539059 } }, + { { 463360,1539059 }, { 459680,1546900 } }, + { { 459680,1546900 }, { 458439,1547160 } }, + { { 458439,1547160 }, { 456480,1549319 } }, + { { 456480,1549319 }, { 454160,1551400 } }, + { { 454160,1551400 }, { 452819,1550820 } }, + { { 452819,1550820 }, { 451699,1549839 } }, + { { 451699,1549839 }, { 449620,1548440 } }, + { { 449620,1548440 }, { 449419,1548080 } }, + { { 449419,1548080 }, { 447879,1547720 } }, + { { 447879,1547720 }, { 446540,1546819 } }, + { { 446540,1546819 }, { 445720,1545640 } }, + { { 445720,1545640 }, { 444800,1545100 } }, + { { 444800,1545100 }, { 443500,1542899 } }, + { { 443500,1542899 }, { 443320,1541799 } }, + { { 443320,1541799 }, { 443519,1540220 } }, + { { 443519,1540220 }, { 445060,1537099 } }, + { { 445060,1537099 }, { 445840,1533040 } }, + { { 445840,1533040 }, { 442720,1529079 } }, + { { 442720,1529079 }, { 442479,1528360 } }, + { { 442479,1528360 }, { 436820,1529240 } }, + { { 436820,1529240 }, { 436279,1529200 } }, + { { 436279,1529200 }, { 433280,1529859 } }, + { { 433280,1529859 }, { 420220,1529899 } }, + { { 420220,1529899 }, { 414740,1528539 } }, + { { 414740,1528539 }, { 411340,1527960 } }, + { { 411340,1527960 }, { 406860,1524660 } }, + { { 406860,1524660 }, { 405379,1523080 } }, + { { 405379,1523080 }, { 403639,1520320 } }, + { { 403639,1520320 }, { 402040,1517220 } }, + { { 402040,1517220 }, { 400519,1517059 } }, + { { 400519,1517059 }, { 399180,1516720 } }, + { { 399180,1516720 }, { 395300,1515179 } }, + { { 395300,1515179 }, { 394780,1515080 } }, + { { 394780,1515080 }, { 394759,1515900 } }, + { { 394759,1515900 }, { 394339,1516579 } }, + { { 394339,1516579 }, { 393200,1516640 } }, + { { 393200,1516640 }, { 392599,1521799 } }, + { { 392599,1521799 }, { 391699,1525200 } }, + { { 391699,1525200 }, { 391040,1525600 } }, + { { 391040,1525600 }, { 390540,1526500 } }, + { { 390540,1526500 }, { 388999,1527939 } }, + { { 388999,1527939 }, { 387059,1531100 } }, + { { 387059,1531100 }, { 386540,1531440 } }, + { { 386540,1531440 }, { 382140,1531839 } }, + { { 382140,1531839 }, { 377360,1532619 } }, + { { 377360,1532619 }, { 375640,1532220 } }, + { { 375640,1532220 }, { 372580,1531019 } }, + { { 372580,1531019 }, { 371079,1529019 } }, + { { 371079,1529019 }, { 367280,1526039 } }, + { { 367280,1526039 }, { 366460,1521900 } }, + { { 366460,1521900 }, { 364320,1516400 } }, + { { 364320,1516400 }, { 363779,1515780 } }, + { { 363779,1515780 }, { 362220,1515320 } }, + { { 362220,1515320 }, { 361979,1515060 } }, + { { 361979,1515060 }, { 360820,1515739 } }, + { { 360820,1515739 }, { 353360,1518620 } }, + { { 353360,1518620 }, { 347840,1520080 } }, + { { 347840,1520080 }, { 342399,1521140 } }, + { { 342399,1521140 }, { 334899,1523380 } }, + { { 334899,1523380 }, { 333220,1523400 } }, + { { 333220,1523400 }, { 332599,1522919 } }, + { { 332599,1522919 }, { 329780,1521640 } }, + { { 329780,1521640 }, { 325360,1521220 } }, + { { 325360,1521220 }, { 319000,1520999 } }, + { { 319000,1520999 }, { 316180,1520240 } }, + { { 316180,1520240 }, { 312700,1518960 } }, + { { 312700,1518960 }, { 310520,1517679 } }, + { { 310520,1517679 }, { 309280,1517260 } }, + { { 309280,1517260 }, { 306440,1515040 } }, + { { 306440,1515040 }, { 304140,1512780 } }, + { { 304140,1512780 }, { 301640,1509720 } }, + { { 301640,1509720 }, { 301500,1509879 } }, + { { 301500,1509879 }, { 300320,1509059 } }, + { { 300320,1509059 }, { 299140,1507339 } }, + { { 299140,1507339 }, { 297340,1502659 } }, + { { 297340,1502659 }, { 298960,1508280 } }, + { { 298960,1508280 }, { 299120,1509299 } }, + { { 299120,1509299 }, { 298720,1510100 } }, + { { 298720,1510100 }, { 298420,1512240 } }, + { { 298420,1512240 }, { 297420,1514540 } }, + { { 297420,1514540 }, { 296900,1515340 } }, + { { 296900,1515340 }, { 294780,1517500 } }, + { { 294780,1517500 }, { 293040,1518380 } }, + { { 293040,1518380 }, { 289140,1521360 } }, + { { 289140,1521360 }, { 283600,1523300 } }, + { { 283600,1523300 }, { 280140,1525220 } }, + { { 280140,1525220 }, { 279620,1525679 } }, + { { 279620,1525679 }, { 274960,1527379 } }, + { { 274960,1527379 }, { 273440,1528819 } }, + { { 273440,1528819 }, { 269840,1532840 } }, + { { 269840,1532840 }, { 264800,1536240 } }, + { { 264800,1536240 }, { 261199,1540419 } }, + { { 261199,1540419 }, { 257359,1541400 } }, + { { 257359,1541400 }, { 250460,1539299 } }, + { { 250460,1539299 }, { 250240,1539400 } }, + { { 250240,1539400 }, { 249840,1540460 } }, + { { 249840,1540460 }, { 249779,1541140 } }, + { { 249779,1541140 }, { 248482,1539783 } }, + { { 248482,1539783 }, { 251320,1544120 } }, + { { 251320,1544120 }, { 252500,1548320 } }, + { { 252500,1548320 }, { 252519,1549740 } }, + { { 252519,1549740 }, { 253000,1553140 } }, + { { 253000,1553140 }, { 252920,1556539 } }, + { { 252920,1556539 }, { 253160,1556700 } }, + { { 253160,1556700 }, { 254019,1558220 } }, + { { 254019,1558220 }, { 253039,1559339 } }, + { { 253039,1559339 }, { 252300,1561920 } }, + { { 252300,1561920 }, { 251080,1565260 } }, + { { 251080,1565260 }, { 251120,1566160 } }, + { { 251120,1566160 }, { 249979,1570240 } }, + { { 249979,1570240 }, { 248799,1575380 } }, + { { 248799,1575380 }, { 247180,1579520 } }, + { { 247180,1579520 }, { 243380,1588440 } }, + { { 243380,1588440 }, { 241700,1591780 } }, + { { 241700,1591780 }, { 240280,1593080 } }, + { { 240280,1593080 }, { 231859,1598380 } }, + { { 231859,1598380 }, { 228840,1600060 } }, + { { 228840,1600060 }, { 226420,1601080 } }, + { { 226420,1601080 }, { 223620,1601940 } }, + { { 223620,1601940 }, { 220919,1603819 } }, + { { 220919,1603819 }, { 219599,1604420 } }, + { { 219599,1604420 }, { 218380,1605200 } }, + { { 218380,1605200 }, { 213219,1607260 } }, + { { 213219,1607260 }, { 210040,1607740 } }, + { { 210040,1607740 }, { 186439,1596440 } }, + { { 186439,1596440 }, { 185159,1594559 } }, + { { 185159,1594559 }, { 182239,1588300 } }, + { { 182239,1588300 }, { 181040,1585380 } }, + { { 181040,1585380 }, { 180380,1578580 } }, + { { 180380,1578580 }, { 180679,1573220 } }, + { { 180679,1573220 }, { 181220,1568539 } }, + { { 181220,1568539 }, { 181859,1565020 } }, + { { 181859,1565020 }, { 184499,1555500 } }, + { { 184499,1555500 }, { 183480,1558160 } }, + { { 183480,1558160 }, { 182600,1561700 } }, + { { 182600,1561700 }, { 171700,1554359 } }, + { { 171700,1554359 }, { 176880,1545920 } }, + { { 176880,1545920 }, { 189940,1529000 } }, + { { 189940,1529000 }, { 200040,1535759 } }, + { { 200040,1535759 }, { 207559,1531660 } }, + { { 207559,1531660 }, { 218039,1527520 } }, + { { 218039,1527520 }, { 222360,1526640 } }, + { { 222360,1526640 }, { 225439,1526440 } }, + { { 225439,1526440 }, { 231160,1527079 } }, + { { 231160,1527079 }, { 232300,1527399 } }, + { { 232300,1527399 }, { 236579,1529140 } }, + { { 236579,1529140 }, { 238139,1529120 } }, + { { 238139,1529120 }, { 238799,1529319 } }, + { { 238799,1529319 }, { 240999,1531780 } }, + { { 240999,1531780 }, { 238280,1528799 } }, + { { 238280,1528799 }, { 236900,1523840 } }, + { { 236900,1523840 }, { 236800,1522700 } }, + { { 236800,1522700 }, { 235919,1518880 } }, + { { 235919,1518880 }, { 236080,1514299 } }, + { { 236080,1514299 }, { 238260,1508380 } }, + { { 238260,1508380 }, { 240119,1505159 } }, + { { 240119,1505159 }, { 233319,1496360 } }, + { { 233319,1496360 }, { 239140,1490759 } }, + { { 239140,1490759 }, { 258760,1478080 } }, + { { 258760,1478080 }, { 263940,1484760 } }, + { { 263940,1484760 }, { 263460,1485159 } }, + { { 263460,1485159 }, { 265960,1483519 } }, + { { 265960,1483519 }, { 270380,1482020 } }, + { { 270380,1482020 }, { 272880,1481420 } }, + { { 272880,1481420 }, { 275700,1481400 } }, + { { 275700,1481400 }, { 278380,1481740 } }, + { { 278380,1481740 }, { 281220,1482979 } }, + { { 281220,1482979 }, { 284680,1484859 } }, + { { 284680,1484859 }, { 285979,1486140 } }, + { { 285979,1486140 }, { 290220,1489100 } }, + { { 290220,1489100 }, { 292680,1489520 } }, + { { 292680,1489520 }, { 293280,1490240 } }, + { { 293280,1490240 }, { 293140,1489160 } }, + { { 293140,1489160 }, { 293280,1488580 } }, + { { 293280,1488580 }, { 294100,1486980 } }, + { { 294100,1486980 }, { 294580,1484960 } }, + { { 294580,1484960 }, { 295680,1481660 } }, + { { 295680,1481660 }, { 297840,1477339 } }, + { { 297840,1477339 }, { 302240,1472280 } }, + { { 302240,1472280 }, { 307120,1469000 } }, + { { 307120,1469000 }, { 314500,1466340 } }, + { { 314500,1466340 }, { 324979,1464740 } }, + { { 324979,1464740 }, { 338999,1462059 } }, + { { 338999,1462059 }, { 345599,1461579 } }, + { { 345599,1461579 }, { 349020,1461620 } }, + { { 349020,1461620 }, { 353420,1461160 } }, + { { 353420,1461160 }, { 357000,1461500 } }, + { { 357000,1461500 }, { 359860,1461579 } }, + { { 359860,1461579 }, { 364520,1462740 } }, + { { 364520,1462740 }, { 367280,1464000 } }, + { { 367280,1464000 }, { 372020,1467560 } }, + { { 372020,1467560 }, { 373999,1469980 } }, + { { 373999,1469980 }, { 375580,1472240 } }, + { { 375580,1472240 }, { 376680,1474460 } }, + { { 376680,1474460 }, { 377259,1478620 } }, + { { 377259,1478620 }, { 379279,1480880 } }, + { { 379279,1480880 }, { 379260,1481600 } }, + { { 379260,1481600 }, { 378760,1482000 } }, + { { 378760,1482000 }, { 379300,1482040 } }, + { { 379300,1482040 }, { 380220,1482460 } }, + { { 380220,1482460 }, { 380840,1483020 } }, + { { 380840,1483020 }, { 385519,1482600 } }, + { { 385519,1482600 }, { 386019,1482320 } }, + { { 386019,1482320 }, { 386499,1481600 } }, + { { 386499,1481600 }, { 386540,1480139 } }, + { { 386540,1480139 }, { 387500,1478220 } }, + { { 387500,1478220 }, { 388280,1476100 } }, + { { 388280,1476100 }, { 390060,1473000 } }, + { { 390060,1473000 }, { 393659,1469460 } }, + { { 393659,1469460 }, { 396540,1467860 } }, + { { 396540,1467860 }, { 401260,1466040 } }, + { { 401260,1466040 }, { 406200,1465100 } }, + { { 406200,1465100 }, { 410920,1465439 } }, + { { 410920,1465439 }, { 420659,1467399 } }, + { { 420659,1467399 }, { 433500,1471480 } }, + { { 433500,1471480 }, { 441340,1473540 } }, + { { 441340,1473540 }, { 448620,1475139 } }, + { { 448620,1475139 }, { 450720,1475880 } }, + { { 450720,1475880 }, { 453299,1477059 } }, + { { 453299,1477059 }, { 456620,1478940 } }, + { { 456620,1478940 }, { 458480,1480399 } }, + { { 458480,1480399 }, { 461100,1482780 } }, + { { 461100,1482780 }, { 463820,1486519 } }, + { { 463820,1486519 }, { 464780,1488199 } }, + { { 464780,1488199 }, { 466579,1493960 } }, + { { 466579,1493960 }, { 467120,1497700 } }, + { { 467120,1497700 }, { 466999,1500280 } }, + { { 466999,1500280 }, { 467300,1502580 } }, + { { 467300,1502580 }, { 467399,1505280 } }, + { { 467399,1505280 }, { 466979,1506920 } }, + { { 466979,1506920 }, { 467920,1504780 } }, + { { 467920,1504780 }, { 468159,1505040 } }, + { { 468159,1505040 }, { 469400,1504859 } }, + { { 469400,1504859 }, { 470300,1505540 } }, + { { 470300,1505540 }, { 471240,1505200 } }, + { { 471240,1505200 }, { 471579,1504280 } }, + { { 471579,1504280 }, { 473939,1502379 } }, + { { 473939,1502379 }, { 476860,1500200 } }, + { { 476860,1500200 }, { 479800,1498620 } }, + { { 479800,1498620 }, { 480840,1498120 } }, + { { 480840,1498120 }, { 485220,1497480 } }, + { { 485220,1497480 }, { 489979,1497460 } }, + { { 489979,1497460 }, { 494899,1498700 } }, + { { 494899,1498700 }, { 500099,1501320 } }, + { { 500099,1501320 }, { 501439,1501839 } }, + { { 501439,1501839 }, { 503400,1502939 } }, + { { 503400,1502939 }, { 510760,1508340 } }, + { { 510760,1508340 }, { 513640,1510920 } }, + { { 513640,1510920 }, { 518579,1514599 } }, + { { 518579,1514599 }, { 519020,1515260 } }, + { { 519020,1515260 }, { 520700,1516480 } }, + { { 520700,1516480 }, { 524960,1521480 } }, + { { 524960,1521480 }, { 526820,1524820 } }, + { { 526820,1524820 }, { 528280,1527820 } }, + { { 528280,1527820 }, { 529120,1533120 } }, + { { 529120,1533120 }, { 528820,1537139 } }, + { { 528820,1537139 }, { 527020,1543920 } }, + { { 527020,1543920 }, { 526959,1546780 } }, + { { 526959,1546780 }, { 526420,1548060 } }, + { { 526420,1548060 }, { 527020,1547919 } }, + { { 527020,1547919 }, { 527620,1548160 } }, + { { 527620,1548160 }, { 528980,1548020 } }, + { { 528980,1548020 }, { 535180,1544980 } }, + { { 535180,1544980 }, { 540860,1542979 } }, + { { 540860,1542979 }, { 546480,1542720 } }, + { { 546480,1542720 }, { 547420,1542860 } }, + { { 547420,1542860 }, { 551800,1544140 } }, + { { 551800,1544140 }, { 558740,1547939 } }, + { { 558740,1547939 }, { 569920,1556259 } }, + { { 569920,1556259 }, { 573660,1560220 } }, + { { 573660,1560220 }, { 573040,1559500 } }, + { { 573040,1559500 }, { 574740,1559220 } }, + { { 574740,1559220 }, { 588480,1562899 } }, + { { 588480,1562899 }, { 585180,1576019 } }, + { { 585180,1576019 }, { 583440,1577979 } }, + { { 583440,1577979 }, { 584280,1582399 } }, + { { 584280,1582399 }, { 584520,1588960 } }, + { { 584520,1588960 }, { 583420,1601620 } }, + { { 583420,1601620 }, { 582840,1603880 } }, + { { 582840,1603880 }, { 579860,1611400 } }, + { { 579860,1611400 }, { 577980,1614579 } }, + { { 577980,1614579 }, { 577380,1616080 } }, + { { 577380,1616080 }, { 563800,1621579 } }, + { { 563800,1621579 }, { 561320,1622320 } }, + { { 561320,1622320 }, { 565080,1621960 } }, + { { 565080,1621960 }, { 571680,1620780 } }, + { { 571680,1620780 }, { 583260,1628340 } }, + { { 583260,1628340 }, { 583100,1630399 } }, + { { 583100,1630399 }, { 582200,1632160 } }, + { { 582200,1632160 }, { 595380,1627020 } }, + { { 595380,1627020 }, { 597400,1627320 } }, + { { 597400,1627320 }, { 602240,1628459 } }, + { { 602240,1628459 }, { 605660,1630260 } }, + { { 605660,1630260 }, { 610319,1634140 } }, + { { 610319,1634140 }, { 612340,1636319 } }, + { { 612340,1636319 }, { 614820,1638020 } }, + { { 614820,1638020 }, { 616460,1638740 } }, + { { 616460,1638740 }, { 620420,1639500 } }, + { { 620420,1639500 }, { 623000,1639280 } }, + { { 623000,1639280 }, { 624459,1639359 } }, + { { 624459,1639359 }, { 626180,1640159 } }, + { { 626180,1640159 }, { 627279,1640940 } }, + { { 627279,1640940 }, { 629980,1643759 } }, + { { 629980,1643759 }, { 632380,1648000 } }, + { { 632380,1648000 }, { 635020,1654800 } }, + { { 635020,1654800 }, { 636320,1659140 } }, + { { 636320,1659140 }, { 636680,1663620 } }, + { { 636680,1663620 }, { 636180,1665780 } }, + { { 636180,1665780 }, { 630620,1669720 } }, + { { 630620,1669720 }, { 628760,1672979 } }, + { { 628760,1672979 }, { 627540,1676859 } }, + { { 627540,1676859 }, { 627040,1680699 } }, + { { 627040,1680699 }, { 624700,1686500 } }, + { { 624700,1686500 }, { 623260,1688799 } }, + { { 623260,1688799 }, { 619620,1693799 } }, + { { 619620,1693799 }, { 621720,1694859 } }, + { { 621720,1694859 }, { 624940,1694379 } }, + { { 624940,1694379 }, { 627120,1695600 } }, + { { 627120,1695600 }, { 627740,1696120 } }, + { { 627740,1696120 }, { 631120,1697460 } }, + { { 631120,1697460 }, { 633980,1698340 } }, + { { 633980,1698340 }, { 638380,1700460 } }, + { { 638380,1700460 }, { 642660,1703300 } }, + { { 642660,1703300 }, { 643620,1704140 } }, + { { 643620,1704140 }, { 646300,1707000 } }, + { { 646300,1707000 }, { 649060,1710880 } }, + { { 649060,1710880 }, { 651160,1714879 } }, + { { 651160,1714879 }, { 651740,1716559 } }, + { { 651740,1716559 }, { 653139,1722619 } }, + { { 653139,1722619 }, { 653020,1728320 } }, + { { 653020,1728320 }, { 652719,1731420 } }, + { { 652719,1731420 }, { 651619,1736360 } }, + { { 651619,1736360 }, { 649819,1743160 } }, + { { 649819,1743160 }, { 646440,1749059 } }, + { { 646440,1749059 }, { 645219,1750399 } }, + { { 645219,1750399 }, { 643959,1752679 } }, + { { 643959,1752679 }, { 643959,1753740 } }, + { { 643959,1753740 }, { 642140,1754240 } }, + { { 642140,1754240 }, { 643760,1754099 } }, + { { 643760,1754099 }, { 644320,1754280 } }, + { { 644320,1754280 }, { 645000,1754879 } }, + { { 645000,1754879 }, { 646940,1755620 } }, + { { 646940,1755620 }, { 654779,1757820 } }, + { { 654779,1757820 }, { 661100,1761559 } }, + { { 661100,1761559 }, { 664099,1763980 } }, + { { 664099,1763980 }, { 668220,1768480 } }, + { { 668220,1768480 }, { 671920,1773640 } }, + { { 671920,1773640 }, { 674939,1779540 } }, + { { 674939,1779540 }, { 677760,1782440 } }, + { { 677760,1782440 }, { 679080,1785739 } }, + { { 679080,1785739 }, { 678780,1788100 } }, + { { 678780,1788100 }, { 678020,1791500 } }, + { { 678020,1791500 }, { 677120,1793600 } }, + { { 677120,1793600 }, { 676860,1795800 } }, + { { 676860,1795800 }, { 676440,1797320 } }, + { { 676440,1797320 }, { 676459,1798519 } }, + { { 676459,1798519 }, { 675620,1800159 } }, + { { 675620,1800159 }, { 675520,1801019 } }, + { { 675520,1801019 }, { 673360,1804899 } }, + { { 673360,1804899 }, { 672740,1807079 } }, + { { 672740,1807079 }, { 673300,1809260 } }, + { { 673300,1809260 }, { 674539,1811019 } }, + { { 674539,1811019 }, { 675499,1812020 } }, + { { 675499,1812020 }, { 677660,1817240 } }, + { { 677660,1817240 }, { 679659,1824280 } }, + { { 679659,1824280 }, { 680380,1828779 } }, + { { 680380,1828779 }, { 679519,1837999 } }, + { { 679519,1837999 }, { 677940,1844379 } }, + { { 677940,1844379 }, { 676940,1846900 } }, + { { 676940,1846900 }, { 675479,1849379 } }, + { { 675479,1849379 }, { 674000,1851200 } }, + { { 674000,1851200 }, { 671380,1853480 } }, + { { 671380,1853480 }, { 667019,1855240 } }, + { { 667019,1855240 }, { 662540,1856060 } }, + { { 662540,1856060 }, { 660960,1856599 } }, + { { 660960,1856599 }, { 656240,1857020 } }, + { { 656240,1857020 }, { 655600,1856960 } }, + { { 655600,1856960 }, { 652839,1855880 } }, + { { 652839,1855880 }, { 652019,1855840 } }, + { { 652019,1855840 }, { 651459,1855060 } }, + { { 651459,1855060 }, { 652179,1854359 } }, + { { 652179,1854359 }, { 652019,1849919 } }, + { { 652019,1849919 }, { 650620,1846920 } }, + { { 650620,1846920 }, { 647299,1844540 } }, + { { 647299,1844540 }, { 644500,1843819 } }, + { { 644500,1843819 }, { 641860,1844859 } }, + { { 641860,1844859 }, { 641059,1845340 } }, + { { 641059,1845340 }, { 638860,1845820 } }, + { { 638860,1845820 }, { 638000,1845820 } }, + { { 638000,1845820 }, { 636340,1845479 } }, + { { 636340,1845479 }, { 634980,1844800 } }, + { { 634980,1844800 }, { 632660,1842979 } }, + { { 632660,1842979 }, { 631140,1841120 } }, + { { 631140,1841120 }, { 629140,1839520 } }, + { { 629140,1839520 }, { 626640,1839540 } }, + { { 626640,1839540 }, { 624159,1840739 } }, + { { 624159,1840739 }, { 623820,1841380 } }, + { { 623820,1841380 }, { 622440,1842719 } }, + { { 622440,1842719 }, { 622100,1843680 } }, + { { 622100,1843680 }, { 623780,1846100 } }, + { { 623780,1846100 }, { 624580,1846920 } }, + { { 624580,1846920 }, { 626120,1856720 } }, + { { 626120,1856720 }, { 627440,1860000 } }, + { { 627440,1860000 }, { 628000,1864299 } }, + { { 628000,1864299 }, { 627380,1865999 } }, + { { 627380,1865999 }, { 626260,1867580 } }, + { { 626260,1867580 }, { 623660,1869520 } }, + { { 623660,1869520 }, { 618680,1872780 } }, + { { 618680,1872780 }, { 617699,1873140 } }, + { { 617699,1873140 }, { 612000,1874160 } }, + { { 612000,1874160 }, { 609840,1874220 } }, + { { 609840,1874220 }, { 606940,1873860 } }, + { { 136680,1926960 }, { 135500,1926360 } }, + { { 135500,1926360 }, { 137360,1923060 } }, + { { 137360,1923060 }, { 139500,1918559 } }, + { { 139500,1918559 }, { 140780,1913239 } }, + { { 140780,1913239 }, { 139600,1913020 } }, + { { 139600,1913020 }, { 127380,1923600 } }, + { { 127380,1923600 }, { 122800,1926059 } }, + { { 122800,1926059 }, { 118879,1927719 } }, + { { 118879,1927719 }, { 114420,1928300 } }, + { { 114420,1928300 }, { 111480,1927020 } }, + { { 111480,1927020 }, { 110619,1925399 } }, + { { 110619,1925399 }, { 109620,1924200 } }, + { { 109620,1924200 }, { 108860,1922780 } }, + { { 108860,1922780 }, { 108479,1920999 } }, + { { 108479,1920999 }, { 106600,1918080 } }, + { { 106600,1918080 }, { 106220,1917740 } }, + { { 106220,1917740 }, { 105199,1916960 } }, + { { 105199,1916960 }, { 101460,1914859 } }, + { { 101460,1914859 }, { 99480,1914379 } }, + { { 99480,1914379 }, { 97179,1913499 } }, + { { 97179,1913499 }, { 94900,1911100 } }, + { { 94900,1911100 }, { 94100,1909639 } }, + { { 94100,1909639 }, { 93379,1907740 } }, + { { 93379,1907740 }, { 93960,1898259 } }, + { { 93960,1898259 }, { 93739,1896460 } }, + { { 93739,1896460 }, { 94299,1893080 } }, + { { 94299,1893080 }, { 97240,1883440 } }, + { { 97240,1883440 }, { 99799,1879780 } }, + { { 99799,1879780 }, { 100400,1878120 } }, + { { 100400,1878120 }, { 100199,1877200 } }, + { { 100199,1877200 }, { 98940,1877460 } }, + { { 98940,1877460 }, { 96320,1878480 } }, + { { 96320,1878480 }, { 86020,1881039 } }, + { { 86020,1881039 }, { 84340,1881080 } }, + { { 84340,1881080 }, { 76780,1882600 } }, + { { 76780,1882600 }, { 74380,1883580 } }, + { { 74380,1883580 }, { 72679,1884019 } }, + { { 72679,1884019 }, { 70900,1885940 } }, + { { 70900,1885940 }, { 71240,1888340 } }, + { { 71240,1888340 }, { 72720,1889940 } }, + { { 72720,1889940 }, { 74640,1891360 } }, + { { 74640,1891360 }, { 75620,1893179 } }, + { { 75620,1893179 }, { 77140,1895340 } }, + { { 77140,1895340 }, { 81040,1899500 } }, + { { 81040,1899500 }, { 82760,1900380 } }, + { { 82760,1900380 }, { 83720,1902300 } }, + { { 83720,1902300 }, { 85459,1903700 } }, + { { 85459,1903700 }, { 86960,1905940 } }, + { { 86960,1905940 }, { 88280,1913020 } }, + { { 88280,1913020 }, { 88160,1913539 } }, + { { 88160,1913539 }, { 88020,1913860 } }, + { { 88020,1913860 }, { 86080,1915200 } }, + { { 86080,1915200 }, { 85660,1916740 } }, + { { 85660,1916740 }, { 83899,1918799 } }, + { { 83899,1918799 }, { 79360,1921160 } }, + { { 79360,1921160 }, { 76400,1923140 } }, + { { 76400,1923140 }, { 70800,1926180 } }, + { { 70800,1926180 }, { 64460,1927659 } }, + { { 64460,1927659 }, { 60880,1927820 } }, + { { 60880,1927820 }, { 55780,1925580 } }, + { { 55780,1925580 }, { 54940,1925040 } }, + { { 54940,1925040 }, { 52199,1921700 } }, + { { 52199,1921700 }, { 49680,1916579 } }, + { { 49680,1916579 }, { 48719,1914180 } }, + { { 48719,1914180 }, { 48620,1913080 } }, + { { 48620,1913080 }, { 47640,1909120 } }, + { { 47640,1909120 }, { 48280,1899319 } }, + { { 48280,1899319 }, { 49140,1895600 } }, + { { 49140,1895600 }, { 50320,1892899 } }, + { { 50320,1892899 }, { 51559,1890640 } }, + { { 51559,1890640 }, { 52140,1889960 } }, + { { 52140,1889960 }, { 54640,1887999 } }, + { { 54640,1887999 }, { 55639,1886500 } }, + { { 55639,1886500 }, { 55720,1885080 } }, + { { 55720,1885080 }, { 55439,1883220 } }, + { { 55439,1883220 }, { 54640,1882159 } }, + { { 54640,1882159 }, { 54100,1880300 } }, + { { 54100,1880300 }, { 52479,1874079 } }, + { { 52479,1874079 }, { 51700,1869000 } }, + { { 51700,1869000 }, { 51600,1865419 } }, + { { 51600,1865419 }, { 51720,1859820 } }, + { { 51720,1859820 }, { 52160,1857260 } }, + { { 52160,1857260 }, { 52539,1856120 } }, + { { 52539,1856120 }, { 57240,1845720 } }, + { { 57240,1845720 }, { 58280,1844400 } }, + { { 58280,1844400 }, { 60639,1840820 } }, + { { 60639,1840820 }, { 65580,1835540 } }, + { { 65580,1835540 }, { 68340,1833340 } }, + { { 68340,1833340 }, { 71660,1831480 } }, + { { 71660,1831480 }, { 73460,1829960 } }, + { { 73460,1829960 }, { 75200,1829319 } }, + { { 75200,1829319 }, { 77200,1828960 } }, + { { 77200,1828960 }, { 78640,1828920 } }, + { { 78640,1828920 }, { 111780,1842700 } }, + { { 111780,1842700 }, { 112800,1843480 } }, + { { 112800,1843480 }, { 113879,1844879 } }, + { { 113879,1844879 }, { 116379,1847379 } }, + { { 116379,1847379 }, { 116360,1847580 } }, + { { 116360,1847580 }, { 117100,1848799 } }, + { { 117100,1848799 }, { 120160,1851799 } }, + { { 120160,1851799 }, { 121860,1852320 } }, + { { 121860,1852320 }, { 124280,1852679 } }, + { { 124280,1852679 }, { 128920,1854659 } }, + { { 128920,1854659 }, { 130840,1856360 } }, + { { 130840,1856360 }, { 133520,1859460 } }, + { { 133520,1859460 }, { 135079,1860860 } }, + { { 135079,1860860 }, { 137280,1864440 } }, + { { 137280,1864440 }, { 142980,1872899 } }, + { { 142980,1872899 }, { 144600,1875840 } }, + { { 144600,1875840 }, { 147240,1883480 } }, + { { 147240,1883480 }, { 147460,1886539 } }, + { { 147460,1886539 }, { 147660,1886920 } }, + { { 147660,1886920 }, { 148399,1891720 } }, + { { 148399,1891720 }, { 148820,1896799 } }, + { { 148820,1896799 }, { 148399,1898880 } }, + { { 148399,1898880 }, { 148799,1899420 } }, + { { 148799,1899420 }, { 150520,1898539 } }, + { { 150520,1898539 }, { 154760,1892760 } }, + { { 154760,1892760 }, { 156580,1889240 } }, + { { 156580,1889240 }, { 156940,1888900 } }, + { { 156940,1888900 }, { 157540,1889540 } }, + { { 157540,1889540 }, { 156860,1896819 } }, + { { 156860,1896819 }, { 155639,1903940 } }, + { { 155639,1903940 }, { 153679,1908100 } }, + { { 153679,1908100 }, { 152859,1909039 } }, + { { 152859,1909039 }, { 149660,1915580 } }, + { { 149660,1915580 }, { 148000,1918600 } }, + { { 148000,1918600 }, { 141640,1926980 } }, + { { 141640,1926980 }, { 140060,1927899 } }, + { { 140060,1927899 }, { 136960,1929019 } }, + { { 136960,1929019 }, { 136680,1926960 } }, + { { 627100,1941519 }, { 625120,1940060 } }, + { { 625120,1940060 }, { 614580,1934680 } }, + { { 614580,1934680 }, { 608780,1929319 } }, + { { 608780,1929319 }, { 607400,1927679 } }, + { { 607400,1927679 }, { 606160,1925920 } }, + { { 606160,1925920 }, { 604480,1922240 } }, + { { 604480,1922240 }, { 602420,1916819 } }, + { { 602420,1916819 }, { 602279,1915260 } }, + { { 602279,1915260 }, { 602880,1907960 } }, + { { 602880,1907960 }, { 604140,1902719 } }, + { { 604140,1902719 }, { 605880,1898539 } }, + { { 605880,1898539 }, { 606640,1897399 } }, + { { 606640,1897399 }, { 609680,1894420 } }, + { { 609680,1894420 }, { 611099,1893640 } }, + { { 611099,1893640 }, { 616099,1890340 } }, + { { 616099,1890340 }, { 617520,1889160 } }, + { { 617520,1889160 }, { 620220,1885540 } }, + { { 620220,1885540 }, { 624480,1882260 } }, + { { 624480,1882260 }, { 628660,1880280 } }, + { { 628660,1880280 }, { 632520,1879659 } }, + { { 632520,1879659 }, { 637760,1879859 } }, + { { 637760,1879859 }, { 640899,1881500 } }, + { { 640899,1881500 }, { 644220,1883980 } }, + { { 644220,1883980 }, { 643900,1890860 } }, + { { 643900,1890860 }, { 643060,1894160 } }, + { { 643060,1894160 }, { 642459,1900320 } }, + { { 642459,1900320 }, { 642400,1903120 } }, + { { 642400,1903120 }, { 643819,1908519 } }, + { { 643819,1908519 }, { 644700,1912560 } }, + { { 644700,1912560 }, { 644640,1916380 } }, + { { 644640,1916380 }, { 644959,1918600 } }, + { { 644959,1918600 }, { 642540,1925620 } }, + { { 642540,1925620 }, { 642439,1926640 } }, + { { 642439,1926640 }, { 641860,1928300 } }, + { { 641860,1928300 }, { 638700,1932939 } }, + { { 638700,1932939 }, { 634820,1934200 } }, + { { 634820,1934200 }, { 631980,1936539 } }, + { { 631980,1936539 }, { 630160,1940600 } }, + { { 630160,1940600 }, { 627740,1941640 } }, + { { 627740,1941640 }, { 627400,1941660 } }, + { { 627400,1941660 }, { 627100,1941519 } } + }; + +#if 0 + // Verify whether two any two non-neighbor line segments intersect. They should not, otherwise the Voronoi builder + // is not guaranteed to succeed. + for (size_t i = 0; i < lines.size(); ++ i) + for (size_t j = i + 1; j < lines.size(); ++ j) { + Point &ip1 = lines[i].a; + Point &ip2 = lines[i].b; + Point &jp1 = lines[j].a; + Point &jp2 = lines[j].b; + if (&ip1 != &jp2 && &jp1 != &ip2) { + REQUIRE(! Slic3r::Geometry::segments_intersect(ip1, ip2, jp1, jp2)); + } + } +#endif + + VD vd; + construct_voronoi(lines.begin(), lines.end(), &vd); + + for (const auto& edge : vd.edges()) + if (edge.is_finite()) { + auto v0 = edge.vertex0(); + auto v1 = edge.vertex1(); + REQUIRE((v0->x() == 0 || std::isnormal(v0->x()))); + REQUIRE((v0->y() == 0 || std::isnormal(v0->y()))); + REQUIRE((v1->x() == 0 || std::isnormal(v1->x()))); + REQUIRE((v1->y() == 0 || std::isnormal(v1->y()))); + } + +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-NaNs.svg").c_str(), + vd, Points(), lines, Polygons(), Lines(), 0.015); +#endif +} + +struct OffsetTest { + double distance; + size_t num_outer; + size_t num_inner; +}; + +TEST_CASE("Voronoi offset", "[VoronoiOffset]") +{ + Polygons poly_with_hole = { Polygon { + { 0, 10000000}, + { 700000, 0}, + { 700000, 9000000}, + { 9100000, 9000000}, + { 9100000, 0}, + {10000000, 10000000} + } + }; + + double area = std::accumulate(poly_with_hole.begin(), poly_with_hole.end(), 0., [](double a, auto &poly){ return a + poly.area(); }); + REQUIRE(area > 0.); + + VD vd; + Lines lines = to_lines(poly_with_hole); + construct_voronoi(lines.begin(), lines.end(), &vd); + + for (const OffsetTest &ot : { + OffsetTest { scale_(0.2), 1, 1 }, + OffsetTest { scale_(0.4), 1, 1 }, + OffsetTest { scale_(0.5), 1, 1 }, + OffsetTest { scale_(0.505), 1, 2 }, + OffsetTest { scale_(0.51), 1, 2 }, + OffsetTest { scale_(0.52), 1, 1 }, + OffsetTest { scale_(0.53), 1, 1 }, + OffsetTest { scale_(0.54), 1, 1 }, + OffsetTest { scale_(0.55), 1, 0 } + }) { + + Polygons offsetted_polygons_out = voronoi_offset(vd, lines, ot.distance, scale_(0.005)); + REQUIRE(offsetted_polygons_out.size() == ot.num_outer); + +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-offset-out-%lf.svg", ot.distance).c_str(), + vd, Points(), lines, offsetted_polygons_out); +#endif + + Polygons offsetted_polygons_in = voronoi_offset(vd, lines, - ot.distance, scale_(0.005)); + REQUIRE(offsetted_polygons_in.size() == ot.num_inner); + +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-offset-in-%lf.svg", ot.distance).c_str(), + vd, Points(), lines, offsetted_polygons_in); +#endif + } +} + +TEST_CASE("Voronoi offset 2", "[VoronoiOffset]") +{ + coord_t mm = coord_t(scale_(1.)); + Polygons poly = { + Polygon { + { 0, 0 }, + { 1, 0 }, + { 1, 1 }, + { 2, 1 }, + { 2, 0 }, + { 3, 0 }, + { 3, 2 }, + { 0, 2 } + }, + Polygon { + { 0, - 1 - 2 }, + { 3, - 1 - 2 }, + { 3, - 1 - 0 }, + { 2, - 1 - 0 }, + { 2, - 1 - 1 }, + { 1, - 1 - 1 }, + { 1, - 1 - 0 }, + { 0, - 1 - 0 } + }, + }; + for (Polygon &p : poly) + for (Point &pt : p.points) + pt *= mm; + + double area = std::accumulate(poly.begin(), poly.end(), 0., [](double a, auto &poly){ return a + poly.area(); }); + REQUIRE(area > 0.); + + VD vd; + Lines lines = to_lines(poly); + construct_voronoi(lines.begin(), lines.end(), &vd); + + for (const OffsetTest &ot : { + OffsetTest { scale_(0.2), 2, 2 }, + OffsetTest { scale_(0.4), 2, 2 }, + OffsetTest { scale_(0.45), 2, 2 }, + OffsetTest { scale_(0.48), 2, 2 }, +//FIXME Exact intersections of an Offset curve with any Voronoi vertex are not handled correctly yet. +// OffsetTest { scale_(0.5), 2, 2 }, + OffsetTest { scale_(0.505), 2, 4 }, + OffsetTest { scale_(0.7), 2, 0 }, + OffsetTest { scale_(0.8), 1, 0 } + }) { + + Polygons offsetted_polygons_out = voronoi_offset(vd, lines, ot.distance, scale_(0.005)); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-offset2-out-%lf.svg", ot.distance).c_str(), + vd, Points(), lines, offsetted_polygons_out); +#endif + REQUIRE(offsetted_polygons_out.size() == ot.num_outer); + + Polygons offsetted_polygons_in = voronoi_offset(vd, lines, - ot.distance, scale_(0.005)); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-offset2-in-%lf.svg", ot.distance).c_str(), + vd, Points(), lines, offsetted_polygons_in); +#endif + REQUIRE(offsetted_polygons_in.size() == ot.num_inner); + } +} + +TEST_CASE("Voronoi offset 3", "[VoronoiOffset]") +{ + coord_t mm = coord_t(scale_(1.)); + Polygons poly = { + Polygon { + { 0, 0 }, + { 2, 0 }, + { 2, 1 }, + { 3, 1 }, + { 3, 0 }, + { 5, 0 }, + { 5, 2 }, + { 4, 2 }, + { 4, 3 }, + { 1, 3 }, + { 1, 2 }, + { 0, 2 } + }, + Polygon { + { 0, -1 - 2 }, + { 1, -1 - 2 }, + { 1, -1 - 3 }, + { 4, -1 - 3 }, + { 4, -1 - 2 }, + { 5, -1 - 2 }, + { 5, -1 - 0 }, + { 3, -1 - 0 }, + { 3, -1 - 1 }, + { 2, -1 - 1 }, + { 2, -1 - 0 }, + { 0, -1 - 0 } + }, + }; + for (Polygon &p : poly) { + REQUIRE(p.area() > 0.); + for (Point &pt : p.points) + pt *= mm; + } + + VD vd; + Lines lines = to_lines(poly); + construct_voronoi(lines.begin(), lines.end(), &vd); + + for (const OffsetTest &ot : { + OffsetTest { scale_(0.2), 2, 2 }, + OffsetTest { scale_(0.4), 2, 2 }, + OffsetTest { scale_(0.49), 2, 2 }, +//FIXME this fails +// OffsetTest { scale_(0.5), 2, 2 }, + OffsetTest { scale_(0.51), 2, 2 }, + OffsetTest { scale_(0.56), 2, 2 }, + OffsetTest { scale_(0.6), 2, 2 }, + OffsetTest { scale_(0.7), 2, 2 }, + OffsetTest { scale_(0.8), 1, 6 }, + OffsetTest { scale_(0.9), 1, 6 }, + OffsetTest { scale_(0.99), 1, 6 }, +//FIXME this fails +// OffsetTest { scale_(1.0), 1, 6 }, + OffsetTest { scale_(1.01), 1, 0 }, + }) { + + Polygons offsetted_polygons_out = voronoi_offset(vd, lines, ot.distance, scale_(0.005)); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-offset2-out-%lf.svg", ot.distance).c_str(), + vd, Points(), lines, offsetted_polygons_out); +#endif + REQUIRE(offsetted_polygons_out.size() == ot.num_outer); + + Polygons offsetted_polygons_in = voronoi_offset(vd, lines, - ot.distance, scale_(0.005)); +#ifdef VORONOI_DEBUG_OUT + dump_voronoi_to_svg(debug_out_path("voronoi-offset2-in-%lf.svg", ot.distance).c_str(), + vd, Points(), lines, offsetted_polygons_in); +#endif + REQUIRE(offsetted_polygons_in.size() == ot.num_inner); + } +} diff --git a/tests/sla_print/sla_raycast_tests.cpp b/tests/sla_print/sla_raycast_tests.cpp index 74c7994723a..c82e4569a89 100644 --- a/tests/sla_print/sla_raycast_tests.cpp +++ b/tests/sla_print/sla_raycast_tests.cpp @@ -39,7 +39,7 @@ TEST_CASE("Raycaster - find intersections of a line and cylinder") REQUIRE(std::abs(out[1].first - std::sqrt(72.f)) < 0.001f); } - +#ifdef SLIC3R_HOLE_RAYCASTER // Create a simple scene with a 20mm cube and a big hole in the front wall // with 5mm radius. Then shoot rays from interesting positions and see where // they land. @@ -94,3 +94,4 @@ TEST_CASE("Raycaster with loaded drillholes", "[sla_raycast]") // Check for support tree correctness test_support_model_collision("20mm_cube.obj", {}, hcfg, holes); } +#endif diff --git a/tests/sla_print/sla_test_utils.cpp b/tests/sla_print/sla_test_utils.cpp index 883e4268a81..1eaf796c005 100644 --- a/tests/sla_print/sla_test_utils.cpp +++ b/tests/sla_print/sla_test_utils.cpp @@ -105,8 +105,13 @@ void test_supports(const std::string &obj_filename, // Create the special index-triangle mesh with spatial indexing which // is the input of the support point and support mesh generators sla::EigenMesh3D emesh{mesh}; + +#ifdef SLIC3R_HOLE_RAYCASTER if (hollowingcfg.enabled) emesh.load_holes(drainholes); +#endif + + // TODO: do the cgal hole cutting... // Create the support point generator sla::SupportPointGenerator::Config autogencfg; diff --git a/xs/CMakeLists.txt b/xs/CMakeLists.txt index a59a1993686..75d236a54cb 100644 --- a/xs/CMakeLists.txt +++ b/xs/CMakeLists.txt @@ -185,6 +185,16 @@ if (MSVC) string(REPLACE "/" "\\" PROPS_CMAKE_SOURCE_DIR "${CMAKE_SOURCE_DIR}") configure_file("../cmake/msvc/xs.wperl.props.in" "${CMAKE_BINARY_DIR}/xs.wperl.props" NEWLINE_STYLE CRLF) set_target_properties(XS PROPERTIES VS_USER_PROPS "${CMAKE_BINARY_DIR}/xs.wperl.props") + + if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") + set(_bits 64) + elseif ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "4") + set(_bits 32) + endif () + add_custom_command(TARGET XS POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${TOP_LEVEL_PROJECT_DIR}/deps/GMP/gmp/lib/win${_bits}/libgmp-10.dll "${PERL_LOCAL_LIB_ARCH_DIR}/auto/Slic3r/XS/" + COMMENT "Installing gmp runtime into the local-lib directory ..." + VERBATIM) endif() # Installation diff --git a/xs/main.xs.in b/xs/main.xs.in index 3523d569ea3..c10f432d839 100644 --- a/xs/main.xs.in +++ b/xs/main.xs.in @@ -5,7 +5,7 @@ // #include #ifdef __cplusplus -extern "C" { +/* extern "C" { */ #endif #include "EXTERN.h" #include "perl.h" @@ -14,7 +14,7 @@ extern "C" { #undef do_open #undef do_close #ifdef __cplusplus -} +/* } */ #endif #ifdef _WIN32 diff --git a/xs/src/xsinit.h b/xs/src/xsinit.h index f14e1262dc8..2082dfb8839 100644 --- a/xs/src/xsinit.h +++ b/xs/src/xsinit.h @@ -40,7 +40,7 @@ // #include #ifdef SLIC3RXS -extern "C" { +// extern "C" { #include "EXTERN.h" #include "perl.h" #include "XSUB.h" @@ -88,7 +88,7 @@ extern "C" { #undef Zero #undef Packet #undef _ -} +// } #endif #include diff --git a/xs/xsp/Geometry.xsp b/xs/xsp/Geometry.xsp index 5d6454e8a86..e44d1694939 100644 --- a/xs/xsp/Geometry.xsp +++ b/xs/xsp/Geometry.xsp @@ -13,7 +13,7 @@ Pointfs arrange(size_t total_parts, Vec2d* part, coordf_t dist, BoundingBoxf* bb %code{% Pointfs points; if (! Slic3r::Geometry::arrange(total_parts, *part, dist, bb, points)) - CONFESS(PRINTF_ZU " parts won't fit in your print area!\n", total_parts); + CONFESS("%zu parts won't fit in your print area!\n", total_parts); RETVAL = points; %}; diff --git a/xs/xsp/PerimeterGenerator.xsp b/xs/xsp/PerimeterGenerator.xsp index 07059de6140..f2c0d025cc6 100644 --- a/xs/xsp/PerimeterGenerator.xsp +++ b/xs/xsp/PerimeterGenerator.xsp @@ -3,6 +3,7 @@ %{ #include #include "libslic3r/PerimeterGenerator.hpp" +#include "libslic3r/Layer.hpp" %} %name{Slic3r::Layer::PerimeterGenerator} class PerimeterGenerator {