diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 3251d2c62..06ab0fa65 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -152,6 +152,9 @@ include(MPFR/MPFR.cmake) include(CGAL/CGAL.cmake) include(wxWidgets/wxWidgets.cmake) +add_dependencies(dep_blosc ${ZLIB_PKG}) +add_dependencies(dep_openexr ${ZLIB_PKG}) + if (MSVC) add_custom_target(deps ALL diff --git a/deps/deps-windows.cmake b/deps/deps-windows.cmake index 8be9888bc..ac93b4932 100644 --- a/deps/deps-windows.cmake +++ b/deps/deps-windows.cmake @@ -35,8 +35,6 @@ else () set(DEP_PLATFORM "x64") endif () - - if (${DEP_DEBUG}) set(DEP_BOOST_DEBUG "debug") else () @@ -217,7 +215,6 @@ ExternalProject_Add(dep_blosc #URL_HASH SHA256=7463a1df566704f212263312717ab2c36b45d45cba6cd0dccebf91b2cc4b4da9 GIT_REPOSITORY https://github.com/Blosc/c-blosc.git GIT_TAG e63775855294b50820ef44d1b157f4de1cc38d3e #v1.17.0 - DEPENDS ${ZLIB_PKG} CMAKE_GENERATOR "${DEP_MSVC_GEN}" CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" CMAKE_ARGS @@ -243,8 +240,7 @@ add_debug_dep(dep_blosc) ExternalProject_Add(dep_openexr EXCLUDE_FROM_ALL 1 GIT_REPOSITORY https://github.com/openexr/openexr.git - GIT_TAG eae0e337c9f5117e78114fd05f7a415819df413a #v2.4.0 - DEPENDS ${ZLIB_PKG} + GIT_TAG eae0e337c9f5117e78114fd05f7a415819df413a #v2.4.0 CMAKE_GENERATOR "${DEP_MSVC_GEN}" CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}" CMAKE_ARGS diff --git a/src/imgui/imconfig.h b/src/imgui/imconfig.h index c425bbef2..eb47459ea 100644 --- a/src/imgui/imconfig.h +++ b/src/imgui/imconfig.h @@ -107,6 +107,13 @@ namespace ImGui const char ColorMarkerStart = 0x2; // STX const char ColorMarkerEnd = 0x3; // ETX + // Special ASCII characters are used here as a ikons markers + const char PrintIconMarker = 0x4; + const char PrinterIconMarker = 0x5; + const char PrinterSlaIconMarker = 0x6; + const char FilamentIconMarker = 0x7; + const char MaterialIconMarker = 0x8; + // void MyFunction(const char* name, const MyMatrix44& v); } diff --git a/src/libslic3r/Fill/FillRectilinear2.cpp b/src/libslic3r/Fill/FillRectilinear2.cpp index b88520b55..d83249939 100644 --- a/src/libslic3r/Fill/FillRectilinear2.cpp +++ b/src/libslic3r/Fill/FillRectilinear2.cpp @@ -1361,7 +1361,6 @@ static void traverse_graph_generate_polylines( continue; } - dont_connect: // No way to continue the current polyline. Take the rest of the line up to the outer contour. // This will finish the polyline, starting another polyline at a new point. going_up ? ++ it : -- it; @@ -1442,6 +1441,8 @@ struct MonotonousRegionLink AntPath *next_flipped; }; +// Matrix of paths (AntPath) connecting ends of MontonousRegions. +// AntPath lengths and their derived visibilities refer to the length of the perimeter line if such perimeter segment exists. class AntPathMatrix { public: @@ -1456,6 +1457,12 @@ public: // From end of one region to the start of another region, both flipped or not flipped. m_matrix(regions.size() * regions.size() * 4, AntPath{ -1., -1., initial_pheromone}) {} + void update_inital_pheromone(float initial_pheromone) + { + for (AntPath &ap : m_matrix) + ap.pheromone = initial_pheromone; + } + AntPath& operator()(const MonotonousRegion ®ion_from, bool flipped_from, const MonotonousRegion ®ion_to, bool flipped_to) { int row = 2 * int(®ion_from - m_regions.data()) + flipped_from; @@ -1478,7 +1485,7 @@ public: // Just apply the Eucledian distance of the end points. path.length = unscale<float>(Vec2f(vline_to.pos - vline_from.pos, vline_to.intersections[i_to].pos() - vline_from.intersections[i_from].pos()).norm()); } - path.visibility = 1. / (path.length + EPSILON); + path.visibility = 1.f / (path.length + float(EPSILON)); } return path; } @@ -1497,7 +1504,7 @@ private: const ExPolygonWithOffset &m_poly_with_offset; const std::vector<SegmentedIntersectionLine> &m_segs; // From end of one region to the start of another region, both flipped or not flipped. - //FIXME one may possibly use sparse representation of the matrix. + //FIXME one may possibly use sparse representation of the matrix, likely using hashing. std::vector<AntPath> m_matrix; }; @@ -1724,7 +1731,98 @@ static std::vector<MonotonousRegion> generate_montonous_regions(std::vector<Segm return monotonous_regions; } -static void connect_monotonous_regions(std::vector<MonotonousRegion> ®ions, std::vector<SegmentedIntersectionLine> &segs) +// Traverse path, calculate length of the draw for the purpose of optimization. +// This function is very similar to polylines_from_paths() in the way how it traverses the path, but +// polylines_from_paths() emits a path, while this function just calculates the path length. +static float montonous_region_path_length(const MonotonousRegion ®ion, bool dir, const ExPolygonWithOffset &poly_with_offset, const std::vector<SegmentedIntersectionLine> &segs) +{ + // From the initial point (i_vline, i_intersection), follow a path. + int i_intersection = region.left_intersection_point(dir); + int i_vline = region.left.vline; + float total_length = 0.; + bool no_perimeter = false; + Vec2f last_point; + + for (;;) { + const SegmentedIntersectionLine &vline = segs[i_vline]; + const SegmentIntersection *it = &vline.intersections[i_intersection]; + const bool going_up = it->is_low(); + + if (no_perimeter) + total_length += (last_point - Vec2f(vline.pos, (it + (going_up ? - 1 : 1))->pos())).norm(); + + int iright = it->right_horizontal(); + if (going_up) { + // Traverse the complete vertical segment up to the inner contour. + for (;;) { + do { + ++ it; + iright = std::max(iright, it->right_horizontal()); + assert(it->is_inner()); + } while (it->type != SegmentIntersection::INNER_HIGH || (it + 1)->type != SegmentIntersection::OUTER_HIGH); + int inext = it->vertical_up(); + if (inext == -1 || it->vertical_up_quality() != SegmentIntersection::LinkQuality::Valid) + break; + assert(it->iContour == vline.intersections[inext].iContour); + it = vline.intersections.data() + inext; + } + } else { + // Going down. + assert(it->is_high()); + assert(i_intersection > 0); + for (;;) { + do { + -- it; + if (int iright_new = it->right_horizontal(); iright_new != -1) + iright = iright_new; + assert(it->is_inner()); + } while (it->type != SegmentIntersection::INNER_LOW || (it - 1)->type != SegmentIntersection::OUTER_LOW); + int inext = it->vertical_down(); + if (inext == -1 || it->vertical_down_quality() != SegmentIntersection::LinkQuality::Valid) + break; + assert(it->iContour == vline.intersections[inext].iContour); + it = vline.intersections.data() + inext; + } + } + + if (i_vline == region.right.vline) + break; + + int inext = it->right_horizontal(); + if (inext != -1 && it->next_on_contour_quality == SegmentIntersection::LinkQuality::Valid) { + // Summarize length of the connection line along the perimeter. + //FIXME should it be weighted with a lower weight than non-extruding connection line? What weight? + // Taking half of the length. + total_length += 0.5f * float(measure_perimeter_horizontal_segment_length(poly_with_offset, segs, i_vline, it - vline.intersections.data(), inext)); + // Don't add distance to the next vertical line start to the total length. + no_perimeter = false; + i_intersection = inext; + } else { + // Finish the current vertical line, + going_up ? ++ it : -- it; + assert(it->is_outer()); + assert(it->is_high() == going_up); + // Mark the end of this vertical line. + last_point = Vec2f(vline.pos, it->pos()); + // Remember to add distance to the last point. + no_perimeter = true; + if (inext == -1) { + // Find the end of the next overlapping vertical segment. + const SegmentedIntersectionLine &vline_right = segs[i_vline + 1]; + const SegmentIntersection *right = going_up ? + &vertical_run_top(vline_right, vline_right.intersections[iright]) : &vertical_run_bottom(vline_right, vline_right.intersections[iright]); + i_intersection = int(right - vline_right.intersections.data()); + } else + i_intersection = inext; + } + + ++ i_vline; + } + + return unscale<float>(total_length); +} + +static void connect_monotonous_regions(std::vector<MonotonousRegion> ®ions, const ExPolygonWithOffset &poly_with_offset, std::vector<SegmentedIntersectionLine> &segs) { // Map from low intersection to left / right side of a monotonous region. using MapType = std::pair<SegmentIntersection*, MonotonousRegion*>; @@ -1816,6 +1914,20 @@ static void connect_monotonous_regions(std::vector<MonotonousRegion> ®ions, s } } #endif /* NDEBUG */ + + // Fill in sum length of connecting lines of a region. This length is used for optimizing the infill path for minimum length. + for (MonotonousRegion ®ion : regions) { + region.len1 = montonous_region_path_length(region, false, poly_with_offset, segs); + region.len2 = montonous_region_path_length(region, true, poly_with_offset, segs); + // Subtract the smaller length from the longer one, so we will optimize just with the positive difference of the two. + if (region.len1 > region.len2) { + region.len1 -= region.len2; + region.len2 = 0; + } else { + region.len2 -= region.len1; + region.len1 = 0; + } + } } // Raad Salman: Algorithms for the Precedence Constrained Generalized Travelling Salesperson Problem @@ -1851,6 +1963,7 @@ inline void print_ant(const std::string& fmt, TArgs&&... args) { } // Find a run through monotonous infill blocks using an 'Ant colony" optimization method. +// http://www.scholarpedia.org/article/Ant_colony_optimization static std::vector<MonotonousRegionLink> chain_monotonous_regions( std::vector<MonotonousRegion> ®ions, const ExPolygonWithOffset &poly_with_offset, const std::vector<SegmentedIntersectionLine> &segs, std::mt19937_64 &rng) { @@ -1940,14 +2053,18 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions( }; #endif /* NDEBUG */ - // How many times to repeat the ant simulation. - constexpr int num_rounds = 10; + // How many times to repeat the ant simulation (number of ant generations). + constexpr int num_rounds = 25; + // After how many rounds without an improvement to exit? + constexpr int num_rounds_no_change_exit = 8; // With how many ants each of the run will be performed? - constexpr int num_ants = 10; - // Base (initial) pheromone level. - constexpr float pheromone_initial_deposit = 0.5f; + const int num_ants = std::min<int>(regions.size(), 10); + // Base (initial) pheromone level. This value will be adjusted based on the length of the first greedy path found. + float pheromone_initial_deposit = 0.5f; // Evaporation rate of pheromones. constexpr float pheromone_evaporation = 0.1f; + // Evaporation rate to diversify paths taken by individual ants. + constexpr float pheromone_diversification = 0.1f; // Probability at which to take the next best path. Otherwise take the the path based on the cost distribution. constexpr float probability_take_best = 0.9f; // Exponents of the cost function. @@ -1956,6 +2073,73 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions( AntPathMatrix path_matrix(regions, poly_with_offset, segs, pheromone_initial_deposit); + // Find an initial path in a greedy way, set the initial pheromone value to 10% of the cost of the greedy path. + { + // Construct the first path in a greedy way to calculate an initial value of the pheromone value. + queue = queue_initial; + left_neighbors_unprocessed = left_neighbors_unprocessed_initial; + assert(validate_unprocessed()); + // Pick the last of the queue. + MonotonousRegionLink path_end { queue.back(), false }; + queue.pop_back(); + -- left_neighbors_unprocessed[path_end.region - regions.data()]; + + float total_length = path_end.region->length(false); + while (! queue.empty() || ! path_end.region->right_neighbors.empty()) { + // Chain. + MonotonousRegion ®ion = *path_end.region; + bool dir = path_end.flipped; + NextCandidate next_candidate; + next_candidate.probability = 0; + for (MonotonousRegion *next : region.right_neighbors) { + int &unprocessed = left_neighbors_unprocessed[next - regions.data()]; + assert(unprocessed > 1); + if (left_neighbors_unprocessed[next - regions.data()] == 2) { + // Dependencies of the successive blocks are satisfied. + AntPath &path1 = path_matrix(region, dir, *next, false); + 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) + next_candidate = { next, &path2, &path2, path2.visibility, true }; + } + } + bool from_queue = next_candidate.probability == 0; + if (from_queue) { + for (MonotonousRegion *next : queue) { + AntPath &path1 = path_matrix(region, dir, *next, false); + 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) + next_candidate = { next, &path2, &path2, path2.visibility, true }; + } + } + // Move the other right neighbors with satisified constraints to the queue. + for (MonotonousRegion *next : region.right_neighbors) + if (-- left_neighbors_unprocessed[next - regions.data()] == 1 && next_candidate.region != next) + queue.emplace_back(next); + if (from_queue) { + // Remove the selected path from the queue. + auto it = std::find(queue.begin(), queue.end(), next_candidate.region); + assert(it != queue.end()); + *it = queue.back(); + queue.pop_back(); + } + // Extend the path. + MonotonousRegion *next_region = next_candidate.region; + bool next_dir = next_candidate.dir; + total_length += next_region->length(next_dir) + path_matrix(*path_end.region, path_end.flipped, *next_region, next_dir).length; + path_end = { next_region, next_dir }; + assert(left_neighbors_unprocessed[next_region - regions.data()] == 1); + left_neighbors_unprocessed[next_region - regions.data()] = 0; + } + + // Set an initial pheromone value to 10% of the greedy path's value. + pheromone_initial_deposit = 0.1 / total_length; + path_matrix.update_inital_pheromone(pheromone_initial_deposit); + } + // Probability (unnormalized) of traversing a link between two monotonous regions. auto path_probability = [pheromone_alpha, pheromone_beta](AntPath &path) { return pow(path.pheromone, pheromone_alpha) * pow(path.visibility, pheromone_beta); @@ -1966,8 +2150,10 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions( ++ irun; #endif /* SLIC3R_DEBUG_ANTS */ - for (int round = 0; round < num_rounds; ++ round) + int num_rounds_no_change = 0; + for (int round = 0; round < num_rounds && num_rounds_no_change < num_rounds_no_change_exit; ++ round) { + bool improved = false; for (int ant = 0; ant < num_ants; ++ ant) { // Find a new path following the pheromones deposited by the previous ants. @@ -1977,6 +2163,8 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions( left_neighbors_unprocessed = left_neighbors_unprocessed_initial; assert(validate_unprocessed()); // Pick randomly the first from the queue at random orientation. + //FIXME picking the 1st monotonous region should likely be done based on accumulated pheromone level as well, + // but the inefficiency caused by the random pick of the 1st monotonous region is likely insignificant. int first_idx = std::uniform_int_distribution<>(0, int(queue.size()) - 1)(rng); path.emplace_back(MonotonousRegionLink{ queue[first_idx], rng() > rng.max() / 2 }); *(queue.begin() + first_idx) = std::move(queue.back()); @@ -2051,7 +2239,7 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions( for (std::vector<NextCandidate>::iterator it_next_candidate = next_candidates.begin(); it_next_candidate != next_candidates.begin() + num_direct_neighbors; ++ it_next_candidate) if ((queue.empty() || it_next_candidate->region != queue.back()) && it_next_candidate->region != take_path->region) queue.emplace_back(it_next_candidate->region); - if (take_path - next_candidates.begin() >= num_direct_neighbors) { + if (size_t(take_path - next_candidates.begin()) >= num_direct_neighbors) { // Remove the selected path from the queue. auto it = std::find(queue.begin(), queue.end(), take_path->region); assert(it != queue.end()); @@ -2083,8 +2271,10 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions( path.back().flipped == path.back().region->flips ? path.back().region->right.high : path.back().region->right.low, path.back().flipped == path.back().region->flips ? path.back().region->right.low : path.back().region->right.high); - // Update pheromones along this link. - take_path->link->pheromone = (1.f - pheromone_evaporation) * take_path->link->pheromone + pheromone_evaporation * pheromone_initial_deposit; + // Update pheromones along this link, see Ant Colony System (ACS) update rule. + // http://www.scholarpedia.org/article/Ant_colony_optimization + // The goal here is to lower the pheromone trace for paths taken to diversify the next path picked in the same batch of ants. + take_path->link->pheromone = (1.f - pheromone_diversification) * take_path->link->pheromone + pheromone_diversification * pheromone_initial_deposit; assert(validate_unprocessed()); } @@ -2104,18 +2294,33 @@ static std::vector<MonotonousRegionLink> chain_monotonous_regions( if (path_length < best_path_length) { best_path_length = path_length; std::swap(best_path, path); +#if 0 // #if ! defined(SLIC3R_DEBUG_ANTS) && ! defined(ndebug) + if (round == 0 && ant == 0) + std::cout << std::endl; + std::cout << Slic3r::format("round %1% ant %2% path length %3%", round, ant, path_length) << std::endl; +#endif + if (path_length == 0) + // Perfect path found. + goto end; + improved = true; } } - // Reinforce the path feromones with the best path. - float total_cost = best_path_length + EPSILON; + // Reinforce the path pheromones with the best path. + float total_cost = best_path_length + float(EPSILON); for (size_t i = 0; i + 1 < path.size(); ++ i) { MonotonousRegionLink &link = path[i]; link.next->pheromone = (1.f - pheromone_evaporation) * link.next->pheromone + pheromone_evaporation / total_cost; } + + if (improved) + num_rounds_no_change = 0; + else + ++ num_rounds_no_change; } - return best_path; +end: + return best_path; } // Traverse path, produce polylines. @@ -2195,7 +2400,6 @@ static void polylines_from_paths(const std::vector<MonotonousRegionLink> &path, int inext = it->vertical_up(); if (inext == -1 || it->vertical_up_quality() != SegmentIntersection::LinkQuality::Valid) break; - const Polygon &poly = poly_with_offset.contour(it->iContour); assert(it->iContour == vline.intersections[inext].iContour); emit_perimeter_segment_on_vertical_line(poly_with_offset, segs, i_vline, it->iContour, it - vline.intersections.data(), inext, *polyline, it->has_left_vertical_up()); it = vline.intersections.data() + inext; @@ -2215,7 +2419,6 @@ static void polylines_from_paths(const std::vector<MonotonousRegionLink> &path, int inext = it->vertical_down(); if (inext == -1 || it->vertical_down_quality() != SegmentIntersection::LinkQuality::Valid) break; - const Polygon &poly = poly_with_offset.contour(it->iContour); assert(it->iContour == vline.intersections[inext].iContour); emit_perimeter_segment_on_vertical_line(poly_with_offset, segs, i_vline, it->iContour, it - vline.intersections.data(), inext, *polyline, it->has_right_vertical_down()); it = vline.intersections.data() + inext; @@ -2285,8 +2488,8 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP ExPolygonWithOffset poly_with_offset( surface->expolygon, - rotate_vector.first, - scale_(this->overlap - (0.5 - INFILL_OVERLAP_OVER_SPACING) * this->spacing), - scale_(this->overlap - 0.5 * this->spacing)); + float(scale_(this->overlap - (0.5 - INFILL_OVERLAP_OVER_SPACING) * this->spacing)), + float(scale_(this->overlap - 0.5 * this->spacing))); if (poly_with_offset.n_contours_inner == 0) { // Not a single infill line fits. //FIXME maybe one shall trigger the gap fill here? @@ -2317,7 +2520,7 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP size_t n_vlines = (bounding_box.max(0) - bounding_box.min(0) + line_spacing - 1) / line_spacing; coord_t x0 = bounding_box.min(0); if (params.full_infill()) - x0 += (line_spacing + SCALED_EPSILON) / 2; + x0 += (line_spacing + coord_t(SCALED_EPSILON)) / 2; #ifdef SLIC3R_DEBUG static int iRun = 0; @@ -2359,7 +2562,7 @@ bool FillRectilinear2::fill_surface_by_lines(const Surface *surface, const FillP bool monotonous_infill = params.monotonous; // || params.density > 0.99; if (monotonous_infill) { std::vector<MonotonousRegion> regions = generate_montonous_regions(segs); - connect_monotonous_regions(regions, segs); + connect_monotonous_regions(regions, poly_with_offset, segs); if (! regions.empty()) { std::mt19937_64 rng; std::vector<MonotonousRegionLink> path = chain_monotonous_regions(regions, poly_with_offset, segs, rng); @@ -2478,10 +2681,10 @@ Polylines FillCubic::fill_surface(const Surface *surface, const FillParams ¶ params3.dont_connect = true; Polylines polylines_out; coordf_t dx = sqrt(0.5) * z; - if (! fill_surface_by_lines(surface, params2, 0.f, dx, polylines_out) || - ! fill_surface_by_lines(surface, params2, float(M_PI / 3.), - dx, polylines_out) || + if (! fill_surface_by_lines(surface, params2, 0.f, float(dx), polylines_out) || + ! fill_surface_by_lines(surface, params2, float(M_PI / 3.), - float(dx), polylines_out) || // Rotated by PI*2/3 + PI to achieve reverse sloping wall. - ! fill_surface_by_lines(surface, params3, float(M_PI * 2. / 3.), dx, polylines_out)) { + ! fill_surface_by_lines(surface, params3, float(M_PI * 2. / 3.), float(dx), polylines_out)) { printf("FillCubic::fill_surface() failed to fill a region.\n"); } return polylines_out; diff --git a/src/libslic3r/GCode/PreviewData.cpp b/src/libslic3r/GCode/PreviewData.cpp index 58b15e9a4..8aec327db 100644 --- a/src/libslic3r/GCode/PreviewData.cpp +++ b/src/libslic3r/GCode/PreviewData.cpp @@ -119,7 +119,8 @@ const Color GCodePreviewData::Extrusion::Default_Extrusion_Role_Colors[erCount] Color(1.0f, 1.0f, 0.0f, 1.0f), // erInternalInfill Color(1.0f, 0.0f, 1.0f, 1.0f), // erSolidInfill Color(0.0f, 1.0f, 1.0f, 1.0f), // erTopSolidInfill - Color(0.0f, 1.0f, 1.0f, 1.0f), // erIroning +// Color(1.0f, 0.7f, 0.61f, 1.0f), // erIroning + Color(1.0f, 0.55f, 0.41f, 1.0f), // erIroning Color(0.5f, 0.5f, 0.5f, 1.0f), // erBridgeInfill Color(1.0f, 1.0f, 1.0f, 1.0f), // erGapFill Color(0.5f, 0.0f, 0.0f, 1.0f), // erSkirt diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index d291be36c..fefc12ba8 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -218,6 +218,12 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Linux") target_link_libraries(libslic3r_gui ${DBUS_LIBRARIES}) endif() +if (SLIC3R_STATIC) + # FIXME: This was previously exported by wx-config but the wxWidgets + # cmake build forgets this and the build fails in debug mode (or on raspberry release) + target_compile_definitions(libslic3r_gui PUBLIC -DwxDEBUG_LEVEL=0) +endif() + if(APPLE) target_link_libraries(libslic3r_gui ${DISKARBITRATION_LIBRARY}) endif() diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 8be8f8b37..259b0c03b 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -4323,6 +4323,9 @@ void GLCanvas3D::update_ui_from_settings() _refresh_if_shown_on_screen(); } #endif // ENABLE_RETINA_GL + + bool enable_collapse = wxGetApp().app_config->get("show_collapse_button") == "1"; + enable_collapse_toolbar(enable_collapse); } @@ -4376,6 +4379,16 @@ void GLCanvas3D::msw_rescale() m_warning_texture.msw_rescale(*this); } +void GLCanvas3D::update_tooltip_for_settings_item_in_main_toolbar() +{ + std::string new_tooltip = _u8L("Switch to Settings") + + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "2] - " + _u8L("Print Settings Tab") + + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "3] - " + (m_process->current_printer_technology() == ptFFF ? _u8L("Filament Settings Tab") : _u8L("Material Settings Tab")) + + "\n" + "[" + GUI::shortkey_ctrl_prefix() + "4] - " + _u8L("Printer Settings Tab") ; + + m_main_toolbar.set_tooltip(get_main_toolbar_item_id("settings"), new_tooltip); +} + bool GLCanvas3D::has_toolpaths_to_export() const { return m_volumes.has_toolpaths_to_export(); @@ -5003,10 +5016,26 @@ bool GLCanvas3D::_init_main_toolbar() if (!m_main_toolbar.add_separator()) return false; + item.name = "settings"; + 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") ; + item.sprite_id = 10; + item.enabling_callback = GLToolbarItem::Default_Enabling_Callback; + item.visibility_callback = [this]() { return (wxGetApp().app_config->get("new_settings_layout_mode") == "1" || + wxGetApp().app_config->get("dlg_settings_layout_mode") == "1"); }; + item.left.action_callback = [this]() { wxGetApp().mainframe->select_tab(); }; + if (!m_main_toolbar.add_item(item)) + return false; + + if (!m_main_toolbar.add_separator()) + return false; + item.name = "layersediting"; item.icon_filename = "layers_white.svg"; item.tooltip = _utf8(L("Variable layer height")); - item.sprite_id = 10; + item.sprite_id = 11; item.left.toggable = true; item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); }; item.visibility_callback = [this]()->bool @@ -5028,7 +5057,7 @@ bool GLCanvas3D::_init_main_toolbar() item.name = "search"; item.icon_filename = "search_.svg"; item.tooltip = _utf8(L("Search")) + " [" + GUI::shortkey_ctrl_prefix() + "F]"; - item.sprite_id = 11; + item.sprite_id = 12; item.left.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) { @@ -5164,7 +5193,7 @@ bool GLCanvas3D::_init_view_toolbar() bool GLCanvas3D::_init_collapse_toolbar() { - if (!m_collapse_toolbar.is_enabled()) + if (!m_collapse_toolbar.is_enabled() && m_collapse_toolbar.get_items_count() > 0) return true; BackgroundTexture::Metadata background_data; @@ -5205,61 +5234,10 @@ bool GLCanvas3D::_init_collapse_toolbar() wxGetApp().plater()->collapse_sidebar(!wxGetApp().plater()->is_sidebar_collapsed()); }; - if (!m_collapse_toolbar.add_item(item)) - return false; - - if (!m_collapse_toolbar.add_separator()) - return false; - - item.name = "print"; - item.icon_filename = "cog.svg"; - item.tooltip = _utf8(L("Switch to Print Settings")) + " [" + GUI::shortkey_ctrl_prefix() + "2]"; - item.sprite_id = 1; - item.left.action_callback = [this]() { wxGetApp().mainframe->select_tab(/*0*/1); }; - - if (!m_collapse_toolbar.add_item(item)) - return false; - - item.name = "filament"; - item.icon_filename = "spool.svg"; - item.tooltip = _utf8(L("Switch to Filament Settings")) + " [" + GUI::shortkey_ctrl_prefix() + "3]"; - item.sprite_id = 2; - item.left.action_callback = [this]() { wxGetApp().mainframe->select_tab(/*1*/2); }; - item.visibility_callback = [this]() { return wxGetApp().plater()->printer_technology() == ptFFF; }; - - if (!m_collapse_toolbar.add_item(item)) - return false; - - item.name = "printer"; - item.icon_filename = "printer.svg"; - item.tooltip = _utf8(L("Switch to Printer Settings")) + " [" + GUI::shortkey_ctrl_prefix() + "4]"; - item.sprite_id = 3; - item.left.action_callback = [this]() { wxGetApp().mainframe->select_tab(/*2*/3); }; - - if (!m_collapse_toolbar.add_item(item)) - return false; - - item.name = "resin"; - item.icon_filename = "resin.svg"; - item.tooltip = _utf8(L("Switch to SLA Material Settings")) + " [" + GUI::shortkey_ctrl_prefix() + "3]"; - item.sprite_id = 4; - item.left.action_callback = [this]() { wxGetApp().mainframe->select_tab(/*1*/2); }; - item.visibility_callback = [this]() { return m_process->current_printer_technology() == ptSLA; }; - - if (!m_collapse_toolbar.add_item(item)) - return false; - - item.name = "sla_printer"; - item.icon_filename = "sla_printer.svg"; - item.tooltip = _utf8(L("Switch to Printer Settings")) + " [" + GUI::shortkey_ctrl_prefix() + "4]"; - item.sprite_id = 5; - item.left.action_callback = [this]() { wxGetApp().mainframe->select_tab(/*2*/3); }; - if (!m_collapse_toolbar.add_item(item)) return false; return true; - } bool GLCanvas3D::_set_current() diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 5684901f3..cc43e7d14 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -739,6 +739,7 @@ public: int get_main_toolbar_item_id(const std::string& name) const { return m_main_toolbar.get_item_id(name); } void force_main_toolbar_left_action(int item_id) { m_main_toolbar.force_left_action(item_id, *this); } void force_main_toolbar_right_action(int item_id) { m_main_toolbar.force_right_action(item_id, *this); } + void update_tooltip_for_settings_item_in_main_toolbar(); bool has_toolpaths_to_export() const; void export_toolpaths_to_obj(const char* filename) const; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index 0d437958c..e7f784773 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -247,7 +247,7 @@ bool GLToolbar::is_enabled() const void GLToolbar::set_enabled(bool enable) { - m_enabled = true; + m_enabled = enable;//true; etFIXME } bool GLToolbar::add_item(const GLToolbarItem::Data& data) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 5e4f85e87..dcef48de9 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -412,7 +412,8 @@ bool GUI_App::on_init_inner() if (wxImage::FindHandler(wxBITMAP_TYPE_PNG) == nullptr) wxImage::AddHandler(new wxPNGHandler()); mainframe = new MainFrame(); - mainframe->switch_to(true); // hide settings tabs after first Layout + // hide settings tabs after first Layout + mainframe->select_tab(0); sidebar().obj_list()->init_objects(); // propagate model objects to object list // update_mode(); // !!! do that later @@ -601,6 +602,8 @@ void GUI_App::recreate_GUI() MainFrame *old_main_frame = mainframe; mainframe = new MainFrame(); + // hide settings tabs after first Layout + mainframe->select_tab(0); // Propagate model objects to object list. sidebar().obj_list()->init_objects(); SetTopWindow(mainframe); diff --git a/src/slic3r/GUI/ImGuiWrapper.cpp b/src/slic3r/GUI/ImGuiWrapper.cpp index ea8b2afd3..4385fa276 100644 --- a/src/slic3r/GUI/ImGuiWrapper.cpp +++ b/src/slic3r/GUI/ImGuiWrapper.cpp @@ -7,6 +7,7 @@ #include <boost/format.hpp> #include <boost/log/trivial.hpp> +#include <boost/filesystem.hpp> #include <wx/string.h> #include <wx/event.h> @@ -27,10 +28,22 @@ #include "I18N.hpp" #include "Search.hpp" +#include "../Utils/MacDarkMode.hpp" +#include "nanosvg/nanosvg.h" +#include "nanosvg/nanosvgrast.h" + namespace Slic3r { namespace GUI { +static const std::map<const char, std::string> font_icons = { + {ImGui::PrintIconMarker , "cog" }, + {ImGui::PrinterIconMarker , "printer" }, + {ImGui::PrinterSlaIconMarker, "sla_printer"}, + {ImGui::FilamentIconMarker , "spool" }, + {ImGui::MaterialIconMarker , "resin" } +}; + ImGuiWrapper::ImGuiWrapper() : m_glyph_ranges(nullptr) , m_font_cjk(false) @@ -735,9 +748,9 @@ 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("Type"), view_params.type); check_box(_L("Category"), view_params.category); check_box(_L("Group"), view_params.group); + check_box(_L("Search in English"), view_params.english); } void ImGuiWrapper::disabled_begin(bool disabled) @@ -791,6 +804,59 @@ static const ImWchar ranges_keyboard_shortcuts[] = }; #endif // __APPLE__ + +std::vector<unsigned char> 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<unsigned char> empty_vector; + +#ifdef __WXMSW__ + std::string folder = "white\\"; +#else + std::string folder = "white/"; +#endif + if (!boost::filesystem::exists(Slic3r::var(folder + bitmap_name + ".svg"))) + folder.clear(); + + NSVGimage* image = ::nsvgParseFromFile(Slic3r::var(folder + bitmap_name + ".svg").c_str(), "px", 96.0f); + 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; + + int width = (int)(svg_scale * image->width + 0.5f); + int height = (int)(svg_scale * image->height + 0.5f); + int n_pixels = width * height; + if (n_pixels <= 0) { + ::nsvgDelete(image); + return empty_vector; + } + + NSVGrasterizer* rast = ::nsvgCreateRasterizer(); + if (rast == nullptr) { + ::nsvgDelete(image); + return empty_vector; + } + + std::vector<unsigned char> data(n_pixels * 4, 0); + ::nsvgRasterize(rast, image, 0, 0, svg_scale, data.data(), width, height, width * 4); + ::nsvgDeleteRasterizer(rast); + ::nsvgDelete(image); + + return data; +} + void ImGuiWrapper::init_font(bool compress) { destroy_font(); @@ -829,11 +895,33 @@ void ImGuiWrapper::init_font(bool compress) } #endif + float font_scale = m_font_size/15; + int icon_sz = lround(16 * font_scale); // default size of icon is 16 px + + int rect_id = io.Fonts->CustomRects.Size; // id of the rectangle added next + // add rectangles for the icons to the font atlas + for (auto& icon : font_icons) + io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz, icon_sz, 3.0 * font_scale + icon_sz); + // Build texture atlas unsigned char* pixels; int width, height; io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. + // Fill rectangles from the SVG-icons + for (auto icon : font_icons) { + if (const ImFontAtlas::CustomRect* rect = io.Fonts->GetCustomRectByIndex(rect_id)) { + std::vector<unsigned char> raw_data = load_svg(icon.second, icon_sz, icon_sz); + const ImU32* pIn = (ImU32*)raw_data.data(); + for (int y = 0; y < icon_sz; y++) { + ImU32* pOut = (ImU32*)pixels + (rect->Y + y) * width + (rect->X); + for (int x = 0; x < icon_sz; x++) + *pOut++ = *pIn++; + } + } + rect_id++; + } + // Upload texture to graphics system GLint last_texture; glsafe(::glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); diff --git a/src/slic3r/GUI/ImGuiWrapper.hpp b/src/slic3r/GUI/ImGuiWrapper.hpp index 6a1e27dcb..efb8acc9a 100644 --- a/src/slic3r/GUI/ImGuiWrapper.hpp +++ b/src/slic3r/GUI/ImGuiWrapper.hpp @@ -96,6 +96,7 @@ private: void render_draw_data(ImDrawData *draw_data); bool display_initialized() const; void destroy_font(); + std::vector<unsigned char> load_svg(const std::string& bitmap_name, unsigned target_width, unsigned target_height); static const char* clipboard_get(void* user_data); static void clipboard_set(void* user_data, const char* text); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index dd39f86d1..62fffc423 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -90,10 +90,12 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S // initialize layout auto sizer = new wxBoxSizer(wxVERTICAL); - if (m_plater) + if (m_plater && m_layout != slOld) sizer->Add(m_plater, 1, wxEXPAND); - if (m_tabpanel) + + if (m_tabpanel && m_layout != slDlg) sizer->Add(m_tabpanel, 1, wxEXPAND); + sizer->SetSizeHints(this); SetSizer(sizer); Fit(); @@ -227,6 +229,9 @@ 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) + m_settings_dialog->Destroy(); + // Stop the background thread (Windows and Linux). // Disconnect from a 3DConnextion driver (OSX). m_plater->get_mouse3d_controller().shutdown(); @@ -284,12 +289,25 @@ void MainFrame::update_title() void MainFrame::init_tabpanel() { - // 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_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; + + // From the very beginning the Print settings should be selected + m_last_selected_tab = m_layout == slDlg ? 0 : 1; + + if (m_layout == slDlg) { + m_settings_dialog = new SettingsDialog(this); + m_tabpanel = m_settings_dialog->get_tabpanel(); + } + else { + // 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()); + m_tabpanel->SetFont(Slic3r::GUI::wxGetApp().normal_font()); #endif + } m_tabpanel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, [this](wxEvent&) { auto panel = m_tabpanel->GetCurrentPage(); @@ -302,17 +320,22 @@ void MainFrame::init_tabpanel() // On GTK, the wxEVT_NOTEBOOK_PAGE_CHANGED event is triggered // before the MainFrame is fully set up. static_cast<Tab*>(panel)->OnActivate(); + m_last_selected_tab = m_tabpanel->GetSelection(); } else select_tab(0); }); -//! m_plater = new Slic3r::GUI::Plater(m_tabpanel, this); - m_plater = new Plater(this, this); - + if (m_layout == slOld) { + m_plater = new Plater(m_tabpanel, this); + m_tabpanel->AddPage(m_plater, _L("Plater")); + } + else { + m_plater = new Plater(this, this); + if (m_layout == slNew) + m_tabpanel->AddPage(new wxPanel(m_tabpanel), _L("Plater")); // empty panel just for Plater tab + } wxGetApp().plater_ = m_plater; -// m_tabpanel->AddPage(m_plater, _(L("Plater"))); - m_tabpanel->AddPage(new wxPanel(m_tabpanel), _L("Plater")); // empty panel just for Plater tab wxGetApp().obj_list()->create_popup_menus(); @@ -338,13 +361,6 @@ void MainFrame::init_tabpanel() } } -void MainFrame::switch_to(bool plater) -{ - this->m_plater->Show(plater); - this->m_tabpanel->Show(!plater); - this->Layout(); -} - void MainFrame::create_preset_tabs() { wxGetApp().update_label_colours_from_appconfig(); @@ -460,6 +476,11 @@ bool MainFrame::can_slice() const bool MainFrame::can_change_view() const { + if (m_layout == slNew) + return m_plater->IsShown(); + if (m_layout == slDlg) + return true; + // slOld layout mode int page_id = m_tabpanel->GetSelection(); return page_id != wxNOT_FOUND && dynamic_cast<const Slic3r::GUI::Plater*>(m_tabpanel->GetPage((size_t)page_id)) != nullptr; } @@ -758,25 +779,21 @@ void MainFrame::init_menubar() // Window menu auto windowMenu = new wxMenu(); { -//! size_t tab_offset = 0; if (m_plater) { append_menu_item(windowMenu, wxID_HIGHEST + 1, _(L("&Plater Tab")) + "\tCtrl+1", _(L("Show the plater")), - [this/*, tab_offset*/](wxCommandEvent&) { select_tab(/*(size_t)(-1)*/0); }, "plater", nullptr, + [this](wxCommandEvent&) { select_tab(0); }, "plater", nullptr, [this]() {return true; }, this); -//! tab_offset += 1; -//! } -//! if (tab_offset > 0) { windowMenu->AppendSeparator(); } append_menu_item(windowMenu, wxID_HIGHEST + 2, _(L("P&rint Settings Tab")) + "\tCtrl+2", _(L("Show the print settings")), - [this/*, tab_offset*/](wxCommandEvent&) { select_tab(/*tab_offset + 0*/1); }, "cog", nullptr, + [this/*, tab_offset*/](wxCommandEvent&) { select_tab(1); }, "cog", nullptr, [this]() {return true; }, this); wxMenuItem* item_material_tab = append_menu_item(windowMenu, wxID_HIGHEST + 3, _(L("&Filament Settings Tab")) + "\tCtrl+3", _(L("Show the filament settings")), - [this/*, tab_offset*/](wxCommandEvent&) { select_tab(/*tab_offset + 1*/2); }, "spool", nullptr, + [this/*, tab_offset*/](wxCommandEvent&) { select_tab(2); }, "spool", nullptr, [this]() {return true; }, this); m_changeable_menu_items.push_back(item_material_tab); wxMenuItem* item_printer_tab = append_menu_item(windowMenu, wxID_HIGHEST + 4, _(L("Print&er Settings Tab")) + "\tCtrl+4", _(L("Show the printer settings")), - [this/*, tab_offset*/](wxCommandEvent&) { select_tab(/*tab_offset + 2*/3); }, "printer", nullptr, + [this/*, tab_offset*/](wxCommandEvent&) { select_tab(3); }, "printer", nullptr, [this]() {return true; }, this); m_changeable_menu_items.push_back(item_printer_tab); if (m_plater) { @@ -1240,17 +1257,28 @@ void MainFrame::load_config(const DynamicPrintConfig& config) #endif } -void MainFrame::select_tab(size_t tab) +void MainFrame::select_tab(size_t tab/* = size_t(-1)*/) { - if (tab == /*(size_t)(-1)*/0) { - if (m_plater && !m_plater->IsShown()) - this->switch_to(true); + if (m_layout == slDlg) { + if (tab==0) { + if (m_settings_dialog->IsShown()) + this->SetFocus(); + return; + } + // Show/Activate Settings Dialog + if (m_settings_dialog->IsShown()) + m_settings_dialog->SetFocus(); + else + m_settings_dialog->Show(); } - else { - if (m_plater && m_plater->IsShown()) - switch_to(false); - m_tabpanel->SetSelection(tab); + else if (m_layout == slNew) { + m_plater->Show(tab == 0); + m_tabpanel->Show(tab != 0); + Layout(); } + + // when tab == -1, it means we should to show the last selected tab + m_tabpanel->SetSelection(tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == slDlg && tab != 0) ? tab-1 : tab); } // Set a camera direction, zoom to all objects. @@ -1355,5 +1383,79 @@ std::string MainFrame::get_dir_name(const wxString &full_name) const return boost::filesystem::path(full_name.wx_str()).parent_path().string(); } + +// ---------------------------------------------------------------------------- +// SettingsDialog +// ---------------------------------------------------------------------------- + +SettingsDialog::SettingsDialog(MainFrame* mainframe) +: DPIDialog(nullptr, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _L("Settings")), + m_main_frame(mainframe) +{ + this->SetFont(wxGetApp().normal_font()); + + wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + this->SetBackgroundColour(bgr_clr); + + // Load the icon either from the exe, or from the ico file. +#if _WIN32 + { + TCHAR szExeFileName[MAX_PATH]; + GetModuleFileName(nullptr, szExeFileName, MAX_PATH); + SetIcon(wxIcon(szExeFileName, wxBITMAP_TYPE_ICO)); + } +#else + SetIcon(wxIcon(var("PrusaSlicer_128px.png"), wxBITMAP_TYPE_PNG)); +#endif // _WIN32 + + // 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->Bind(wxEVT_KEY_UP, [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; } + default:break; + } + } + }); + + // initialize layout + auto sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(m_tabpanel, 1, wxEXPAND); + sizer->SetSizeHints(this); + SetSizer(sizer); + Fit(); + + const wxSize min_size = wxSize(85 * em_unit(), 50 * 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 + SetSize(min_size); +#else + SetMinSize(min_size); + SetSize(GetMinSize()); +#endif + Layout(); +} + +void SettingsDialog::on_dpi_changed(const wxRect& suggested_rect) +{ + const int& em = em_unit(); + const wxSize& size = wxSize(85 * em, 50 * em); + + SetMinSize(size); + Fit(); + Refresh(); +} + + } // GUI } // Slic3r diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 8fc0ed1f2..219f68319 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -43,6 +43,23 @@ struct PresetTab { PrinterTechnology technology; }; +// ---------------------------------------------------------------------------- +// SettingsDialog +// ---------------------------------------------------------------------------- + +class SettingsDialog : public DPIDialog +{ + wxNotebook* m_tabpanel { nullptr }; + MainFrame* m_main_frame {nullptr }; +public: + SettingsDialog(MainFrame* mainframe); + ~SettingsDialog() {} + wxNotebook* get_tabpanel() { return m_tabpanel; } + +protected: + void on_dpi_changed(const wxRect& suggested_rect) override; +}; + class MainFrame : public DPIFrame { bool m_loaded {false}; @@ -57,6 +74,8 @@ class MainFrame : public DPIFrame PrintHostQueueDialog *m_printhost_queue_dlg; + size_t m_last_selected_tab; + std::string get_base_name(const wxString &full_name, const char *extension = nullptr) const; std::string get_dir_name(const wxString &full_name) const; @@ -94,6 +113,12 @@ class MainFrame : public DPIFrame wxFileHistory m_recent_projects; + enum SettingsLayout { + slOld = 0, + slNew, + slDlg, + } m_layout; + protected: virtual void on_dpi_changed(const wxRect &suggested_rect); @@ -109,7 +134,6 @@ public: void update_title(); void init_tabpanel(); - void switch_to(bool plater); void create_preset_tabs(); void add_created_tab(Tab* panel); void init_menubar(); @@ -130,7 +154,9 @@ public: void export_configbundle(); void load_configbundle(wxString file = wxEmptyString); void load_config(const DynamicPrintConfig& config); - void select_tab(size_t tab); + // Select tab in m_tabpanel + // When tab == -1, will be selected last selected tab + void select_tab(size_t tab = size_t(-1)); void select_view(const std::string& direction); // Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig void on_config_changed(DynamicPrintConfig* cfg) const ; @@ -141,6 +167,7 @@ public: Plater* m_plater { nullptr }; wxNotebook* m_tabpanel { nullptr }; + SettingsDialog* m_settings_dialog { nullptr }; wxProgressDialog* m_progress_dialog { nullptr }; std::shared_ptr<ProgressStatusBar> m_statusbar; diff --git a/src/slic3r/GUI/ObjectDataViewModel.cpp b/src/slic3r/GUI/ObjectDataViewModel.cpp index 0905f4915..d9b9af016 100644 --- a/src/slic3r/GUI/ObjectDataViewModel.cpp +++ b/src/slic3r/GUI/ObjectDataViewModel.cpp @@ -1710,10 +1710,11 @@ bool BitmapChoiceRenderer::Render(wxRect rect, wxDC* dc, int state) { dc->DrawBitmap(icon, rect.x, rect.y + (rect.height - icon.GetHeight()) / 2); xoffset = icon.GetWidth() + 4; + + if (rect.height==0) + rect.height= icon.GetHeight(); } - if (rect.height==0) - rect.height= icon.GetHeight(); RenderText(m_value.GetText(), xoffset, rect, dc, state); return true; diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 83eb6c76f..5b2b5703b 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -105,6 +105,7 @@ void OptionsGroup::add_undo_buttuns_to_sizer(wxSizer* sizer, const t_field& fiel if (!m_show_modified_btns) { field->m_Undo_btn->set_as_hidden(); field->m_Undo_to_sys_btn->set_as_hidden(); + field->m_blinking_bmp->Hide(); return; } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index c4a43fbb0..0648c9f47 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -356,10 +356,10 @@ PresetBitmapComboBox(parent, wxSize(15 * wxGetApp().em_unit(), -1)), if (page_id == wxNOT_FOUND) return; - wxGetApp().tab_panel()->ChangeSelection(page_id); + wxGetApp().tab_panel()->SetSelection(page_id); // Switch to Settings NotePad - wxGetApp().mainframe->switch_to(false); + wxGetApp().mainframe->select_tab(); /* In a case of a multi-material printing, for editing another Filament Preset * it's needed to select this preset for the "Filament settings" Tab @@ -1101,9 +1101,8 @@ void Sidebar::jump_to_option(size_t selected) const Search::Option& opt = p->searcher.get_option(selected); wxGetApp().get_tab(opt.type)->activate_option(boost::nowide::narrow(opt.opt_key), boost::nowide::narrow(opt.category)); - // Switch to the Settings NotePad, if plater is shown - if (p->plater->IsShown()) - wxGetApp().mainframe->switch_to(false); + // Switch to the Settings NotePad + wxGetApp().mainframe->select_tab(); } ObjectManipulation* Sidebar::obj_manipul() @@ -1646,6 +1645,7 @@ struct Plater::priv void reset_all_gizmos(); void update_ui_from_settings(); + void update_main_toolbar_tooltips(); std::shared_ptr<ProgressStatusBar> statusbar(); std::string get_config(const std::string &key) const; BoundingBoxf bed_shape_bb() const; @@ -2046,7 +2046,11 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // collapse sidebar according to saved value - sidebar->collapse(wxGetApp().app_config->get("collapsed_sidebar") == "1"); + 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() @@ -2121,6 +2125,13 @@ void Plater::priv::update_ui_from_settings() preview->get_canvas3d()->update_ui_from_settings(); } +// Called after the print technology was changed. +// Update the tooltips for "Switch to Settings" button in maintoolbar +void Plater::priv::update_main_toolbar_tooltips() +{ + view3D->get_canvas3d()->update_tooltip_for_settings_item_in_main_toolbar(); +} + std::shared_ptr<ProgressStatusBar> Plater::priv::statusbar() { return main_frame->m_statusbar; @@ -5286,6 +5297,10 @@ void Plater::set_printer_technology(PrinterTechnology printer_technology) if (wxGetApp().mainframe) wxGetApp().mainframe->update_menubar(); + + p->update_main_toolbar_tooltips(); + + p->sidebar->get_searcher().set_printer_technology(printer_technology); } void Plater::changed_object(int obj_idx) diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index e2bccb1c7..88c643add 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -117,6 +117,13 @@ void PreferencesDialog::build() m_optgroup_general->append_single_option_line(option); #endif + def.label = L("Show the button for the collapse sidebar"); + 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" }); + option = Option(def, "show_collapse_button"); + m_optgroup_general->append_single_option_line(option); + m_optgroup_camera = std::make_shared<ConfigOptionsGroup>(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) { @@ -157,6 +164,8 @@ void PreferencesDialog::build() create_icon_size_slider(); m_icon_size_sizer->ShowItems(app_config->get("use_custom_toolbar_size") == "1"); + create_settings_mode_widget(); + 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); @@ -167,7 +176,7 @@ void PreferencesDialog::build() auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL); wxButton* btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this)); btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); }); - sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10); + sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 5); SetSizer(sizer); sizer->SetSizeHints(this); @@ -179,17 +188,46 @@ void PreferencesDialog::accept() warning_catcher(this, wxString::Format(_(L("You need to restart %s to make the changes effective.")), SLIC3R_APP_NAME)); } - auto app_config = get_app_config(); - for (std::map<std::string, std::string>::iterator it = m_values.begin(); it != m_values.end(); ++it) { - app_config->set(it->first, it->second); + auto app_config = get_app_config(); + + bool settings_layout_changed = m_values.find("old_settings_layout_mode") != m_values.end() || + m_values.find("new_settings_layout_mode") != m_values.end() || + m_values.find("dlg_settings_layout_mode") != m_values.end(); + + if (settings_layout_changed) { + // 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 + wxMessageDialog dialog(nullptr, + _L("Switching the settings layout mode will trigger application restart.\n" + "You will lose content of the plater.") + "\n\n" + + _L("Do you want to proceed?"), + wxString(SLIC3R_APP_NAME) + " - " + _L("Switching the settings layout mode"), + wxICON_QUESTION | wxOK | wxCANCEL); + + if (dialog.ShowModal() == wxID_CANCEL) + { + int selection = app_config->get("old_settings_layout_mode") == "1" ? 0 : + app_config->get("new_settings_layout_mode") == "1" ? 1 : + app_config->get("dlg_settings_layout_mode") == "1" ? 2 : 0; + + m_layout_mode_box->SetSelection(selection); + return; + } } - app_config->save(); + for (std::map<std::string, std::string>::iterator it = m_values.begin(); it != m_values.end(); ++it) + app_config->set(it->first, it->second); + app_config->save(); EndModal(wxID_OK); - // Nothify the UI to update itself from the ini file. - wxGetApp().update_ui_from_settings(); + if (settings_layout_changed) + // recreate application, if settings layout was changed + wxGetApp().recreate_GUI(); + else + // Nothify the UI to update itself from the ini file. + wxGetApp().update_ui_from_settings(); } void PreferencesDialog::on_dpi_changed(const wxRect &suggested_rect) @@ -272,6 +310,38 @@ void PreferencesDialog::create_icon_size_slider() m_optgroup_gui->sizer->Add(m_icon_size_sizer, 0, wxEXPAND | wxALL, em); } +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") }; + + auto app_config = get_app_config(); + int selection = app_config->get("old_settings_layout_mode") == "1" ? 0 : + app_config->get("new_settings_layout_mode") == "1" ? 1 : + app_config->get("dlg_settings_layout_mode") == "1" ? 2 : 0; + + wxWindow* parent = m_optgroup_gui->ctrl_parent(); + + m_layout_mode_box = new wxRadioBox(parent, wxID_ANY, _L("Settings layout mode"), wxDefaultPosition, wxDefaultSize, WXSIZEOF(choices), choices, + 3, wxRA_SPECIFY_ROWS); + m_layout_mode_box->SetFont(wxGetApp().normal_font()); + m_layout_mode_box->SetSelection(selection); + + m_layout_mode_box->Bind(wxEVT_RADIOBOX, [this](wxCommandEvent& e) { + int selection = e.GetSelection(); + + m_values["old_settings_layout_mode"] = boost::any_cast<bool>(selection == 0) ? "1" : "0"; + m_values["new_settings_layout_mode"] = boost::any_cast<bool>(selection == 1) ? "1" : "0"; + m_values["dlg_settings_layout_mode"] = boost::any_cast<bool>(selection == 2) ? "1" : "0"; + }); + + auto sizer = new wxBoxSizer(wxHORIZONTAL); + sizer->Add(m_layout_mode_box, 1, wxALIGN_CENTER_VERTICAL); + + m_optgroup_gui->sizer->Add(sizer, 0, wxEXPAND); +} + } // GUI } // Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/Preferences.hpp b/src/slic3r/GUI/Preferences.hpp index 35bf2b8c5..738e805b2 100644 --- a/src/slic3r/GUI/Preferences.hpp +++ b/src/slic3r/GUI/Preferences.hpp @@ -7,6 +7,8 @@ #include <wx/dialog.h> #include <map> +class wxRadioBox; + namespace Slic3r { namespace GUI { @@ -19,6 +21,7 @@ class PreferencesDialog : public DPIDialog std::shared_ptr<ConfigOptionsGroup> m_optgroup_camera; std::shared_ptr<ConfigOptionsGroup> m_optgroup_gui; wxSizer* m_icon_size_sizer; + wxRadioBox* m_layout_mode_box; bool isOSX {false}; public: PreferencesDialog(wxWindow* parent); @@ -31,6 +34,7 @@ protected: void on_dpi_changed(const wxRect &suggested_rect) override; void layout(); void create_icon_size_slider(); + void create_settings_mode_widget(); }; } // GUI diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index 844d2244c..33f097362 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -39,6 +39,23 @@ static const std::vector<std::wstring>& NameByType() return data; } +static char marker_by_type(Preset::Type type, PrinterTechnology pt) +{ + switch(type) { + case Preset::TYPE_PRINT: + case Preset::TYPE_SLA_PRINT: + return ImGui::PrintIconMarker; + case Preset::TYPE_FILAMENT: + return ImGui::FilamentIconMarker; + case Preset::TYPE_SLA_MATERIAL: + return ImGui::MaterialIconMarker; + case Preset::TYPE_PRINTER: + return pt == ptSLA ? ImGui::PrinterSlaIconMarker : ImGui::PrinterIconMarker; + default: + return ' '; + } +} + void FoundOption::get_marked_label_and_tooltip(const char** label_, const char** tooltip_) const { *label_ = marked_label.c_str(); @@ -106,17 +123,8 @@ void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type ty emplace(opt_key, label); else for (int i = 0; i < cnt; ++i) - emplace(opt_key + "[" + std::to_string(i) + "]", label); - - /*const GroupAndCategory& gc = groups_and_categories[opt_key]; - if (gc.group.IsEmpty() || gc.category.IsEmpty()) - continue; - - if (!label.IsEmpty()) - options.emplace_back(Option{opt_key, type, - label, _(label), - gc.group, _(gc.group), - gc.category, _(gc.category) });*/ + // ! It's very important to use "#". opt_key#n is a real option key used in GroupAndCategory + emplace(opt_key + "#" + std::to_string(i), label); } } @@ -183,19 +191,19 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) bool full_list = search.empty(); std::wstring sep = L" : "; - const std::vector<std::wstring>& name_by_type = NameByType(); - auto get_label = [this, &name_by_type, &sep](const Option& opt) + auto get_label = [this, &sep](const Option& opt) { std::wstring out; + out += marker_by_type(opt.type, printer_technology); const std::wstring *prev = nullptr; for (const std::wstring * const s : { - view_params.type ? &(name_by_type[opt.type]) : nullptr, view_params.category ? &opt.category_local : nullptr, view_params.group ? &opt.group_local : nullptr, &opt.label_local }) if (s != nullptr && (prev == nullptr || *prev != *s)) { - if (! out.empty()) +// if (! out.empty()) + if (out.size()>2) out += sep; out += *s; prev = s; @@ -203,17 +211,18 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) return out; }; - auto get_label_english = [this, &name_by_type, &sep](const Option& opt) + auto get_label_english = [this, &sep](const Option& opt) { std::wstring out; + out += marker_by_type(opt.type, printer_technology); const std::wstring*prev = nullptr; for (const std::wstring * const s : { - view_params.type ? &name_by_type[opt.type] : nullptr, view_params.category ? &opt.category : nullptr, view_params.group ? &opt.group : nullptr, &opt.label }) if (s != nullptr && (prev == nullptr || *prev != *s)) { - if (! out.empty()) +// if (! out.empty()) + if (out.size()>2) out += sep; out += *s; prev = s; @@ -221,9 +230,9 @@ bool OptionsSearcher::search(const std::string& search, bool force/* = false*/) return out; }; - auto get_tooltip = [this, &name_by_type, &sep](const Option& opt) + auto get_tooltip = [this, &sep](const Option& opt) { - return name_by_type[opt.type] + sep + + return marker_by_type(opt.type, printer_technology) + opt.category_local + sep + opt.group_local + sep + opt.label_local; }; @@ -423,15 +432,15 @@ SearchDialog::SearchDialog(OptionsSearcher* searcher) wxBoxSizer* check_sizer = new wxBoxSizer(wxHORIZONTAL); - check_type = new wxCheckBox(this, wxID_ANY, _L("Type")); check_category = new wxCheckBox(this, wxID_ANY, _L("Category")); check_group = new wxCheckBox(this, wxID_ANY, _L("Group")); + check_english = new wxCheckBox(this, wxID_ANY, _L("Search in English")); wxStdDialogButtonSizer* cancel_btn = this->CreateStdDialogButtonSizer(wxCANCEL); - check_sizer->Add(check_type, 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); + check_sizer->Add(check_english, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border); check_sizer->AddStretchSpacer(border); check_sizer->Add(cancel_btn, 0, wxALIGN_CENTER_VERTICAL); @@ -450,7 +459,7 @@ SearchDialog::SearchDialog(OptionsSearcher* searcher) search_list->Bind(wxEVT_LEFT_UP, &SearchDialog::OnMouseClick, this); search_list->Bind(wxEVT_KEY_DOWN,&SearchDialog::OnKeyDown, this); - check_type ->Bind(wxEVT_CHECKBOX, &SearchDialog::OnCheck, this); + check_english ->Bind(wxEVT_CHECKBOX, &SearchDialog::OnCheck, this); check_category->Bind(wxEVT_CHECKBOX, &SearchDialog::OnCheck, this); check_group ->Bind(wxEVT_CHECKBOX, &SearchDialog::OnCheck, this); @@ -470,9 +479,9 @@ void SearchDialog::Popup(wxPoint position /*= wxDefaultPosition*/) update_list(); const OptionViewParameters& params = searcher->view_params; - check_type->SetValue(params.type); check_category->SetValue(params.category); check_group->SetValue(params.group); + check_english->SetValue(params.english); this->SetPosition(position); this->ShowModal(); @@ -538,7 +547,7 @@ void SearchDialog::update_list() const std::vector<FoundOption>& filters = searcher->found_options(); for (const FoundOption& item : filters) - search_list->Append(from_u8(item.label)); + search_list->Append(from_u8(item.label).Remove(0, 1)); } void SearchDialog::OnKeyDown(wxKeyEvent& event) @@ -570,7 +579,7 @@ void SearchDialog::OnKeyDown(wxKeyEvent& event) void SearchDialog::OnCheck(wxCommandEvent& event) { OptionViewParameters& params = searcher->view_params; - params.type = check_type->GetValue(); + params.english = check_english->GetValue(); params.category = check_category->GetValue(); params.group = check_group->GetValue(); diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index 5fcb58f1e..60ef16c0f 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -66,9 +66,9 @@ struct FoundOption { struct OptionViewParameters { - bool type {false}; bool category {false}; bool group {true }; + bool english {false}; int hovered_id {-1}; }; @@ -77,6 +77,7 @@ class OptionsSearcher { std::string search_line; std::map<std::string, GroupAndCategory> groups_and_categories; + PrinterTechnology printer_technology; std::vector<Option> options {}; std::vector<FoundOption> found {}; @@ -120,6 +121,8 @@ public: const std::vector<FoundOption>& found_options() { return found; } const GroupAndCategory& get_group_and_category (const std::string& opt_key) { return groups_and_categories[opt_key]; } std::string& search_string() { return search_line; } + + void set_printer_technology(PrinterTechnology pt) { printer_technology = pt; } }; @@ -167,9 +170,9 @@ class SearchDialog : public GUI::DPIDialog wxTextCtrl* search_line { nullptr }; wxListBox* search_list { nullptr }; - wxCheckBox* check_type { nullptr }; wxCheckBox* check_category { nullptr }; wxCheckBox* check_group { nullptr }; + wxCheckBox* check_english { nullptr }; OptionsSearcher* searcher;