From 453386fbc1520e479b279e623dc00c51c543c046 Mon Sep 17 00:00:00 2001 From: supermerill Date: Tue, 30 Apr 2024 20:37:09 +0200 Subject: [PATCH 01/19] Fix & optimize windows github builds --- .github/workflows/ccpp_win.yml | 59 ++++++-------- .github/workflows/ccpp_win_debug.yml | 69 ++++++++++++++-- .github/workflows/ccpp_win_deps.yml | 118 +++++++++++++++++++++++++++ .github/workflows/ccpp_win_rc.yml | 60 ++++++-------- 4 files changed, 233 insertions(+), 73 deletions(-) create mode 100644 .github/workflows/ccpp_win_deps.yml diff --git a/.github/workflows/ccpp_win.yml b/.github/workflows/ccpp_win.yml index 1bd841de42c..6ede51d046f 100644 --- a/.github/workflows/ccpp_win.yml +++ b/.github/workflows/ccpp_win.yml @@ -6,31 +6,11 @@ on: - Nigthly - nightly_dev - nightly_master - - debug_win jobs: - build_dep: - runs-on: windows-2019 - - steps: - - uses: actions/checkout@v3 - - uses: ilammy/msvc-dev-cmd@v1 - - name: mkdir in deps - run: mkdir deps/build - - name: cmake and make deps - working-directory: ./deps/build - run: | - cmake .. -G "Visual Studio 16 2019" -A x64 - msbuild /m ALL_BUILD.vcxproj - - name: Upload artifact - uses: actions/upload-artifact@v1.0.0 - with: - name: deps_win - path: ./deps/build/destdir/ build: runs-on: windows-2019 - needs: build_dep steps: - uses: actions/checkout@v1 @@ -41,19 +21,32 @@ jobs: - name: change date in version shell: powershell run: (Get-Content version.inc) | Foreach-Object {$_ -replace "\+UNKNOWN", ("_" + [datetime]::Today.ToString("yyyy-MM-dd"))} | Set-Content version.inc - - name: mkdir in deps directory - run: mkdir deps/destdir - - name: download deps - uses: actions/download-artifact@v1 - with: - name: deps_win - path: deps/destdir - - name: echo dir deps - run: dir deps - - name: echo dir deps destdir - run: dir deps/destdir - - name: mkdir - run: mkdir build + - run: mkdir deps/destdir + - name: copy deps + working-directory: ./deps/destdir + shell: powershell + run: '(new-object System.Net.WebClient).DownloadFile("https://github.com/supermerill/SuperSlicer_deps/releases/download/deps_25/deps_win.zip", "deps_win.zip")' + - name: unzip deps + working-directory: ./deps/destdir + shell: cmd + run: '"C:/Program Files/7-Zip/7z.exe" x deps_win.zip' + - run: dir deps + - run: dir deps/destdir + - run: dir deps/destdir/usr + - run: mkdir msgfmt_bin + - name: copy gettext + working-directory: ./msgfmt_bin + shell: powershell + run: '(new-object System.Net.WebClient).DownloadFile("https://github.com/supermerill/SuperSlicer_deps/releases/download/gettext/gettext-tools-static-0.18.1.1.zip", "gettext.zip")' + - name: unzip + working-directory: ./msgfmt_bin + shell: cmd + run: '"C:/Program Files/7-Zip/7z.exe" x gettext.zip' + - name: add msgfmt to path + shell: powershell + working-directory: msgfmt_bin + run: echo "$pwd;" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - run: mkdir build - name: cmake working-directory: ./build run: cmake .. -G "Visual Studio 16 2019" -A x64 -DCMAKE_PREFIX_PATH="d:\a\${{ github.event.repository.name }}\${{ github.event.repository.name }}\deps\destdir\usr\local" diff --git a/.github/workflows/ccpp_win_debug.yml b/.github/workflows/ccpp_win_debug.yml index 73c10012627..fe6abf9cb46 100644 --- a/.github/workflows/ccpp_win_debug.yml +++ b/.github/workflows/ccpp_win_debug.yml @@ -6,8 +6,55 @@ on: - debug_win jobs: + + build_pot: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v1 + - uses: ilammy/msvc-dev-cmd@v1 + - name: update submodule profiles + working-directory: ./resources/profiles + run: git submodule update --init + - run: mkdir deps/destdir + - name: copy deps + working-directory: ./deps/destdir + shell: powershell + run: '(new-object System.Net.WebClient).DownloadFile("https://github.com/supermerill/SuperSlicer_deps/releases/download/deps_25/deps_win.zip", "deps_win.zip")' + - name: unzip deps + working-directory: ./deps/destdir + shell: cmd + run: '"C:/Program Files/7-Zip/7z.exe" x deps_win.zip' + - run: dir deps + - run: dir deps/destdir + - run: dir deps/destdir/usr + - run: mkdir msgfmt_bin + - name: copy gettext + working-directory: ./msgfmt_bin + shell: powershell + run: '(new-object System.Net.WebClient).DownloadFile("https://github.com/supermerill/SuperSlicer_deps/releases/download/gettext/gettext-tools-static-0.18.1.1.zip", "gettext.zip")' + - name: unzip + working-directory: ./msgfmt_bin + shell: cmd + run: '"C:/Program Files/7-Zip/7z.exe" x gettext.zip' + - name: add msgfmt to path + shell: powershell + working-directory: msgfmt_bin + run: echo "$pwd;" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - run: mkdir build + - name: cmake + working-directory: ./build + run: cmake .. -G "Visual Studio 16 2019" -A x64 -DCMAKE_PREFIX_PATH="d:\a\${{ github.event.repository.name }}\${{ github.event.repository.name }}\deps\destdir\usr\local" + - name: make .mo + working-directory: ./build + run: msbuild /m /P:Configuration=RelWithDebInfo gettext_po_to_mo.vcxproj + - name: make .pot + working-directory: ./build + run: msbuild /m /P:Configuration=RelWithDebInfo gettext_make_pot.vcxproj + build_dep: runs-on: windows-2019 + needs: build_pot steps: - uses: actions/checkout@v2 @@ -47,12 +94,22 @@ jobs: with: name: deps_win path: deps/destdir - - name: echo dir deps - run: dir deps - - name: echo dir deps destdir - run: dir deps/destdir - - name: mkdir - run: mkdir build + - run: dir deps + - run: dir deps/destdir + - run: mkdir msgfmt_bin + - name: copy gettext + working-directory: ./msgfmt_bin + shell: powershell + run: '(new-object System.Net.WebClient).DownloadFile("https://github.com/supermerill/SuperSlicer_deps/releases/download/gettext/gettext-tools-static-0.18.1.1.zip", "gettext.zip")' + - name: unzip + working-directory: ./msgfmt_bin + shell: cmd + run: '"C:/Program Files/7-Zip/7z.exe" x gettext.zip' + - name: add msgfmt to path + shell: powershell + working-directory: msgfmt_bin + run: echo "$pwd;" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - run: mkdir build - name: cmake working-directory: ./build run: cmake .. -G "Visual Studio 16 2019" -A x64 -DCMAKE_PREFIX_PATH="d:\a\${{ github.event.repository.name }}\${{ github.event.repository.name }}\deps\destdir\usr\local" diff --git a/.github/workflows/ccpp_win_deps.yml b/.github/workflows/ccpp_win_deps.yml new file mode 100644 index 00000000000..9155f24fd8f --- /dev/null +++ b/.github/workflows/ccpp_win_deps.yml @@ -0,0 +1,118 @@ +name: C/C++ dep build windows x64 + +on: + push: + branches: + - debug_win + +jobs: + build_dep: + runs-on: windows-2019 + + steps: + - uses: actions/checkout@v3 + - uses: ilammy/msvc-dev-cmd@v1 + - name: mkdir in deps + run: mkdir deps/build + - name: cmake and make deps + working-directory: ./deps/build + run: | + cmake .. -G "Visual Studio 16 2019" -A x64 + msbuild /m ALL_BUILD.vcxproj + - name: Upload artifact + uses: actions/upload-artifact@v1.0.0 + with: + name: deps_win + path: ./deps/build/destdir/ + + build: + runs-on: windows-2019 + needs: build_dep + + steps: + - uses: actions/checkout@v1 + - uses: ilammy/msvc-dev-cmd@v1 + - name: update submodule profiles + working-directory: ./resources/profiles + run: git submodule update --init + - name: change date in version + shell: powershell + run: (Get-Content version.inc) | Foreach-Object {$_ -replace "\+UNKNOWN", ("_" + [datetime]::Today.ToString("yyyy-MM-dd"))} | Set-Content version.inc + - run: mkdir deps/destdir + - name: download deps + uses: actions/download-artifact@v1 + with: + name: deps_win + path: deps/destdir + - run: dir deps + - run: dir deps/destdir + - run: mkdir msgfmt_bin + - name: copy gettext + working-directory: ./msgfmt_bin + shell: powershell + run: '(new-object System.Net.WebClient).DownloadFile("https://github.com/supermerill/SuperSlicer_deps/releases/download/gettext/gettext-tools-static-0.18.1.1.zip", "gettext.zip")' + - name: unzip + working-directory: ./msgfmt_bin + shell: cmd + run: '"C:/Program Files/7-Zip/7z.exe" x gettext.zip' + - name: add msgfmt to path + shell: powershell + working-directory: msgfmt_bin + run: echo "$pwd;" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - run: mkdir build + - name: cmake + working-directory: ./build + run: cmake .. -G "Visual Studio 16 2019" -A x64 -DCMAKE_PREFIX_PATH="d:\a\${{ github.event.repository.name }}\${{ github.event.repository.name }}\deps\destdir\usr\local" + - name: make + working-directory: ./build + run: msbuild /m /P:Configuration=RelWithDebInfo INSTALL.vcxproj + - name: make .mo + working-directory: ./build + run: msbuild /m /P:Configuration=RelWithDebInfo gettext_po_to_mo.vcxproj + - name: make .pot + working-directory: ./build + run: msbuild /m /P:Configuration=RelWithDebInfo gettext_make_pot.vcxproj + - name: create directory and copy into it + working-directory: ./build + run: ls + - name: create directory and copy into it + working-directory: ./build + shell: powershell + #todo: add the opengl folder + run: mkdir package + - name: copy from release + working-directory: ./build + shell: powershell + run: '(new-object System.Net.WebClient).DownloadFile("https://github.com/supermerill/SuperSlicer_deps/releases/download/1.75/Slic3r_win_build.zip", "Slic3r_win_build.zip")' + - name: unzip + working-directory: ./build + shell: cmd + run: '"C:/Program Files/7-Zip/7z.exe" x Slic3r_win_build.zip' + - name: copy missing dll content from old release + working-directory: ./build + shell: cmd + run: | + xcopy /RCYIE Slic3r_win_build\*.dll package\ + xcopy /RCYIE Slic3r_win_build\local-settings.bat package\${{ github.event.repository.name }}_local-settings.bat + xcopy /RCYIE Slic3r_win_build\mesa package\ + - name: copy new resources + working-directory: ./build + shell: cmd + run: xcopy /RCYIE ..\resources package\resources + - name: copy dll & exe + working-directory: ./build + shell: cmd + run: | + xcopy /RCYIE src\RelWithDebInfo\*.dll package\ + xcopy /RCYIE src\RelWithDebInfo\*.exe package\ + xcopy /RCYIE c:\windows\system32\VCRUNTIME140* package\ + del package\opengl32.dll +# - name: create zip +# working-directory: ./build +# shell: cmd +# run: '"C:/Program Files/7-Zip/7z.exe" a -tzip nightly.zip *' + - name: Upload artifact + uses: actions/upload-artifact@v1.0.0 + with: + name: nightly_win64 + path: build/package/ diff --git a/.github/workflows/ccpp_win_rc.yml b/.github/workflows/ccpp_win_rc.yml index 56a3968754f..ea5f0563844 100644 --- a/.github/workflows/ccpp_win_rc.yml +++ b/.github/workflows/ccpp_win_rc.yml @@ -6,30 +6,9 @@ on: - rc jobs: - build_dep: - runs-on: windows-2019 - - steps: - - uses: actions/checkout@v2 - with: - ref: 'rc' - - uses: ilammy/msvc-dev-cmd@v1 - - name: mkdir in deps - run: mkdir deps/build - - name: cmake and make deps - working-directory: ./deps/build - run: | - cmake .. -G "Visual Studio 16 2019" -A x64 - msbuild /m ALL_BUILD.vcxproj - - name: Upload artifact - uses: actions/upload-artifact@v1.0.0 - with: - name: deps_win - path: ./deps/build/destdir/ build: runs-on: windows-2019 - needs: build_dep steps: - uses: actions/checkout@v1 @@ -40,19 +19,32 @@ jobs: - name: change date in version shell: powershell run: (Get-Content version.inc) | Foreach-Object {$_ -replace "\+UNKNOWN", ("")} | Set-Content version.inc - - name: mkdir in deps directory - run: mkdir deps/destdir - - name: download deps - uses: actions/download-artifact@v1 - with: - name: deps_win - path: deps/destdir - - name: echo dir deps - run: dir deps - - name: echo dir deps destdir - run: dir deps/destdir - - name: mkdir - run: mkdir build + - run: mkdir deps/destdir + - name: copy deps + working-directory: ./deps/destdir + shell: powershell + run: '(new-object System.Net.WebClient).DownloadFile("https://github.com/supermerill/SuperSlicer_deps/releases/download/deps_25/deps_win.zip", "deps_win.zip")' + - name: unzip deps + working-directory: ./deps/destdir + shell: cmd + run: '"C:/Program Files/7-Zip/7z.exe" x deps_win.zip' + - run: dir deps + - run: dir deps/destdir + - run: dir deps/destdir/usr + - run: mkdir msgfmt_bin + - name: copy gettext + working-directory: ./msgfmt_bin + shell: powershell + run: '(new-object System.Net.WebClient).DownloadFile("https://github.com/supermerill/SuperSlicer_deps/releases/download/gettext/gettext-tools-static-0.18.1.1.zip", "gettext.zip")' + - name: unzip + working-directory: ./msgfmt_bin + shell: cmd + run: '"C:/Program Files/7-Zip/7z.exe" x gettext.zip' + - name: add msgfmt to path + shell: powershell + working-directory: msgfmt_bin + run: echo "$pwd;" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append + - run: mkdir build - name: cmake working-directory: ./build run: cmake .. -G "Visual Studio 16 2019" -A x64 -DCMAKE_PREFIX_PATH="d:\a\${{ github.event.repository.name }}\${{ github.event.repository.name }}\deps\destdir\usr\local" From b84ec9a97cebe154874f857b1b9bcc83d753e43c Mon Sep 17 00:00:00 2001 From: supermerill Date: Tue, 30 Apr 2024 21:18:51 +0200 Subject: [PATCH 02/19] upload mac_arm dmg for rc --- .github/workflows/ccpp_mac_arm_rc.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ccpp_mac_arm_rc.yml b/.github/workflows/ccpp_mac_arm_rc.yml index 5b68de5f80c..22ab2f12560 100644 --- a/.github/workflows/ccpp_mac_arm_rc.yml +++ b/.github/workflows/ccpp_mac_arm_rc.yml @@ -16,6 +16,11 @@ jobs: ref: 'rc' - name: build deps & slicer run: ./BuildMacOS.sh -ia + - name: Upload artifact + uses: actions/upload-artifact@v1.0.0 + with: + name: nightly_macos_arm_debug.dmg + path: build/${{ github.event.repository.name }}.dmg - name: Upload artifact uses: actions/upload-artifact@v1.0.0 with: From 5c91aa33b62d8bfcbbd41e8c28430d907477ee3f Mon Sep 17 00:00:00 2001 From: supermerill Date: Wed, 14 Feb 2024 18:56:03 +0100 Subject: [PATCH 03/19] Add solid_infill_below_layer_area & solid_infill_below_width if small layer, then full solid for the layer if a surface don't have a width higher than threshold, then full solid for it --- resources/ui_layout/default/print.ui | 6 +++- src/libslic3r/Fill/Fill.cpp | 3 +- src/libslic3r/LayerRegion.cpp | 45 +++++++++++++++++++++++++-- src/libslic3r/Preset.cpp | 2 ++ src/libslic3r/Print.hpp | 1 + src/libslic3r/PrintConfig.cpp | 22 +++++++++++++ src/libslic3r/PrintConfig.hpp | 2 ++ src/libslic3r/PrintObject.cpp | 43 +++++++++++++++++++++++++ src/slic3r/GUI/ConfigManipulation.cpp | 3 +- 9 files changed, 122 insertions(+), 5 deletions(-) diff --git a/resources/ui_layout/default/print.ui b/resources/ui_layout/default/print.ui index 92d3863a64c..9c1b3dc24b1 100644 --- a/resources/ui_layout/default/print.ui +++ b/resources/ui_layout/default/print.ui @@ -211,7 +211,11 @@ group:sidetext_width$5:Infill angle # setting:fill_angle_template group:sidetext_width$5:Advanced setting:solid_infill_every_layers - setting:solid_infill_below_area + line:Solid infill is area below + setting:label$From region:solid_infill_below_area + setting:label$From the whole layer:solid_infill_below_layer_area + end_line + setting:solid_infill_below_width line:Anchor solid infill by X mm setting:label_width$8:width$5:external_infill_margin setting:label_width$6:width$5:bridged_infill_margin diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 913a30cb45c..d7b5fb83551 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -716,7 +716,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: double area = unscaled(unscaled(real_surface)); assert(compute_volume.volume <= area * surface_fill.params.layer_height * 1.001 || f->debug_verify_flow_mult <= 0.8); if(compute_volume.volume > 0) //can fail for thin regions - assert(compute_volume.volume >= area * surface_fill.params.layer_height * 0.999 || f->debug_verify_flow_mult >= 1.3 || area < std::max(1.,surface_fill.params.config->solid_infill_below_area.value)); + assert(compute_volume.volume >= area * surface_fill.params.layer_height * 0.999 || f->debug_verify_flow_mult >= 1.3 + || area < std::max(1.,surface_fill.params.config->solid_infill_below_area.value) || area < std::max(1.,surface_fill.params.config->solid_infill_below_layer_area.value)); } #endif } diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index becd1881226..4e12c4f2096 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -517,13 +517,54 @@ void LayerRegion::prepare_fill_surfaces() } // turn too small internal regions into solid regions according to the user setting - if (! spiral_vase && this->region().config().fill_density.value > 0) { + if (!spiral_vase && this->region().config().fill_density.value > 0) { + // apply solid_infill_below_area // scaling an area requires two calls! double min_area = scale_(scale_(this->region().config().solid_infill_below_area.value)); - for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { + for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); + surface != this->fill_surfaces.surfaces.end(); ++surface) { if (surface->has_fill_sparse() && surface->has_pos_internal() && surface->area() <= min_area) surface->surface_type = stPosInternal | stDensSolid; } + // also Apply solid_infill_below_width + double spacing = this->flow(frSolidInfill).spacing(); + coordf_t scaled_spacing = scale_d(spacing); + coordf_t min_half_width = scale_d(this->region().config().solid_infill_below_width.get_abs_value(spacing)) / 2; + if (min_half_width > 0) { + Surfaces srfs_to_add; + for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); + surface != this->fill_surfaces.surfaces.end(); ++surface) { + if (surface->has_fill_sparse() && surface->has_pos_internal()) { + // try to collapse the surface + // grow it a bit more to have an easy time to intersect + ExPolygons results = offset2_ex({surface->expolygon}, -min_half_width - SCALED_EPSILON, + min_half_width + SCALED_EPSILON + + std::min(scaled_spacing / 5, min_half_width / 5)); + // TODO: find a way to have both intersect & cut + ExPolygons cut = diff_ex(ExPolygons{surface->expolygon}, results); + ExPolygons intersect = intersection_ex(ExPolygons{surface->expolygon}, results); + if (intersect.size() == 1 && cut.empty()) + continue; + if (!intersect.empty()) { + //not possible ot have multiple intersect no cut from a single expoly. + assert(!cut.empty()); + surface->expolygon = std::move(intersect[0]); + for (int i = 1; i < intersect.size(); i++) { + srfs_to_add.emplace_back(*surface, std::move(intersect[i])); + } + for (ExPolygon& expoly : cut) { + srfs_to_add.emplace_back(*surface, std::move(expoly)); + srfs_to_add.back().surface_type = stPosInternal | stDensSolid; + } + } else { + //no intersec => all in solid + assert(cut.size() == 1); + surface->surface_type = stPosInternal | stDensSolid; + } + } + } + append(this->fill_surfaces.surfaces, std::move(srfs_to_add)); + } } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index ca667080cfa..8cf7689662c 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -515,6 +515,8 @@ static std::vector s_Preset_print_options { "fill_angle_template", "bridge_angle", "solid_infill_below_area", + "solid_infill_below_layer_area", + "solid_infill_below_width", "only_retract_when_crossing_perimeters", "enforce_retract_first_layer", "infill_first", "avoid_crossing_perimeters_max_detour", diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 96a3804ff5e..c35c4e74e9e 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -394,6 +394,7 @@ class PrintObject : public PrintObjectBaseWithStatemode = comExpert | comPrusa; def->set_default_value(new ConfigOptionFloat(70)); + def = this->add("solid_infill_below_layer_area", coFloat); + def->label = L("Solid infill layer threshold area"); + def->category = OptionCategory::infill; + def->tooltip = L("Force solid infill for the whole layer when the combined area of all objects that are printed at the same layer is smaller than this value."); + def->sidetext = L("mm²"); + def->min = 0; + def->mode = comExpert | comSuSi; + def->set_default_value(new ConfigOptionFloat(0)); + + def = this->add("solid_infill_below_width", coFloatOrPercent); + def->label = L("Solid infill threshold width"); + def->category = OptionCategory::infill; + def->tooltip = L("Force solid infill for parts of regions having a smaller width than the specified threshold." + "\nCan be a % of the current solid infill spacing." + "\nSet 0 to disable"); + def->sidetext = L("mm or %"); + def->min = 0; + def->mode = comExpert | comSuSi; + def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); + def = this->add("solid_infill_overlap", coPercent); def->label = L("Solid fill overlap"); def->category = OptionCategory::width; @@ -8255,6 +8275,8 @@ std::unordered_set prusa_export_to_remove_keys = { "solid_infill_fan_speed", "solid_infill_overlap", "start_gcode_manual", +"solid_infill_below_layer_area", +"solid_infill_below_thickness", "support_material_angle_height", "support_material_acceleration", "support_material_contact_distance_type", diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index d08278c77e1..2673fd1186f 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -936,6 +936,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatOrPercent, small_perimeter_max_length)) ((ConfigOptionEnum, solid_fill_pattern)) ((ConfigOptionFloat, solid_infill_below_area)) + ((ConfigOptionFloat, solid_infill_below_layer_area)) + ((ConfigOptionFloatOrPercent, solid_infill_below_width)) ((ConfigOptionInt, solid_infill_extruder)) ((ConfigOptionFloatOrPercent, solid_infill_extrusion_width)) ((ConfigOptionFloatOrPercent, solid_infill_extrusion_spacing)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 7db8108f277..ca048a06bf6 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -428,6 +428,9 @@ namespace Slic3r { m_print->throw_if_canceled(); } + // solid_infill_below_area has just beeing applied at the end of prepare_fill_surfaces() + apply_solid_infill_below_layer_area(); + // this will detect bridges and reverse bridges // and rearrange top/bottom/internal surfaces // It produces enlarged overlapping bridging areas. @@ -977,6 +980,8 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "infill_only_where_needed" || opt_key == "ironing_type" || opt_key == "solid_infill_below_area" + || opt_key == "solid_infill_below_layer_area" + || opt_key == "solid_infill_below_width" || opt_key == "solid_infill_extruder" || opt_key == "solid_infill_every_layers" || opt_key == "solid_over_perimeters" @@ -1780,6 +1785,44 @@ bool PrintObject::invalidate_state_by_config_options( // Mark the object to have the region slices classified (typed, which also means they are split based on whether they are supported, bridging, top layers etc.) m_typed_slices = true; } + + void PrintObject::apply_solid_infill_below_layer_area() + { + // compute the total layer surface for the bed, for solid_infill_below_layer_area + for (auto *my_layer : this->m_layers) { + bool exists = false; + for (auto *region : my_layer->m_regions) { + exists |= region->region().config().solid_infill_below_layer_area.value > 0; + } + if (!exists) + return; + double total_area = 0; + if (this->print()->config().complete_objects.value) { + // sequential printing: only consider myself + for (const ExPolygon &slice : layer->lslices) { total_area += slice.area(); } + } else { + // parallel printing: get all objects + for (const PrintObject *object : this->print()->objects()) { + for (auto *layer : object->m_layers) { + if (std::abs(layer->print_z - my_layer->print_z) < EPSILON) { + for (const ExPolygon &slice : layer->lslices) { total_area += slice.area(); } + } + } + } + } + // is it low enough to apply solid_infill_below_layer_area? + for (auto *region : my_layer->m_regions) { + if (!this->print()->config().spiral_vase.value && region->region().config().fill_density.value > 0) { + double min_area = scale_d(scale_d(region->region().config().solid_infill_below_layer_area.value)); + for (Surfaces::iterator surface = region->fill_surfaces.surfaces.begin(); + surface != region->fill_surfaces.surfaces.end(); ++surface) { + if (surface->has_fill_sparse() && surface->has_pos_internal() && total_area <= min_area) + surface->surface_type = stPosInternal | stDensSolid; + } + } + } + } + } void PrintObject::process_external_surfaces() { diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 741a97146d9..ea682e59c32 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -396,7 +396,8 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) bool have_infill = config->option("fill_density")->value > 0; // infill_extruder uses the same logic as in Print::extruders() for (auto el : { "fill_aligned_z", "fill_pattern", "infill_connection", "infill_every_layers", "infill_only_where_needed", - "solid_infill_every_layers", "solid_infill_below_area", "infill_extruder", "infill_anchor_max" }) + "solid_infill_every_layers", "solid_infill_below_area", "solid_infill_below_layer_area", "solid_infill_below_width", + "infill_extruder", "infill_anchor_max" }) toggle_field(el, have_infill); // Only allow configuration of open anchors if the anchoring is enabled. bool has_infill_anchors = have_infill && config->option>("infill_connection")->value != InfillConnection::icNotConnected; From 8bc140509bc6129dd716bd51ba10c7c22d6a621c Mon Sep 17 00:00:00 2001 From: supermerill Date: Wed, 14 Feb 2024 22:28:38 +0100 Subject: [PATCH 04/19] DoubleSlider: can show layer area (of printable slice), activated by preference --- src/libslic3r/AppConfig.cpp | 3 +++ src/libslic3r/GCode.cpp | 14 ++++++++--- src/libslic3r/GCode/GCodeProcessor.hpp | 1 + src/libslic3r/Print.hpp | 1 + src/libslic3r/PrintObject.cpp | 2 +- src/slic3r/GUI/DoubleSlider.cpp | 32 +++++++++++++++++++++++++- src/slic3r/GUI/DoubleSlider.hpp | 2 ++ src/slic3r/GUI/GUI_Preview.cpp | 23 ++++++++++++++++++ src/slic3r/GUI/Preferences.cpp | 11 +++++++-- 9 files changed, 82 insertions(+), 7 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index da87e70e831..7298244ac0c 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -476,6 +476,9 @@ void AppConfig::set_defaults() if (get("show_layer_time_doubleslider").empty()) set("show_layer_time_doubleslider", "0"); + if (get("show_layer_area_doubleslider").empty()) + set("show_layer_area_doubleslider", "0"); + } else { #ifdef _WIN32 if (get("associate_gcode").empty()) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index f0cfb77b1a6..c16ab21d610 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -2958,9 +2958,9 @@ LayerResult GCode::process_layer( return result; // Extract 1st object_layer and support_layer of this set of layers with an equal print_z. - coordf_t print_z = layer.print_z; - bool first_layer = layer.id() == 0; - uint16_t first_extruder_id = layer_tools.extruders.front(); + coordf_t print_z = layer.print_z; + bool first_layer = layer.id() == 0; + uint16_t first_extruder_id = layer_tools.extruders.front(); // Initialize config with the 1st object to be printed at this layer. m_config.apply(print.default_region_config(), true); @@ -3632,6 +3632,14 @@ LayerResult GCode::process_layer( file.write(gcode); #endif + // set area used in this layer + double layer_area = 0; + for (const LayerToPrint &print_layer : layers) + for (auto poly : print_layer.layer()->lslices) + layer_area += poly.area(); + layer_area = unscaled(unscaled(layer_area)); + status_monitor.stats().layer_area_stats.emplace_back(print_z, layer_area); + BOOST_LOG_TRIVIAL(trace) << "Exported layer " << layer.id() << " print_z " << print_z << log_memory_info(); diff --git a/src/libslic3r/GCode/GCodeProcessor.hpp b/src/libslic3r/GCode/GCodeProcessor.hpp index 472ed3e5dab..a5b60fd891d 100644 --- a/src/libslic3r/GCode/GCodeProcessor.hpp +++ b/src/libslic3r/GCode/GCodeProcessor.hpp @@ -49,6 +49,7 @@ namespace Slic3r { std::vector> moves_times; std::vector> roles_times; std::vector layers_times; + std::vector layers_areas; void reset() { time = 0.0f; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index c35c4e74e9e..1fb63252897 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -499,6 +499,7 @@ struct PrintStatistics std::string initial_filament_type; std::string printing_filament_types; std::map filament_stats; + std::vector> layer_area_stats; // print_z to area // Config with the filled in print statistics. DynamicConfig config() const; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index ca048a06bf6..00a6e0435d0 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1799,7 +1799,7 @@ bool PrintObject::invalidate_state_by_config_options( double total_area = 0; if (this->print()->config().complete_objects.value) { // sequential printing: only consider myself - for (const ExPolygon &slice : layer->lslices) { total_area += slice.area(); } + for (const ExPolygon &slice : my_layer->lslices) { total_area += slice.area(); } } else { // parallel printing: get all objects for (const PrintObject *object : this->print()->objects()) { diff --git a/src/slic3r/GUI/DoubleSlider.cpp b/src/slic3r/GUI/DoubleSlider.cpp index bc347621b42..d8b1e6720ec 100644 --- a/src/slic3r/GUI/DoubleSlider.cpp +++ b/src/slic3r/GUI/DoubleSlider.cpp @@ -449,7 +449,7 @@ void Control::SetLayersTimes(const std::vector& layers_times, float total m_layers_times.push_back(total_time); Refresh(); Update(); -} + } } void Control::SetLayersTimes(const std::vector& layers_times) @@ -460,6 +460,22 @@ void Control::SetLayersTimes(const std::vector& layers_times) m_layers_times[i] += m_layers_times[i - 1]; } +void Control::SetLayersAreas(const std::vector& layers_areas) +{ + m_layers_areas.clear(); + m_layers_areas.reserve(layers_areas.size()); + for(float area : layers_areas) + m_layers_areas.push_back(area); + if (m_layers_values.size() == m_layers_areas.size() + 1) + m_layers_areas.insert(m_layers_areas.begin(), 0.); + if (m_is_wipe_tower && m_values.size() != m_layers_areas.size()) { + // When whipe tower is used to the end of print, there is one layer which is not marked in layers_times + // So, add this value from the total print time value + for (size_t i = m_layers_areas.size(); i < m_layers_values.size(); i++) + m_layers_areas.push_back(0.); + } +} + void Control::SetDrawMode(bool is_sla_print, bool is_sequential_print) { m_draw_mode = is_sla_print ? dmSlaPrint : @@ -796,6 +812,7 @@ wxString Control::get_label(int tick, LabelType label_type/* = ltHeightWithLayer size_t layer_number = m_is_wipe_tower ? get_layer_number(value, label_type) /**/ + 1 : (m_values.empty() ? value : value /* + 1 */ ); bool show_lheight = GUI::wxGetApp().app_config->get("show_layer_height_doubleslider") == "1"; bool show_ltime = GUI::wxGetApp().app_config->get("show_layer_time_doubleslider") == "1"; + bool show_larea = GUI::wxGetApp().app_config->get("show_layer_area_doubleslider") == "1"; int nb_lines = 2; // to move things down if the slider is on top wxString comma = "\n"; if (show_lheight) { @@ -827,6 +844,19 @@ wxString Control::get_label(int tick, LabelType label_type/* = ltHeightWithLayer } } } + if (show_larea && !m_layers_areas.empty()) { + if (m_layers_areas.size() +1 >= m_values.size()) { + size_t layer_idx_time = layer_number; + if (m_values.size() > m_layers_areas.size()) { + layer_idx_time--; + } + if (layer_idx_time < m_layers_areas.size()) { + nb_lines++; + str = str + comma + wxString::Format("%.*f", m_layers_areas[layer_idx_time] < 1 ? 3 : m_layers_areas[layer_idx_time] < 10 ? 2 : m_layers_areas[layer_idx_time] < 100 ? 1 : 0, m_layers_areas[layer_idx_time]); + comma = "\n"; + } + } + } int nb_step_down = layer_number - m_values.size() + nb_lines - 1; while (nb_step_down > 0) { str = "\n" + str; diff --git a/src/slic3r/GUI/DoubleSlider.hpp b/src/slic3r/GUI/DoubleSlider.hpp index 10c1a050076..616f86221cd 100644 --- a/src/slic3r/GUI/DoubleSlider.hpp +++ b/src/slic3r/GUI/DoubleSlider.hpp @@ -249,6 +249,7 @@ class Control : public wxControl void SetTicksValues(const Info &custom_gcode_per_print_z); void SetLayersTimes(const std::vector& layers_times, float total_time); void SetLayersTimes(const std::vector& layers_times); + void SetLayersAreas(const std::vector& layers_areas); void SetDrawMode(bool is_sla_print, bool is_sequential_print); void SetDrawMode(DrawMode mode) { m_draw_mode = mode; } @@ -424,6 +425,7 @@ class Control : public wxControl std::vector m_values; TickCodeInfo m_ticks; std::vector m_layers_times; + std::vector m_layers_areas; std::vector m_layers_values; std::vector m_extruder_colors; std::string m_print_obj_idxs; diff --git a/src/slic3r/GUI/GUI_Preview.cpp b/src/slic3r/GUI/GUI_Preview.cpp index ded6e93b6e7..9d54ac891e5 100644 --- a/src/slic3r/GUI/GUI_Preview.cpp +++ b/src/slic3r/GUI/GUI_Preview.cpp @@ -745,6 +745,29 @@ void Preview::update_layers_slider(const std::vector& layers_z, bool kee auto print_mode_stat = m_gcode_result->print_statistics.modes.front(); m_layers_slider->SetLayersTimes(print_mode_stat.layers_times, print_mode_stat.time); } + { + // create area array + //area not computed for sla_print_technology //TODO + if (!sla_print_technology){ + const std::vector> &layerz_to_area = plater->fff_print().print_statistics().layer_area_stats; + std::vector areas; + for(auto [z, area] : layerz_to_area) + areas.push_back(area); + m_layers_slider->SetLayersAreas(areas); + //auto objects = plater->fff_print().objects(); + //for (auto object : objects) { + // for (auto layer : object->layers()) { + // assert(layer->print_z > 100); + // coord_t layer_z = 100*(coord_t(layer->print_z + 50)/100); + // int32_t area = layerz_to_area[layer_z]; + // for (auto poly : layer->lslices) { + // area += poly.area(); + // } + // layerz_to_area[layer_z] = area; + // } + //} + } + } // Suggest the auto color change, if model looks like sign if (m_layers_slider->IsNewPrint()) diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp index c3715877fff..e6c0f16fe47 100644 --- a/src/slic3r/GUI/Preferences.cpp +++ b/src/slic3r/GUI/Preferences.cpp @@ -656,17 +656,24 @@ void PreferencesDialog::build(size_t selected_tab) def.label = L("Show layer height on the scroll bar"); def.type = coBool; - def.tooltip = L("Add the layer height (first number in parentheses) next to a widget of the layer double-scrollbar."); + def.tooltip = L("Add the layer height (first number after the layer z position) next to a widget of the layer double-scrollbar."); def.set_default_value(new ConfigOptionBool{ app_config->get("show_layer_height_doubleslider") == "1" }); option = Option(def, "show_layer_height_doubleslider"); m_optgroups_gui.back()->append_single_option_line(option); def.label = L("Show layer time on the scroll bar"); def.type = coBool; - def.tooltip = L("Add the layer height (before the layer count in parentheses) next to a widget of the layer double-scrollbar."); + def.tooltip = L("Add the layer time (after the layer height, or if it's hidden after the layer z position) next to a widget of the layer double-scrollbar."); def.set_default_value(new ConfigOptionBool{ app_config->get("show_layer_time_doubleslider") == "1" }); option = Option(def, "show_layer_time_doubleslider"); m_optgroups_gui.back()->append_single_option_line(option); + + def.label = L("Show layer area on the scroll bar"); + def.type = coBool; + def.tooltip = L("Add the layer area (the number just below the layer id) next to a widget of the layer double-scrollbar."); + def.set_default_value(new ConfigOptionBool{ app_config->get("show_layer_area_doubleslider") == "1" }); + option = Option(def, "show_layer_area_doubleslider"); + m_optgroups_gui.back()->append_single_option_line(option); } From 6f735d31498181d7bdcbf9a83d9f3becc6d50733 Mon Sep 17 00:00:00 2001 From: supermerill Date: Wed, 7 Feb 2024 10:07:54 +0100 Subject: [PATCH 05/19] Split perimeters (perimeter count) into contour & hole (if not -1) only for classic process --- resources/ui_layout/default/print.ui | 1 + src/libslic3r/ExPolygon.hpp | 26 +- src/libslic3r/PerimeterGenerator.cpp | 727 ++++++++++++++---- src/libslic3r/PerimeterGenerator.hpp | 2 +- src/libslic3r/Preset.cpp | 5 +- src/libslic3r/PrintConfig.cpp | 21 +- src/libslic3r/PrintConfig.hpp | 1 + src/libslic3r/PrintObject.cpp | 1 + src/slic3r/GUI/ConfigManipulation.cpp | 6 +- .../test_clipper_utils.cpp | 29 + 10 files changed, 659 insertions(+), 160 deletions(-) diff --git a/resources/ui_layout/default/print.ui b/resources/ui_layout/default/print.ui index 9c1b3dc24b1..a86dda9fe89 100644 --- a/resources/ui_layout/default/print.ui +++ b/resources/ui_layout/default/print.ui @@ -2,6 +2,7 @@ page:Perimeters & Shell:shell group:Vertical shells setting:width$6:perimeters + setting:width$6:perimeters_hole setting:tags$Simple$Expert$SuSi:script:float:depends$perimeter_spacing$external_perimeter_spacing:label$Wall Thickness:tooltip$Change the perimeter extrusion widths to ensure that there is an exact number of perimeters for this wall thickness value. It won't put the perimeter width below the nozzle diameter, and up to double.\nNote that the value displayed is just a view of the current perimeter thickness, like the info text below. The number of perimeters used to compute this value is one loop, or the custom variable 'wall_thickness_lines' (advanced mode) if defined.\nIf the value is too low, it will revert the widths to the saved value.\nIf the value is set to 0, it will show 0.:s_wall_thickness setting:spiral_vase recommended_thin_wall_thickness_description diff --git a/src/libslic3r/ExPolygon.hpp b/src/libslic3r/ExPolygon.hpp index f1396fcbdcd..c3adb94d1a5 100644 --- a/src/libslic3r/ExPolygon.hpp +++ b/src/libslic3r/ExPolygon.hpp @@ -221,6 +221,8 @@ inline Polylines to_polylines(ExPolygons &&src) inline Polygons to_polygons(const ExPolygon &src) { + assert(src.contour.is_counter_clockwise()); + assert(src.holes.empty() || src.holes.front().is_clockwise()); Polygons polygons; polygons.reserve(src.holes.size() + 1); polygons.push_back(src.contour); @@ -233,6 +235,8 @@ inline Polygons to_polygons(const ExPolygons &src) Polygons polygons; polygons.reserve(number_polygons(src)); for (const ExPolygon& ex_poly : src) { + assert(ex_poly.contour.is_counter_clockwise()); + assert(ex_poly.holes.empty() || ex_poly.holes.front().is_clockwise()); polygons.push_back(ex_poly.contour); polygons.insert(polygons.end(), ex_poly.holes.begin(), ex_poly.holes.end()); } @@ -241,6 +245,8 @@ inline Polygons to_polygons(const ExPolygons &src) inline ConstPolygonPtrs to_polygon_ptrs(const ExPolygon &src) { + assert(src.contour.is_counter_clockwise()); + assert(src.holes.empty() || src.holes.front().is_clockwise()); ConstPolygonPtrs polygons; polygons.reserve(src.holes.size() + 1); polygons.emplace_back(&src.contour); @@ -254,6 +260,8 @@ inline ConstPolygonPtrs to_polygon_ptrs(const ExPolygons &src) ConstPolygonPtrs polygons; polygons.reserve(number_polygons(src)); for (const ExPolygon &expoly : src) { + assert(expoly.contour.is_counter_clockwise()); + assert(expoly.holes.empty() || expoly.holes.front().is_clockwise()); polygons.emplace_back(&expoly.contour); for (const Polygon &hole : expoly.holes) polygons.emplace_back(&hole); @@ -276,6 +284,8 @@ inline Polygons to_polygons(ExPolygons &&src) Polygons polygons; polygons.reserve(number_polygons(src)); for (ExPolygons::iterator it = src.begin(); it != src.end(); ++it) { + assert(it->contour.is_counter_clockwise()); + assert(it->holes.empty() || it->holes.front().is_clockwise()); polygons.push_back(std::move(it->contour)); std::move(std::begin(it->holes), std::end(it->holes), std::back_inserter(polygons)); it->holes.clear(); @@ -287,8 +297,10 @@ inline ExPolygons to_expolygons(const Polygons &polys) { ExPolygons ex_polys; ex_polys.assign(polys.size(), ExPolygon()); - for (size_t idx = 0; idx < polys.size(); ++idx) + for (size_t idx = 0; idx < polys.size(); ++idx) { + assert(polys[idx].is_counter_clockwise()); ex_polys[idx].contour = polys[idx]; + } return ex_polys; } @@ -296,13 +308,17 @@ inline ExPolygons to_expolygons(Polygons &&polys) { ExPolygons ex_polys; ex_polys.assign(polys.size(), ExPolygon()); - for (size_t idx = 0; idx < polys.size(); ++idx) + for (size_t idx = 0; idx < polys.size(); ++idx) { + assert(polys[idx].is_counter_clockwise()); ex_polys[idx].contour = std::move(polys[idx]); + } return ex_polys; } inline void polygons_append(Polygons &dst, const ExPolygon &src) { + assert(src.contour.is_counter_clockwise()); + assert(src.holes.empty() || src.holes.front().is_clockwise()); dst.reserve(dst.size() + src.holes.size() + 1); dst.push_back(src.contour); dst.insert(dst.end(), src.holes.begin(), src.holes.end()); @@ -312,6 +328,8 @@ inline void polygons_append(Polygons &dst, const ExPolygons &src) { dst.reserve(dst.size() + number_polygons(src)); for (ExPolygons::const_iterator it = src.begin(); it != src.end(); ++ it) { + assert(it->contour.is_counter_clockwise()); + assert(it->holes.empty() || it->holes.front().is_clockwise()); dst.push_back(it->contour); dst.insert(dst.end(), it->holes.begin(), it->holes.end()); } @@ -319,6 +337,8 @@ inline void polygons_append(Polygons &dst, const ExPolygons &src) inline void polygons_append(Polygons &dst, ExPolygon &&src) { + assert(src.contour.is_counter_clockwise()); + assert(src.holes.empty() || src.holes.front().is_clockwise()); dst.reserve(dst.size() + src.holes.size() + 1); dst.push_back(std::move(src.contour)); std::move(std::begin(src.holes), std::end(src.holes), std::back_inserter(dst)); @@ -329,6 +349,8 @@ inline void polygons_append(Polygons &dst, ExPolygons &&src) { dst.reserve(dst.size() + number_polygons(src)); for (ExPolygons::iterator it = src.begin(); it != src.end(); ++ it) { + assert(it->contour.is_counter_clockwise()); + assert(it->holes.empty() || it->holes.front().is_clockwise()); dst.push_back(std::move(it->contour)); std::move(std::begin(it->holes), std::end(it->holes), std::back_inserter(dst)); it->holes.clear(); diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 18e9c1ea03f..b7ae004ee8f 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -263,13 +263,13 @@ ProcessSurfaceResult PerimeterGenerator::process_arachne(int& loop_number, const const Polygons last_p = to_polygons(last); Arachne::WallToolPaths wallToolPaths(last_p, this->get_ext_perimeter_spacing(), this->get_ext_perimeter_width(), - this->get_perimeter_spacing(), this->get_perimeter_width(), loop_number + 1, coord_t(0), + this->get_perimeter_spacing(), this->get_perimeter_width(), loop_number, coord_t(0), this->layer->height, *this->object_config, *this->print_config); std::vector perimeters = wallToolPaths.getToolPaths(); #if _DEBUG - for (auto periemter : perimeters) { - for (Arachne::ExtrusionLine &extrusion : periemter) { + for (auto perimeter : perimeters) { + for (Arachne::ExtrusionLine &extrusion : perimeter) { if (extrusion.isZeroLength()) continue; for (Slic3r::Arachne::ExtrusionJunction &junction : extrusion.junctions) { @@ -324,7 +324,7 @@ ProcessSurfaceResult PerimeterGenerator::process_arachne(int& loop_number, const perimeters.insert(perimeters.begin(), out_shell.begin(), out_shell.end()); } - loop_number = int(perimeters.size()) - 1; + loop_number = int(perimeters.size()); #ifdef ARACHNE_DEBUG { @@ -490,10 +490,7 @@ ProcessSurfaceResult PerimeterGenerator::process_arachne(int& loop_number, const if (offset_ex(infill_contour, -float(spacing / 2.)).empty()) infill_contour.clear(); // Infill region is too small, so let's filter it out. - result.inner_perimeter = (loop_number < 0) ? infill_contour : - (loop_number == 0) - ? offset_ex(infill_contour, ext_perimeter_spacing / 2) - : offset_ex(infill_contour, perimeter_spacing / 2); + result.inner_perimeter = infill_contour; return result; } @@ -683,7 +680,7 @@ void PerimeterGenerator::process() // internal flow which is unrelated. <- i don't undertand, so revert to ext_perimeter_spacing2 //const coord_t min_spacing = (coord_t)( perimeter_spacing * (1 - 0.05/*INSET_OVERLAP_TOLERANCE*/) ); //const coord_t ext_min_spacing = (coord_t)( ext_perimeter_spacing2 * (1 - 0.05/*INSET_OVERLAP_TOLERANCE*/) ); - // now the tolerance is built in thin_periemter settings + // now the tolerance is built in thin_perimeter settings // prepare grown lower layer slices for overhang detection if (this->lower_slices != NULL && (this->config->overhangs_width.value > 0 || this->config->overhangs_width_speed.value > 0)) { @@ -765,26 +762,41 @@ void PerimeterGenerator::process() int surface_idx = 0; const int extra_odd_perimeter = (config->extra_perimeters_odd_layers && layer->id() % 2 == 1 ? 1 : 0); for (const Surface& surface : all_surfaces) { - // detect how many perimeters must be generated for this island - int loop_number = this->config->perimeters + surface.extra_perimeters - 1 + extra_odd_perimeter; // 0-indexed loops surface_idx++; - if (print_config->spiral_vase) { - if (layer->id() >= config->bottom_solid_layers) { - loop_number = 0; + // detect how many perimeters must be generated for this island + int nb_loop_contour = this->config->perimeters; + if (nb_loop_contour > 0) + nb_loop_contour += extra_odd_perimeter + surface.extra_perimeters; + int nb_loop_holes = this->config->perimeters_hole; + if (nb_loop_holes > 0) + nb_loop_holes += extra_odd_perimeter + surface.extra_perimeters; + + if (nb_loop_contour < 0) + nb_loop_contour = std::max(0, nb_loop_holes); + if (nb_loop_holes < 0) + nb_loop_holes = std::max(0, nb_loop_contour); + + if (print_config->spiral_vase) { + if (layer->id() >= config->bottom_solid_layers) { + nb_loop_contour = 1; + nb_loop_holes = 0; + } } - } - if ((layer->id() == 0 && this->config->only_one_perimeter_first_layer) || (this->config->only_one_perimeter_top && loop_number > 0 && this->upper_slices == NULL)) { - loop_number = 0; + if ((layer->id() == 0 && this->config->only_one_perimeter_first_layer) || + (this->config->only_one_perimeter_top && this->upper_slices == NULL)) { + nb_loop_contour = std::min(nb_loop_contour, 1); + nb_loop_holes = std::min(nb_loop_holes, 1); } ProcessSurfaceResult surface_process_result; //core generation - if (use_arachne) { - surface_process_result = process_arachne(loop_number, surface); + if (use_arachne && nb_loop_holes == nb_loop_contour) { + surface_process_result = process_arachne(nb_loop_contour, surface); + nb_loop_holes = nb_loop_contour; // nb_loop_contour is in/out } else { - surface_process_result = process_classic(loop_number, surface); + surface_process_result = process_classic(nb_loop_contour, nb_loop_holes, surface); } this->throw_if_canceled(); @@ -793,17 +805,9 @@ void PerimeterGenerator::process() // we offset by half the perimeter spacing (to get to the actual infill boundary) // and then we offset back and forth by half the infill spacing to only consider the // non-collapsing regions - coord_t inset = 0; coord_t infill_peri_overlap = 0; // only apply infill overlap if we actually have one perimeter - if (loop_number >= 0) { - // half infill / perimeter - inset = (loop_number == 0) ? - // one loop - this->get_ext_perimeter_spacing() / 2 : - // two or more loops? - this->get_perimeter_spacing() / 2; - //infill_peri_overlap = scale_t(this->config->get_abs_value("infill_overlap", unscale(perimeter_spacing + solid_infill_spacing) / 2)); + if (nb_loop_contour > 0 || nb_loop_holes > 0) { //give the overlap size to let the infill do his overlap //add overlap if at least one perimeter coordf_t perimeter_spacing_for_encroach = 0; @@ -818,37 +822,31 @@ void PerimeterGenerator::process() infill_peri_overlap = scale_t(this->config->get_abs_value("infill_overlap", perimeter_spacing_for_encroach)); } - //remove gapfill from last - ExPolygons last_no_gaps = (surface_process_result.gap_srf.empty()) ? surface_process_result.inner_perimeter : diff_ex(surface_process_result.inner_perimeter, surface_process_result.gap_srf); - // simplify infill contours according to resolution Polygons not_filled_p; - for (ExPolygon& ex : last_no_gaps) + for (const ExPolygon& ex : surface_process_result.inner_perimeter) ex.simplify_p(scale_t(std::max(this->print_config->resolution.value, print_config->resolution_internal / 4)), ¬_filled_p); ExPolygons not_filled_exp = union_ex(not_filled_p); // collapse too narrow infill areas coord_t min_perimeter_infill_spacing = (coord_t)(this->get_solid_infill_spacing() * (1. - INSET_OVERLAP_TOLERANCE)); ExPolygons infill_exp; + infill_exp = offset2_ex(not_filled_exp, + double(- min_perimeter_infill_spacing / 2 + infill_peri_overlap - this->get_infill_gap()), + double(min_perimeter_infill_spacing / 2)); //special branch if gap : don't inset away from gaps! - if (surface_process_result.gap_srf.empty()) { + ExPolygons gap_fill_exps; + if (!surface_process_result.gap_srf.empty()) { + //not_filled_exp = union_ex(not_filled_p); infill_exp = offset2_ex(not_filled_exp, - double(-inset - min_perimeter_infill_spacing / 2 + infill_peri_overlap - this->get_infill_gap()), + double(- min_perimeter_infill_spacing / 2 + infill_peri_overlap - this->get_infill_gap()), double(min_perimeter_infill_spacing / 2)); - } else { - //store the infill_exp but not offseted, it will be used as a clip to remove the gapfill portion - const ExPolygons infill_exp_no_gap = offset2_ex(not_filled_exp, - double(-inset - min_perimeter_infill_spacing / 2 + infill_peri_overlap - this->get_infill_gap()), - double(inset + min_perimeter_infill_spacing / 2 - infill_peri_overlap + this->get_infill_gap())); - //redo the same as not_filled_exp but with last instead of last_no_gaps + //remove gaps surfaces not_filled_p.clear(); - for (ExPolygon& ex : surface_process_result.inner_perimeter) + for (ExPolygon& ex : surface_process_result.gap_srf) ex.simplify_p(scale_t(std::max(this->print_config->resolution.value, print_config->resolution_internal / 4)), ¬_filled_p); - not_filled_exp = union_ex(not_filled_p); - infill_exp = offset2_ex(not_filled_exp, - double(-inset - min_perimeter_infill_spacing / 2 + infill_peri_overlap - this->get_infill_gap()), - double(min_perimeter_infill_spacing / 2)); - // intersect(growth(surface_process_result.inner_perimeter-gap) , surface_process_result.inner_perimeter), so you have the (surface_process_result.inner_perimeter - small gap) but without voids betweeng gap & surface_process_result.inner_perimeter - infill_exp = intersection_ex(infill_exp, infill_exp_no_gap); + gap_fill_exps = union_ex(not_filled_p); + gap_fill_exps = offset_ex(gap_fill_exps, -infill_peri_overlap); + infill_exp = diff_ex(infill_exp, gap_fill_exps); } this->throw_if_canceled(); @@ -865,12 +863,15 @@ void PerimeterGenerator::process() if (min_perimeter_infill_spacing / 2 > infill_peri_overlap) polyWithoutOverlap = offset2_ex( not_filled_exp, - double(-inset - infill_gap - min_perimeter_infill_spacing / 2 + infill_peri_overlap), + double(- infill_gap - min_perimeter_infill_spacing / 2 + infill_peri_overlap), double(min_perimeter_infill_spacing / 2 - infill_peri_overlap)); else polyWithoutOverlap = offset_ex( not_filled_exp, - double(-inset - this->get_infill_gap())); + double(- this->get_infill_gap())); + if (!gap_fill_exps.empty()) { + polyWithoutOverlap = diff_ex(polyWithoutOverlap, gap_fill_exps); + } if (!surface_process_result.top_fills.empty()) { polyWithoutOverlap = union_ex(polyWithoutOverlap, top_infill_exp); } @@ -1103,7 +1104,243 @@ void PerimeterGenerator::processs_no_bridge(Surfaces& all_surfaces) { } } -ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const Surface& surface) +Polygons get_contours(const ExPolygons &expolys) { + Polygons polys; + for (const ExPolygon &expoly : expolys) { + assert(expoly.contour.is_counter_clockwise()); + polys.push_back(expoly.contour); + } + return polys; +} + +Polygons as_contour(const Polygons &holes) { + Polygons out; + for (const Polygon &hole : holes) { + assert(hole.is_clockwise()); + out.push_back(hole); + out.back().make_counter_clockwise(); + } + return out; +} + +Polygons get_holes_as_contour(const ExPolygon &expoly) { + Polygons polys; + for(const Polygon hole : expoly.holes){ + assert(hole.is_clockwise()); + polys.push_back(hole); + polys.back().make_counter_clockwise(); + } + return polys; +} + +Polygons get_holes_as_contour(const ExPolygons &expolys) { + Polygons polys; + for(const ExPolygon &expoly : expolys) + for(const Polygon hole : expoly.holes){ + assert(hole.is_clockwise()); + polys.push_back(hole); + polys.back().make_counter_clockwise(); + } + return polys; +} + +// expolygon representing the perimeter path +struct ExPolygonAsynch +{ + enum ExPolygonAsynchType { + epatGrowHole, + epatShrinkContour + }; + ExPolygonAsynchType type; + ExPolygon expoly; + // shrink the contour by this value to get the end of the spacing (should be negative, to shrink from centerline or edge) + coordf_t offset_contour_inner; + // shrink the contour by this value to get the external shell (the spacing position) (can be negative to grow from centreline, and be positive to shrink from surface polygon) + coordf_t offset_contour_outer; + // grow the holes by this value to get the end of the spacing (should be negative, to grow from centerline or edge) + coordf_t offset_holes_inner; + // grow the holes by this value to get the external shell (the spacing position) (should be the same value as offset_contour_outer) + coordf_t offset_holes_outer; + +}; + +// next_onion can be partially filled +void grow_holes_only(std::vector &unmoveable_contours, + ExPolygons & next_onion, + coordf_t spacing, + coordf_t overlap_spacing, + bool round_peri, + float min_round_spacing = 3.f) +{ + assert(spacing > 0); + assert(overlap_spacing >= 0); + Polygons new_contours; + for (size_t idx_unmoveable = 0; idx_unmoveable < unmoveable_contours.size(); ++idx_unmoveable) { + ExPolygonAsynch & unmoveable_contour = unmoveable_contours[idx_unmoveable]; + assert(unmoveable_contour.type == ExPolygonAsynch::ExPolygonAsynchType::epatGrowHole); + ExPolygon &expoly = unmoveable_contour.expoly; + assert(unmoveable_contour.offset_holes_inner <= 0); + // grow fake contours, can now have fake holes and/or less fake contours. + Polygons ok_holes = offset(get_holes_as_contour(expoly), + -unmoveable_contour.offset_holes_inner + spacing / 2 + overlap_spacing, + (round_peri ? ClipperLib::JoinType::jtRound : + ClipperLib::JoinType::jtMiter), + (round_peri ? min_round_spacing : 3)); + for (size_t i = 0; i < ok_holes.size(); ++i) { + if (ok_holes[i].is_clockwise()) { + // hole, it's a new peri, move it. + new_contours.push_back(std::move(ok_holes[i])); + ok_holes.erase(ok_holes.begin() + i); + new_contours.back().make_counter_clockwise(); + i--; + } + } + ok_holes = union_(ok_holes); + for (const Polygon &p : ok_holes) assert(p.is_counter_clockwise()); + //shrink contour, can now be multiple contour. + coordf_t computed_offset = unmoveable_contour.offset_contour_inner; + computed_offset -= spacing / 2; + computed_offset -= overlap_spacing; + Polygons ex_contour_offset = offset(Polygons{expoly.contour}, computed_offset); + bool ex_contour_offset_now_fake_hole = false; + for (size_t idx_hole = 0; idx_hole < ok_holes.size(); ++idx_hole) { + const Polygon &hole = ok_holes[idx_hole]; + assert(hole.is_counter_clockwise()); + // Check if it can fuse with contour + // TODO: bounding box for quicker cut search + auto it_contour_candidate_for_fuse = ex_contour_offset.begin(); + Polygons fused_contour; + while (it_contour_candidate_for_fuse != ex_contour_offset.end()) { + ExPolygons result = diff_ex(Polygons{*it_contour_candidate_for_fuse}, Polygons{hole}); + // Only two options here, it can fuse and then there is 1 or more contour, no holes. + // Or it don't touch the contour and so nothing happen. (the hole can be inside or outside) + // SO, we can check it it slip or if the contour has been modified + if (result.size() > 1 || (result.size() == 1 && result.front().contour != *it_contour_candidate_for_fuse)) { + for (ExPolygon &expoly : result) assert(expoly.holes.empty()); + // now use this one. + append(fused_contour, to_polygons(result)); + ex_contour_offset_now_fake_hole = true; + // remove from useful holes + ok_holes.erase(ok_holes.begin() + idx_hole); + idx_hole--; + it_contour_candidate_for_fuse = ex_contour_offset.erase(it_contour_candidate_for_fuse); + } else { + ++it_contour_candidate_for_fuse; + } + } + if(!fused_contour.empty()) + append(ex_contour_offset, std::move(fused_contour)); + } + // if moved from unmoveable_contours to growing_contours, then move the expoly + if (ex_contour_offset_now_fake_hole) { + // add useful holes to the contours, and push them + if (overlap_spacing != 0) + append(next_onion, offset_ex(diff_ex(ex_contour_offset, ok_holes), overlap_spacing)); + else + append(next_onion, diff_ex(ex_contour_offset, ok_holes)); + // remove from unmoveable + unmoveable_contours.erase(unmoveable_contours.begin() + idx_unmoveable); + idx_unmoveable--; + } else { + // update holes + // shrink to centerline + if (overlap_spacing != 0) + ok_holes = offset(ok_holes, -overlap_spacing); + /*test*/ for (const Polygon &p : ok_holes) assert(p.is_counter_clockwise()); + ExPolygons new_unmoveable = diff_ex(Polygons{unmoveable_contour.expoly.contour}, ok_holes); + // check if size is good. It's not possible to split the peri: it isn't srhunk, and holes intersect are alreedy detected (not unmoveable anymore) + assert(new_unmoveable.size() <= 1); + if (new_unmoveable.empty()) { + // remove from unmoveable + unmoveable_contours.erase(unmoveable_contours.begin() + idx_unmoveable); + idx_unmoveable--; + } else if(new_unmoveable.size() == 1){ + // update + unmoveable_contour.expoly = new_unmoveable.front(); + unmoveable_contour.offset_holes_inner = -spacing / 2; + unmoveable_contour.offset_holes_outer = spacing / 2; + } else { + assert(false); + //add all + for(ExPolygon & new_expoly : new_unmoveable) + unmoveable_contours.push_back({unmoveable_contour.type, new_expoly, unmoveable_contour.offset_contour_inner, + unmoveable_contour.offset_contour_outer, -spacing / 2, + spacing / 2}); + // remove from unmoveable + unmoveable_contours.erase(unmoveable_contours.begin() + idx_unmoveable); + idx_unmoveable--; + } + } + } +} + + +// next_onion can be partially filled +void grow_contour_only(std::vector &unmoveable_holes, coordf_t spacing, coordf_t overlap_spacing, bool round_peri, float min_round_spacing = 3.f) { + assert(spacing > 0); + assert(overlap_spacing >= 0); + Polygons new_contours; + // mutable size to allow insert at the same time. + size_t unmoveable_holes_size = unmoveable_holes.size(); + for (size_t idx_unmoveable = 0; idx_unmoveable < unmoveable_holes_size; ++idx_unmoveable) { + ExPolygonAsynch & unmoveable_hole = unmoveable_holes[idx_unmoveable]; + assert(unmoveable_hole.type == ExPolygonAsynch::ExPolygonAsynchType::epatShrinkContour); + ExPolygon &expoly = unmoveable_hole.expoly; + // shrink contour, can now have more contours. + assert(unmoveable_hole.offset_contour_inner <=0); + Polygons ok_contours = offset(expoly.contour, unmoveable_hole.offset_contour_inner - spacing/2 - overlap_spacing, + (round_peri ? ClipperLib::JoinType::jtRound : + ClipperLib::JoinType::jtMiter), + (round_peri ? min_round_spacing : 3)); + //we shrunk -> new peri can appear, holes can disapear, but there is already none. + for (const Polygon &p : ok_contours) assert(p.is_counter_clockwise()); + //grow holes to right size + assert(-unmoveable_hole.offset_holes_inner + spacing/2 - overlap_spacing > 0); + Polygons original_holes = get_holes_as_contour(expoly); + Polygons offsetted_holes = offset(original_holes, -unmoveable_hole.offset_holes_inner + spacing/2 + overlap_spacing); + // remove fake periemter, i don't want them. + for (size_t i = 0; i < offsetted_holes.size(); ++i) { + if (offsetted_holes[i].is_clockwise()) { + offsetted_holes.erase(offsetted_holes.begin() + i); + new_contours.back().make_counter_clockwise(); + i--; + } + } + offsetted_holes = union_(offsetted_holes); + for (const Polygon &p : offsetted_holes) assert(p.is_counter_clockwise()); + + for (Polygon simple_contour : ok_contours) { + // remove holes + ExPolygons test_expoly = diff_ex(Polygons{simple_contour}, offsetted_holes); + if (overlap_spacing != 0) { + test_expoly = offset_ex(test_expoly, overlap_spacing); + } + if (test_expoly.size() == 1) { + // no merge, then i can use the right hole size + ExPolygons new_unmoveable_hole = diff_ex(Polygons{test_expoly[0].contour}, original_holes); + // diff with smaller holes, so it has to be only one contour. + assert(new_unmoveable_hole.size() == 1); + expoly = new_unmoveable_hole[0]; + unmoveable_hole.offset_contour_inner = -spacing / 2; + unmoveable_hole.offset_contour_outer = spacing / 2; + } else { + // a hole cut it, or clear it. + for (ExPolygon &new_expoly : test_expoly) { + ExPolygons new_unmoveable_holes = diff_ex(Polygons{new_expoly.contour}, original_holes); + for(ExPolygon & new_unmoveable_hole : new_unmoveable_holes) + unmoveable_holes.push_back({unmoveable_hole.type, new_unmoveable_hole, -spacing / 2, spacing / 2, + unmoveable_hole.offset_holes_inner, unmoveable_hole.offset_holes_outer}); + } + unmoveable_holes.erase(unmoveable_holes.begin() + idx_unmoveable); + idx_unmoveable--; + unmoveable_holes_size--; + } + } + //we shrink periemter, so it doesn't create holes, so we don't have anythign to add to next_onion. + } +} + +ProcessSurfaceResult PerimeterGenerator::process_classic(int& contour_count, int& holes_count, const Surface& surface) { ProcessSurfaceResult results; ExPolygons gaps; @@ -1114,7 +1351,11 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const ExPolygons last = union_ex(surface.expolygon.simplify_p((resolution < SCALED_EPSILON ? SCALED_EPSILON : resolution))); double last_area = -1; - if (loop_number >= 0) { + // list of Expolygons where contour or holes aren't growing. + std::vector last_asynch; + bool last_asynch_initialized = false; + + if (contour_count > 0 || holes_count > 0) { //increase surface for milling_post-process if (this->mill_extra_size > SCALED_EPSILON) { @@ -1184,9 +1425,9 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const } } - // In case no perimeters are to be generated, loop_number will equal to -1. - std::vector contours(loop_number + 1); // depth => loops - std::vector holes(loop_number + 1); // depth => loops + // In case no perimeters are to be generated, contour_count / holes_count will equal to 0. + std::vector contours(contour_count); // depth => loops + std::vector holes(holes_count); // depth => loops ThickPolylines thin_walls_thickpolys; ExPolygons no_last_gapfill; // we loop one time more than needed in order to find gaps after the last perimeter was applied @@ -1218,48 +1459,68 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const } // Calculate next onion shell of perimeters. - //this variable stored the next onion + // this variable stored the next onion ExPolygons next_onion; + // like next_onion, but with all polygons, even ones that didn't grow and so won't be added as periemter + ExPolygons area_used; + ExPolygons* all_next_onion = &next_onion; + if (perimeter_idx == 0) { // compute next onion // the minimum thickness of a single loop is: // ext_width/2 + ext_spacing/2 + spacing/2 + width/2 - if (thin_perimeter > 0.98) { - next_onion = offset_ex( - last, - -(float)(ext_perimeter_width / 2), - ClipperLib::JoinType::jtMiter, - 3); - } else if (thin_perimeter > 0.01) { - next_onion = offset2_ex( - last, - -(float)(ext_perimeter_width / 2 + (1 - thin_perimeter) * ext_perimeter_spacing / 2 - 1), - +(float)((1 - thin_perimeter) * ext_perimeter_spacing / 2 - 1), - ClipperLib::JoinType::jtMiter, - 3); + coordf_t good_spacing = ext_perimeter_width / 2; + coordf_t overlap_spacing = (1 - thin_perimeter) * ext_perimeter_spacing / 2; + if (holes_count == 0 || contour_count == 0) { + + if (holes_count == 0) { + for (ExPolygon &expoly : last) { + last_asynch.push_back(ExPolygonAsynch{ExPolygonAsynch::ExPolygonAsynchType::epatShrinkContour, expoly, + // inner_offset outer_offset (go spacing limit) + -coordf_t(this->get_perimeter_width() - get_perimeter_spacing())/2, -coordf_t(this->get_perimeter_width() - get_perimeter_spacing())/2, + -coordf_t(this->get_perimeter_width() - get_perimeter_spacing())/2, -coordf_t(this->get_perimeter_width() - get_perimeter_spacing())/2}); + } + last_asynch_initialized = true; + grow_contour_only(last_asynch, get_perimeter_spacing(), 0 /*no overlap for external*/, false /*no round peri for external*/); + } else { + for (ExPolygon &expoly : last) { + last_asynch.push_back(ExPolygonAsynch{ExPolygonAsynch::ExPolygonAsynchType::epatGrowHole, expoly, + // inner_offset outer_offset (go spacing limit) + -coordf_t(this->get_perimeter_width() - get_perimeter_spacing())/2, -coordf_t(this->get_perimeter_width() - get_perimeter_spacing())/2, + -coordf_t(this->get_perimeter_width() - get_perimeter_spacing())/2, -coordf_t(this->get_perimeter_width() - get_perimeter_spacing())/2}); + } + last_asynch_initialized = true; + grow_holes_only(last_asynch, next_onion, get_perimeter_spacing(), 0 /*no overlap for external*/, false /*no round peri for external*/); + } } else { - next_onion = offset2_ex( - last, - -(float)(ext_perimeter_width / 2 + ext_perimeter_spacing / 2 - 1), - +(float)(ext_perimeter_spacing / 2 + 1), - ClipperLib::JoinType::jtMiter, - 3); + if (thin_perimeter > 0.98) { + next_onion = offset_ex(last, -(float) (ext_perimeter_width / 2), + ClipperLib::JoinType::jtMiter, 3); + } else { + coordf_t good_spacing = ext_perimeter_width / 2; + coordf_t overlap_spacing = (1 - thin_perimeter) * ext_perimeter_spacing / 2; + next_onion = offset2_ex(last, -(float) (good_spacing + overlap_spacing - 1), + +(float) (overlap_spacing + 1), ClipperLib::JoinType::jtMiter, 3); + } + if (thin_perimeter < 0.7) { + // offset2_ex can create artifacts, if too big. see superslicer#2428 + next_onion = intersection_ex(next_onion, offset_ex(last, -(float) (ext_perimeter_width / 2), + ClipperLib::JoinType::jtMiter, 3)); + } } - if (thin_perimeter < 0.7) { - //offset2_ex can create artifacts, if too big. see superslicer#2428 - next_onion = intersection_ex(next_onion, - offset_ex( - last, - -(float)(ext_perimeter_width / 2), - ClipperLib::JoinType::jtMiter, - 3)); + + bool special_area = contour_count == 0 || holes_count == 0; + if (special_area && (this->config->thin_walls || m_spiral_vase)) { + area_used = next_onion; + for(auto& expolycontainer : last_asynch) + area_used.push_back(expolycontainer.expoly); + all_next_onion = &area_used; } - - // look for thin walls if (this->config->thin_walls) { + // detect edge case where a curve can be split in multiple small chunks. - if (allow_perimeter_anti_hysteresis) { + if (allow_perimeter_anti_hysteresis && !special_area) { std::vector divs = { 2.1f, 1.9f, 2.2f, 1.75f, 1.5f }; //don't go too far, it's not possible to print thin wall after that size_t idx_div = 0; while (next_onion.size() > last.size() && idx_div < divs.size()) { @@ -1280,7 +1541,7 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const // (actually, something larger than that still may exist due to mitering or other causes) coord_t min_width = scale_t(this->config->thin_walls_min_width.get_abs_value(this->ext_perimeter_flow.nozzle_diameter())); - ExPolygons no_thin_zone = offset_ex(next_onion, double(ext_perimeter_width / 2), jtSquare); + ExPolygons no_thin_zone = offset_ex(*all_next_onion, double(ext_perimeter_width / 2), jtSquare); // medial axis requires non-overlapping geometry ExPolygons thin_zones = diff_ex(last, no_thin_zone, ApplySafetyOffset::Yes); //don't use offset2_ex, because we don't want to merge the zones that have been separated. @@ -1328,7 +1589,7 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const // use perimeters to extrude area that can't be printed by thin walls // it's a bit like re-add thin area into perimeter area. // it can over-extrude a bit, but it's for a better good. - { + if(!special_area) { if (thin_perimeter > 0.98) next_onion = union_ex(next_onion, offset_ex(diff_ex(last, thins, ApplySafetyOffset::Yes), -(float)(ext_perimeter_width / 2), @@ -1355,9 +1616,10 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const next_onion = intersection_ex(next_onion_temp, last); } } - if (m_spiral_vase && next_onion.size() > 1) { + if (m_spiral_vase && all_next_onion->size() > 1) { + assert(contour_count > 0); // Remove all but the largest area polygon. - keep_largest_contour_only(next_onion); + keep_largest_contour_only(*all_next_onion); } } else { //FIXME Is this offset correct if the line width of the inner perimeters differs @@ -1366,7 +1628,7 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const if (thin_perimeter <= 0.98) { // This path will ensure, that the perimeters do not overfill, as in // prusa3d/Slic3r GH #32, but with the cost of rounding the perimeters - // excessively, creating gaps, which then need to be filled in by the not very + // excessively, creating gaps, which then need to be filled in by the not very // reliable gap fill algorithm. // Also the offset2(perimeter, -x, x) may sometimes lead to a perimeter, which is larger than // the original. @@ -1377,22 +1639,20 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const (round_peri ? min_round_spacing : 3)); if (allow_perimeter_anti_hysteresis) { // now try with different min spacing if we fear some hysteresis - //TODO, do that for each polygon from last, instead to do for all of them in one go. + // TODO, do that for each polygon from last, instead to do for all of them in one go. ExPolygons no_thin_onion = offset_ex(last, double(-good_spacing)); if (last_area < 0) { last_area = 0; - for (const ExPolygon& expoly : last) { - last_area += expoly.area(); - } + for (const ExPolygon &expoly : last) { last_area += expoly.area(); } } double new_area = 0; - for (const ExPolygon& expoly : next_onion) { - new_area += expoly.area(); - } + for (const ExPolygon &expoly : next_onion) { new_area += expoly.area(); } - std::vector divs{ 1.8f, 1.6f }; //don't over-extrude, so don't use divider >2 - size_t idx_div = 0; - while ((next_onion.size() > no_thin_onion.size() || (new_area != 0 && last_area > new_area * 100)) && idx_div < divs.size()) { + std::vector divs{1.8f, 1.6f}; // don't over-extrude, so don't use divider >2 + size_t idx_div = 0; + while ((next_onion.size() > no_thin_onion.size() || + (new_area != 0 && last_area > new_area * 100)) && + idx_div < divs.size()) { float div = divs[idx_div]; //use a sightly bigger spacing to try to drastically improve the split, that can lead to very thick gapfill ExPolygons next_onion_secondTry = offset2_ex( @@ -1405,9 +1665,7 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const } else if (next_onion.size() > next_onion_secondTry.size() || last_area > new_area * 100) { // don't get it if it's too small double area_new = 0; - for (const ExPolygon& expoly : next_onion_secondTry) { - area_new += expoly.area(); - } + for (const ExPolygon &expoly : next_onion_secondTry) { area_new += expoly.area(); } if (last_area > area_new * 100 || new_area == 0) { next_onion = next_onion_secondTry; } @@ -1423,14 +1681,42 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const (round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter), (round_peri ? min_round_spacing : 3)); } + + std::vector *touse; + std::vector copy; + if (perimeter_idx < std::max(contour_count, holes_count)) { + touse = &last_asynch; + } else { + // for gap fill only: use a copy + copy = last_asynch; + touse = © + } + if (contour_count > perimeter_idx && holes_count <= perimeter_idx) { + grow_contour_only(*touse, good_spacing, (1 - thin_perimeter) * perimeter_spacing / 2, + round_peri, min_round_spacing); + } + if (holes_count > perimeter_idx && contour_count <= perimeter_idx) { + grow_holes_only(*touse, next_onion, good_spacing, + (1 - thin_perimeter) * perimeter_spacing / 2, round_peri, min_round_spacing); + } + + bool special_area = contour_count == 0 || holes_count == 0; + if (special_area && (this->config->thin_walls || m_spiral_vase)) { + area_used = next_onion; + for (auto &expolycontainer : *touse) area_used.push_back(expolycontainer.expoly); + all_next_onion = &area_used; + } + + // look for gaps if (this->config->gap_fill_enabled.value //check if we are going to have an other perimeter - && (perimeter_idx <= loop_number || has_overhang || next_onion.empty() || (this->config->gap_fill_last.value && perimeter_idx == loop_number + 1))) { + && (perimeter_idx < std::max(contour_count, holes_count) || has_overhang || all_next_onion->empty() || + (this->config->gap_fill_last.value && perimeter_idx == std::max(contour_count, holes_count)))) { // not using safety offset here would "detect" very narrow gaps // (but still long enough to escape the area threshold) that gap fill // won't be able to fill but we'd still remove from infill area - no_last_gapfill = offset_ex(next_onion, 0.5f * good_spacing + 10, + no_last_gapfill = offset_ex(*all_next_onion, 0.5f * good_spacing + 10, (round_peri ? ClipperLib::JoinType::jtRound : ClipperLib::JoinType::jtMiter), (round_peri ? min_round_spacing : 3)); if (perimeter_idx == 1) { @@ -1455,15 +1741,29 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const // svg.Close(); //} - if (next_onion.empty()) { + if (next_onion.empty() && last_asynch.empty()) { // Store the number of loops actually generated. - loop_number = perimeter_idx - 1; + if (perimeter_idx < contour_count) { + assert(contours.size() == contour_count); + for(size_t i = perimeter_idx; i loop_number) { + } else if (perimeter_idx >= std::max(contour_count, holes_count)) { if (has_overhang) { - loop_number++; + contour_count++; + holes_count++; //TODO: only increase the ones that are needed (or just use 2.7) contours.emplace_back(); holes.emplace_back(); } else { @@ -1471,34 +1771,90 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const break; } } + if (contour_count <= perimeter_idx && !next_onion.empty()) { + assert(contour_count <= perimeter_idx); + assert(holes_count > perimeter_idx); + //assert(contours.size() == perimeter_idx); + contour_count = perimeter_idx + 1; + while (contours.size() < contour_count) { + contours.emplace_back(); + } + } + + assert(contours.size() == contour_count); + assert(holes.size() == holes_count); - // fuzzify + // fuzzify params const bool fuzzify_contours = this->config->fuzzy_skin != FuzzySkinType::None && perimeter_idx == 0 && this->layer->id() > 0; const bool fuzzify_holes = this->config->fuzzy_skin == FuzzySkinType::Shell && perimeter_idx == 0 && this->layer->id() > 0 ; const bool fuzzify_all = this->config->fuzzy_skin == FuzzySkinType::All && this->layer->id() > 0 ; + + //push last_asynch or next_onion into contours & holes + if (!last_asynch.empty()) { + // we already put the last hole, now add contours. + for (auto &exp : last_asynch) { + if (exp.type == ExPolygonAsynch::epatShrinkContour) { + assert(next_onion.empty()); + assert(exp.expoly.contour.is_counter_clockwise()); + if (exp.expoly.contour.length() > SCALED_EPSILON) // TODO: atleastLength + contours[perimeter_idx].emplace_back(exp.expoly.contour, perimeter_idx, true, + has_steep_overhang, fuzzify_contours || fuzzify_all); + } else { + // we already put the last contour, now add holes + // contours from hole collapse is added via next_onion + assert(exp.type == ExPolygonAsynch::epatGrowHole); + for (auto &hole : exp.expoly.holes) { + assert(hole.is_clockwise()); + if(hole.length() > SCALED_EPSILON) // TODO: atleastLength + holes[perimeter_idx].emplace_back(hole, perimeter_idx, false, has_steep_overhang, + fuzzify_contours || fuzzify_all); + } + } + } + } for (const ExPolygon& expolygon : next_onion) { //TODO: add width here to allow variable width (if we want to extrude a sightly bigger perimeter, see thin wall) - contours[perimeter_idx].emplace_back(expolygon.contour, perimeter_idx, true, has_steep_overhang, fuzzify_contours || fuzzify_all); - if (!expolygon.holes.empty()) { + if(contour_count > perimeter_idx && expolygon.contour.length() > SCALED_EPSILON) // TODO: atleastLength + contours[perimeter_idx].emplace_back(expolygon.contour, perimeter_idx, true, has_steep_overhang, fuzzify_contours || fuzzify_all); + if (!expolygon.holes.empty() && holes_count > perimeter_idx) { holes[perimeter_idx].reserve(holes[perimeter_idx].size() + expolygon.holes.size()); for (const Polygon& hole : expolygon.holes) - holes[perimeter_idx].emplace_back(hole, perimeter_idx, false, has_steep_overhang, fuzzify_holes || fuzzify_all); + if(hole.length() > SCALED_EPSILON) // TODO: atleastLength + holes[perimeter_idx].emplace_back(hole, perimeter_idx, false, has_steep_overhang, fuzzify_holes || fuzzify_all); } } //simplify the loop to avoid artifacts when shrinking almost-0 segments resolution = get_resolution(perimeter_idx + 1, false, &surface); last.clear(); - for(ExPolygon& exp : next_onion) + for (ExPolygon &exp : next_onion) exp.simplify((resolution < SCALED_EPSILON ? SCALED_EPSILON : resolution), &last); // store surface for top infill if only_one_perimeter_top - if (perimeter_idx == 0 && (config->only_one_perimeter_top && this->upper_slices != NULL)) { + if (perimeter_idx == 0 && (config->only_one_perimeter_top && this->upper_slices != NULL) + && contour_count > 1 && holes_count > 1) { ExPolygons next; split_top_surfaces(this->lower_slices, this->upper_slices, last, results.top_fills, next, results.fill_clip); last = next; } + + //if next turn we are in asynch mode, move from last to last_asynch + if ( !last_asynch_initialized && ( + (holes_count == perimeter_idx + 1 && contour_count > perimeter_idx + 1) || + (contour_count == perimeter_idx + 1 && holes_count > perimeter_idx + 1))) { + coordf_t last_spacing = perimeter_idx == 0 ? get_ext_perimeter_spacing() / 2 : + get_perimeter_spacing() / 2; + // populate last_asynch from last + for (ExPolygon &expoly : last) { + last_asynch.push_back( + {holes_count == perimeter_idx + 1 ? ExPolygonAsynch::ExPolygonAsynchType::epatShrinkContour : + ExPolygonAsynch::ExPolygonAsynchType::epatGrowHole, + std::move(expoly), -last_spacing, last_spacing, -last_spacing, last_spacing}); + } + last.clear(); + last_asynch_initialized = true; + } } // fuzzify @@ -1506,7 +1862,8 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const // check for extracting extra perimeters from gapfill if (!gaps.empty()) { // if needed, add it to the first empty contour list - const size_t contours_size = loop_number + 1; + const size_t contours_size = contour_count; + assert(contours.size() == contour_count); //first, find loops and try to extract a perimeter from them. for (size_t gap_idx = 0; gap_idx < gaps.size(); gap_idx++) { ExPolygon& expoly = gaps[gap_idx]; @@ -1517,11 +1874,12 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const if (contour_expolygon.size() == 1 && !contour_expolygon.front().holes.empty()) { //OK // update list & variable to let the new perimeter be taken into account - loop_number = contours_size; + contour_count = contours_size + 1; if (contours_size >= contours.size()) { contours.emplace_back(); holes.emplace_back(); } + assert(contours.size() == contour_count); //Add the new perimeter contours[contours_size].emplace_back(contour_expolygon.front().contour, contours_size, true, has_steep_overhang, fuzzify_gapfill); //create the new gapfills @@ -1538,15 +1896,17 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const } } } - + assert(contours.size() == contour_count); + assert(holes.size() == holes_count); // nest loops: holes first - for (int d = 0; d <= loop_number; ++d) { + for (int d = 0; d < holes_count; ++d) { PerimeterGeneratorLoops& holes_d = holes[d]; // loop through all holes having depth == d for (int hole_idx = 0; hole_idx < (int)holes_d.size(); ++hole_idx) { const PerimeterGeneratorLoop& loop = holes_d[hole_idx]; + assert(loop.polygon.length() > SCALED_EPSILON); // find the hole loop that contains this one, if any - for (int t = d + 1; t <= loop_number; ++t) { + for (int t = d + 1; t < holes_count; ++t) { for (int j = 0; j < (int)holes[t].size(); ++j) { PerimeterGeneratorLoop& candidate_parent = holes[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { @@ -1558,7 +1918,7 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const } } // if no hole contains this hole, find the contour loop that contains it - for (int t = loop_number; t >= 0; --t) { + for (int t = contours.size() - 1; t >= 0; --t) { for (int j = 0; j < (int)contours[t].size(); ++j) { PerimeterGeneratorLoop& candidate_parent = contours[t][j]; if (candidate_parent.polygon.contains(loop.polygon.first_point())) { @@ -1569,15 +1929,22 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const } } } + // no perimeter, then add the hole like a perimeter. + while(d >= contours.size()) + contours.emplace_back(); + contours[d].push_back(loop); + holes_d.erase(holes_d.begin() + hole_idx); + --hole_idx; NEXT_LOOP:; } } // nest contour loops - for (int d = loop_number; d >= 1; --d) { + for (int d = contours.size() - 1; d >= 1; --d) { PerimeterGeneratorLoops& contours_d = contours[d]; // loop through all contours having depth == d for (int contour_idx = 0; contour_idx < (int)contours_d.size(); ++contour_idx) { const PerimeterGeneratorLoop& loop = contours_d[contour_idx]; + assert(loop.polygon.length() > SCALED_EPSILON); // find the contour loop that contains it for (int t = d - 1; t >= 0; --t) { for (size_t j = 0; j < contours[t].size(); ++j) { @@ -1590,45 +1957,62 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const } } } + //can't find one, put in front + if (contours.front().empty()) { + contours.front().push_back(loop); + } else { + contours.front().front().children.push_back(loop); + } + contours_d.erase(contours_d.begin() + contour_idx); + --contour_idx; NEXT_CONTOUR:; } } + //remove all empty perimeters + while(contours.size() > 1 && contours.front().empty()) + contours.erase(contours.begin()); + // fuse all unfused // at this point, all loops should be in contours[0] (= contours.front() ) + // or no perimeters nor holes have been generated, too small area. + + assert(contours.empty() || contours.front().size() >= 1); // collection of loops to add into loops ExtrusionEntityCollection peri_entities; - if (config->perimeter_loop.value) { - //onlyone_perimeter = >fusion all perimeterLoops - for (PerimeterGeneratorLoop& loop : contours.front()) { - ExtrusionLoop extr_loop = this->_traverse_and_join_loops(loop, get_all_Childs(loop), loop.polygon.points.front()); - //ExtrusionLoop extr_loop = this->_traverse_and_join_loops_old(loop, loop.polygon.points.front(), true); - if (extr_loop.paths.back().polyline.back() != extr_loop.paths.front().polyline.front()) { - extr_loop.paths.back().polyline.append(extr_loop.paths.front().polyline.front()); - assert(false); + if (!contours.empty()) { + if (config->perimeter_loop.value) { + // onlyone_perimeter = >fusion all perimeterLoops + for (PerimeterGeneratorLoop &loop : contours.front()) { + ExtrusionLoop extr_loop = this->_traverse_and_join_loops(loop, get_all_Childs(loop), + loop.polygon.points.front()); + // ExtrusionLoop extr_loop = this->_traverse_and_join_loops_old(loop, loop.polygon.points.front(), true); + if (extr_loop.paths.back().polyline.back() != extr_loop.paths.front().polyline.front()) { + extr_loop.paths.back().polyline.append(extr_loop.paths.front().polyline.front()); + assert(false); + } + peri_entities.append(extr_loop); } - peri_entities.append(extr_loop); - } - // append thin walls - if (!thin_walls_thickpolys.empty()) { + // append thin walls + if (!thin_walls_thickpolys.empty()) { + if (this->object_config->thin_walls_merge) { + _merge_thin_walls(peri_entities, thin_walls_thickpolys); + } else { + peri_entities.append( + Geometry::thin_variable_width(thin_walls_thickpolys, erThinWall, this->ext_perimeter_flow, + std::max(ext_perimeter_width / 4, + scale_t(this->print_config->resolution)), + false)); + } + thin_walls_thickpolys.clear(); + } + } else { if (this->object_config->thin_walls_merge) { + ThickPolylines no_thin_walls; + peri_entities = this->_traverse_loops(contours.front(), no_thin_walls); _merge_thin_walls(peri_entities, thin_walls_thickpolys); } else { - peri_entities.append(Geometry::thin_variable_width( - thin_walls_thickpolys, - erThinWall, - this->ext_perimeter_flow, - std::max(ext_perimeter_width / 4, scale_t(this->print_config->resolution)), - false)); + peri_entities = this->_traverse_loops(contours.front(), thin_walls_thickpolys); } - thin_walls_thickpolys.clear(); - } - } else { - if (this->object_config->thin_walls_merge) { - ThickPolylines no_thin_walls; - peri_entities = this->_traverse_loops(contours.front(), no_thin_walls); - _merge_thin_walls(peri_entities, thin_walls_thickpolys); - } else { - peri_entities = this->_traverse_loops(contours.front(), thin_walls_thickpolys); } } #if _DEBUG @@ -1785,7 +2169,46 @@ ProcessSurfaceResult PerimeterGenerator::process_classic(int& loop_number, const } } - results.inner_perimeter = last; + if (contour_count == 0 && holes_count == 0) { + // for the infill shell, move it a little bit inside so the extrusion tip don't go over the sides. + results.inner_perimeter = offset_ex(last, -(this->get_perimeter_width() - get_perimeter_spacing()) / 2); + } else { + coordf_t last_spacing = std::max(contour_count, holes_count) == 1 ? + get_ext_perimeter_spacing() / 2 : + get_perimeter_spacing() / 2; + results.inner_perimeter = offset_ex(last, -last_spacing); + if (!last_asynch.empty()) { + // merge with last_async + for (auto &exp : last_asynch) { + if (exp.offset_contour_inner == exp.offset_holes_inner) { + append(results.inner_perimeter, offset_ex(exp.expoly, exp.offset_contour_inner)); + } else { + // offset contour & holes separatly + // first holes: + assert(exp.offset_holes_inner <= 0); + Polygons holes = offset(get_holes_as_contour(exp.expoly), -exp.offset_holes_inner); + // we are growing (fake) perimeter, so it can creates holes. + for (size_t i = 0; i < holes.size(); ++i) { + Polygon &fakeperi = holes[i]; + if (fakeperi.is_clockwise()) { + // put real perimeters in results.inner_perimeter + fakeperi.make_counter_clockwise(); + results.inner_perimeter.push_back(ExPolygon(fakeperi)); + holes.erase(holes.begin() + i); + i--; + } + } + // now shrink perimeter + Polygons perimeters = offset(exp.expoly.contour, exp.offset_contour_inner); + // as it shrink, it can creates more perimeter, not a big deal. + for (auto &p : perimeters) assert(p.is_counter_clockwise()); + + // now diff and add + append(results.inner_perimeter, diff_ex(perimeters, holes)); + } + } + } + } return results; } @@ -3222,7 +3645,7 @@ void PerimeterGenerator::_merge_thin_walls(ExtrusionEntityCollection &extrusions entity->visit(*this); } }; - //max dist to branch: ~half external periemeter width + //max dist to branch: ~half external perimeter width coord_t max_width = this->ext_perimeter_flow.scaled_width(); SearchBestPoint searcher; ThickPolylines not_added; diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index b0d36ca9a7b..ab03d6b1d5e 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -153,7 +153,7 @@ class PerimeterGenerator { ExPolygons unmillable; coord_t mill_extra_size; - ProcessSurfaceResult process_classic(int& loop_number, const Surface& surface); + ProcessSurfaceResult process_classic(int& contour_count, int& holes_count, const Surface& surface); ProcessSurfaceResult process_arachne(int& loop_number, const Surface& surface); void processs_no_bridge(Surfaces& all_surfaces); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 8cf7689662c..f4b0710ad87 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -449,7 +449,10 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config) static std::vector s_Preset_print_options { "layer_height", - "first_layer_height", "perimeters", "spiral_vase", + "first_layer_height", + "perimeters", + "perimeters_hole", + "spiral_vase", "slice_closing_radius", "slicing_mode", "top_solid_layers", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 35ef216b4d7..0592d0ae042 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4269,8 +4269,10 @@ void PrintConfigDef::init_fff_params() def->label = L("Perimeters"); def->full_label = L("Perimeters count"); def->category = OptionCategory::perimeter; - def->tooltip = L("This option sets the number of perimeters to generate for each layer. " - "Note that Slic3r may increase this number automatically when it detects " + def->tooltip = L("This option sets the number of perimeters to generate for each layer." + "\nIf perimeters_hole is activated, then this number is only for contour periemters." + "Note that if a contour perimeter encounter a hole, it will go around like a hole perimeter." + "\nNote that Slic3r may increase this number automatically when it detects " "sloping surfaces which benefit from a higher number of perimeters " "if the Extra Perimeters option is enabled."); def->sidetext = L("(minimum)."); @@ -4280,6 +4282,21 @@ void PrintConfigDef::init_fff_params() def->mode = comSimpleAE | comPrusa; def->set_default_value(new ConfigOptionInt(3)); + def = this->add("perimeters_hole", coInt); + def->label = L("Max perimeter count for holes"); + def->category = OptionCategory::perimeter; + def->tooltip = L("This option sets the number of perimeters to have over holes." + " Note that if a hole-perimeter fuse with the contour, then it will go around like a contour perimeter.." + "\nSet to -1 to deactivate, then holes will have the same number of perimeters as contour." + "\nNote that Slic3r may increase this number automatically when it detects " + "sloping surfaces which benefit from a higher number of perimeters " + "if the Extra Perimeters option is enabled."); + def->sidetext = L("(minimum)."); + def->min = -1; + def->max = 10000; + def->mode = comAdvancedE | comSuSi; + def->set_default_value(new ConfigOptionInt(-1)); + def = this->add("post_process", coStrings); def->label = L("Post-processing scripts"); def->category = OptionCategory::customgcode; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 2673fd1186f..f034f4c9929 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -928,6 +928,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatOrPercent, perimeter_speed)) // Total number of perimeters. ((ConfigOptionInt, perimeters)) + ((ConfigOptionInt, perimeters_hole)) ((ConfigOptionPercent, print_extrusion_multiplier)) ((ConfigOptionFloat, print_retract_length)) ((ConfigOptionFloat, print_retract_lift)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 00a6e0435d0..6b64bf14491 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1047,6 +1047,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "no_perimeter_unsupported_algo" || opt_key == "filament_max_overlap" || opt_key == "perimeters" + || opt_key == "perimeters_hole" || opt_key == "perimeter_overlap" || opt_key == "solid_infill_extrusion_change_odd_layers" || opt_key == "solid_infill_extrusion_spacing" diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index ea682e59c32..cd9ee58b3f5 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -344,8 +344,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) toggle_field(el, have_perimeters); bool has_spiral_vase = have_perimeters && config->opt_bool("spiral_vase"); - - bool have_arachne = have_perimeters && config->opt_enum("perimeter_generator") == PerimeterGeneratorType::Arachne; + + bool have_arachne = have_perimeters && (config->opt_int("perimeters") == config->opt_int("perimeters_hole") || config->opt_int("perimeters_hole") < 0); + toggle_field("perimeter_generator", have_arachne); + have_arachne = have_arachne && config->opt_enum("perimeter_generator") == PerimeterGeneratorType::Arachne; for (auto el : { "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle", "wall_distribution_count", "min_feature_size", "min_bead_width", "aaa" }) toggle_field(el, have_arachne); diff --git a/tests/superslicerlibslic3r/test_clipper_utils.cpp b/tests/superslicerlibslic3r/test_clipper_utils.cpp index 0af733e2d3f..0d7ae72fdce 100644 --- a/tests/superslicerlibslic3r/test_clipper_utils.cpp +++ b/tests/superslicerlibslic3r/test_clipper_utils.cpp @@ -323,6 +323,35 @@ TEST_CASE("Traversing Clipper PolyTree", "[ClipperUtils]") { } } +TEST_CASE("Testing offset and offset_ex ", "[ClipperUtils]") { + + SECTION("contour growth create hole") + { + std::vector> unscaled_array = {{0, 0}, {10, 0}, {10, 4.5}, {7, 4.5}, {7,3}, {3,3}, {3,7}, {7,7}, {7,5.5}, {10,5.5}, {10,10}, {0,10}}; + Slic3r::Polygon src_polygon; + for(auto pt: unscaled_array) + src_polygon.points.emplace_back(scale_t(pt.first), scale_t(pt.second)); + REQUIRE(src_polygon.is_valid()); + + Polygons result = Slic3r::offset(src_polygon, scale_d(1), ClipperLib::JoinType::jtMiter, 3); + REQUIRE(result.size() == 2); + REQUIRE(result[0].is_counter_clockwise() || result[1].is_counter_clockwise()); + REQUIRE(result[0].is_clockwise() || result[1].is_clockwise()); + + ExPolygons result_ex = Slic3r::offset_ex(ExPolygon(src_polygon), scale_d(1), ClipperLib::JoinType::jtMiter, 3); + REQUIRE(result_ex.size() == 1); + REQUIRE(result_ex[0].holes.size() == 1); + REQUIRE(result_ex[0].contour.is_counter_clockwise()); + REQUIRE(result_ex[0].holes[0].is_clockwise()); + + result_ex = union_ex(result); + REQUIRE(result_ex.size() == 1); + REQUIRE(result_ex[0].holes.size() == 1); + REQUIRE(result_ex[0].contour.is_counter_clockwise()); + REQUIRE(result_ex[0].holes[0].is_clockwise()); + } +} + TEST_CASE("Testing ", "[ClipperUtils]") { Slic3r::Polygon src_polygon( {{-29766902, -30710288}, {-30290102, -30802646}, {-30799114, -30715083}, {-31876243, -30562718}, From 98e01abb091de37fe913f1c19aeec7bc4fdc471e Mon Sep 17 00:00:00 2001 From: supermerill Date: Tue, 20 Feb 2024 10:34:03 +0100 Subject: [PATCH 06/19] fix import/export prusa thumbnails --- src/libslic3r/PrintConfig.cpp | 59 ++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 0592d0ae042..9cfa6dca939 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -7868,6 +7868,38 @@ std::map PrintConfigDef::from_prusa(t_config_option_key if (value == "PNG") output["thumbnails_tag_format"] = "0"; } + + if ("thumbnails" == opt_key) { + //check if their format is inside the size + if (value.find('/') != std::string::npos) { + std::vector sizes; + boost::split(sizes, value, boost::is_any_of(","), boost::token_compress_off); + value = ""; + std::string coma = ""; + size_t added = 0; + for (std::string &size : sizes) { + size_t pos = size.find('/'); + assert(pos != std::string::npos); + if (pos != std::string::npos) { + assert(size.find('/', pos + 1) == std::string::npos); + value = value + coma + size.substr(0, pos); + } else { + value = value + coma + size; + } + coma = ","; + added++; + if (added >= 2) + break; + } + //if less than 2: add 0X0 until two. + while (added < 2) { + value = value + coma + "0x0"; + coma = ","; + added++; + } + // format (the first) is still set by prusa, no need to parse it. + } + } // ---- custom gcode: ---- @@ -7895,6 +7927,11 @@ std::map PrintConfigDef::from_prusa(t_config_option_key return output; } +//keys that needs to go through from_prusa before beeing deserialized. +std::unordered_set prusa_import_to_review_keys = +{ + "thumbnails" +}; template void _convert_from_prusa(CONFIG_CLASS& conf, const DynamicPrintConfig& global_config, bool with_phony) { @@ -7970,7 +8007,7 @@ void _deserialize_maybe_from_prusa(const std::maphas(opt_key)) { + if (!def->has(opt_key) || (check_prusa && prusa_import_to_review_keys.find(opt_key) != prusa_import_to_review_keys.end())) { unknown_keys.emplace(key, value); } else { config.set_deserialize(opt_key, opt_value, config_substitutions); @@ -8525,6 +8562,26 @@ std::map PrintConfigDef::to_prusa(t_config_option_key& new_entries["fan_always_on"] = "1"; } } + + if ("thumbnails" == opt_key) { + // add format to thumbnails + const ConfigOptionEnum *format_opt = all_conf.option>("thumbnails_format"); + std::string format = format_opt->serialize(); + std::vector sizes; + boost::split(sizes, value, boost::is_any_of(","), boost::token_compress_off); + value = ""; + std::string coma = ""; + for (std::string &size : sizes) { + //if first or second dimension is 0: ignore. + size_t test1 = size.find("0x"); + size_t test2 = size.find("x0"); + if (size.find("0x") == 0 || size.find("x0") + 2 == size.size()) + continue; + assert(size.find('/') == std::string::npos); + value = value + coma + size + std::string("/") + format; + coma = ","; + } + } // ---- custom gcode: ---- static const std::vector> custom_gcode_replace = From a6cf86589d140b9539ca5da2a8b29e032c650f5e Mon Sep 17 00:00:00 2001 From: supermerill Date: Tue, 20 Feb 2024 11:48:14 +0100 Subject: [PATCH 07/19] order of magnitude quicker for simple layers with lots of holes, while using avoid_crossing_perimeters or/and only_retract_when_crossing_perimeters with a faster avoid_crossing_perimeter detection ported back from 2.7 & improved upon. TODO: create test pieces to find the right threshold. TODO: add kdtree for hole check --- src/libslic3r/ClipperUtils.cpp | 2 + src/libslic3r/ClipperUtils.hpp | 1 + src/libslic3r/GCode.cpp | 160 +++++++++++++++++++++++---------- src/libslic3r/GCode.hpp | 4 +- src/libslic3r/MultiPoint.cpp | 6 +- src/libslic3r/Polyline.hpp | 4 +- 6 files changed, 125 insertions(+), 52 deletions(-) diff --git a/src/libslic3r/ClipperUtils.cpp b/src/libslic3r/ClipperUtils.cpp index 2d339c0a104..0d2149b3b53 100644 --- a/src/libslic3r/ClipperUtils.cpp +++ b/src/libslic3r/ClipperUtils.cpp @@ -1169,6 +1169,8 @@ Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygo { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::PolygonsProvider(clip)); } Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip) { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::ExPolygonProvider(clip)); } +Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::Polygon &clip) + { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::SinglePathProvider(subject.points), ClipperUtils::SinglePathProvider(clip.points)); } Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip) { return _clipper_pl_open(ClipperLib::ctDifference, ClipperUtils::PolylinesProvider(subject), ClipperUtils::ExPolygonProvider(clip)); } Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip) diff --git a/src/libslic3r/ClipperUtils.hpp b/src/libslic3r/ClipperUtils.hpp index 55a566851f5..283458c02a3 100644 --- a/src/libslic3r/ClipperUtils.hpp +++ b/src/libslic3r/ClipperUtils.hpp @@ -419,6 +419,7 @@ Slic3r::ExPolygons diff_ex(const Slic3r::Surfaces &subject, const Slic3r::Surfac Slic3r::ExPolygons diff_ex(const Slic3r::SurfacesPtr &subject, const Slic3r::Polygons &clip, ApplySafetyOffset do_safety_offset = ApplySafetyOffset::No); Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::Polygons &clip); Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::ExPolygon &clip); +Slic3r::Polylines diff_pl(const Slic3r::Polyline &subject, const Slic3r::Polygon &clip); Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygon &clip); Slic3r::Polylines diff_pl(const Slic3r::Polylines &subject, const Slic3r::ExPolygons &clip); Slic3r::Polylines diff_pl(const Slic3r::Polygons &subject, const Slic3r::Polygons &clip); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index c16ab21d610..151bc093b45 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -6010,24 +6010,24 @@ Polyline GCode::travel_to(std::string &gcode, const Point &point, ExtrusionRole && !m_avoid_crossing_perimeters.disabled_once() && m_avoid_crossing_perimeters.is_init() && !(m_config.avoid_crossing_not_first_layer && this->on_first_layer()); - + // check / compute avoid_crossing_perimeters - bool will_cross_perimeter = this->can_cross_perimeter(travel, can_avoid_cross_peri); - - // if a retraction would be needed (with a low min_dist threshold), try to use avoid_crossing_perimeters to plan a - // multi-hop travel path inside the configuration space - if (will_cross_perimeter && this->needs_retraction(travel, role, scale_d(EXTRUDER_CONFIG_WITH_DEFAULT(nozzle_diameter, 0.4)) * 3) - && can_avoid_cross_peri) { - this->m_throw_if_canceled(); - travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled); + bool may_need_avoid_crossing = can_avoid_cross_peri && this->needs_retraction(travel, role, scale_d(EXTRUDER_CONFIG_WITH_DEFAULT(nozzle_diameter, 0.4)) * 3); + + if (may_need_avoid_crossing) { + // if a retraction would be needed (with a low min_dist threshold), try to use avoid_crossing_perimeters to + // plan a multi-hop travel path inside the configuration space + if (this->can_cross_perimeter(travel, can_avoid_cross_peri)) { + this->m_throw_if_canceled(); + travel = m_avoid_crossing_perimeters.travel_to(*this, point, &could_be_wipe_disabled); + } } - if(can_avoid_cross_peri) - will_cross_perimeter = this->can_cross_perimeter(travel, false); // check whether a straight travel move would need retraction bool needs_retraction = this->needs_retraction(travel, role); - if (m_config.only_retract_when_crossing_perimeters && !(m_config.enforce_retract_first_layer && m_layer_index == 0)) - needs_retraction = needs_retraction && will_cross_perimeter; + if (m_config.only_retract_when_crossing_perimeters && + !(m_config.enforce_retract_first_layer && m_layer_index == 0)) + needs_retraction = needs_retraction && can_avoid_cross_peri && this->can_cross_perimeter(travel, false); // Re-allow avoid_crossing_perimeters for the next travel moves m_avoid_crossing_perimeters.reset_once_modifiers(); @@ -6039,10 +6039,39 @@ Polyline GCode::travel_to(std::string &gcode, const Point &point, ExtrusionRole // m_wipe.reset_path(); //} else { //check if it cross hull - auto result = diff_pl(Polylines{ travel }, to_polygons(m_layer->lslices)); - if (result.empty()) { - m_wipe.reset_path(); + + //TODO: add bbox cache & checks like for can_cross_perimeter + bool has_intersect = false; + for (const ExPolygon &expoly : m_layer->lslices) { + // first, check if it's inside the contour (still, it can go over holes) + Polylines diff_result = diff_pl(travel, expoly.contour); + if (diff_result.size() == 1 && diff_result.front() == travel) + // not inside/cross this contour, try another one. + continue; + if (!diff_result.empty()) { + //it's crossing this contour! + has_intersect = true; + } else { + // it's inside this contour, does it cross a hole? + Line travel_line; + Point whatever; + for (size_t idx_travel = travel.size() - 1; idx_travel > 0; --idx_travel) { + travel_line.a = travel.points[idx_travel]; + travel_line.b = travel.points[idx_travel - 1]; + for (const Polygon &hole : expoly.holes) { + if (hole.first_intersection(travel_line, &whatever) || + Line(hole.first_point(), hole.last_point()).intersection(travel_line, &whatever)) { + has_intersect = true; + break; + } + } + } } + break; + } + if (!has_intersect) { + m_wipe.reset_path(); + } //} } @@ -6206,46 +6235,83 @@ bool GCode::needs_retraction(const Polyline& travel, ExtrusionRole role /*=erNon bool GCode::can_cross_perimeter(const Polyline& travel, bool offset) { - if(m_layer != nullptr) - if ( ( (m_config.only_retract_when_crossing_perimeters && !(m_config.enforce_retract_first_layer && m_layer_index == 0)) && m_config.fill_density.value > 0) || m_config.avoid_crossing_perimeters) - { - //test && m_layer->any_internal_region_slice_contains(travel) - // Skip retraction if travel is contained in an internal slice *and* - // internal infill is enabled (so that stringing is entirely not visible). - //note: any_internal_region_slice_contains() is potentionally very slow, it shall test for the bounding boxes first. - //bool inside = false; - //BoundingBox bbtravel(travel.points); - //for (const BoundingBox &bbox : m_layer->lslices_bboxes) { - // inside = bbox.overlap(bbtravel); - // if(inside) break; - //} - ////have to do a bit more work to be sure - //if (inside) { - //contained inside at least one bb - //construct m_layer_slices_offseted if needed + if (m_layer != nullptr) + if (((m_config.only_retract_when_crossing_perimeters && + !(m_config.enforce_retract_first_layer && m_layer_index == 0)) && + m_config.fill_density.value > 0) || + m_config.avoid_crossing_perimeters) { + // FROM 2.7 if (m_layer_slices_offseted.layer != m_layer) { - m_layer_slices_offseted.layer = m_layer; + m_layer_slices_offseted.layer = m_layer; m_layer_slices_offseted.diameter = scale_t(EXTRUDER_CONFIG_WITH_DEFAULT(nozzle_diameter, 0.4)); - m_layer_slices_offseted.slices = m_layer->lslices; - m_layer_slices_offseted.slices_offsetted = offset_ex(m_layer->lslices, -m_layer_slices_offseted.diameter * 1.5f); - //remove top surfaces - for (const LayerRegion* reg : m_layer->regions()) { + ExPolygons slices = m_layer->lslices; + ExPolygons slices_offsetted = offset_ex(m_layer->lslices, -m_layer_slices_offseted.diameter * 1.5f); + // remove top surfaces + for (const LayerRegion *reg : m_layer->regions()) { m_throw_if_canceled(); - m_layer_slices_offseted.slices_offsetted = diff_ex(m_layer_slices_offseted.slices_offsetted, to_expolygons(reg->fill_surfaces.filter_by_type_flag(SurfaceType::stPosTop))); - m_layer_slices_offseted.slices = diff_ex(m_layer_slices_offseted.slices, to_expolygons(reg->fill_surfaces.filter_by_type_flag(SurfaceType::stPosTop))); + slices_offsetted = diff_ex(slices_offsetted, to_expolygons(reg->fill_surfaces.filter_by_type_flag(SurfaceType::stPosTop))); + slices = diff_ex(slices, to_expolygons(reg->fill_surfaces.filter_by_type_flag(SurfaceType::stPosTop))); + } + // create bb for speeding things up. + m_layer_slices_offseted.slices.clear(); + for (ExPolygon &ex : slices) { + BoundingBox bb{ex.contour.points}; + // simplify as much as possible + for (ExPolygon &ex_simpl : ex.simplify(m_layer_slices_offseted.diameter)) { + m_layer_slices_offseted.slices.emplace_back(std::move(ex_simpl), std::move(bb)); + } + } + m_layer_slices_offseted.slices_offsetted.clear(); + for (ExPolygon &ex : slices_offsetted) { + BoundingBox bb{ex.contour.points}; + for (ExPolygon &ex_simpl : ex.simplify(m_layer_slices_offseted.diameter)) { + m_layer_slices_offseted.slices_offsetted.emplace_back(std::move(ex_simpl), std::move(bb)); + } } - } // test if a expoly contains the entire travel - for (const ExPolygon &poly : + for (const std::pair &expoly_2_bb : offset ? m_layer_slices_offseted.slices_offsetted : m_layer_slices_offseted.slices) { - m_throw_if_canceled(); - if (poly.contains(travel)) { - return false; + // first check if it's roughtly inside the bb, to reject quickly. + if (travel.size() > 1 && expoly_2_bb.second.contains(travel.front()) && + expoly_2_bb.second.contains(travel.back()) && + expoly_2_bb.second.contains(travel.points[travel.size() / 2])) { + // first, check if it's inside the contour (still, it can go over holes) + Polylines diff_result = diff_pl(travel, expoly_2_bb.first.contour); + if (diff_result.size() == 1 && diff_result.front() == travel) + //if (!diff_pl(travel, expoly_2_bb.first.contour).empty()) + continue; + //second, check if it's crossing this contour + if (!diff_result.empty()) { + //has_intersect = true; + return true; + } + // third, check if it's going over a hole + // TODO: kdtree to get the ones interesting + //bool has_intersect = false; + Line travel_line; + Point whatever; + for (const Polygon &hole : expoly_2_bb.first.holes) { + m_throw_if_canceled(); + for (size_t idx_travel = travel.size() - 1; idx_travel > 0; --idx_travel) { + travel_line.a = travel.points[idx_travel]; + travel_line.b = travel.points[idx_travel - 1]; + if (hole.first_intersection(travel_line, &whatever) || + Line(hole.first_point(), hole.last_point()).intersection(travel_line, &whatever)) { + //has_intersect = true; + //break; + return true; + } + } + //if (has_intersect) + // break; + } + // if inside contour and does not inersect hole -> inside expoly, you don't need to avoid. + //if (!has_intersect) + return false; } } - //} - } + } // retract if only_retract_when_crossing_perimeters is disabled or doesn't apply return true; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index ee1527d0120..3d02b8808ed 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -444,8 +444,8 @@ class GCode : ExtrusionVisitorConst { // For crossing perimeter retraction detection (contain the layer & nozzle widdth used to construct it) // !!!! not thread-safe !!!! if threaded per layer, please store it in the thread. struct SliceOffsetted { - ExPolygons slices; - ExPolygons slices_offsetted; + std::vector> slices; + std::vector> slices_offsetted; const Layer* layer; coord_t diameter; } m_layer_slices_offseted{ {},{},nullptr, 0}; diff --git a/src/libslic3r/MultiPoint.cpp b/src/libslic3r/MultiPoint.cpp index 37c6a8df2de..eac91ece652 100644 --- a/src/libslic3r/MultiPoint.cpp +++ b/src/libslic3r/MultiPoint.cpp @@ -132,7 +132,11 @@ bool MultiPoint::first_intersection(const Line& line, Point* intersection) const { bool found = false; double dmin = 0.; - for (const Line &l : this->lines()) { + Line l; + //for (const Line &l : this->lines()) { + for (size_t idx = 1; idx < points.size(); ++idx) { + l.a = points[idx-1]; + l.b = points[idx]; Point ip; if (l.intersection(line, &ip)) { if (! found) { diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 8666eaa4b1e..06ffb8ea164 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -81,8 +81,8 @@ class Polyline : public MultiPoint { bool is_closed() const { return this->points.front() == this->points.back(); } }; -inline bool operator==(const Polyline &lhs, const Polyline &rhs) { return lhs.points == rhs.points; } -inline bool operator!=(const Polyline &lhs, const Polyline &rhs) { return lhs.points != rhs.points; } +//inline bool operator==(const Polyline &lhs, const Polyline &rhs) { return lhs.points == rhs.points; } +//inline bool operator!=(const Polyline &lhs, const Polyline &rhs) { return lhs.points != rhs.points; } // Don't use this class in production code, it is used exclusively by the Perl binding for unit tests! #ifdef PERL_UCHAR_MIN From 94c9244a873cf31fbe6933410264dc61a946d6cb Mon Sep 17 00:00:00 2001 From: supermerill Date: Thu, 28 Mar 2024 10:18:43 +0100 Subject: [PATCH 08/19] add is_percent to ConfigOption --- src/libslic3r/Config.hpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp index 3797c13ebad..e3a5014a02d 100644 --- a/src/libslic3r/Config.hpp +++ b/src/libslic3r/Config.hpp @@ -413,6 +413,7 @@ class ConfigOption { virtual void set(const ConfigOption *option) = 0; virtual int32_t get_int(size_t idx = 0) const { throw BadOptionTypeException("Calling ConfigOption::get_int on a non-int ConfigOption"); } virtual double get_float(size_t idx = 0) const { throw BadOptionTypeException("Calling ConfigOption::get_float on a non-float ConfigOption"); } + virtual bool is_percent(size_t idx = 0) const { return false; } virtual bool get_bool(size_t idx = 0) const { throw BadOptionTypeException("Calling ConfigOption::get_bool on a non-boolean ConfigOption"); } virtual void set_enum_int(int32_t /* val */) { throw BadOptionTypeException("Calling ConfigOption::set_enum_int on a non-enum ConfigOption"); } virtual boost::any get_any(int32_t idx = -1) const { throw BadOptionTypeException("Calling ConfigOption::get_any on a raw ConfigOption"); } @@ -1159,8 +1160,9 @@ class ConfigOptionPercent : public ConfigOptionFloat ConfigOptionPercent& operator= (const ConfigOption *opt) { this->set(opt); return *this; } bool operator==(const ConfigOptionPercent &rhs) const throw() { return this->value == rhs.value; } bool operator< (const ConfigOptionPercent &rhs) const throw() { return this->value < rhs.value; } - + double get_abs_value(double ratio_over) const { return ratio_over * this->value / 100.; } + bool is_percent(size_t idx = 0) const override { return true; } std::string serialize() const override { @@ -1203,6 +1205,7 @@ class ConfigOptionPercentsTempl : public ConfigOptionFloatsTempl bool operator==(const ConfigOptionPercentsTempl &rhs) const throw() { return ConfigOptionFloatsTempl::vectors_equal(this->values, rhs.values); } bool operator< (const ConfigOptionPercentsTempl &rhs) const throw() { return ConfigOptionFloatsTempl::vectors_lower(this->values, rhs.values); } double get_abs_value(size_t i, double ratio_over) const { return this->is_nil(i) ? 0 : ratio_over * this->get_at(i) / 100; } + bool is_percent(size_t idx = 0) const override { return true; } std::string serialize() const override { @@ -1272,6 +1275,7 @@ class ConfigOptionFloatOrPercent : public ConfigOptionPercent double get_abs_value(double ratio_over) const { return this->percent ? (ratio_over * this->value / 100) : this->value; } double get_float(size_t idx = 0) const override { return get_abs_value(1.); } + bool is_percent(size_t idx = 0) const override { return this->percent; } // special case for get/set any: use a FloatOrPercent like for FloatsOrPercents, to have the is_percent boost::any get_any(int32_t idx = 0) const override { return boost::any(FloatOrPercent{value, percent}); } void set_any(boost::any anyval, int32_t idx = -1) override @@ -1357,6 +1361,12 @@ class ConfigOptionFloatsOrPercentsTempl : public ConfigOptionVectoris_nil(idx)) + return false; + return this->get_at(idx).percent; + } static inline bool is_nil(const boost::any &to_check) { bool ok = std::isnan(boost::any_cast(to_check).value) || boost::any_cast(to_check).value == NIL_VALUE().value From 4d101ca61283888280c85f5e1411a71ebbc10cb3 Mon Sep 17 00:00:00 2001 From: supermerill Date: Fri, 5 Apr 2024 09:58:28 +0200 Subject: [PATCH 09/19] add top_solid_infill_overlap --- resources/ui_layout/default/print.ui | 5 +++- src/libslic3r/Fill/Fill.cpp | 4 ++-- src/libslic3r/Flow.cpp | 5 ++-- src/libslic3r/Preset.cpp | 1 + src/libslic3r/PrintConfig.cpp | 34 +++++++++++++++++++++------- src/libslic3r/PrintConfig.hpp | 1 + src/libslic3r/PrintRegion.cpp | 2 +- 7 files changed, 38 insertions(+), 14 deletions(-) diff --git a/resources/ui_layout/default/print.ui b/resources/ui_layout/default/print.ui index a86dda9fe89..91299f0f8c1 100644 --- a/resources/ui_layout/default/print.ui +++ b/resources/ui_layout/default/print.ui @@ -449,7 +449,10 @@ group:Overlap setting:label_width$7:label$External:external_perimeter_overlap setting:label_width$7:label$Gap Fill:gap_fill_overlap end_line - setting:width$4:solid_infill_overlap + line:Solid infill ovelrap + setting:label$Inside:width$4:solid_infill_overlap + setting:label$Top:width$4:top_solid_infill_overlap + end_line line:Bridge lines density setting:label_width$7:bridge_overlap_min setting:label_width$7:bridge_overlap diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index d7b5fb83551..990d1de7015 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -935,8 +935,8 @@ void Layer::make_ironing() fill.link_max_length = (coord_t)scale_(3. * fill.get_spacing()); double extrusion_height = ironing_params.height * fill.get_spacing() / nozzle_dmr; //FIXME FLOW decide if it's good - double max_overlap = region_config.get_computed_value("filament_max_overlap", ironing_params.extruder - 1); - double overlap = std::min(max_overlap, region_config.solid_infill_overlap.get_abs_value(1.)); + // note: don't use filament_max_overlap, as it's a top surface + double overlap = region_config.top_solid_infill_overlap.get_abs_value(1.); float extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(float(nozzle_dmr), float(extrusion_height), float(overlap)); double flow_mm3_per_mm = nozzle_dmr * extrusion_height; //Flow flow = Flow::new_from_spacing(float(nozzle_dmr), 0., float(height), 1.f, false); diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index 85ddccc3a0e..e8be7b07bba 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -306,7 +306,7 @@ Flow Flow::new_from_config(FlowRole role, const DynamicConfig& print_config, flo } else if (role == frTopSolidInfill) { config_width = print_config.opt("top_infill_extrusion_width"); config_spacing = print_config.opt("top_infill_extrusion_spacing"); - overlap = (float)print_config.get_abs_value("solid_infill_overlap", 1.); + overlap = (float)print_config.get_abs_value("top_solid_infill_overlap", 1.); } else { throw Slic3r::InvalidArgument("Unknown role"); } @@ -322,7 +322,8 @@ Flow Flow::new_from_config(FlowRole role, const DynamicConfig& print_config, flo // Get the configured nozzle_diameter for the extruder associated to the flow role requested. // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right. - return Flow::new_from_config_width(role, config_width, config_spacing, nozzle_diameter, layer_height, std::min(overlap, filament_max_overlap)); + return Flow::new_from_config_width(role, config_width, config_spacing, nozzle_diameter, layer_height, + std::min(role == frTopSolidInfill ? 1.f : overlap, filament_max_overlap)); //bridge ? (float)m_config.bridge_flow_ratio.get_abs_value(1) : 0.0f); } diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index f4b0710ad87..bca62852dc5 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -664,6 +664,7 @@ static std::vector s_Preset_print_options { "bridge_flow_ratio", "bridge_type", "solid_infill_overlap", + "top_solid_infill_overlap", "infill_anchor", "infill_anchor_max", "clip_multipart_objects", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 9cfa6dca939..4128c49e053 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2193,7 +2193,8 @@ void PrintConfigDef::init_fff_params() def = this->add("filament_max_overlap", coPercents); def->label = L("Max line overlap"); def->tooltip = L("This setting will ensure that all 'overlap' are not higher than this value." - " This is useful for filaments that are too viscous, as the line can't flow under the previous one."); + " This is useful for filaments that are too viscous, as the line can't flow under the previous one." + "\nNote: top solid infill lines are excluded, to prevent visual defects."); def->sidetext = L("%"); def->ratio_over = ""; def->min = 0; @@ -5001,10 +5002,11 @@ void PrintConfigDef::init_fff_params() def->set_default_value(new ConfigOptionFloatOrPercent(0, false)); def = this->add("solid_infill_overlap", coPercent); - def->label = L("Solid fill overlap"); + def->label = L("Solid infill overlap"); def->category = OptionCategory::width; def->tooltip = L("This setting allows you to reduce the overlap between the lines of the solid fill, to reduce the % filled if you see overextrusion signs on solid areas." - " Note that you should be sure that your flow (filament extrusion multiplier) is well calibrated and your filament max overlap is set before thinking to modify this."); + " Note that you should be sure that your flow (filament extrusion multiplier) is well calibrated and your filament max overlap is set before thinking to modify this." + "\nNote: top surfaces are still extruded with 100% overlap to prevent gaps."); def->sidetext = L("%"); def->min = 0; def->max = 100; @@ -5978,6 +5980,19 @@ void PrintConfigDef::init_fff_params() def->mode = comExpert | comSuSi; def->set_default_value(new ConfigOptionFloatOrPercent(0,false)); + def = this->add("top_solid_infill_overlap", coPercent); + def->label = L("Top solid infill overlap"); + def->category = OptionCategory::width; + def->tooltip = L("This setting allows you to reduce the overlap between the lines of the top solid fill, to reduce the % filled if you see overextrusion signs on solid areas." + "\nNote that you should be sure that your flow (filament extrusion multiplier) is well calibrated and your filament max overlap is set before thinking to modify this." + "\nAlso, lowering it below 100% may create visible gaps in the top surfaces" + "\nSet overlap setting is the only one that can't be reduced by the filament's max overlap."); + def->sidetext = L("%"); + def->min = 0; + def->max = 100; + def->mode = comExpert | comSuSi; + def->set_default_value(new ConfigOptionPercent(100)); + def = this->add("top_solid_infill_speed", coFloatOrPercent); def->label = L("Top solid"); def->full_label = L("Top solid speed"); @@ -8363,6 +8378,7 @@ std::unordered_set prusa_export_to_remove_keys = { "top_fan_speed", "top_infill_extrusion_spacing", "top_solid_infill_acceleration", +"top_solid_infill_overlap", "travel_acceleration", "travel_deceleration_use_target", "travel_speed_z", @@ -8973,7 +8989,9 @@ std::set DynamicPrintConfig::value_changed(const t_co return { this }; return {}; } - if (opt_key == "filament_max_overlap" || opt_key == "perimeter_overlap" || opt_key == "external_perimeter_overlap" || opt_key == "solid_infill_overlap") { + if (opt_key == "filament_max_overlap" || opt_key == "perimeter_overlap" || + opt_key == "external_perimeter_overlap" || opt_key == "solid_infill_overlap" || + opt_key == "top_solid_infill_overlap") { for (auto conf : config_collection) { if (conf->option("extrusion_width")) if (!conf->update_phony(config_collection).empty()) @@ -9094,7 +9112,7 @@ std::set DynamicPrintConfig::value_changed(const t_co } } if (opt_key == "top_infill_extrusion_spacing") { - const ConfigOptionPercent* solid_infill_overlap_option = find_option("solid_infill_overlap", this, config_collection); + const ConfigOptionPercent* top_solid_infill_overlap_option = find_option("top_solid_infill_overlap", this, config_collection); ConfigOptionFloatOrPercent* width_option = this->option("top_infill_extrusion_width"); if (width_option) { width_option->set_phony(true); @@ -9102,7 +9120,7 @@ std::set DynamicPrintConfig::value_changed(const t_co if (spacing_value == 0) width_option->value = 0; else { - float spacing_ratio = (std::min(flow.spacing_ratio(), float(solid_infill_overlap_option->get_abs_value(1)))); + float spacing_ratio = (std::min(flow.spacing_ratio(), float(top_solid_infill_overlap_option->get_abs_value(1)))); flow = flow.with_width(spacing_option->get_abs_value(max_nozzle_diameter) + layer_height_option->value * (1. - 0.25 * PI) * spacing_ratio); width_option->value = (spacing_option->percent) ? std::round(100 * flow.width() / max_nozzle_diameter) : (std::round(flow.width() * 10000) / 10000); } @@ -9252,7 +9270,7 @@ std::set DynamicPrintConfig::value_changed(const t_co } } if (opt_key == "top_infill_extrusion_width") { - const ConfigOptionPercent* solid_infill_overlap_option = find_option("solid_infill_overlap", this, config_collection); + const ConfigOptionPercent* top_solid_infill_overlap_option = find_option("top_solid_infill_overlap", this, config_collection); spacing_option = this->option("top_infill_extrusion_spacing"); if (width_option) { width_option->set_phony(false); @@ -9263,7 +9281,7 @@ std::set DynamicPrintConfig::value_changed(const t_co Flow flow = Flow::new_from_config_width(FlowRole::frTopSolidInfill, width_option->value == 0 ? *default_width_option : *width_option, *spacing_option, max_nozzle_diameter, layer_height_option->value, - std::min(overlap_ratio, float(solid_infill_overlap_option->get_abs_value(1.))), 0); + std::min(overlap_ratio, float(top_solid_infill_overlap_option->get_abs_value(1.))), 0); if (flow.width() < flow.height()) flow = flow.with_height(flow.width()); spacing_option->value = (width_option->percent) ? std::round(100 * flow.spacing() / max_nozzle_diameter) : (std::round(flow.spacing() * 10000) / 10000); } diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index f034f4c9929..907ef505088 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -959,6 +959,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloatOrPercent, top_infill_extrusion_spacing)) ((ConfigOptionInt, top_solid_layers)) ((ConfigOptionFloat, top_solid_min_thickness)) + ((ConfigOptionPercent, top_solid_infill_overlap)) ((ConfigOptionFloatOrPercent, top_solid_infill_speed)) ((ConfigOptionBool, wipe_into_infill)) diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp index 03fd40de5f8..22c95a6daeb 100644 --- a/src/libslic3r/PrintRegion.cpp +++ b/src/libslic3r/PrintRegion.cpp @@ -75,7 +75,7 @@ Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_he } else if (role == frTopSolidInfill) { config_width = m_config.top_infill_extrusion_width; config_spacing = m_config.top_infill_extrusion_spacing; - overlap = this->config().solid_infill_overlap.get_abs_value(1); + overlap = this->config().top_solid_infill_overlap.get_abs_value(1); } else { throw Slic3r::InvalidArgument("Unknown role"); } From 09ab9eb1f7d0242bea194598adb77a19229ed3c5 Mon Sep 17 00:00:00 2001 From: supermerill Date: Mon, 8 Apr 2024 12:18:46 +0200 Subject: [PATCH 10/19] Add extruder_extrusion_multiplier_speed with gui also improve the graph. --- resources/ui_layout/default/extruder.ui | 1 + src/libslic3r/GCode.cpp | 61 +++-- src/libslic3r/GCode.hpp | 1 + src/libslic3r/Preset.cpp | 4 +- src/libslic3r/Print.cpp | 1 + src/libslic3r/PrintConfig.cpp | 11 + src/libslic3r/PrintConfig.hpp | 1 + src/slic3r/CMakeLists.txt | 2 + src/slic3r/GUI/GraphDialog.cpp | 288 ++++++++++++++++++++++++ src/slic3r/GUI/GraphDialog.hpp | 40 ++++ src/slic3r/GUI/OptionsGroup.cpp | 1 + src/slic3r/GUI/RammingChart.cpp | 159 +++++++++---- src/slic3r/GUI/RammingChart.hpp | 39 +++- src/slic3r/GUI/Search.cpp | 27 +++ src/slic3r/GUI/Search.hpp | 7 +- src/slic3r/GUI/Tab.cpp | 32 ++- src/slic3r/GUI/Tab.hpp | 2 +- src/slic3r/GUI/UnsavedChangesDialog.cpp | 51 +++-- src/slic3r/GUI/WipeTowerDialog.cpp | 14 +- 19 files changed, 638 insertions(+), 104 deletions(-) create mode 100644 src/slic3r/GUI/GraphDialog.cpp create mode 100644 src/slic3r/GUI/GraphDialog.hpp diff --git a/resources/ui_layout/default/extruder.ui b/resources/ui_layout/default/extruder.ui index f3896c7d60e..e7db8c7e341 100644 --- a/resources/ui_layout/default/extruder.ui +++ b/resources/ui_layout/default/extruder.ui @@ -11,6 +11,7 @@ group:Offsets (for multi-extruder printers) setting:idx:extruder_offset setting:idx:extruder_temperature_offset setting:idx:extruder_fan_offset + extruder_extrusion_multiplier_speed group:Retraction setting:idx:retract_length setting:idx:retract_lift diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 151bc093b45..8494c1a7c87 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -3904,11 +3904,7 @@ std::string GCode::extrude_loop_vase(const ExtrusionLoop &original_loop, const s } // calculate extrusion length per distance unit - double e_per_mm_per_height = (path->mm3_per_mm / this->m_layer->height) - * m_writer.tool()->e_per_mm3() - * this->config().print_extrusion_multiplier.get_abs_value(1); - if (m_writer.extrusion_axis().empty()) - e_per_mm_per_height = 0; + double e_per_mm_per_height = compute_e_per_mm(path->mm3_per_mm); //extrude { std::string comment = m_config.gcode_comments ? description : ""; @@ -4898,10 +4894,7 @@ std::string GCode::extrude_multi_path3D(const ExtrusionMultiPath3D &multipath3D, gcode += this->_before_extrude(path, description, speed); // calculate extrusion length per distance unit - double e_per_mm = path.mm3_per_mm - * m_writer.tool()->e_per_mm3() - * this->config().print_extrusion_multiplier.get_abs_value(1); - if (m_writer.extrusion_axis().empty()) e_per_mm = 0; + double e_per_mm = compute_e_per_mm(path.mm3_per_mm); double path_length = 0.; { std::string comment = m_config.gcode_comments ? description : ""; @@ -5015,10 +5008,7 @@ std::string GCode::extrude_path_3D(const ExtrusionPath3D &path, const std::strin std::string gcode = this->_before_extrude(path, description, speed); // calculate extrusion length per distance unit - double e_per_mm = path.mm3_per_mm - * m_writer.tool()->e_per_mm3() - * this->config().print_extrusion_multiplier.get_abs_value(1); - if (m_writer.extrusion_axis().empty()) e_per_mm = 0; + double e_per_mm = compute_e_per_mm(path.mm3_per_mm); double path_length = 0.; { std::string comment = m_config.gcode_comments ? description : ""; @@ -5341,11 +5331,48 @@ void GCode::_extrude_line_cut_corner(std::string& gcode_str, const Line& line, c comment); } - //relance + // relance last_pos = line.a; } } +double GCode::compute_e_per_mm(double path_mm3_per_mm) { + // no e if no extrusion axis + if (m_writer.extrusion_axis().empty()) + return 0; + // compute + double e_per_mm = path_mm3_per_mm + * m_writer.tool()->e_per_mm3() // inside is the filament_extrusion_multiplier + * this->config().print_extrusion_multiplier.get_abs_value(1); + // extrusion mult per speed + std::string str = this->config().extruder_extrusion_multiplier_speed.get_at(this->m_writer.tool()->id()); + if (str.size() > 2 && !(str.at(0) == '0' && str.at(1) == ' ')) { + assert(e_per_mm > 0); + double current_speed = this->writer().get_speed(); + std::vector extrusion_mult; + std::stringstream stream{str}; + double parsed = 0.f; + while (stream >> parsed) + extrusion_mult.push_back(parsed); + int idx_before = int(current_speed/10); + if (idx_before >= extrusion_mult.size() - 1) { + // last or after the last + e_per_mm *= extrusion_mult.back(); + } else { + assert(idx_before + 1 < extrusion_mult.size()); + float percent_before = 1 - (current_speed/10 - idx_before); + double mult = extrusion_mult[idx_before] * percent_before; + mult += extrusion_mult[idx_before+1] * (1-percent_before); + e_per_mm *= (mult / 2); + } + assert(e_per_mm > 0); + } + // first layer mult + if (this->m_layer->bottom_z() < EPSILON) + e_per_mm *= this->config().first_layer_flow_ratio.get_abs_value(1); + return e_per_mm; +} + std::string GCode::_extrude(const ExtrusionPath &path, const std::string &description, double speed) { std::string descr = description.empty() ? ExtrusionEntity::role_to_string(path.role()) : description; @@ -5360,11 +5387,7 @@ std::string GCode::_extrude(const ExtrusionPath &path, const std::string &descri }; // calculate extrusion length per distance unit - double e_per_mm = path.mm3_per_mm - * m_writer.tool()->e_per_mm3() - * this->config().print_extrusion_multiplier.get_abs_value(1); - if (m_layer->bottom_z() < EPSILON) e_per_mm *= this->config().first_layer_flow_ratio.get_abs_value(1); - if (m_writer.extrusion_axis().empty()) e_per_mm = 0; + double e_per_mm = compute_e_per_mm(path.mm3_per_mm); path.polyline.ensure_fitting_result_valid(); if (path.polyline.lines().size() > 0) { std::string comment = m_config.gcode_comments ? descr : ""; diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index 3d02b8808ed..00ee268902e 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -507,6 +507,7 @@ class GCode : ExtrusionVisitorConst { std::function m_throw_if_canceled = [](){}; + double compute_e_per_mm(double path_mm3_per_mm); std::string _extrude(const ExtrusionPath &path, const std::string &description, double speed = -1); void _extrude_line(std::string& gcode_str, const Line& line, const double e_per_mm, const std::string& comment); void _extrude_line_cut_corner(std::string& gcode_str, const Line& line, const double e_per_mm, const std::string& comment, Point& last_pos, const double path_width); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index bca62852dc5..1006eddf894 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1549,7 +1549,9 @@ inline t_config_option_keys deep_diff(const ConfigBase &config_this, const Confi && (ignore_phony || !(this_opt->is_phony() && other_opt->is_phony())) && ((*this_opt != *other_opt) || (this_opt->is_phony() != other_opt->is_phony()))) { - if (opt_key == "bed_shape" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || opt_key == "filament_ramming_parameters" || opt_key == "gcode_substitutions") { + //if (opt_key == "bed_shape" || opt_key == "compatible_prints" || opt_key == "compatible_printers" || + // opt_key == "filament_ramming_parameters" || opt_key == "gcode_substitutions") { + if (this_opt->is_vector() && !(static_cast(this_opt)->is_extruder_size())) { // Scalar variable, or a vector variable, which is independent from number of extruders, // thus the vector is presented to the user as a single input. // Merill: these are 'button' special settings. diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index dd21018f5c6..e1a7ad7fc06 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -93,6 +93,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver& /* ne "extruder_clearance_height", "extruder_clearance_radius", "extruder_colour", + "extruder_extrusion_multiplier_speed", "extruder_offset", "extruder_fan_offset" "extruder_temperature_offset", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 4128c49e053..c153be10123 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1747,6 +1747,14 @@ void PrintConfigDef::init_fff_params() def->is_vector_extruder = true; def->set_default_value(new ConfigOptionStrings{ "" }); + def = this->add("extruder_extrusion_multiplier_speed", coStrings); + def->label = L("Extrusion multipler"); + def->tooltip = L("This string is edited by a Dialog and contains extusion multiplier for different speeds."); + def->mode = comExpert | comSuSi; + def->is_vector_extruder = true; + def->set_default_value(new ConfigOptionStrings { "0 1 1 1 1 1 1 1 1 1 1 1|" + " 10 1. 20 1. 30 1. 40 1. 60 1. 80 1. 120 1. 160 1. 240 1. 320 1. 480 1. 640 1. 960 1. 1280 1." }); + def = this->add("extruder_offset", coPoints); def->label = L("Extruder offset"); def->category = OptionCategory::extruders; @@ -6606,6 +6614,7 @@ void PrintConfigDef::init_extruder_option_keys() "default_filament_profile", "deretract_speed", "extruder_colour", + "extruder_extrusion_multiplier_speed", "extruder_fan_offset", "extruder_offset", "extruder_temperature_offset", @@ -8194,6 +8203,7 @@ std::unordered_set prusa_export_to_remove_keys = { "external_perimeters_vase", "extra_perimeters_odd_layers", "extra_perimeters_overhangs", +"extruder_extrusion_multiplier_speed", "extruder_fan_offset", "extruder_temperature_offset", "extrusion_spacing", @@ -8249,6 +8259,7 @@ std::unordered_set prusa_export_to_remove_keys = { "gcode_precision_e", "gcode_precision_xyz", "hole_size_compensation", +"hole_size_compensations_curve", "hole_size_threshold", "hole_to_polyhole_threshold", "hole_to_polyhole_twisted", diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 907ef505088..e074b24c15f 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1028,6 +1028,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionString, end_gcode)) ((ConfigOptionStrings, end_filament_gcode)) ((ConfigOptionFloat, extra_loading_move)) + ((ConfigOptionStrings, extruder_extrusion_multiplier_speed)) ((ConfigOptionPercents, extruder_fan_offset)) ((ConfigOptionFloats, extruder_temperature_offset)) ((ConfigOptionString, extrusion_axis)) diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index c11f860c59b..81241b45238 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -95,6 +95,8 @@ set(SLIC3R_GUI_SOURCES GUI/GLTexture.cpp GUI/GLToolbar.hpp GUI/GLToolbar.cpp + GUI/GraphDialog.hpp + GUI/GraphDialog.cpp GUI/GCodeViewer.hpp GUI/GCodeViewer.cpp GUI/Preferences.cpp diff --git a/src/slic3r/GUI/GraphDialog.cpp b/src/slic3r/GUI/GraphDialog.cpp new file mode 100644 index 00000000000..c8a895e1a65 --- /dev/null +++ b/src/slic3r/GUI/GraphDialog.cpp @@ -0,0 +1,288 @@ +#include +#include +#include "GraphDialog.hpp" +#include "BitmapCache.hpp" +#include "GUI.hpp" +#include "I18N.hpp" +#include "GUI_App.hpp" +#include "MsgDialog.hpp" + +#include +#include + +namespace Slic3r { namespace GUI { + +int scale(const int val) { return val * Slic3r::GUI::wxGetApp().em_unit(); } +#ifdef __WXGTK3__ +int ITEM_WIDTH() { return scale(10); } +#else +int ITEM_WIDTH() { return scale(6); } +#endif + +static void update_ui(wxWindow *window) { Slic3r::GUI::wxGetApp().UpdateDarkUI(window); } + +GraphDialog::GraphDialog(wxWindow *parent, const std::string ¶meters) + : wxDialog(parent, + wxID_ANY, + _(L("Extrusion multiplier per extrusion speed")), + wxDefaultPosition, + wxDefaultSize, + wxDEFAULT_DIALOG_STYLE /* | wxRESIZE_BORDER*/) +{ + update_ui(this); + m_panel_graph = new GraphPanel(this, parameters); + + // Not found another way of getting the background colours of GraphDialog, GraphPanel and Chart correct than + // setting them all explicitely. Reading the parent colour yielded colour that didn't really match it, no + // wxSYS_COLOUR_... matched colour used for the dialog. Same issue (and "solution") here : + // https://forums.wxwidgets.org/viewtopic.php?f=1&t=39608 Whoever can fix this, feel free to do so. +#ifndef _WIN32 + this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK)); + m_panel_graph->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_FRAMEBK)); +#endif + m_panel_graph->Show(true); + this->Show(); + + auto main_sizer = new wxBoxSizer(wxVERTICAL); + main_sizer->Add(m_panel_graph, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5); + main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxTOP | wxBOTTOM, 10); + SetSizer(main_sizer); + main_sizer->SetSizeHints(this); + + update_ui(static_cast(this->FindWindowById(wxID_OK, this))); + update_ui(static_cast(this->FindWindowById(wxID_CANCEL, this))); + + this->Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &e) { + EndModal(wxCANCEL); + }); + + this->Bind( + wxEVT_BUTTON, + [this](wxCommandEvent &) { + m_output_data = m_panel_graph->get_parameters(); + EndModal(wxID_OK); + }, + wxID_OK); + this->Show(); + // Slic3r::GUI::MessageDialog dlg(this, _(L("Graph .")), _(L("Warning")), wxOK | wxICON_EXCLAMATION); + // dlg.ShowModal(); +} + +#ifdef _WIN32 +#define style wxSP_ARROW_KEYS | wxBORDER_SIMPLE +#else +#define style wxSP_ARROW_KEYS +#endif + + + +GraphPanel::GraphPanel(wxWindow *parent, const std::string ¶meters) + : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize /*,wxPoint(50,50), wxSize(800,350),wxBORDER_RAISED*/) +{ + update_ui(this); + auto sizer_chart = new wxBoxSizer(wxVERTICAL); + + std::stringstream stream{parameters}; + //stream >> m_graph_line_width_multiplicator >> m_graph_step_multiplicator; + int Graph_speed_size = 0; + float dummy = 0.f; + float min = 2.f; + float max = 0.f; + while (stream >> dummy) { + ++Graph_speed_size; + if (dummy > 0 && dummy <= 2) { + min = std::min(min, dummy); + max = std::max(max, dummy); + } + } + stream.clear(); + stream.get(); + + if (min >= max) { + max = 1.2f; + min = 0.9f; + } else { + min = int(min * 10 - 1 + EPSILON) / 10.f; + max = int(1.9f + max * 10 - EPSILON) / 10.f; + } + + std::vector> buttons; + float x = 0.f; + float y = 0.f; + while (stream >> x >> y) buttons.push_back(std::make_pair(x, y)); + + m_chart = new Chart(this, wxRect(scale(1), scale(1), scale(64), scale(36)), buttons, scale(1)); + m_chart->set_manual_points_manipulation(true); + m_chart->set_xy_range(0, min, Graph_speed_size * 10.f, max); + m_chart->set_x_label(_L("Print speed") + " ("+_L("mm/s")+")", 1.f); + m_chart->set_y_label(_L("Extrusion multiplier"), 0.001f); + m_chart->set_no_point_label(_L("No compensation")); +#ifdef _WIN32 + update_ui(m_chart); +#else + m_chart->SetBackgroundColour(parent->GetBackgroundColour()); // see comment in GraphDialog constructor +#endif + sizer_chart->Add(new wxStaticText(this, wxID_ANY, + _L("Choose the extrusion multipler value for multiple speeds.\nYou can add/remove points with a right clic."))); + sizer_chart->Add(m_chart, 0, wxALL, 5); + + m_last_speed = Graph_speed_size * 10; + m_widget_speed = new wxSpinCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH(), -1), + style | wxTE_PROCESS_ENTER, 10, 2000, m_last_speed); + // note: wxTE_PROCESS_ENTER allow the wxSpinCtrl to receive wxEVT_TEXT_ENTER events + + + m_widget_min_flow = new wxSpinCtrlDouble(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH(), -1), + style, 0.1, 2, min, 0.1f); + m_widget_max_flow = new wxSpinCtrlDouble(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(ITEM_WIDTH(), -1), + style, 0.1, 2, max, 0.1f); + +#ifdef _WIN32 + update_ui(m_widget_speed); + update_ui(m_widget_min_flow); + update_ui(m_widget_max_flow); +#endif + // line for speed max & reset + wxBoxSizer *size_line = new wxBoxSizer(wxHORIZONTAL); + size_line->Add(new wxStaticText(this, wxID_ANY, wxString(_L("Graph max speed") + " :")), 0, wxALIGN_CENTER_VERTICAL); + size_line->Add(m_widget_speed); + size_line->AddSpacer(80); + wxButton *bt_reset = new wxButton(this, wxID_ANY, _L("Reset")); + bt_reset->SetToolTip(_L("Reset all values to 1. Also reset all points to defaults.")); + size_line->Add(bt_reset); + sizer_chart->Add(size_line); + + //line for y min & max + size_line = new wxBoxSizer(wxHORIZONTAL); + size_line->Add(new wxStaticText(this, wxID_ANY, wxString(_L("Minimum flow") + " :")), 0, wxALIGN_CENTER_VERTICAL); + size_line->Add(m_widget_min_flow); + size_line->AddSpacer(20); + size_line->Add(new wxStaticText(this, wxID_ANY, wxString(_L("Maximum flow") + " :")), 0, wxALIGN_CENTER_VERTICAL); + size_line->Add(m_widget_max_flow); + sizer_chart->Add(size_line); + + sizer_chart->SetSizeHints(this); + SetSizer(sizer_chart); + + bt_reset->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) { + std::vector> buttons;// = m_chart->get_buttons(); + //for (std::pair &button : buttons) { + // button.second = 1.f; + //} + //buttons.emplace_back(5,1.f); + buttons.emplace_back(10,1.f); + //buttons.emplace_back(15,1.f); + buttons.emplace_back(20,1.f); + buttons.emplace_back(30,1.f); + buttons.emplace_back(40,1.f); + buttons.emplace_back(60,1.f); + buttons.emplace_back(80,1.f); + buttons.emplace_back(120,1.f); + buttons.emplace_back(160,1.f); + buttons.emplace_back(240,1.f); + buttons.emplace_back(480,1.f); + buttons.emplace_back(640,1.f); + buttons.emplace_back(960,1.f); + buttons.emplace_back(1280,1.f); + m_chart->set_buttons(buttons); + })); + + m_widget_speed->Bind(wxEVT_TEXT_ENTER, [this](wxCommandEvent &) { + int old_speed = m_last_speed; + m_last_speed = 10 * ((m_widget_speed->GetValue() + 5) / 10); + m_last_speed = std::min(std::max(m_last_speed, 20), 2000); + m_widget_speed->SetValue(m_last_speed); + if (old_speed < m_last_speed) { + if (old_speed < 1000 && m_last_speed >= 1000) + m_chart->set_x_label(_L("Print speed") + " (" + _L("mm/s") + ")", 100.f); + else if (old_speed < 100 && m_last_speed >= 100) + m_chart->set_x_label(_L("Print speed") + " (" + _L("mm/s") + ")", 10.f); + } else { + if (old_speed >= 100 && m_last_speed < 100) + m_chart->set_x_label(_L("Print speed") + " (" + _L("mm/s") + ")", 1.f); + else if (old_speed >= 1000 && m_last_speed < 1000) + m_chart->set_x_label(_L("Print speed") + " (" + _L("mm/s") + ")", 10.f); + } + m_chart->set_xy_range(0, -1, m_last_speed, -1); + }); + m_widget_speed->Bind(wxEVT_SPINCTRL, [this](wxSpinEvent &evt) { + assert(evt.GetInt() != m_last_speed); + int incr = 10; + if (m_last_speed >= 60) + incr = 20; + if (m_last_speed >= 100) + incr = 50; + if (m_last_speed >= 300) + incr = 100; + if (m_last_speed >= 600) + incr = 200; + if (m_last_speed >= 1000) + incr = 500; + if (evt.GetInt() > m_last_speed) { + if (m_last_speed < 100 && m_last_speed + incr >= 100) + m_chart->set_x_label(_L("Print speed") + " (" + _L("mm/s") + ")", 10.f); + else if (m_last_speed < 1000 && m_last_speed + incr >= 1000) + m_chart->set_x_label(_L("Print speed") + " (" + _L("mm/s") + ")", 100.f); + m_last_speed += incr; + } else { + if (m_last_speed >= 100 && m_last_speed - incr < 100) + m_chart->set_x_label(_L("Print speed") + " (" + _L("mm/s") + ")", 1.f); + else if (m_last_speed >= 1000 && m_last_speed - incr < 1000) + m_chart->set_x_label(_L("Print speed") + " (" + _L("mm/s") + ")", 10.f); + m_last_speed -= incr; + } + m_last_speed = std::min(std::max(m_last_speed, 20), 2000); + m_widget_speed->SetValue(m_last_speed); + m_chart->set_xy_range(0, 0, m_last_speed, -1); + }); + // thses don't work, i don't know why. + //m_widget_speed->Bind(wxEVT_SPIN_UP, [this](wxSpinEvent &evt) { + // std::cout<<"up"; + // }); + //m_widget_speed->Bind(wxEVT_SPIN_DOWN, [this](wxSpinEvent &evt) { + // std::cout<<"down"; + // }); + + m_widget_min_flow->Bind(wxEVT_TEXT, [this](wxCommandEvent &) { + m_chart->set_xy_range(-1, m_widget_min_flow->GetValue(), -1, m_widget_max_flow->GetValue()); + }); + m_widget_min_flow->Bind(wxEVT_CHAR, [](wxKeyEvent &) {}); // do nothing - prevents the user to change the value + m_widget_max_flow->Bind(wxEVT_TEXT, [this](wxCommandEvent &) { + m_chart->set_xy_range(-1, m_widget_min_flow->GetValue(), -1, m_widget_max_flow->GetValue()); + }); + m_widget_max_flow->Bind(wxEVT_CHAR, [](wxKeyEvent &) {}); // do nothing - prevents the user to change the value + Bind(EVT_WIPE_TOWER_CHART_CHANGED, [this](wxCommandEvent &) { + int nb_samples = m_chart->get_speed(10.f).size(); + m_last_speed = 10 * nb_samples; + m_widget_speed->SetValue(m_last_speed); + }); + Refresh(true); // erase background +} + +std::string GraphPanel::get_parameters() +{ + std::vector flow_rates = m_chart->get_speed(10.f); + std::vector> buttons = m_chart->get_buttons(); + + //write string + std::stringstream stream; + //stream << m_graph_line_width_multiplicator << " " << m_graph_step_multiplicator; + //if all are at 1, then set the first to 0 so it's "disabled" + bool disabled = true; + for (const float &flow_rate : flow_rates) + if (flow_rate != 1.) + disabled = false; + for (size_t i = 0; i < flow_rates.size() ; i++) { + const float &flow_rate = flow_rates[i]; + if (0 == i) { + stream << (disabled ? 0.f : flow_rate); + } else { + stream << " " << flow_rate; + } + } + stream << "|"; + for (const auto &button : buttons) stream << " " << button.first << " " << button.second; + return stream.str(); +} + +}} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/GraphDialog.hpp b/src/slic3r/GUI/GraphDialog.hpp new file mode 100644 index 00000000000..eb20f4a93fc --- /dev/null +++ b/src/slic3r/GUI/GraphDialog.hpp @@ -0,0 +1,40 @@ +#ifndef _GRAPH_DIALOG_H_ +#define _GRAPH_DIALOG_H_ + +#include +#include +#include +#include +#include + +#include "RammingChart.hpp" + +namespace Slic3r { namespace GUI { + +class GraphPanel : public wxPanel +{ +public: + GraphPanel(wxWindow *parent, const std::string &data); + std::string get_parameters(); + +private: + Chart * m_chart = nullptr; + wxSpinCtrl * m_widget_speed = nullptr; + wxSpinCtrlDouble *m_widget_min_flow = nullptr; + wxSpinCtrlDouble *m_widget_max_flow = nullptr; + int m_last_speed = 120; +}; + +class GraphDialog : public wxDialog +{ +public: + GraphDialog(wxWindow *parent, const std::string ¶meters); + std::string get_parameters() { return m_output_data; } + +private: + GraphPanel *m_panel_graph = nullptr; + std::string m_output_data; +}; + +}} // namespace Slic3r::GUI +#endif // _GRAPH_DIALOG_H_ \ No newline at end of file diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index f165f092ff4..facaefc6734 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -1035,6 +1035,7 @@ bool OptionsGroup::launch_browser(const std::string& path_end) static const std::vector option_without_field = { "bed_shape", "filament_ramming_parameters", + "extruder_extrusion_multiplier_speed", "gcode_substitutions", "compatible_prints", "compatible_printers" diff --git a/src/slic3r/GUI/RammingChart.cpp b/src/slic3r/GUI/RammingChart.cpp index 244d83a9b87..f906809609f 100644 --- a/src/slic3r/GUI/RammingChart.cpp +++ b/src/slic3r/GUI/RammingChart.cpp @@ -8,7 +8,6 @@ wxDEFINE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent); - void Chart::draw() { wxAutoBufferedPaintDC dc(this); // unbuffered DC caused flickering on win @@ -26,7 +25,8 @@ void Chart::draw() { dc.DrawRectangle(m_rect); if (visible_area.m_width < 0.499) { - dc.DrawText(_(L("NO RAMMING AT ALL")),wxPoint(m_rect.GetLeft()+m_rect.GetWidth()/2-legend_side,m_rect.GetBottom()-m_rect.GetHeight()/2)); + dc.DrawText(m_no_point_legend, wxPoint(m_rect.GetLeft() + m_rect.GetWidth() / 2 - legend_side, + m_rect.GetBottom() - m_rect.GetHeight() / 2)); return; } @@ -35,19 +35,19 @@ void Chart::draw() { for (unsigned int i=0;iget_pos().m_x); + float pos_y = float(m_dragged->get_pos().m_y); + // show on bottom right + // TODO: compute legend height instead of '3 * scale_unit' + wxPoint ptx = math_to_screen(wxPoint2DDouble(visible_area.m_x + visible_area.m_width, visible_area.m_y)); + wxPoint pty = math_to_screen(wxPoint2DDouble(visible_area.m_x + visible_area.m_width, visible_area.m_y)); + ptx.x -= 1*legend_side; + ptx.y -= 1*legend_side; + pty.x -= 1*legend_side; + pty.y -= 0.5*legend_side; + dc.DrawText(wxString().Format(wxT("x: %.3f"), pos_x), ptx); + dc.DrawText(wxString().Format(wxT("y: %.3f"), pos_y), pty); + } } void Chart::mouse_right_button_clicked(wxMouseEvent& event) { - if (!manual_points_manipulation) + if (!m_manual_points_manipulation) return; wxPoint point = event.GetPosition(); int button_index = which_button_is_clicked(point); - if (button_index != -1 && m_buttons.size()>2) { - m_buttons.erase(m_buttons.begin()+button_index); + if (button_index != -1 && m_buttons.size() > 2) { + m_buttons.erase(m_buttons.begin() + button_index); + recalculate_line(); + } else { + // create a new point + wxPoint point = event.GetPosition(); + if (!m_rect.Contains(point)) // the click is outside the chart + return; + wxPoint2DDouble dblpoint = screen_to_math(point); + // trunc by precision + dblpoint.m_x = int((dblpoint.m_x + this->m_x_legend_incr / 2) / this->m_x_legend_incr) * this->m_x_legend_incr; + dblpoint.m_y = int((dblpoint.m_y + this->m_y_legend_incr / 2) / this->m_y_legend_incr) * this->m_y_legend_incr; + //check it doesn't exist + for (const ButtonToDrag &bt : m_buttons) + if(bt.get_pos().m_x == dblpoint.m_x) + return; + m_buttons.push_back(dblpoint); + std::sort(m_buttons.begin(), m_buttons.end()); recalculate_line(); } } - - void Chart::mouse_clicked(wxMouseEvent& event) { wxPoint point = event.GetPosition(); int button_index = which_button_is_clicked(point); if ( button_index != -1) { m_dragged = &m_buttons[button_index]; - m_previous_mouse = point; + m_previous_mouse = point; + Refresh(); } } - - - + void Chart::mouse_moved(wxMouseEvent& event) { if (!event.Dragging() || !m_dragged) return; wxPoint pos = event.GetPosition(); @@ -129,18 +178,17 @@ void Chart::mouse_moved(wxMouseEvent& event) { if (!(rect.Contains(pos))) { // the mouse left chart area mouse_left_window(event); return; - } + } int delta_x = pos.x - m_previous_mouse.x; int delta_y = pos.y - m_previous_mouse.y; - m_dragged->move(fixed_x?0:double(delta_x)/m_rect.GetWidth() * visible_area.m_width,-double(delta_y)/m_rect.GetHeight() * visible_area.m_height); + m_dragged->move(fixed_x ? 0 : double(delta_x) / m_rect.GetWidth() * visible_area.m_width, + -double(delta_y) / m_rect.GetHeight() * visible_area.m_height); m_previous_mouse = pos; recalculate_line(); } - - void Chart::mouse_double_clicked(wxMouseEvent& event) { - if (!manual_points_manipulation) + if (!m_manual_points_manipulation) return; wxPoint point = event.GetPosition(); if (!m_rect.Contains(point)) // the click is outside the chart @@ -151,8 +199,23 @@ void Chart::mouse_double_clicked(wxMouseEvent& event) { return; } - - +void Chart::mouse_left_window(wxMouseEvent &e) +{ + mouse_released(e); +} +void Chart::mouse_released(wxMouseEvent &) +{ + if (m_dragged != nullptr) { + if (!fixed_x) { + float m_x = int((m_dragged->get_pos().m_x + this->m_x_legend_incr / 2) / this->m_x_legend_incr) * this->m_x_legend_incr; + m_dragged->move(m_x - m_dragged->get_pos().m_x, 0); + } + float m_y = int((m_dragged->get_pos().m_y + this->m_y_legend_incr / 2) / this->m_y_legend_incr) * this->m_y_legend_incr; + m_dragged->move(0, m_y - m_dragged->get_pos().m_y); + m_dragged = nullptr; + recalculate_line(); + } +} void Chart::recalculate_line() { m_line_to_draw.clear(); @@ -243,6 +306,15 @@ void Chart::recalculate_line() { m_line_to_draw.push_back(math_to_screen(wxPoint2DDouble(x_math,m_buttons[i].get_pos().m_y)).y); } + m_line_to_draw.back() = std::max(m_line_to_draw.back(), m_rect.GetTop()-1); + m_line_to_draw.back() = std::min(m_line_to_draw.back(), m_rect.GetBottom()-1); + m_total_volume += (m_rect.GetBottom() - m_line_to_draw.back()) * (visible_area.m_width / m_rect.GetWidth()) * (visible_area.m_height / m_rect.GetHeight()); + } + } else if (points.size() == 1) { + + for (int x=m_rect.GetLeft(); x<=m_rect.GetRight() ; ++x) { + float x_math = screen_to_math(wxPoint(x,0)).m_x; + m_line_to_draw.push_back(math_to_screen(wxPoint2DDouble(x_math,m_buttons[0].get_pos().m_y)).y); m_line_to_draw.back() = std::max(m_line_to_draw.back(), m_rect.GetTop()-1); m_line_to_draw.back() = std::min(m_line_to_draw.back(), m_rect.GetBottom()-1); m_total_volume += (m_rect.GetBottom() - m_line_to_draw.back()) * (visible_area.m_width / m_rect.GetWidth()) * (visible_area.m_height / m_rect.GetHeight()); @@ -253,9 +325,7 @@ void Chart::recalculate_line() { Refresh(); } - - -std::vector Chart::get_ramming_speed(float sampling) const { +std::vector Chart::get_speed(float sampling) const { std::vector speeds_out; const int number_of_samples = std::round( visible_area.m_width / sampling); @@ -270,16 +340,21 @@ std::vector Chart::get_ramming_speed(float sampling) const { return speeds_out; } - std::vector> Chart::get_buttons() const { std::vector> buttons_out; for (const auto& button : m_buttons) buttons_out.push_back(std::make_pair(float(button.get_pos().m_x),float(button.get_pos().m_y))); return buttons_out; } - - - + + +void Chart::set_buttons(std::vector> new_buttons) { + m_buttons.clear(); + for (std::pair &new_button : new_buttons) { + m_buttons.emplace_back(wxPoint2DDouble(new_button.first, new_button.second)); + } + recalculate_line(); +} BEGIN_EVENT_TABLE(Chart, wxWindow) EVT_MOTION(Chart::mouse_moved) diff --git a/src/slic3r/GUI/RammingChart.hpp b/src/slic3r/GUI/RammingChart.hpp index f62546b1d53..5193db98185 100644 --- a/src/slic3r/GUI/RammingChart.hpp +++ b/src/slic3r/GUI/RammingChart.hpp @@ -13,30 +13,40 @@ wxDECLARE_EVENT(EVT_WIPE_TOWER_CHART_CHANGED, wxCommandEvent); class Chart : public wxWindow { public: - Chart(wxWindow* parent, wxRect rect,const std::vector>& initial_buttons,int ramming_speed_size, float sampling, int scale_unit=10) : + Chart(wxWindow* parent, wxRect rect,const std::vector>& initial_buttons, int scale_unit=10) : wxWindow(parent,wxID_ANY,rect.GetTopLeft(),rect.GetSize()), scale_unit(scale_unit), legend_side(5*scale_unit) { SetBackgroundStyle(wxBG_STYLE_PAINT); m_rect = wxRect(wxPoint(legend_side,0),rect.GetSize()-wxSize(legend_side,legend_side)); - visible_area = wxRect2DDouble(0.0, 0.0, sampling*ramming_speed_size, 20.); + visible_area = wxRect2DDouble(0.0, 0.0, 20., 20.); m_buttons.clear(); if (initial_buttons.size()>0) for (const auto& pair : initial_buttons) m_buttons.push_back(wxPoint2DDouble(pair.first,pair.second)); recalculate_line(); } - void set_xy_range(float x,float y) { - x = int(x/0.5) * 0.5; - if (x>=0) visible_area.SetRight(x); - if (y>=0) visible_area.SetBottom(y); + void set_xy_range(float min_x, float min_y, float max_x, float max_y) { + if (min_x >= 0 && max_x > min_x) { + visible_area.SetLeft(min_x); + visible_area.SetRight(max_x); + } + if (min_y >= 0 && max_y > min_y) { + visible_area.SetTop(min_y); + visible_area.SetBottom(max_y); + } recalculate_line(); } + void set_manual_points_manipulation(bool manip) { m_manual_points_manipulation = manip; } + void set_x_label(wxString &label, float incr = 0.1f) { m_x_legend = label; m_x_legend_incr = incr; } + void set_y_label(wxString &label, float incr = 0.1f) { m_y_legend = label; m_y_legend_incr = incr; } + void set_no_point_label(wxString &label) { m_no_point_legend = label; } float get_volume() const { return m_total_volume; } - float get_time() const { return visible_area.m_width; } + float get_max_x() const { return visible_area.m_width; } - std::vector get_ramming_speed(float sampling) const; //returns sampled ramming speed - std::vector> get_buttons() const; // returns buttons position + std::vector get_speed(float sampling) const; //returns sampled ramming speed + std::vector> get_buttons() const; // returns buttons position + void set_buttons(std::vector>); void draw(); @@ -44,8 +54,8 @@ class Chart : public wxWindow { void mouse_right_button_clicked(wxMouseEvent& event); void mouse_moved(wxMouseEvent& event); void mouse_double_clicked(wxMouseEvent& event); - void mouse_left_window(wxMouseEvent&) { m_dragged = nullptr; } - void mouse_released(wxMouseEvent&) { m_dragged = nullptr; } + void mouse_left_window(wxMouseEvent &); + void mouse_released(wxMouseEvent &); void paint_event(wxPaintEvent&) { draw(); } DECLARE_EVENT_TABLE() @@ -55,11 +65,16 @@ class Chart : public wxWindow { private: static const bool fixed_x = true; static const bool splines = true; - static const bool manual_points_manipulation = false; static const int side = 10; // side of draggable button const int scale_unit; int legend_side; + wxString m_x_legend; + wxString m_y_legend; + float m_x_legend_incr = 0.1f; + float m_y_legend_incr = 1.f; + wxString m_no_point_legend; + bool m_manual_points_manipulation = false; class ButtonToDrag { public: diff --git a/src/slic3r/GUI/Search.cpp b/src/slic3r/GUI/Search.cpp index f801e4a6c36..85a6ede914d 100644 --- a/src/slic3r/GUI/Search.cpp +++ b/src/slic3r/GUI/Search.cpp @@ -152,6 +152,8 @@ void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type ty const ConfigDef* defs = config->def(); auto emplace_option = [this, type](const std::string grp_key, const int16_t idx) { + assert(groups_and_categories.find(grp_key) == groups_and_categories.end() + || !groups_and_categories[grp_key].empty()); for (const GroupAndCategory& gc : groups_and_categories[grp_key]) { if (gc.group.IsEmpty() || gc.category.IsEmpty()) return; @@ -159,6 +161,7 @@ void OptionsSearcher::append_options(DynamicPrintConfig* config, Preset::Type ty Option option = create_option(gc.gui_opt.opt_key, idx, type, gc); if (!option.label.empty()) { options.push_back(std::move(option)); + sorted = false; } //wxString suffix; @@ -508,6 +511,7 @@ void OptionsSearcher::check_and_update(PrinterTechnology pt_in, ConfigOptionMode return; options.clear(); + sorted = false; printer_technology = pt_in; current_tags = tags_in; @@ -573,6 +577,18 @@ const Option& OptionsSearcher::get_option(const std::string& opt_key, Preset::Ty size_t pos_hash = opt_key.find('#'); if (pos_hash == std::string::npos) { auto it = std::lower_bound(options.begin(), options.end(), Option({ boost::nowide::widen(opt_key), type, idx })); +#ifdef _DEBUG + if (options[it - options.begin()].opt_key_with_idx() != opt_key) { + std::wstring wopt_key = boost::nowide::widen(opt_key); + for (const Option &opt : options) { + if (opt.key == wopt_key) { + if (opt.type == type) { + std::cout << "found\n"; + } + } + } + } +#endif assert(it != options.end()); return options[it - options.begin()]; } else { @@ -580,6 +596,17 @@ const Option& OptionsSearcher::get_option(const std::string& opt_key, Preset::Ty std::string opt_idx = opt_key.substr(pos_hash + 1); idx = atoi(opt_idx.c_str()); auto it = std::lower_bound(options.begin(), options.end(), Option({ boost::nowide::widen(raw_opt_key), type, idx })); +#ifdef _DEBUG + if (options[it - options.begin()].opt_key_with_idx() != opt_key) { + for (const Option &opt : options) { + if (opt.opt_key_with_idx() == opt_key) { + if (opt.type == type) { + std::cout << "found\n"; + } + } + } + } +#endif assert(it != options.end()); return options[it - options.begin()]; } diff --git a/src/slic3r/GUI/Search.hpp b/src/slic3r/GUI/Search.hpp index c836379214d..a4a2e8ec7f0 100644 --- a/src/slic3r/GUI/Search.hpp +++ b/src/slic3r/GUI/Search.hpp @@ -106,6 +106,7 @@ class OptionsSearcher ConfigOptionMode current_tags {comNone}; std::vector