From 495a71ed00a295847edaec879d8cc7006a810636 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 5 Feb 2020 16:53:26 +0100 Subject: [PATCH 1/4] Implemented top_solid_min_thickness / bottom_solid_min_thickness. The two new config keys define a minimum vertical shell thickness. The top shell thickness is calculated as a maximum of sum over top_solid_layers * layer heights and top_solid_min_thickness, the bottom shell thickness is calculated as a maximum of sum over bottom_solid_layers * layer heights and bottom_solid_min_thickness. The results of the formula above are shown at the Print parameter page below the two new values to hint the user about the interaction of the old versus new config values. top_solid_min_thickness has no meaning if top_solid_layers is zero, bottom_solid_min_thickness has no meaning if bottom_solid_layers is zero. --- src/libslic3r/CustomGCode.hpp | 2 +- src/libslic3r/Layer.hpp | 1 + src/libslic3r/PrintConfig.cpp | 30 +++++++ src/libslic3r/PrintConfig.hpp | 4 + src/libslic3r/PrintObject.cpp | 117 +++++++++++++++++--------- src/libslic3r/Slicing.cpp | 17 ++++ src/libslic3r/Slicing.hpp | 13 ++- src/slic3r/GUI/ConfigManipulation.cpp | 15 ++-- src/slic3r/GUI/Plater.cpp | 2 +- src/slic3r/GUI/Preset.cpp | 3 +- src/slic3r/GUI/PresetHints.cpp | 69 ++++++++++++++- src/slic3r/GUI/PresetHints.hpp | 5 ++ src/slic3r/GUI/Tab.cpp | 14 +++ src/slic3r/GUI/Tab.hpp | 6 +- 14 files changed, 246 insertions(+), 52 deletions(-) diff --git a/src/libslic3r/CustomGCode.hpp b/src/libslic3r/CustomGCode.hpp index 5ab4c76efc..e54599ca64 100644 --- a/src/libslic3r/CustomGCode.hpp +++ b/src/libslic3r/CustomGCode.hpp @@ -42,7 +42,7 @@ enum Mode SingleExtruder, // Single extruder printer preset is selected MultiAsSingle, // Multiple extruder printer preset is selected, but // this mode works just for Single extruder print - // (For all print from objects settings is used just one extruder) + // (The same extruder is assigned to all ModelObjects and ModelVolumes). MultiExtruder // Multiple extruder printer preset is selected }; diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index b7725d11da..d66aa8f013 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -105,6 +105,7 @@ public: coordf_t slice_z; // Z used for slicing in unscaled coordinates coordf_t print_z; // Z used for printing in unscaled coordinates coordf_t height; // layer height in unscaled coordinates + coordf_t bottom_z() const { return this->print_z - this->height; } // Collection of expolygons generated by slicing the possibly multiple meshes of the source geometry // (with possibly differing extruder ID and slicing parameters) and merged. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index ebb05772fd..b3b686f03a 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -168,6 +168,17 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->set_default_value(new ConfigOptionInt(3)); + def = this->add("bottom_solid_min_thickness", coFloat); + //TRN To be shown in Print Settings "Top solid layers" + def->label = L("Bottom"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("The number of bottom solid layers is increased above bottom_solid_layers if necessary to satisfy " + "minimum thickness of bottom shell."); + def->full_label = L("Minimum bottom shell thickness"); + def->sidetext = L("mm"); + def->min = 0; + def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("bridge_acceleration", coFloat); def->label = L("Bridge"); def->tooltip = L("This is the acceleration your printer will use for bridges. " @@ -1782,6 +1793,13 @@ void PrintConfigDef::init_fff_params() def->shortcut.push_back("bottom_solid_layers"); def->min = 0; + def = this->add("solid_min_thickness", coFloat); + def->label = L("Minimum thickness of a top / bottom shell"); + def->tooltip = L("Minimum thickness of a top / bottom shell"); + def->shortcut.push_back("top_solid_min_thickness"); + def->shortcut.push_back("bottom_solid_min_thickness"); + def->min = 0; + def = this->add("spiral_vase", coBool); def->label = L("Spiral vase"); def->tooltip = L("This feature will raise Z gradually while printing a single-walled object " @@ -2128,6 +2146,18 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->set_default_value(new ConfigOptionInt(3)); + def = this->add("top_solid_min_thickness", coFloat); + //TRN To be shown in Print Settings "Top solid layers" + def->label = L("Top"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("The number of top solid layers is increased above top_solid_layers if necessary to satisfy " + "minimum thickness of top shell." + " This is useful to prevent pillowing effect when printing with variable layer height."); + def->full_label = L("Minimum top shell thickness"); + def->sidetext = L("mm"); + def->min = 0; + def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("travel_speed", coFloat); def->label = L("Travel"); def->tooltip = L("Speed for travel moves (jumps between distant extrusion points)."); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 5130d3b05b..c854feafc8 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -466,6 +466,7 @@ class PrintRegionConfig : public StaticPrintConfig public: ConfigOptionFloat bridge_angle; ConfigOptionInt bottom_solid_layers; + ConfigOptionFloat bottom_solid_min_thickness; ConfigOptionFloat bridge_flow_ratio; ConfigOptionFloat bridge_speed; ConfigOptionBool ensure_vertical_shell_thickness; @@ -501,6 +502,7 @@ public: ConfigOptionBool thin_walls; ConfigOptionFloatOrPercent top_infill_extrusion_width; ConfigOptionInt top_solid_layers; + ConfigOptionFloat top_solid_min_thickness; ConfigOptionFloatOrPercent top_solid_infill_speed; ConfigOptionBool wipe_into_infill; @@ -509,6 +511,7 @@ protected: { OPT_PTR(bridge_angle); OPT_PTR(bottom_solid_layers); + OPT_PTR(bottom_solid_min_thickness); OPT_PTR(bridge_flow_ratio); OPT_PTR(bridge_speed); OPT_PTR(ensure_vertical_shell_thickness); @@ -542,6 +545,7 @@ protected: OPT_PTR(top_infill_extrusion_width); OPT_PTR(top_solid_infill_speed); OPT_PTR(top_solid_layers); + OPT_PTR(top_solid_min_thickness); OPT_PTR(wipe_into_infill); } }; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 5dcaf8dfbd..2ff3613090 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -507,7 +507,9 @@ bool PrintObject::invalidate_state_by_config_options(const std::vectorslicing_parameters().min_layer_height; + // Does this region possibly produce more than 1 top or bottom layer? + auto has_extra_layers_fn = [min_layer_height](const PrintRegionConfig &config) { + auto num_extra_layers = [min_layer_height](int num_solid_layers, coordf_t min_shell_thickness) { + if (num_solid_layers == 0) + return 0; + int n = num_solid_layers - 1; + int n2 = int(ceil(min_shell_thickness / min_layer_height)); + return std::max(n, n2 - 1); + }; + return num_extra_layers(config.top_solid_layers, config.top_solid_min_thickness) + + num_extra_layers(config.bottom_solid_layers, config.bottom_solid_min_thickness) > 0; + }; std::vector cache_top_botom_regions(m_layers.size(), DiscoverVerticalShellsCacheEntry()); bool top_bottom_surfaces_all_regions = this->region_volumes.size() > 1 && ! m_config.interface_shells.value; if (top_bottom_surfaces_all_regions) { @@ -921,11 +936,11 @@ void PrintObject::discover_vertical_shells() // is calculated over all materials. // Is the "ensure vertical wall thickness" applicable to any region? bool has_extra_layers = false; - for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) { - const PrintRegion ®ion = *m_print->get_region(idx_region); - if (region.config().ensure_vertical_shell_thickness.value && - (region.config().top_solid_layers.value > 1 || region.config().bottom_solid_layers.value > 1)) { + for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++idx_region) { + const PrintRegionConfig &config = m_print->get_region(idx_region)->config(); + if (config.ensure_vertical_shell_thickness.value && has_extra_layers_fn(config)) { has_extra_layers = true; + break; } } if (! has_extra_layers) @@ -1006,9 +1021,7 @@ void PrintObject::discover_vertical_shells() if (! region.config().ensure_vertical_shell_thickness.value) // This region will be handled by discover_horizontal_shells(). continue; - int n_extra_top_layers = std::max(0, region.config().top_solid_layers.value - 1); - int n_extra_bottom_layers = std::max(0, region.config().bottom_solid_layers.value - 1); - if (n_extra_top_layers + n_extra_bottom_layers == 0) + if (! has_extra_layers_fn(region.config())) // Zero or 1 layer, there is no additional vertical wall thickness enforced. continue; @@ -1049,7 +1062,7 @@ void PrintObject::discover_vertical_shells() BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - start : ensure vertical wall thickness"; tbb::parallel_for( tbb::blocked_range(0, m_layers.size(), grain_size), - [this, idx_region, n_extra_top_layers, n_extra_bottom_layers, &cache_top_botom_regions] + [this, idx_region, &cache_top_botom_regions] (const tbb::blocked_range& range) { // printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end()); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { @@ -1060,8 +1073,9 @@ void PrintObject::discover_vertical_shells() ++ debug_idx; #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - Layer *layer = m_layers[idx_layer]; - LayerRegion *layerm = layer->m_regions[idx_region]; + Layer *layer = m_layers[idx_layer]; + LayerRegion *layerm = layer->m_regions[idx_region]; + const PrintRegionConfig ®ion_config = layerm->region()->config(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING layerm->export_region_slices_to_svg_debug("4_discover_vertical_shells-initial"); @@ -1101,30 +1115,47 @@ void PrintObject::discover_vertical_shells() } } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - // Reset the top / bottom inflated regions caches of entries, which are out of the moving window. - bool hole_first = true; - for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) - if (n >= 0 && n < (int)m_layers.size()) { - const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[n]; - if (hole_first) { - hole_first = false; - polygons_append(holes, cache.holes); - } - else if (! holes.empty()) { - holes = intersection(holes, cache.holes); - } - size_t n_shell_old = shell.size(); - if (n > int(idx_layer)) - // Collect top surfaces. - polygons_append(shell, cache.top_surfaces); - else if (n < int(idx_layer)) - // Collect bottom and bottom bridge surfaces. - polygons_append(shell, cache.bottom_surfaces); - // Running the union_ using the Clipper library piece by piece is cheaper - // than running the union_ all at once. - if (n_shell_old < shell.size()) - shell = union_(shell, false); - } + polygons_append(holes, cache_top_botom_regions[idx_layer].holes); + { + // Gather top regions projected to this layer. + coordf_t print_z = layer->print_z; + int n_top_layers = region_config.top_solid_layers.value; + for (int i = int(idx_layer) + 1; + i < int(m_layers.size()) && + (i < int(idx_layer) + n_top_layers || + m_layers[i]->print_z - print_z < region_config.top_solid_min_thickness - EPSILON); + ++ i) { + const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; + if (! holes.empty()) + holes = intersection(holes, cache.holes); + if (! cache.top_surfaces.empty()) { + polygons_append(shell, cache.top_surfaces); + // Running the union_ using the Clipper library piece by piece is cheaper + // than running the union_ all at once. + shell = union_(shell, false); + } + } + } + { + // Gather bottom regions projected to this layer. + coordf_t bottom_z = layer->bottom_z(); + int n_bottom_layers = region_config.bottom_solid_layers.value; + for (int i = int(idx_layer) - 1; + i >= 0 && + (i > int(idx_layer) - n_bottom_layers || + bottom_z - m_layers[i]->bottom_z() < region_config.bottom_solid_min_thickness - EPSILON); + -- i) { + const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; + if (! holes.empty()) + holes = intersection(holes, cache.holes); + if (! cache.bottom_surfaces.empty()) { + polygons_append(shell, cache.bottom_surfaces); + // Running the union_ using the Clipper library piece by piece is cheaper + // than running the union_ all at once. + shell = union_(shell, false); + } + } + } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell)); @@ -2280,7 +2311,8 @@ void PrintObject::discover_horizontal_shells() for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { for (size_t i = 0; i < m_layers.size(); ++ i) { m_print->throw_if_canceled(); - LayerRegion *layerm = m_layers[i]->regions()[region_id]; + Layer *layer = m_layers[i]; + LayerRegion *layerm = layer->regions()[region_id]; const PrintRegionConfig ®ion_config = layerm->region()->config(); if (region_config.solid_infill_every_layers.value > 0 && region_config.fill_density.value > 0 && (i % region_config.solid_infill_every_layers) == 0) { @@ -2295,6 +2327,8 @@ void PrintObject::discover_horizontal_shells() if (region_config.ensure_vertical_shell_thickness.value) continue; + coordf_t print_z = layer->print_z; + coordf_t bottom_z = layer->bottom_z(); for (size_t idx_surface_type = 0; idx_surface_type < 3; ++ idx_surface_type) { m_print->throw_if_canceled(); SurfaceType type = (idx_surface_type == 0) ? stTop : (idx_surface_type == 1) ? stBottom : stBottomBridge; @@ -2323,10 +2357,15 @@ void PrintObject::discover_horizontal_shells() continue; // Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == stTop) ? 'top' : 'bottom'; - size_t solid_layers = (type == stTop) ? region_config.top_solid_layers.value : region_config.bottom_solid_layers.value; - for (int n = (type == stTop) ? i-1 : i+1; std::abs(n - (int)i) < solid_layers; (type == stTop) ? -- n : ++ n) { - if (n < 0 || n >= int(m_layers.size())) - continue; + // Scatter top / bottom regions to other layers. Scattering process is inherently serial, it is difficult to parallelize without locking. + for (int n = (type == stTop) ? int(i) - 1 : int(i) + 1; + (type == stTop) ? + (n >= 0 && (int(i) - n < region_config.top_solid_layers.value || + print_z - m_layers[n]->print_z < region_config.top_solid_min_thickness.value - EPSILON)) : + (n < int(m_layers.size()) && (n - int(i) < region_config.bottom_solid_layers.value || + m_layers[n]->bottom_z() - bottom_z < region_config.bottom_solid_min_thickness.value - EPSILON)); + (type == stTop) ? -- n : ++ n) + { // Slic3r::debugf " looking for neighbors on layer %d...\n", $n; // Reference to the lower layer of a TOP surface, or an upper layer of a BOTTOM surface. LayerRegion *neighbor_layerm = m_layers[n]->regions()[region_id]; diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 2a32ba5ef4..82d2d19890 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -41,6 +41,23 @@ inline coordf_t max_layer_height_from_nozzle(const PrintConfig &print_config, in return std::max(min_layer_height, (max_layer_height == 0.) ? (0.75 * nozzle_dmr) : max_layer_height); } +// Minimum layer height for the variable layer height algorithm. +coordf_t Slicing::min_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle) +{ + coordf_t min_layer_height = print_config.opt_float("min_layer_height", idx_nozzle - 1); + return (min_layer_height == 0.) ? MIN_LAYER_HEIGHT_DEFAULT : std::max(MIN_LAYER_HEIGHT, min_layer_height); +} + +// Maximum layer height for the variable layer height algorithm, 3/4 of a nozzle dimaeter by default, +// it should not be smaller than the minimum layer height. +coordf_t Slicing::max_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle) +{ + coordf_t min_layer_height = min_layer_height_from_nozzle(print_config, idx_nozzle); + coordf_t max_layer_height = print_config.opt_float("max_layer_height", idx_nozzle - 1); + coordf_t nozzle_dmr = print_config.opt_float("nozzle_diameter", idx_nozzle - 1); + return std::max(min_layer_height, (max_layer_height == 0.) ? (0.75 * nozzle_dmr) : max_layer_height); +} + SlicingParameters SlicingParameters::create_from_config( const PrintConfig &print_config, const PrintObjectConfig &object_config, diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index 036344b224..95cf6891b2 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -99,7 +99,6 @@ struct SlicingParameters }; static_assert(IsTriviallyCopyable::value, "SlicingParameters class is not POD (and it should be - see constructor)."); - // The two slicing parameters lead to the same layering as long as the variable layer thickness is not in action. inline bool equal_layering(const SlicingParameters &sp1, const SlicingParameters &sp2) { @@ -183,7 +182,17 @@ extern int generate_layer_height_texture( const std::vector &layers, void *data, int rows, int cols, bool level_of_detail_2nd_level); -}; // namespace Slic3r +namespace Slicing { + // Minimum layer height for the variable layer height algorithm. Nozzle index is 1 based. + coordf_t min_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle); + + // Maximum layer height for the variable layer height algorithm, 3/4 of a nozzle dimaeter by default, + // it should not be smaller than the minimum layer height. + // Nozzle index is 1 based. + coordf_t max_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle); +} // namespace Slicing + +} // namespace Slic3r namespace cereal { diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 966f347618..a8773d7361 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -233,22 +233,27 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) "solid_infill_every_layers", "solid_infill_below_area", "infill_extruder" }) toggle_field(el, have_infill); - bool have_solid_infill = config->opt_int("top_solid_layers") > 0 || config->opt_int("bottom_solid_layers") > 0; + bool has_spiral_vase = config->opt_bool("spiral_vase"); + bool has_top_solid_infill = config->opt_int("top_solid_layers") > 0; + bool has_bottom_solid_infill = config->opt_int("bottom_solid_layers") > 0; + bool has_solid_infill = has_top_solid_infill || has_bottom_solid_infill; // solid_infill_extruder uses the same logic as in Print::extruders() for (auto el : { "top_fill_pattern", "bottom_fill_pattern", "infill_first", "solid_infill_extruder", "solid_infill_extrusion_width", "solid_infill_speed" }) - toggle_field(el, have_solid_infill); + toggle_field(el, has_solid_infill); for (auto el : { "fill_angle", "bridge_angle", "infill_extrusion_width", "infill_speed", "bridge_speed" }) - toggle_field(el, have_infill || have_solid_infill); + toggle_field(el, have_infill || has_solid_infill); + + toggle_field("top_solid_min_thickness", ! has_spiral_vase && has_top_solid_infill); + toggle_field("bottom_solid_min_thickness", ! has_spiral_vase && has_bottom_solid_infill); // Gap fill is newly allowed in between perimeter lines even for empty infill (see GH #1476). toggle_field("gap_fill_speed", have_perimeters); - bool have_top_solid_infill = config->opt_int("top_solid_layers") > 0; for (auto el : { "top_infill_extrusion_width", "top_solid_infill_speed" }) - toggle_field(el, have_top_solid_infill); + toggle_field(el, has_top_solid_infill); bool have_default_acceleration = config->opt_float("default_acceleration") > 0; for (auto el : { "perimeter_acceleration", "infill_acceleration", diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 1b69decfc2..aadfdd0dd6 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1980,7 +1980,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) "extruder_colour", "filament_colour", "max_print_height", "printer_model", "printer_technology", // These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor. "layer_height", "first_layer_height", "min_layer_height", "max_layer_height", - "brim_width", "perimeters", "perimeter_extruder", "fill_density", "infill_extruder", "top_solid_layers", "bottom_solid_layers", "solid_infill_extruder", + "brim_width", "perimeters", "perimeter_extruder", "fill_density", "infill_extruder", "top_solid_layers", "support_material", "support_material_extruder", "support_material_interface_extruder", "support_material_contact_distance", "raft_layers" })) , sidebar(new Sidebar(q)) diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 98fcf3f421..00e004f758 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -386,7 +386,8 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config) const std::vector& Preset::print_options() { static std::vector s_opts { - "layer_height", "first_layer_height", "perimeters", "spiral_vase", "slice_closing_radius", "top_solid_layers", "bottom_solid_layers", + "layer_height", "first_layer_height", "perimeters", "spiral_vase", "slice_closing_radius", + "top_solid_layers", "top_solid_min_thickness", "bottom_solid_layers", "bottom_solid_min_thickness", "extra_perimeters", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs", "seam_position", "external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern", "infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle", diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index f6281d7af4..71db6d35b2 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -1,6 +1,7 @@ #include #include "libslic3r/Flow.hpp" +#include "libslic3r/Slicing.hpp" #include "libslic3r/libslic3r.h" #include "PresetBundle.hpp" @@ -242,7 +243,7 @@ std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &pre float nozzle_diameter = float(printer_config.opt_float("nozzle_diameter", 0)); std::string out; - if (layer_height <= 0.f){ + if (layer_height <= 0.f) { out += _utf8(L("Recommended object thin wall thickness: Not available due to invalid layer height.")); return out; } @@ -272,4 +273,70 @@ std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &pre return out; } + +// Produce a textual explanation of the combined effects of the top/bottom_solid_layers +// versus top/bottom_min_shell_thickness. Which of the two values wins depends +// on the active layer height. +std::string PresetHints::top_bottom_shell_thickness_explanation(const PresetBundle &preset_bundle) +{ + const DynamicPrintConfig &print_config = preset_bundle.prints .get_edited_preset().config; + const DynamicPrintConfig &printer_config = preset_bundle.printers .get_edited_preset().config; + + std::string out; + + int top_solid_layers = print_config.opt_int("top_solid_layers"); + int bottom_solid_layers = print_config.opt_int("bottom_solid_layers"); + bool has_top_layers = top_solid_layers > 0; + bool has_bottom_layers = bottom_solid_layers > 0; + bool has_shell = has_top_layers && has_bottom_layers; + double top_solid_min_thickness = print_config.opt_float("top_solid_min_thickness"); + 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. + double min_layer_height = (has_shell && variable_layer_height) ? Slicing::min_layer_height_from_nozzle(printer_config, 1) : layer_height; + double max_layer_height = (has_shell && 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.")); + return out; + } + + if (has_top_layers) { + double top_shell_thickness = top_solid_layers * layer_height; + if (top_shell_thickness < top_solid_min_thickness) { + // top_solid_min_shell_thickness triggers even in case of normal layer height. Round the top_shell_thickness up + // to an integer multiply of layer_height. + double n = ceil(top_solid_min_thickness / layer_height); + top_shell_thickness = n * layer_height; + } + double top_shell_thickness_minimum = std::max(top_solid_min_thickness, top_solid_layers * min_layer_height); + out += (boost::format(_utf8(L("Top shell is %1% mm thick for layer height %2% mm."))) % top_shell_thickness % layer_height).str(); + if (variable_layer_height && top_shell_thickness_minimum < top_shell_thickness) { + out += " "; + out += (boost::format(_utf8(L("Minimum top shell thickness is %1% mm."))) % top_shell_thickness_minimum).str(); + } + } + + if (has_bottom_layers) { + double bottom_shell_thickness = bottom_solid_layers * layer_height; + if (bottom_shell_thickness < bottom_solid_min_thickness) { + // bottom_solid_min_shell_thickness triggers even in case of normal layer height. Round the bottom_shell_thickness up + // to an integer multiply of layer_height. + double n = ceil(bottom_solid_min_thickness / layer_height); + bottom_shell_thickness = n * layer_height; + } + double bottom_shell_thickness_minimum = std::max(bottom_solid_min_thickness, bottom_solid_layers * min_layer_height); + if (! out.empty()) + out += "\n"; + out += (boost::format(_utf8(L("Bottom shell is %1% mm thick for layer height %2% mm."))) % bottom_shell_thickness % layer_height).str(); + if (variable_layer_height && bottom_shell_thickness_minimum < bottom_shell_thickness) { + out += " "; + out += (boost::format(_utf8(L("Minimum bottom shell thickness is %1% mm."))) % bottom_shell_thickness_minimum).str(); + } + } + + return out; +} + }; // namespace Slic3r diff --git a/src/slic3r/GUI/PresetHints.hpp b/src/slic3r/GUI/PresetHints.hpp index 39bf0b100b..be049c2c87 100644 --- a/src/slic3r/GUI/PresetHints.hpp +++ b/src/slic3r/GUI/PresetHints.hpp @@ -23,6 +23,11 @@ public: // Produce a textual description of a recommended thin wall thickness // from the provided number of perimeters and the external / internal perimeter width. static std::string recommended_thin_wall_thickness(const PresetBundle &preset_bundle); + + // Produce a textual explanation of the combined effects of the top/bottom_solid_layers + // versus top/bottom_min_shell_thickness. Which of the two values wins depends + // on the active layer height. + static std::string top_bottom_shell_thickness_explanation(const PresetBundle &preset_bundle); }; } // namespace Slic3r diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 72e2091670..5a41f12a5a 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1056,6 +1056,16 @@ void TabPrint::build() line.append_option(optgroup->get_option("top_solid_layers")); line.append_option(optgroup->get_option("bottom_solid_layers")); optgroup->append_line(line); + line = { _(L("Minimum shell thickness")), "" }; + line.append_option(optgroup->get_option("top_solid_min_thickness")); + line.append_option(optgroup->get_option("bottom_solid_min_thickness")); + optgroup->append_line(line); + line = { "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_top_bottom_shell_thickness_explanation); + }; + optgroup->append_line(line); optgroup = page->new_optgroup(_(L("Quality (slower slicing)"))); optgroup->append_single_option_line("extra_perimeters"); @@ -1277,6 +1287,8 @@ void TabPrint::update() m_recommended_thin_wall_thickness_description_line->SetText( from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); + m_top_bottom_shell_thickness_explanation->SetText( + from_u8(PresetHints::top_bottom_shell_thickness_explanation(*m_preset_bundle))); Layout(); // Thaw(); @@ -1295,6 +1307,8 @@ void TabPrint::OnActivate() { m_recommended_thin_wall_thickness_description_line->SetText( from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); + m_top_bottom_shell_thickness_explanation->SetText( + from_u8(PresetHints::top_bottom_shell_thickness_explanation(*m_preset_bundle))); Tab::OnActivate(); } diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index cfa5ae56d6..c88a74e53f 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -327,8 +327,9 @@ public: Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_PRINT) {} ~TabPrint() {} - ogStaticText* m_recommended_thin_wall_thickness_description_line; - bool m_support_material_overhangs_queried = false; + ogStaticText* m_recommended_thin_wall_thickness_description_line = nullptr; + ogStaticText* m_top_bottom_shell_thickness_explanation = nullptr; + bool m_support_material_overhangs_queried = false; void build() override; void reload_config() override; @@ -336,6 +337,7 @@ public: void OnActivate() override; bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } }; + class TabFilament : public Tab { ogStaticText* m_volumetric_speed_description_line; From bd76c345f22d8232fd8e524fbc35bda295d32424 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 5 Feb 2020 17:39:56 +0100 Subject: [PATCH 2/4] Handle CGAL exceptions and add tests for mesh boolean operations Add conversion to exact predicates exact construction kernel format for consecutive booleans (experiments) --- sandboxes/meshboolean/MeshBoolean.cpp | 68 +++----- src/libslic3r/MeshBoolean.cpp | 224 ++++++++++++++++++-------- src/libslic3r/MeshBoolean.hpp | 34 ++-- src/libslic3r/SLAPrintSteps.cpp | 6 +- tests/libslic3r/CMakeLists.txt | 1 + tests/libslic3r/test_meshboolean.cpp | 26 +++ 6 files changed, 231 insertions(+), 128 deletions(-) create mode 100644 tests/libslic3r/test_meshboolean.cpp diff --git a/sandboxes/meshboolean/MeshBoolean.cpp b/sandboxes/meshboolean/MeshBoolean.cpp index d339ef5c30..392d907074 100644 --- a/sandboxes/meshboolean/MeshBoolean.cpp +++ b/sandboxes/meshboolean/MeshBoolean.cpp @@ -11,66 +11,34 @@ #include -namespace Slic3r { - -} // namespace Slic3r - int main(const int argc, const char * argv[]) { using namespace Slic3r; - if (argc <= 1) return EXIT_FAILURE; + if (argc <= 1) { + std::cout << "Usage: meshboolean " << std::endl; + return EXIT_FAILURE; + } - DynamicPrintConfig cfg; - auto model = Model::read_from_file(argv[1], &cfg); - if (model.objects.empty()) return EXIT_SUCCESS; + TriangleMesh input; - SLAPrint print; - print.apply(model, cfg); - PrintBase::TaskParams task; - task.to_object_step = slaposHollowing; - - print.set_task(task); - print.process(); + input.ReadSTLFile(argv[1]); + input.repair(); Benchmark bench; - for (SLAPrintObject *po : print.objects()) { - TriangleMesh holes; - sla::DrainHoles holepts = po->transformed_drainhole_points(); - - for (auto &hole: holepts) - holes.merge(sla::to_triangle_mesh(hole.to_mesh())); - - TriangleMesh hollowed_mesh = po->transformed_mesh(); - hollowed_mesh.merge(po->hollowed_interior_mesh()); - - hollowed_mesh.require_shared_vertices(); - holes.require_shared_vertices(); - - TriangleMesh drilled_mesh_igl = hollowed_mesh; - bench.start(); - MeshBoolean::minus(drilled_mesh_igl, holes); - bench.stop(); - - std::cout << "Mesh boolean duration with IGL: " << bench.getElapsedSec() << std::endl; - - TriangleMesh drilled_mesh_cgal = hollowed_mesh; - bench.start(); - MeshBoolean::cgal::self_union(drilled_mesh_cgal); - MeshBoolean::cgal::minus(drilled_mesh_cgal, holes); - bench.stop(); - - std::cout << "Mesh boolean duration with CGAL: " << bench.getElapsedSec() << std::endl; - - std::string name("obj"), outf; - outf = name + "igl" + std::to_string(po->model_object()->id().id) + ".obj"; - drilled_mesh_igl.WriteOBJFile(outf.c_str()); - - outf = name + "cgal" + std::to_string(po->model_object()->id().id) + ".obj"; - drilled_mesh_cgal.WriteOBJFile(outf.c_str()); - } + bench.start(); + bool fckd = MeshBoolean::cgal::does_self_intersect(input); + bench.stop(); + + std::cout << "Self intersect test: " << fckd << " duration: " << bench.getElapsedSec() << std::endl; + + bench.start(); + MeshBoolean::self_union(input); + bench.stop(); + + std::cout << "Self union duration: " << bench.getElapsedSec() << std::endl; return 0; } diff --git a/src/libslic3r/MeshBoolean.cpp b/src/libslic3r/MeshBoolean.cpp index 1c848eb5f8..6db1493ecc 100644 --- a/src/libslic3r/MeshBoolean.cpp +++ b/src/libslic3r/MeshBoolean.cpp @@ -10,17 +10,24 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include namespace Slic3r { namespace MeshBoolean { -typedef Eigen::Map> MapMatrixXfUnaligned; -typedef Eigen::Map> MapMatrixXiUnaligned; +using MapMatrixXfUnaligned = Eigen::Map>; +using MapMatrixXiUnaligned = Eigen::Map>; -typedef std::pair EigenMesh; - -static TriangleMesh eigen_to_triangle_mesh(const Eigen::MatrixXd& VC, const Eigen::MatrixXi& FC) +TriangleMesh eigen_to_triangle_mesh(const EigenMesh &emesh) { + auto &VC = emesh.first; auto &FC = emesh.second; + Pointf3s points(size_t(VC.rows())); std::vector facets(size_t(FC.rows())); @@ -35,7 +42,7 @@ static TriangleMesh eigen_to_triangle_mesh(const Eigen::MatrixXd& VC, const Eige return out; } -static EigenMesh triangle_mesh_to_eigen_mesh(const TriangleMesh &mesh) +EigenMesh triangle_mesh_to_eigen(const TriangleMesh &mesh) { EigenMesh emesh; emesh.first = MapMatrixXfUnaligned(mesh.its.vertices.front().data(), @@ -48,70 +55,116 @@ static EigenMesh triangle_mesh_to_eigen_mesh(const TriangleMesh &mesh) return emesh; } -void minus(TriangleMesh& A, const TriangleMesh& B) +void minus(EigenMesh &A, const EigenMesh &B) { - auto [VA, FA] = triangle_mesh_to_eigen_mesh(A); - auto [VB, FB] = triangle_mesh_to_eigen_mesh(B); - + auto &[VA, FA] = A; + auto &[VB, FB] = B; + Eigen::MatrixXd VC; Eigen::MatrixXi FC; igl::MeshBooleanType boolean_type(igl::MESH_BOOLEAN_TYPE_MINUS); igl::copyleft::cgal::mesh_boolean(VA, FA, VB, FB, boolean_type, VC, FC); - - A = eigen_to_triangle_mesh(VC, FC); + + VA = std::move(VC); FA = std::move(FC); } -void self_union(TriangleMesh& mesh) +void minus(TriangleMesh& A, const TriangleMesh& B) { - auto [V, F] = triangle_mesh_to_eigen_mesh(mesh); + EigenMesh eA = triangle_mesh_to_eigen(A); + minus(eA, triangle_mesh_to_eigen(B)); + A = eigen_to_triangle_mesh(eA); +} - Eigen::MatrixXd VC; - Eigen::MatrixXi FC; +void self_union(EigenMesh &A) +{ + EigenMesh result; + auto &[V, F] = A; + auto &[VC, FC] = result; igl::MeshBooleanType boolean_type(igl::MESH_BOOLEAN_TYPE_UNION); igl::copyleft::cgal::mesh_boolean(V, F, Eigen::MatrixXd(), Eigen::MatrixXi(), boolean_type, VC, FC); - mesh = eigen_to_triangle_mesh(VC, FC); + A = std::move(result); +} + +void self_union(TriangleMesh& mesh) +{ + auto eM = triangle_mesh_to_eigen(mesh); + self_union(eM); + mesh = eigen_to_triangle_mesh(eM); } namespace cgal { -namespace CGALProc = CGAL::Polygon_mesh_processing; -namespace CGALParams = CGAL::Polygon_mesh_processing::parameters; +namespace CGALProc = CGAL::Polygon_mesh_processing; +namespace CGALParams = CGAL::Polygon_mesh_processing::parameters; -using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel; -using _CGALMesh = CGAL::Surface_mesh; +using EpecKernel = CGAL::Exact_predicates_exact_constructions_kernel; +using EpicKernel = CGAL::Exact_predicates_inexact_constructions_kernel; +using _EpicMesh = CGAL::Surface_mesh; +using _EpecMesh = CGAL::Surface_mesh; -struct CGALMesh { _CGALMesh m; }; +struct CGALMesh { _EpicMesh m; }; -static void triangle_mesh_to_cgal(const TriangleMesh &M, _CGALMesh &out) +// ///////////////////////////////////////////////////////////////////////////// +// Converions from and to CGAL mesh +// ///////////////////////////////////////////////////////////////////////////// + +template void triangle_mesh_to_cgal(const TriangleMesh &M, _Mesh &out) { - for (const Vec3f &v : M.its.vertices) - out.add_vertex(_CGALMesh::Point(v.x(), v.y(), v.z())); - - for (const Vec3crd &face : M.its.indices) { - auto f = face.cast(); - out.add_face(f(0), f(1), f(2)); + using Index3 = std::array; + std::vector points; + std::vector indices; + points.reserve(M.its.vertices.size()); + indices.reserve(M.its.indices.size()); + for (auto &v : M.its.vertices) points.emplace_back(v.x(), v.y(), v.z()); + for (auto &_f : M.its.indices) { + auto f = _f.cast(); + indices.emplace_back(Index3{f(0), f(1), f(2)}); } + + CGALProc::orient_polygon_soup(points, indices); + CGALProc::polygon_soup_to_polygon_mesh(points, indices, out); + + // Number the faces because 'orient_to_bound_a_volume' needs a face <--> index map + unsigned index = 0; + for (auto face : out.faces()) face = CGAL::SM_Face_index(index++); + + if(CGAL::is_closed(out)) + CGALProc::orient_to_bound_a_volume(out); + else + std::runtime_error("Mesh not watertight"); } -static TriangleMesh cgal_to_triangle_mesh(const _CGALMesh &cgalmesh) +inline Vec3d to_vec3d(const _EpicMesh::Point &v) +{ + return {v.x(), v.y(), v.z()}; +} + +inline Vec3d to_vec3d(const _EpecMesh::Point &v) +{ + CGAL::Cartesian_converter cvt; + auto iv = cvt(v); + return {iv.x(), iv.y(), iv.z()}; +} + +template TriangleMesh cgal_to_triangle_mesh(const _Mesh &cgalmesh) { Pointf3s points; std::vector facets; points.reserve(cgalmesh.num_vertices()); facets.reserve(cgalmesh.num_faces()); - + for (auto &vi : cgalmesh.vertices()) { auto &v = cgalmesh.point(vi); // Don't ask... - points.emplace_back(v.x(), v.y(), v.z()); + points.emplace_back(to_vec3d(v)); } - + for (auto &face : cgalmesh.faces()) { auto vtc = cgalmesh.vertices_around_face(cgalmesh.halfedge(face)); int i = 0; Vec3crd trface; - for (auto v : vtc) trface(i++) = static_cast(v); + for (auto v : vtc) trface(i++) = static_cast(v); facets.emplace_back(trface); } @@ -120,59 +173,100 @@ static TriangleMesh cgal_to_triangle_mesh(const _CGALMesh &cgalmesh) return out; } -std::unique_ptr triangle_mesh_to_cgal(const TriangleMesh &M) +std::unique_ptr triangle_mesh_to_cgal(const TriangleMesh &M) { - auto out = std::make_unique(); + std::unique_ptr out(new CGALMesh{}); triangle_mesh_to_cgal(M, out->m); return out; } -void cgal_to_triangle_mesh(const CGALMesh &cgalmesh, TriangleMesh &out) +TriangleMesh cgal_to_triangle_mesh(const CGALMesh &cgalmesh) { - out = cgal_to_triangle_mesh(cgalmesh.m); + return cgal_to_triangle_mesh(cgalmesh.m); } -void minus(CGALMesh &A, CGALMesh &B) +// ///////////////////////////////////////////////////////////////////////////// +// Boolean operations for CGAL meshes +// ///////////////////////////////////////////////////////////////////////////// + +static bool _cgal_diff(CGALMesh &A, CGALMesh &B, CGALMesh &R) { - CGALProc::corefine_and_compute_difference(A.m, B.m, A.m); + const auto &p = CGALParams::throw_on_self_intersection(true); + return CGALProc::corefine_and_compute_difference(A.m, B.m, R.m, p, p); } -void self_union(CGALMesh &A) +static bool _cgal_union(CGALMesh &A, CGALMesh &B, CGALMesh &R) { - CGALProc::corefine(A.m, A.m); + const auto &p = CGALParams::throw_on_self_intersection(true); + return CGALProc::corefine_and_compute_union(A.m, B.m, R.m, p, p); } -void minus(TriangleMesh &A, const TriangleMesh &B) -{ +static bool _cgal_intersection(CGALMesh &A, CGALMesh &B, CGALMesh &R) +{ + const auto &p = CGALParams::throw_on_self_intersection(true); + return CGALProc::corefine_and_compute_intersection(A.m, B.m, R.m, p, p); +} + +template void _cgal_do(Op &&op, CGALMesh &A, CGALMesh &B) +{ + bool success = false; + try { + CGALMesh result; + success = op(A, B, result); + A = std::move(result); // In-place operation does not work + } catch (...) { + success = false; + } + + if (! success) + throw std::runtime_error("CGAL mesh boolean operation failed."); +} + +void minus(CGALMesh &A, CGALMesh &B) { _cgal_do(_cgal_diff, A, B); } +void plus(CGALMesh &A, CGALMesh &B) { _cgal_do(_cgal_union, A, B); } +void intersect(CGALMesh &A, CGALMesh &B) { _cgal_do(_cgal_intersection, A, B); } +bool does_self_intersect(const CGALMesh &mesh) { return CGALProc::does_self_intersect(mesh.m); } + +// ///////////////////////////////////////////////////////////////////////////// +// Now the public functions for TriangleMesh input: +// ///////////////////////////////////////////////////////////////////////////// + +template void _mesh_boolean_do(Op &&op, TriangleMesh &A, const TriangleMesh &B) +{ CGALMesh meshA; CGALMesh meshB; triangle_mesh_to_cgal(A, meshA.m); triangle_mesh_to_cgal(B, meshB.m); - CGALMesh meshResult; - bool success = false; - try { - success = CGALProc::corefine_and_compute_difference(meshA.m, meshB.m, meshResult.m, - CGALParams::throw_on_self_intersection(true), CGALParams::throw_on_self_intersection(true)); - } - catch (const CGAL::Polygon_mesh_processing::Corefinement::Self_intersection_exception&) { - success = false; - } - if (! success) - throw std::runtime_error("CGAL corefine_and_compute_difference failed"); - - A = cgal_to_triangle_mesh(meshResult.m); -} - -void self_union(TriangleMesh &m) -{ - _CGALMesh cgalmesh; - triangle_mesh_to_cgal(m, cgalmesh); - CGALProc::corefine(cgalmesh, cgalmesh); + _cgal_do(op, meshA, meshB); - m = cgal_to_triangle_mesh(cgalmesh); + A = cgal_to_triangle_mesh(meshA.m); } +void minus(TriangleMesh &A, const TriangleMesh &B) +{ + _mesh_boolean_do(_cgal_diff, A, B); +} + +void plus(TriangleMesh &A, const TriangleMesh &B) +{ + _mesh_boolean_do(_cgal_union, A, B); +} + +void intersect(TriangleMesh &A, const TriangleMesh &B) +{ + _mesh_boolean_do(_cgal_intersection, A, B); +} + +bool does_self_intersect(const TriangleMesh &mesh) +{ + CGALMesh cgalm; + triangle_mesh_to_cgal(mesh, cgalm.m); + return CGALProc::does_self_intersect(cgalm.m); +} + +void CGALMeshDeleter::operator()(CGALMesh *ptr) { delete ptr; } + } // namespace cgal } // namespace MeshBoolean diff --git a/src/libslic3r/MeshBoolean.hpp b/src/libslic3r/MeshBoolean.hpp index 14e3d3b7b2..5298906b46 100644 --- a/src/libslic3r/MeshBoolean.hpp +++ b/src/libslic3r/MeshBoolean.hpp @@ -2,13 +2,23 @@ #define libslic3r_MeshBoolean_hpp_ #include +#include + +#include +#include namespace Slic3r { -class TriangleMesh; - namespace MeshBoolean { +using EigenMesh = std::pair; + +TriangleMesh eigen_to_triangle_mesh(const EigenMesh &emesh); +EigenMesh triangle_mesh_to_eigen(const TriangleMesh &mesh); + +void minus(EigenMesh &A, const EigenMesh &B); +void self_union(EigenMesh &A); + void minus(TriangleMesh& A, const TriangleMesh& B); void self_union(TriangleMesh& mesh); @@ -16,20 +26,22 @@ namespace cgal { struct CGALMesh; -std::unique_ptr triangle_mesh_to_cgal(const TriangleMesh &M); -void cgal_to_triangle_mesh(const CGALMesh &cgalmesh, TriangleMesh &out); +struct CGALMeshDeleter { void operator()(CGALMesh *ptr); }; + +std::unique_ptr triangle_mesh_to_cgal(const TriangleMesh &M); +TriangleMesh cgal_to_triangle_mesh(const CGALMesh &cgalmesh); // Do boolean mesh difference with CGAL bypassing igl. void minus(TriangleMesh &A, const TriangleMesh &B); +void plus(TriangleMesh &A, const TriangleMesh &B); +void intersect(TriangleMesh &A, const TriangleMesh &B); -// Do self union only with CGAL. -void self_union(TriangleMesh& mesh); - -// does A = A - B -// CGAL takes non-const objects as arguments. I suppose it doesn't change B but -// there is no official garantee. void minus(CGALMesh &A, CGALMesh &B); -void self_union(CGALMesh &A); +void plus(CGALMesh &A, CGALMesh &B); +void intersect(CGALMesh &A, CGALMesh &B); + +bool does_self_intersect(const TriangleMesh &mesh); +bool does_self_intersect(const CGALMesh &mesh); } diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp index 7810a351f7..0c293c7fb6 100644 --- a/src/libslic3r/SLAPrintSteps.cpp +++ b/src/libslic3r/SLAPrintSteps.cpp @@ -143,11 +143,13 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po) holes_mesh.merge(sla::to_triangle_mesh(holept.to_mesh())); holes_mesh.require_shared_vertices(); - MeshBoolean::self_union(holes_mesh); + if (!holes_mesh.is_manifold() || MeshBoolean::cgal::does_self_intersect(holes_mesh)) { + MeshBoolean::self_union(holes_mesh); + } try { MeshBoolean::cgal::minus(hollowed_mesh, holes_mesh); - } catch (const std::runtime_error&) { + } catch (const std::runtime_error &) { throw std::runtime_error(L( "Drilling holes into the mesh failed. " "This is usually caused by broken model. Try to fix it first.")); diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index adcb2722d8..97d9b0bc7f 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -12,6 +12,7 @@ add_executable(${_TEST_NAME}_tests test_polygon.cpp test_stl.cpp test_meshsimplify.cpp + test_meshboolean.cpp ) if (TARGET OpenVDB::openvdb) diff --git a/tests/libslic3r/test_meshboolean.cpp b/tests/libslic3r/test_meshboolean.cpp new file mode 100644 index 0000000000..97d03ac238 --- /dev/null +++ b/tests/libslic3r/test_meshboolean.cpp @@ -0,0 +1,26 @@ +#include +#include + +#include +#include +#include + +using namespace Slic3r; + +TEST_CASE("CGAL and TriangleMesh conversions", "[MeshBoolean]") { + TriangleMesh sphere = make_sphere(1.); + + auto cgalmesh_ptr = MeshBoolean::cgal::triangle_mesh_to_cgal(sphere); + + REQUIRE(cgalmesh_ptr); + REQUIRE(! MeshBoolean::cgal::does_self_intersect(*cgalmesh_ptr)); + + TriangleMesh M = MeshBoolean::cgal::cgal_to_triangle_mesh(*cgalmesh_ptr); + + REQUIRE(M.its.vertices.size() == sphere.its.vertices.size()); + REQUIRE(M.its.indices.size() == sphere.its.indices.size()); + + REQUIRE(M.volume() == Approx(sphere.volume())); + + REQUIRE(! MeshBoolean::cgal::does_self_intersect(M)); +} From 2d86e3cc4d5c28275af33d106b8eaffefbad4cf2 Mon Sep 17 00:00:00 2001 From: tamasmeszaros Date: Wed, 5 Feb 2020 18:22:17 +0100 Subject: [PATCH 3/4] fix failing test due to missing cgal dlls --- tests/libslic3r/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 97d9b0bc7f..d8dac3c10b 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -22,5 +22,9 @@ endif() 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}) From 0d5ced60a2faef8d11657cb6a54bad0f4adf5344 Mon Sep 17 00:00:00 2001 From: YuSanka Date: Wed, 5 Feb 2020 22:55:18 +0100 Subject: [PATCH 4/4] DoubleSlider: Implemented "Jump to print Z" functionality on Cog icon click OR using "Shift+G" + One more big code refactoring --- src/slic3r/GUI/DoubleSlider.cpp | 458 ++++++++++++++++++-------------- src/slic3r/GUI/DoubleSlider.hpp | 49 ++-- 2 files changed, 290 insertions(+), 217 deletions(-) diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index ff5a565453..a17ba2fe56 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -19,6 +19,7 @@ #include #include +#include "Field.hpp" namespace Slic3r { @@ -293,7 +294,8 @@ CustomGCode::Info Control::GetTicksValues() const values.emplace_back(t_custom_code{m_values[tick.tick], tick.gcode, tick.extruder, tick.color}); } - custom_gcode_per_print_z.mode = m_force_mode_apply ? m_mode : m_ticks.mode; + if (m_force_mode_apply) + custom_gcode_per_print_z.mode = m_mode; return custom_gcode_per_print_z; } @@ -330,6 +332,18 @@ void Control::SetTicksValues(const CustomGCode::Info& custom_gcode_per_print_z) Update(); } +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; + if (!m_ticks.mode) + m_ticks.mode = m_mode; + m_only_extruder = only_extruder; + + UseDefaultColors(m_mode == t_mode::SingleExtruder); +} + void Control::get_lower_and_higher_position(int& lower_pos, int& higher_pos) { const double step = get_scroll_step(); @@ -397,9 +411,9 @@ void Control::draw_action_icon(wxDC& dc, const wxPoint pt_beg, const wxPoint pt_ if (tick == 0) return; - wxBitmap* icon = m_is_action_icon_focesed ? &m_bmp_add_tick_off.bmp() : &m_bmp_add_tick_on.bmp(); + wxBitmap* icon = m_focus == fiActionIcon ? &m_bmp_add_tick_off.bmp() : &m_bmp_add_tick_on.bmp(); if (m_ticks.ticks.find(TickCode{tick}) != m_ticks.ticks.end()) - icon = m_is_action_icon_focesed ? &m_bmp_del_tick_off.bmp() : &m_bmp_del_tick_on.bmp(); + icon = m_focus == fiActionIcon ? &m_bmp_del_tick_off.bmp() : &m_bmp_del_tick_on.bmp(); wxCoord x_draw, y_draw; is_horizontal() ? x_draw = pt_beg.x - 0.5*m_tick_icon_dim : y_draw = pt_beg.y - 0.5*m_tick_icon_dim; @@ -667,8 +681,8 @@ void Control::draw_colored_band(wxDC& dc) void Control::draw_one_layer_icon(wxDC& dc) { const wxBitmap& icon = m_is_one_layer ? - m_is_one_layer_icon_focesed ? m_bmp_one_layer_lock_off.bmp() : m_bmp_one_layer_lock_on.bmp() : - m_is_one_layer_icon_focesed ? m_bmp_one_layer_unlock_off.bmp() : m_bmp_one_layer_unlock_on.bmp(); + m_focus == fiOneLayerIcon ? m_bmp_one_layer_lock_off.bmp() : m_bmp_one_layer_lock_on.bmp() : + m_focus == fiOneLayerIcon ? m_bmp_one_layer_unlock_off.bmp() : m_bmp_one_layer_unlock_on.bmp(); int width, height; get_size(&width, &height); @@ -703,9 +717,6 @@ void Control::draw_revert_icon(wxDC& dc) void Control::draw_cog_icon(wxDC& dc) { - if (m_mode != t_mode::MultiExtruder) - return; - int width, height; get_size(&width, &height); @@ -788,73 +799,25 @@ void Control::OnLeftDown(wxMouseEvent& event) return; this->CaptureMouse(); - wxClientDC dc(this); - wxPoint pos = event.GetLogicalPosition(dc); - if (is_point_in_rect(pos, m_rect_tick_action) && m_is_enabled_tick_manipulation) - { - const auto it = m_ticks.ticks.find(TickCode{ m_selection == ssLower ? m_lower_value : m_higher_value }); - if (it == m_ticks.ticks.end()) - m_force_add_tick = true; - else - m_force_delete_tick = true; - return; - } - m_is_left_down = true; - if (is_point_in_rect(pos, m_rect_one_layer_icon)) { - // switch on/off one layer mode - m_is_one_layer = !m_is_one_layer; - if (!m_is_one_layer) { - SetLowerValue(m_min_value); - SetHigherValue(m_max_value); - } - m_selection == ssLower ? correct_lower_value() : correct_higher_value(); - if (!m_selection) m_selection = ssHigher; - } - else if (is_point_in_rect(pos, m_rect_revert_icon) && m_is_enabled_tick_manipulation) { - // discard all color changes - SetLowerValue(m_min_value); - SetHigherValue(m_max_value); - - m_selection == ssLower ? correct_lower_value() : correct_higher_value(); - if (!m_selection) m_selection = ssHigher; - - m_ticks.ticks.clear(); - post_ticks_changed_event(); - } - else if (is_point_in_rect(pos, m_rect_cog_icon) && m_mode == t_mode::MultiExtruder) { - // show dialog for set extruder sequence - m_force_edit_extruder_sequence = true; - return; - } - else - detect_selected_slider(pos); - - if (!m_selection) { - const int tick_val = get_tick_near_point(pos); - /* Set current thumb position to the nearest tick (if it is) - * OR to a value corresponding to the mouse click - * */ - const int mouse_val = tick_val >= 0 && m_is_enabled_tick_manipulation ? tick_val : - get_value_from_position(pos.x, pos.y); - if (mouse_val >= 0) - { - // if (abs(mouse_val - m_lower_value) < abs(mouse_val - m_higher_value)) { - if ( mouse_val <= m_lower_value ) { - SetLowerValue(mouse_val); - correct_lower_value(); - m_selection = ssLower; - } - else { - SetHigherValue(mouse_val); - correct_higher_value(); - m_selection = ssHigher; - } + m_mouse = maNone; + + wxPoint pos = event.GetLogicalPosition(wxClientDC(this)); + + if (is_point_in_rect(pos, m_rect_one_layer_icon)) + m_mouse = maOneLayerIconClick; + else if (is_point_in_rect(pos, m_rect_cog_icon)) + m_mouse = maCogIconClick; + else if (m_is_enabled_tick_manipulation) + { + if (is_point_in_rect(pos, m_rect_tick_action)) { + auto it = m_ticks.ticks.find(TickCode{ m_selection == ssLower ? m_lower_value : m_higher_value }); + m_mouse = it == m_ticks.ticks.end() ? maAddTick : maDeleteTick; } + else if (is_point_in_rect(pos, m_rect_revert_icon)) + m_mouse = maRevertIconClick; } - Refresh(); - Update(); event.Skip(); } @@ -880,24 +843,27 @@ void Control::correct_higher_value() m_lower_value = m_higher_value; } -wxString Control::get_tooltip(FocusItem focused_item, int tick/*=-1*/) +wxString Control::get_tooltip(int tick/*=-1*/) { - if (focused_item == fiNone) + if (m_focus == fiNone) return ""; - if (focused_item == fiOneLayerIcon) + if (m_focus == fiOneLayerIcon) return _(L("One layer mode")); - if (focused_item == fiRevertIcon) + if (m_focus == fiRevertIcon) return _(L("Discard all custom changes")); - if (focused_item == fiCogIcon) - return _(L("Set extruder sequence for whole print")); - if (focused_item == fiColorBand) + if (m_focus == fiCogIcon) + return m_mode == t_mode::MultiAsSingle ? + _(L("For jump to print Z use left mouse button click OR (Shift+G)")) + "\n" + + _(L("For set extruder sequence for whole print use right mouse button click")) : + _(L("Jump to print Z")) + " (Shift+G)"; + if (m_focus == fiColorBand) return m_mode != t_mode::SingleExtruder ? "" : _(L("For edit current color use right mouse button click on colored band")); wxString tooltip; const auto tick_code_it = m_ticks.ticks.find(TickCode{tick}); - if (tick_code_it == m_ticks.ticks.end() && focused_item == fiActionIcon) // tick doesn't exist + 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("Slider(print) mode")) + ": "; @@ -960,7 +926,7 @@ wxString Control::get_tooltip(FocusItem focused_item, int tick/*=-1*/) "Check your choice to avoid redundant color changes.")); // Show list of actions with existing tick - if (focused_item == fiActionIcon) + if (m_focus == fiActionIcon) tooltip += "\n\n" + _(L("For Delete tick use left mouse button click OR pres \"-\" key")) + "\n" + ( is_osx ? _(L("For Edit tick use Ctrl + Left mouse button click")) : @@ -991,31 +957,26 @@ void Control::OnMotion(wxMouseEvent& event) { bool action = false; - const wxClientDC dc(this); - const wxPoint pos = event.GetLogicalPosition(dc); - - m_is_one_layer_icon_focesed = is_point_in_rect(pos, m_rect_one_layer_icon); - - FocusItem focused_item = fiNone; + const wxPoint pos = event.GetLogicalPosition(wxClientDC(this)); int tick = -1; - if (!m_is_left_down && !m_is_one_layer) { - m_is_action_icon_focesed = is_point_in_rect(pos, m_rect_tick_action); - if (m_is_one_layer_icon_focesed) - focused_item = fiOneLayerIcon; - else if (m_is_action_icon_focesed) { - focused_item = fiActionIcon; + if (!m_is_left_down && !m_is_one_layer) + { + if (is_point_in_rect(pos, m_rect_one_layer_icon)) + m_focus = fiOneLayerIcon; + else if (is_point_in_rect(pos, m_rect_tick_action)) { + m_focus = fiActionIcon; tick = m_selection == ssLower ? m_lower_value : m_higher_value; } else if (!m_ticks.empty() && is_point_in_rect(pos, m_rect_revert_icon)) - focused_item = fiRevertIcon; + m_focus = fiRevertIcon; else if (is_point_in_rect(pos, m_rect_cog_icon)) - focused_item = fiCogIcon; + m_focus = fiCogIcon; else if (m_mode == t_mode::SingleExtruder && is_point_in_rect(pos, get_colored_band_rect()) && get_edited_tick_for_position(pos) >= 0 ) - focused_item = fiColorBand; + m_focus = fiColorBand; else { - focused_item = fiTick; + m_focus = fiTick; tick = get_tick_near_point(pos); } } @@ -1038,7 +999,7 @@ void Control::OnMotion(wxMouseEvent& event) event.Skip(); // Set tooltips with information for each icon - this->SetToolTip(get_tooltip(focused_item, tick)); + this->SetToolTip(get_tooltip(tick)); if (action) { @@ -1120,21 +1081,27 @@ void Control::OnLeftUp(wxMouseEvent& event) this->ReleaseMouse(); m_is_left_down = false; - if (m_force_delete_tick) - { + switch (m_mouse) { + case maNone : + move_current_thumb_to_pos(event.GetLogicalPosition(wxClientDC(this))); + break; + case maDeleteTick : delete_current_tick(); - m_force_delete_tick = false; - } - else - if (m_force_add_tick) - { + break; + case maAddTick : add_current_tick(); - m_force_add_tick = false; - } - else - if (m_force_edit_extruder_sequence) { - edit_extruder_sequence(); - m_force_edit_extruder_sequence = false; + break; + case maCogIconClick : + jump_to_print_z(); + break; + case maOneLayerIconClick: + switch_one_layer_mode(); + break; + case maRevertIconClick: + discard_all_thicks(); + break; + default : + break; } Refresh(); @@ -1261,6 +1228,8 @@ void Control::OnChar(wxKeyEvent& event) delete_current_tick(); m_ticks.suppress_minus(false); } + if (key == 'G') + jump_to_print_z(); } void Control::OnRightDown(wxMouseEvent& event) @@ -1268,29 +1237,25 @@ void Control::OnRightDown(wxMouseEvent& event) if (HasCapture()) return; this->CaptureMouse(); - const wxClientDC dc(this); + const wxPoint pos = event.GetLogicalPosition(wxClientDC(this)); - wxPoint pos = event.GetLogicalPosition(dc); - if (m_is_enabled_tick_manipulation && is_point_in_rect(pos, m_rect_tick_action)) - { - const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; - if (m_ticks.ticks.find(TickCode{ tick }) == m_ticks.ticks.end()) // if on this Z doesn't exist tick - // show context menu on OnRightUp() - m_show_context_menu = true; - else - // show "Edit" and "Delete" menu on OnRightUp() - m_show_edit_menu = true; - return; + m_mouse = maNone; + if (m_is_enabled_tick_manipulation) { + if (is_point_in_rect(pos, m_rect_tick_action)) + { + const int tick = m_selection == ssLower ? m_lower_value : m_higher_value; + m_mouse = m_ticks.ticks.find(TickCode{ tick }) == m_ticks.ticks.end() ? + maAddMenu : maEditMenu; + } + else if (m_mode == t_mode::SingleExtruder && 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)) + m_mouse = maCogIconMenu; } - - if (m_is_enabled_tick_manipulation && m_mode == t_mode::SingleExtruder && - is_point_in_rect(pos, get_colored_band_rect())) - { - m_force_color_edit = true; + if (m_mouse != maNone) return; - } - detect_selected_slider(event.GetLogicalPosition(dc)); + detect_selected_slider(pos); if (!m_selection) return; @@ -1396,6 +1361,60 @@ std::set TickCodeInfo::get_used_extruders_for_tick(int tick, int only_extru return used_extruders; } +void Control::get_add_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); + + UseDefaultColors(false); + } + else { + append_change_extruder_menu_item(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 custom G-code")), "", + [this](wxCommandEvent&) { add_code_as_tick(""); }, "edit_gcode", menu); +} + +void Control::get_edit_menu(wxMenu* 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) + 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")) : + _(L("Edit custom G-code")), "", + [this](wxCommandEvent&) { edit_tick(); }, "edit_uni", menu); + + if (it->gcode == ColorChangeCode && m_mode == t_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")) : + _(L("Delete custom G-code")), "", + [this](wxCommandEvent&) { delete_current_tick();}, "colorchange_del_f", menu); +} + +void Control::get_cog_icon_menu(wxMenu* menu) +{ + append_menu_item(menu, wxID_ANY, _(L("Jump to print Z")) + " (Shift+G)", "", + [this](wxCommandEvent&) { jump_to_print_z(); }, "", menu); + + append_menu_item(menu, wxID_ANY, _(L("Set extruder sequence for whole print")), "", + [this](wxCommandEvent&) { edit_extruder_sequence(); }, "", menu); +} + void Control::OnRightUp(wxMouseEvent& event) { if (!HasCapture()) @@ -1403,73 +1422,25 @@ void Control::OnRightUp(wxMouseEvent& event) this->ReleaseMouse(); m_is_right_down = m_is_one_layer = false; - if (m_show_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, - [](){return true;}, this); - - UseDefaultColors(false); - } - else - { - append_change_extruder_menu_item(&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, - []() {return true; }, this); - - append_menu_item(&menu, wxID_ANY, _(L("Add custom G-code")), "", - [this](wxCommandEvent&) { add_code_as_tick(""); }, "edit_gcode", &menu, - []() {return true; }, this); - - GUI::wxGetApp().plater()->PopupMenu(&menu); - - m_show_context_menu = false; - } - else if (m_show_edit_menu) { - wxMenu 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) - 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")) : - _(L("Edit custom G-code")), "", - [this](wxCommandEvent&) { edit_tick(); }, "edit_uni", &menu, []() {return true; }, this); - - if (it->gcode == ColorChangeCode && m_mode == t_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")) : - _(L("Delete custom G-code")), "", - [this](wxCommandEvent&) { delete_current_tick();}, "colorchange_del_f", &menu, []() {return true; }, this); - - GUI::wxGetApp().plater()->PopupMenu(&menu); - - m_show_edit_menu = false; - } - else if (m_force_color_edit) + if (m_mouse == maForceColorEdit) { - const wxClientDC dc(this); - wxPoint pos = event.GetLogicalPosition(dc); - + wxPoint pos = event.GetLogicalPosition(wxClientDC(this)); int edited_tick = get_edited_tick_for_position(pos); if (edited_tick >= 0) edit_tick(edited_tick); + } + else + { + wxMenu menu; - m_force_color_edit = false; + if (m_mouse == maAddMenu) + get_add_menu(&menu); + else if (m_mouse == maEditMenu) + get_edit_menu(&menu); + else if (m_mouse == maCogIconMenu) + get_cog_icon_menu(&menu); + + GUI::wxGetApp().plater()->PopupMenu(&menu); } Refresh(); @@ -1494,9 +1465,11 @@ static std::string get_new_color(const std::string& color) return ""; } -// To avoid get an empty string from wxTextEntryDialog -// Let disable OK button, if TextCtrl is empty -static void upgrade_text_entry_dialog(wxTextEntryDialog* dlg) +/* To avoid get an empty string from wxTextEntryDialog + * Let disable OK button, if TextCtrl is empty + * OR input value is our of range (min..max), when min a nd max are positive + * */ +static void upgrade_text_entry_dialog(wxTextEntryDialog* dlg, double min = -1.0, double max = -1.0) { // detect TextCtrl and OK button wxTextCtrl* textctrl {nullptr}; @@ -1511,8 +1484,19 @@ static void upgrade_text_entry_dialog(wxTextEntryDialog* dlg) return; wxButton* btn_OK = static_cast(dlg->FindWindowById(wxID_OK)); - btn_OK->Bind(wxEVT_UPDATE_UI, [textctrl](wxUpdateUIEvent& evt) { - evt.Enable(!textctrl->IsEmpty()); + btn_OK->Bind(wxEVT_UPDATE_UI, [textctrl, min, max](wxUpdateUIEvent& evt) + { + bool disable = textctrl->IsEmpty(); + if (!disable && min >= 0.0 && max >= 0.0) + { + double value = -1.0; + if (!textctrl->GetValue().ToCDouble(&value)) // input value couldn't be converted to double + disable = true; + else + disable = value < min || value > max; // is input value is out of valid range ? + } + + evt.Enable(!disable); }, btn_OK->GetId()); } @@ -1548,6 +1532,23 @@ static std::string get_pause_print_msg(const std::string& msg_in, double height) return into_u8(dlg.GetValue()); } +static double get_print_z_to_jump(double active_print_z, double min_z, double max_z) +{ + wxString msg_text = _(L("Enter print z value to jump to")) + " :"; + wxString msg_header = _(L("Jump to print z")); + wxString msg_in = GUI::double_to_string(active_print_z); + + // get custom gcode + wxTextEntryDialog dlg(nullptr, msg_text, msg_header, msg_in, wxTextEntryDialogStyle); + upgrade_text_entry_dialog(&dlg, min_z, max_z); + + if (dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty()) + return -1.0; + + double value = -1.0; + return dlg.GetValue().ToCDouble(&value) ? value : -1.0; +} + void Control::add_code_as_tick(std::string code, int selected_extruder/* = -1*/) { if (m_selection == ssUndef) @@ -1649,6 +1650,55 @@ void Control::edit_tick(int tick/* = -1*/) post_ticks_changed_event(code); } +// switch on/off one layer mode +void Control::switch_one_layer_mode() +{ + m_is_one_layer = !m_is_one_layer; + if (!m_is_one_layer) { + SetLowerValue(m_min_value); + SetHigherValue(m_max_value); + } + m_selection == ssLower ? correct_lower_value() : correct_higher_value(); + if (!m_selection) m_selection = ssHigher; +} + +// discard all custom changes on DoubleSlider +void Control::discard_all_thicks() +{ + SetLowerValue(m_min_value); + SetHigherValue(m_max_value); + + m_selection == ssLower ? correct_lower_value() : correct_higher_value(); + if (!m_selection) m_selection = ssHigher; + + m_ticks.ticks.clear(); + post_ticks_changed_event(); + +} + +// Set current thumb position to the nearest tick (if it is) +// OR to a value corresponding to the mouse click (pos) +void Control::move_current_thumb_to_pos(wxPoint pos) +{ + const int tick_val = get_tick_near_point(pos); + const int mouse_val = tick_val >= 0 && m_is_enabled_tick_manipulation ? tick_val : + get_value_from_position(pos); + if (mouse_val >= 0) + { + // if (abs(mouse_val - m_lower_value) < abs(mouse_val - m_higher_value)) { + if (mouse_val <= m_lower_value) { + SetLowerValue(mouse_val); + correct_lower_value(); + m_selection = ssLower; + } + else { + SetHigherValue(mouse_val); + correct_higher_value(); + m_selection = ssHigher; + } + } +} + void Control::edit_extruder_sequence() { if (!check_ticks_changed_event(ToolChangeCode)) @@ -1698,6 +1748,22 @@ void Control::edit_extruder_sequence() post_ticks_changed_event(ToolChangeCode); } +void Control::jump_to_print_z() +{ + double print_z = get_print_z_to_jump(m_values[m_selection == ssLower ? m_lower_value : m_higher_value], + m_values[m_min_value], m_values[m_max_value]); + if (print_z < 0) + return; + + auto it = std::lower_bound(m_values.begin(), m_values.end(), print_z - epsilon()); + int tick_value = it - m_values.begin(); + + if (m_selection == ssLower) + SetLowerValue(tick_value); + else + SetHigherValue(tick_value); +} + void Control::post_ticks_changed_event(const std::string& gcode /*= ""*/) { m_force_mode_apply = (gcode.empty() || gcode == ColorChangeCode || gcode == ToolChangeCode); diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index 86c0251313..78c568eb42 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -33,7 +33,7 @@ enum SelectedSlider { ssHigher }; -enum FocusItem { +enum FocusedItem { fiNone, fiRevertIcon, fiOneLayerIcon, @@ -52,6 +52,20 @@ enum ConflictType ctRedundant }; +enum MouseAction +{ + maNone, + maAddMenu, // show "Add" context menu for NOTexist active tick + maEditMenu, // show "Edit" context menu for exist active tick + maCogIconMenu, // show context for "cog" icon + maForceColorEdit, // force color editing from colored band + maAddTick, // force tick adding + maDeleteTick, // force tick deleting + maCogIconClick, // LeftMouseClick on "cog" icon + maOneLayerIconClick, // LeftMouseClick on "one_layer" icon + maRevertIconClick, // LeftMouseClick on "revert" icon +}; + using t_mode = CustomGCode::Mode; struct TickCode @@ -188,17 +202,7 @@ public: void SetManipulationMode(t_mode mode) { m_mode = mode; } t_mode GetManipulationMode() const { return m_mode; } - void 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; - if (!m_ticks.mode) - m_ticks.mode = m_mode; - m_only_extruder = only_extruder; - - UseDefaultColors(m_mode == t_mode::SingleExtruder); - } + void SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, const int only_extruder); bool is_horizontal() const { return m_style == wxSL_HORIZONTAL; } bool is_one_layer() const { return m_is_one_layer; } @@ -226,7 +230,14 @@ public: // delete current tick, when press "-" void delete_current_tick(); void edit_tick(int tick = -1); + void switch_one_layer_mode(); + void discard_all_thicks(); + void move_current_thumb_to_pos(wxPoint pos); void edit_extruder_sequence(); + void jump_to_print_z(); + void get_add_menu(wxMenu *menu); + void get_edit_menu(wxMenu *menu); + void get_cog_icon_menu(wxMenu *menu); ExtrudersSequence m_extruders_sequence; @@ -268,7 +279,7 @@ private: wxSize get_size(); void get_size(int *w, int *h); double get_double_value(const SelectedSlider& selection); - wxString get_tooltip(FocusItem focused_item, int tick = -1); + wxString get_tooltip(int tick = -1); int get_edited_tick_for_position(wxPoint pos, const std::string& gcode = ColorChangeCode); std::string get_color_for_tool_change_tick(std::set::const_iterator it) const; @@ -310,19 +321,15 @@ private: bool m_is_right_down = false; bool m_is_one_layer = false; bool m_is_focused = false; - bool m_is_action_icon_focesed = false; - bool m_is_one_layer_icon_focesed = false; bool m_is_enabled_tick_manipulation = true; - bool m_show_context_menu = false; - bool m_show_edit_menu = false; - bool m_force_edit_extruder_sequence = false; bool m_force_mode_apply = true; - bool m_force_add_tick = false; - bool m_force_delete_tick = false; - bool m_force_color_edit = false; + t_mode m_mode = t_mode::SingleExtruder; int m_only_extruder = -1; + MouseAction m_mouse = maNone; + FocusedItem m_focus = fiNone; + wxRect m_rect_lower_thumb; wxRect m_rect_higher_thumb; wxRect m_rect_tick_action;