diff --git a/build_win.bat b/build_win.bat index c447d8a71f..840279e4f3 100644 --- a/build_win.bat +++ b/build_win.bat @@ -52,6 +52,7 @@ call :add_section "Build configuration" call :add_arg config string "" config "release, debug, relwithdebinfo or minsizerel (default: release)" call :add_arg target_arch string "" arch "x64 or arm64 (default: the host architecture)" call :add_arg slicer_asan bool a asan "Build the slicer with ASAN enabled" +call :add_arg no_pch bool "" no-pch "Build without the precompiled header, implied by --cache" call :add_section "Toolchain" call :add_arg use_clang_cl bool l clang-cl "Use clang-cl as the compiler" @@ -60,6 +61,7 @@ call :add_arg use_ninja bool x ninja "Use the Ninja Multi-Config generator" call :add_arg use_msbuild bool "" msbuild "Use the Visual Studio generator (default)" call :add_arg vs_version string "" vs "Visual Studio release: 2019, 2022 or 2026 (default: autodetect)" call :add_arg clang_path string "" clang-path "Path to clang-cl.exe, requires -x (default: the one from Visual Studio)" +call :add_arg cache string "" cache "Compiler cache: ccache, sccache or off, requires -l -x (default: off)" call :add_section "How much gets rebuilt" call :add_arg slicer_target string "" slicer-target "Build one slicer target instead of all, e.g. libslic3r" @@ -391,6 +393,49 @@ if not "%clang_path%" == "" if not "%using_ninja%" == "ON" ( exit /b 1 ) +set "cache_args=" +if "%cache%" == "" goto :cache_ready +if /I "%cache%" == "off" goto :cache_ready +if /I "%cache%" == "ccache" goto :cache_named +if /I "%cache%" == "sccache" goto :cache_named +echo Unknown --cache value "%cache%". Expected ccache, sccache or off. +exit /b 1 + +REM CMake accepts a compiler launcher under any generator but only runs it +REM under Makefile and Ninja. ccache also refuses cl.exe because the build +REM passes /Zi. +:cache_named +REM Only a configure records the launcher, so --no-configure needs none. +if "%no_configure%" == "ON" goto :cache_ready +if "%build_deps%%build_slicer%" == "" goto :cache_ready +if not "%using_ninja%" == "ON" goto :cache_needs_clang +if not "%use_clang_cl%" == "ON" goto :cache_needs_clang +goto :cache_tool + +:cache_needs_clang +echo --cache needs clang-cl and Ninja; add -l -x. +exit /b 1 + +REM Name a full path, so the recorded launcher does not depend on PATH. +:cache_tool +set "cache_exe=" +for /f "tokens=*" %%i in ('where %cache% 2^>nul') do ( + if not defined cache_exe set "cache_exe=%%i" +) +if not defined cache_exe ( + echo %cache% is not on PATH. Install it, or leave --cache off. + exit /b 1 +) +REM Forward slashes, so CMake does not read a backslash as an escape. +set "cache_exe=%cache_exe:\=/%" +set "cache_args=-DCMAKE_C_COMPILER_LAUNCHER="%cache_exe%" -DCMAKE_CXX_COMPILER_LAUNCHER="%cache_exe%"" +REM Neither cache stores a compile that uses a precompiled header. sccache +REM refuses /Fp outright. ccache does too unless its sloppiness is loosened, +REM and even then most hits fall back to the slower preprocessed mode. +set "no_pch=ON" + +:cache_ready + REM Ninja prints [123/456] by default, which says nothing about how far REM along it is or how long is left. %p is a percentage, %w and %W are REM elapsed and remaining, but those two arrived in ninja 1.12 and an @@ -474,6 +519,8 @@ REM real path rather than one glued onto the repository root. for %%p in ("!build_dir!") do set "build_full=%%~fp" echo Configuration: %build_type%, %arch% if not "%clang_exe%" == "" echo Compiler: %clang_exe% +if "%no_pch%" == "ON" echo Precompiled header: off +if not "%cache_args%" == "" echo Compiler cache: %cache_exe% set "SIG_FLAG=" if defined ORCA_UPDATER_SIG_KEY set "SIG_FLAG=-DORCA_UPDATER_SIG_KEY=%ORCA_UPDATER_SIG_KEY%" @@ -570,6 +617,10 @@ if "%build_deps%" == "ON" ( %error_check% ) + if not "!cache_args!" == "" ( + set "deps_args=!deps_args! !cache_args!" + ) + if not "%no_configure%" == "ON" ( call :print_and_run cmake -S deps -B "!DEP_TREE!" -G "%generator%" %gen_args% -DCMAKE_BUILD_TYPE=%build_type% !deps_args! %ORCA_DEPS_CMAKE_ARGS% %error_check% @@ -641,6 +692,16 @@ if "%build_slicer%" == "ON" ( set "slicer_args=!slicer_args! -DSLIC3R_ASAN=ON" ) + REM A later -DSLIC3R_PCH=ON in ORCA_SLICER_CMAKE_ARGS still wins, because + REM CMake takes the last definition on the command line. + if "%no_pch%" == "ON" ( + set "slicer_args=!slicer_args! -DSLIC3R_PCH=OFF" + ) + + if not "!cache_args!" == "" ( + set "slicer_args=!slicer_args! !cache_args!" + ) + REM Configuring against a tree that was never built fails deep inside REM package resolution. Name it here instead. Skipped when -d is about to REM build it in this same run, and under a dry run, which configures @@ -870,6 +931,7 @@ REM get_str_len -> length in %ret% echo %script_name% -s --no-configure -j 8 Rebuild quickly while iterating echo %script_name% -s --slicer-target glad Compile one target to check the toolchain echo %script_name% -l -x --run-tests Test that toolchain's build, not the default one + echo %script_name% -s -l -x --cache ccache Rebuild through a compiler cache echo. echo Environment: echo ORCA_DEPS_CMAKE_ARGS Extra arguments for the deps configure @@ -879,8 +941,8 @@ REM get_str_len -> length in %ret% echo NINJA_STATUS Ninja progress format, if you want your own echo debugscript Set to ON to trace this script echo. - echo set ORCA_SLICER_CMAKE_ARGS=-DSLIC3R_PCH=OFF -DSLIC3R_MSVC_PDB=OFF - echo $env:ORCA_SLICER_CMAKE_ARGS = '-DSLIC3R_PCH=OFF' (PowerShell) + echo set ORCA_SLICER_CMAKE_ARGS=-DSLIC3R_BUILD_SANDBOXES=ON -DSLIC3R_WARNINGS=OFF + echo $env:ORCA_SLICER_CMAKE_ARGS = '-DSLIC3R_BUILD_SANDBOXES=ON' (PowerShell) echo. echo --deps-args and --slicer-args cannot carry a value with spaces; use echo these instead. Neither form supports a value containing an ampersand. diff --git a/resources/images/param_spiralinset.svg b/resources/images/param_spiralinset.svg new file mode 100644 index 0000000000..6ec1ab36aa --- /dev/null +++ b/resources/images/param_spiralinset.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/scripts/test_build_win.ps1 b/scripts/test_build_win.ps1 index 563430559f..34f64117af 100644 --- a/scripts/test_build_win.ps1 +++ b/scripts/test_build_win.ps1 @@ -92,6 +92,12 @@ New-Item -ItemType Directory -Force -Path $clangDir | Out-Null Copy-Item "$env:SystemRoot\System32\where.exe" (Join-Path $clangDir 'clang-cl.exe') -Force $clangOnPath = "$clangDir;$env:PATH" +# A ccache that only has to exist. Nothing runs it; the script only locates it. +$cacheDir = Join-Path $fixtures 'cache' +New-Item -ItemType Directory -Force -Path $cacheDir | Out-Null +Copy-Item "$env:SystemRoot\System32\where.exe" (Join-Path $cacheDir 'ccache.exe') -Force +$ccacheOnPath = "$cacheDir;$env:PATH" + # ProgramFiles(x86) is where the script looks for vswhere, so an empty one # stands in for a machine whose Visual Studio has no clang toolset. $noVs = Join-Path $fixtures 'no-vs' @@ -121,7 +127,7 @@ $cases = @( 'Examples:', 'Environment:') } @{ Name = 'the environment section shows what to set'; Args = @('--help'); DryRun = $false Contains = @('ORCA_DEPS_CMAKE_ARGS', 'ORCA_SLICER_CMAKE_ARGS', 'ORCA_UPDATER_SIG_KEY', 'NINJA_STATUS', - 'set ORCA_SLICER_CMAKE_ARGS=-DSLIC3R_PCH=OFF', '(PowerShell)', 'debugscript') } + 'set ORCA_SLICER_CMAKE_ARGS=-DSLIC3R_BUILD_SANDBOXES=ON', '(PowerShell)', 'debugscript') } @{ Name = 'section headers do not widen the flag column'; Args = @('--help'); DryRun = $false Match = @('^ -d, --deps +Download') } # Windows Terminal opens at 120 columns and wraps at 120, so 119 is the @@ -294,6 +300,14 @@ $cases = @( Contains = @('-DBUILD_TESTS=ON') } @{ Name = '-a enables ASAN for the slicer'; Args = @('-s', '-a') Contains = @('-DSLIC3R_ASAN=ON') } + @{ Name = '--no-pch turns the precompiled header off'; Args = @('-s', '--no-pch') + Contains = @('-DSLIC3R_PCH=OFF') } + @{ Name = '--no-pch says so in the banner'; Args = @('-s', '--no-pch') + Contains = @('Precompiled header: off') } + @{ Name = 'the precompiled header is on unless asked'; Args = @('-s') + NotContains = @('SLIC3R_PCH') } + @{ Name = '--no-pch works without a cache'; Args = @('-s', '--no-pch') + NotContains = @('COMPILER_LAUNCHER') } @{ Name = 'the slicer build runs gettext'; Args = @('-s') Contains = @('run_gettext.bat') } # tools\7z.exe needs a 7z.dll beside it, which the repo does not carry, @@ -324,6 +338,42 @@ $cases = @( @{ Name = 'deps and slicer build in one invocation'; Args = @('-d', '-s', '-x', '-l') Contains = @('cmake -S deps', 'cmake -B "build-clang" ') } + 'the compiler cache' + @{ Name = '--cache needs clang-cl and Ninja'; Args = @('-s', '--cache', 'ccache'); ExpectExit = 1 + Contains = @('needs clang-cl and Ninja') } + # cl.exe is out of scope, since ccache refuses every compile under /Zi. + @{ Name = '--cache under Ninja still needs clang-cl'; Args = @('-s', '-x', '--cache', 'ccache'); ExpectExit = 1 + Contains = @('needs clang-cl and Ninja') } + @{ Name = 'an unknown --cache value is rejected'; Args = @('-s', '-x', '--cache', 'nope'); ExpectExit = 1 + Contains = @('Expected ccache, sccache or off') } + # A bare PATH, since the machine running the tests may have sccache installed. + @{ Name = 'a --cache tool that is not there is caught early'; Args = @('-s', '-l', '-x', '--cache', 'sccache'); ExpectExit = 1 + Env = @{ PATH = 'C:\Windows\system32;C:\Windows' } + Contains = @('is not on PATH') } + @{ Name = '--cache takes any casing'; Args = @('-s', '-l', '-x', '--cache', 'CCACHE') + Env = @{ PATH = $ccacheOnPath } + Contains = @('ccache.exe') } + @{ Name = '--cache off asks for no launcher'; Args = @('-s', '-x', '--cache', 'off') + NotContains = @('COMPILER_LAUNCHER') } + @{ Name = 'no --cache asks for no launcher'; Args = @('-s', '-x') + NotContains = @('COMPILER_LAUNCHER') } + @{ Name = '--cache turns the precompiled header off'; Args = @('-s', '-l', '-x', '--cache', 'ccache') + Env = @{ PATH = $ccacheOnPath } + Contains = @('-DSLIC3R_PCH=OFF', 'COMPILER_LAUNCHER') } + # The resolved path, not the bare name, so PATH cannot change it later. + @{ Name = '--cache names the resolved path in the banner'; Args = @('-s', '-l', '-x', '--cache', 'ccache') + Env = @{ PATH = $ccacheOnPath } + Match = @('^Compiler cache: .*/ccache\.exe$') } + @{ Name = '--cache reaches the dependency configure too'; Args = @('-d', '-l', '-x', '--cache', 'ccache') + Env = @{ PATH = $ccacheOnPath } + Contains = @('-DCMAKE_C_COMPILER_LAUNCHER=') } + # Nothing records a launcher without a configure, so the tool is not needed. + # Reaching the cmake check on a bare PATH is what proves it was skipped. + @{ Name = '--no-configure asks for no cache tool'; Args = @('-s', '-l', '-x', '--no-configure', '--cache', 'ccache'); ExpectExit = 1 + Env = @{ PATH = 'C:\Windows\system32;C:\Windows' } + Contains = @('CMake was not found') + NotContains = @('is not on PATH') } + 'the developer loop' @{ Name = '--slicer-target builds one target'; Args = @('-s', '--slicer-target', 'libslic3r') Contains = @('--config Release --target libslic3r') } diff --git a/src/libslic3r/BlacklistedLibraryCheck.cpp b/src/libslic3r/BlacklistedLibraryCheck.cpp index 938f542497..cf7e6da2aa 100644 --- a/src/libslic3r/BlacklistedLibraryCheck.cpp +++ b/src/libslic3r/BlacklistedLibraryCheck.cpp @@ -1,6 +1,7 @@ #include "BlacklistedLibraryCheck.hpp" #include +#include #include #ifdef WIN32 diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index 2880a3cc6b..d07c42d8f6 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -147,6 +147,8 @@ set(lisbslic3r_sources Fill/FillBase.hpp Fill/FillConcentric.cpp Fill/FillConcentric.hpp + Fill/FillSpiralInset.cpp + Fill/FillSpiralInset.hpp Fill/FillConcentricInternal.cpp Fill/FillConcentricInternal.hpp Fill/FillCornerSmoothing.cpp @@ -663,6 +665,13 @@ if(SLIC3R_PROFILE) target_link_libraries(libslic3r PRIVATE Shiny) endif() +if (WIN32) + # Public, since BlacklistedLibraryCheck.hpp includes windows.h. Empty + # WIN32_LEAN_AND_MEAN matches the sources that define it themselves; bare + # NOMINMAX matches the one libigl already passes. + target_compile_definitions(libslic3r PUBLIC "WIN32_LEAN_AND_MEAN=" "NOMINMAX") +endif () + if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE) endif () diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 0888e1bb55..dc772580ca 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -950,7 +950,7 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p params.extruder = region_config.internal_solid_filament_id; // Orca: forced fill order applies only to top/bottom surfaces filled with a // center-based pattern; everything else stays at Default to keep batching together. - if (params.pattern == ipConcentric || params.pattern == ipArchimedeanChords || params.pattern == ipOctagramSpiral) { + if (params.pattern == ipConcentric || params.pattern == ipSpiralInset || params.pattern == ipArchimedeanChords || params.pattern == ipOctagramSpiral) { if (params.extrusion_role == erTopSolidInfill) params.fill_order = region_config.top_surface_fill_order.value; else if (params.extrusion_role == erBottomSurface) @@ -1332,7 +1332,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive: params.anchor_length = surface_fill.params.anchor_length; params.anchor_length_max = surface_fill.params.anchor_length_max; params.resolution = resolution; - params.use_arachne = surface_fill.params.pattern == ipConcentric || surface_fill.params.pattern == ipConcentricInternal; + params.use_arachne = surface_fill.params.pattern == ipConcentric || surface_fill.params.pattern == ipSpiralInset || + surface_fill.params.pattern == ipConcentricInternal; params.layer_height = layerm->layer()->height; params.lateral_lattice_angle_1 = surface_fill.params.lateral_lattice_angle_1; params.lateral_lattice_angle_2 = surface_fill.params.lateral_lattice_angle_2; @@ -1515,6 +1516,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc case ipCubic: case ipLine: case ipConcentric: + case ipSpiralInset: case ipHoneycomb: case ipLateralHoneycomb: case ip3DHoneycomb: diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index 45157ec42d..2b307007b8 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -15,6 +15,7 @@ #include "FillBase.hpp" #include "FillConcentric.hpp" +#include "FillSpiralInset.hpp" #include "FillHoneycomb.hpp" #include "Fill3DHoneycomb.hpp" #include "FillGyroid.hpp" @@ -41,6 +42,7 @@ Fill* Fill::new_from_type(const InfillPattern type) { switch (type) { case ipConcentric: return new FillConcentric(); + case ipSpiralInset: return new FillSpiralInset(); case ipHoneycomb: return new FillHoneycomb(); case ipLateralHoneycomb: return new FillLateralHoneycomb(); case ip3DHoneycomb: return new Fill3DHoneycomb(); diff --git a/src/libslic3r/Fill/FillSpiralInset.cpp b/src/libslic3r/Fill/FillSpiralInset.cpp new file mode 100644 index 0000000000..e6323c2572 --- /dev/null +++ b/src/libslic3r/Fill/FillSpiralInset.cpp @@ -0,0 +1,426 @@ +#include "../ClipperUtils.hpp" +#include "../ExPolygon.hpp" +#include "../Surface.hpp" +#include "../VariableWidth.hpp" +#include "Arachne/WallToolPaths.hpp" + +#include "FillSpiralInset.hpp" + +#include +#include +#include + +namespace Slic3r { + +// Index of the corner the spiral should start at. Every following loop is split at the point nearest +// the end of the one before it, so this choice propagates inwards and decides where the whole spiral +// hands over from ring to ring. A tight corner is the worst place for it: there the next ring +// retreats along the bisector by spacing/sin(angle), so the spiral has to strike out several spacings +// to reach it instead of stepping across to a ring running parallel one spacing away. +// +// A right angle is taken first when the loop has one. It clips cleanly, since the trimming below +// scales with 1/sin(angle) and so is at its shortest and least sensitive there, and it holds its +// shape as the loop is offset inwards, which keeps the handover in the same place ring after ring. +// Failing that the widest corner is the flattest stretch on offer, which is the next best handover. +// A straight point is no corner at all and only turns up as an artefact of the offsetting, so it is +// skipped. +static int find_spiral_start_corner(const Polygon& loop) +{ + const size_t n = loop.points.size(); + if (n < 3) + return 0; + + // cos(85 deg): a corner within five degrees of square counts as a right angle. + static const double right_angle_cos = 0.08716; + // cos(179 deg): anything flatter than this counts as a straight point rather than a corner. + static const double straight_cos = -0.99985; + + // Only convex corners qualify. A reflex corner spans the same angle between its two edges but + // bulges the other way, so the next ring in steps away from it along the bisector instead of + // hugging it, and starting there hands over across a long diagonal on every single ring. Loops + // arrive counter-clockwise, in which case a convex corner turns left, but check the winding + // rather than trust it. A closed loop always has at least one convex corner. + const double convex_turn = loop.is_counter_clockwise() ? 1.0 : -1.0; + + double best_right_cos = right_angle_cos; + int best_right = -1; + double best_wide_cos = 1.0; + int best_wide = -1; + for (size_t i = 0; i < n; ++i) { + const Point& p_prev = loop.points[(i - 1 + n) % n]; + const Point& p = loop.points[i]; + const Point& p_next = loop.points[(i + 1) % n]; + + Vec2d e_in = (p - p_prev).cast(); + Vec2d e_out = (p_next - p).cast(); + double len1 = e_in.norm(); + double len2 = e_out.norm(); + if (len1 < 1e-6 || len2 < 1e-6) + continue; + if (convex_turn * (e_in.x() * e_out.y() - e_in.y() * e_out.x()) <= 0.0) + continue; + + // Cosine of the angle the two edges span at the corner: 1 at a spike, 0 square, -1 straight. + double cos_val = -e_in.dot(e_out) / (len1 * len2); + if (std::abs(cos_val) < best_right_cos) { + best_right_cos = std::abs(cos_val); + best_right = int(i); + } + if (cos_val > straight_cos && cos_val < best_wide_cos) { + best_wide_cos = cos_val; + best_wide = int(i); + } + } + if (best_right >= 0) + return best_right; + // A loop smooth enough to have no corner at all, a circle say, hands over equally well anywhere. + return best_wide < 0 ? 0 : best_wide; +} + +// Length to trim off the end of a loop so that it does not overlap the start of the next one. +// The theoretical gap is distance/sin(alpha), alpha being the angle between the last segment of the +// loop and the first segment of the next one. +static double loop_clip_length(const Polyline& loop_path, const double gap) +{ + const Point& p_prev = loop_path.points[loop_path.points.size() - 2]; + const Point& p_last = loop_path.points.back(); + const Point& p_next = loop_path.points[1]; + Vec2d v1 = (p_last - p_prev).cast(); + Vec2d v2 = (p_next - p_last).cast(); + if (v1.norm() < 1e-6 || v2.norm() < 1e-6) + return gap; + + double alpha = std::atan2(std::abs(v1.x() * v2.y() - v1.y() * v2.x()), v1.dot(v2)); + // Outside 45deg < alpha < 120deg the 1/sin(alpha) term would clip far too much, so fall back to the plain gap. + return (alpha > M_PI / 4 && alpha < 2 * M_PI / 3) ? gap / std::sin(alpha) : gap; +} + +// The chaining below drives two kinds of loop: the plain offset polygons of the classic path, and +// Arachne's variable width walls. These are the only four steps that differ between them. Widths run +// two per segment, so every point added or removed takes a pair with it. +static Polyline open_loop(const Polygon& loop, int start_index) { return loop.split_at_index(start_index); } + +static ThickPolyline open_loop(const Arachne::ExtrusionLine& loop, int start_index) +{ + ThickPolyline path = Arachne::to_thick_polyline(loop); + // start_at_index() rotates a closed path, and wants it closed with a matching width at both ends. + if (path.points.front() != path.points.back()) { + const coordf_t w_first = path.width.front(), w_last = path.width.back(); + path.points.emplace_back(path.points.front()); + path.width.emplace_back(w_last); + path.width.emplace_back(w_first); + } + path.start_at_index(start_index); + return path; +} + +static void clip_path_end(Polyline& path, double distance) { path.clip_end(distance); } + +static void clip_path_end(ThickPolyline& path, double distance) +{ + // Polyline::clip_end() knows nothing about the widths, so walk back trimming the two together. + while (distance > 0 && path.points.size() >= 2) { + const Point last = path.points.back(); + const coordf_t w_end = path.width.back(); + path.points.pop_back(); + path.width.pop_back(); + const coordf_t w_start = path.width.back(); + path.width.pop_back(); + + const Vec2d v = (path.points.back() - last).cast(); + const double len = v.norm(); + if (len > distance) { + const double t = distance / len; + path.points.emplace_back((last.cast() + v * t).cast()); + path.width.emplace_back(w_start); + path.width.emplace_back(w_start + (w_end - w_start) * (1.0 - t)); + return; + } + distance -= len; + } + path.clear(); +} + +static void append_path(Polyline& dst, Polyline&& src) { dst.append(std::move(src)); } + +static void append_path(ThickPolyline& dst, ThickPolyline&& src) +{ + if (dst.empty()) { + dst = std::move(src); + return; + } + if (dst.points.back() == src.points.front()) { + // Carrying straight on from the same point, so there is no run across to give a width to. + src.points.erase(src.points.begin()); + src.width.erase(src.width.begin(), src.width.begin() + 2); + } else { + // The run across to the next loop tapers between the two ends it joins. + const coordf_t w_from = dst.width.back(), w_to = src.width.front(); + dst.width.emplace_back(w_from); + dst.width.emplace_back(w_to); + } + append(dst.points, std::move(src.points)); + append(dst.width, std::move(src.width)); +} + +// The classic loops all carry the same width, so the innermost one of an island can still ring an +// unfilled pin hole, which the spiral plugs by running into the middle. Arachne's walls widen to take +// up whatever is left over, so there is nothing there to plug and the stub would only double back +// over the wall that just filled it. +static bool leaves_a_centre_hole(const Polygon&) { return true; } +static bool leaves_a_centre_hole(const Arachne::ExtrusionLine&) { return false; } + +static void append_path_point(Polyline& path, const Point& point) { path.points.emplace_back(point); } + +static void append_path_point(ThickPolyline& path, const Point& point) +{ + const coordf_t w = path.width.back(); + path.points.emplace_back(point); + path.width.emplace_back(w); + path.width.emplace_back(w); +} + +// Chain the loops of one surface into as few continuous spirals as its shape allows. The loops arrive +// ordered outside in, depth first, each paired with its outline in loop_outlines; every decision here +// is made on those outlines, so the two kinds of loop take exactly the same route. +template +static std::vector generate_spiral_insets(const FillParams& params, + const std::vector& loops, + const Polygons& loop_outlines, + const coord_t distance, + const ExPolygon& original_expoly) +{ + std::vector output; + PathType spiral; + Point current_pos(0, 0); + // Index into loops of the innermost loop appended to the spiral currently being built. + int innermost_loop = -1; + + // Whether the spiral can run straight from one point to the other. The run across is extruded, + // not travelled, so it has to be a genuine step over to the ring alongside: + // - up to a ring spacing and a half it cannot leave the material, and needs no check at all, + // which covers all but a few of the loops; + // - beyond that it is tested against the surface, which catches the points that are close in a + // straight line but separated by a hole or a notch; + // - past four spacings it is refused outright. A handover does stretch at a corner, where the + // next ring retreats along the bisector by spacing/sin(angle), but four spacings is already a + // fifteen degree wedge, and down a wedge that tight the run across would trace the bisector, + // which is where the tail is filled from anyway. Anything longer is a traverse across the + // surface that prints over what it crosses. Breaking the spiral leaves the G-code to travel it. + const double free_hop = 1.5 * double(distance); + const double max_hop = 4.0 * double(distance); + auto reachable = [&](const Point& from, const Point& to) { + const double hop = from.distance_to(to); + if (hop > max_hop) + return false; + return hop <= free_hop || original_expoly.contains(Line(from, to)); + }; + + // The centre point plugs the pin hole left in the middle of an island, it is not meant to + // traverse it, so it is only worth adding when the innermost loop has shrunk to about a ring. + const double max_center_stub = 2.0 * double(distance); + + // Emit the spiral built so far as one path and start over on a fresh island. + auto flush_spiral = [&]() { + if (spiral.empty()) + return; + // Run into the middle of the innermost loop so the island's centre is filled instead of being + // left as a pin hole. Only where there is a hole to fill: the loop has to still enclose open + // space once its own bead is accounted for, or the stub just runs back over that bead. And + // the point has to sit inside the loop and be reachable, or it runs off across the surface. + if (innermost_loop >= 0 && leaves_a_centre_hole(*loops[innermost_loop])) { + const Polygon& innermost = loop_outlines[innermost_loop]; + const Point centroid = innermost.centroid(); + if (!offset(innermost, -float(0.5 * double(distance))).empty() && centroid != spiral.last_point() && + spiral.last_point().distance_to(centroid) <= max_center_stub && innermost.contains(centroid) && + reachable(spiral.last_point(), centroid)) + append_path_point(spiral, centroid); + } + output.emplace_back(std::move(spiral)); + spiral.clear(); + innermost_loop = -1; + current_pos = Point(0, 0); + }; + + for (size_t i = 0; i < loops.size(); ++i) { + const Polygon& outline = loop_outlines[i]; + if (outline.points.empty()) + continue; + + // The loop is opened into a path with the split point repeated at both ends, so a usable one + // has at least 3 points. Both kinds of loop share the outline's indices, hence its start point. + PathType loop_path = open_loop(*loops[i], spiral.empty() ? find_spiral_start_corner(outline) : + current_pos.nearest_point_index(outline.points)); + if (loop_path.size() < 3) + continue; + + // Island jumping: the loops are ordered by their nesting, depth first, so the next one + // continues the current spiral exactly when it lies inside the one just laid down. Distance + // cannot stand in for that test: at a sharp corner the next ring retreats along the bisector + // by spacing/sin(angle), which leaves it several spacings away while still being the very + // next ring in, and the spiral would break off at every spike. + const bool same_island = innermost_loop >= 0 && loop_outlines[innermost_loop].contains(loop_path.points.front()); + if (!spiral.empty() && (!same_island || !reachable(spiral.last_point(), loop_path.points.front()))) { + flush_spiral(); + loop_path = open_loop(*loops[i], find_spiral_start_corner(outline)); + if (loop_path.size() < 3) + continue; + } + + // Clip the end of the loop to leave room for the run into the next one. The last loop of the + // surface has no successor, so it only gives up half of the gap. + clip_path_end(loop_path, loop_clip_length(loop_path, (i + 1 == loops.size() ? 0.5 : 1.0) * double(distance))); + // Clipping empties the path when the loop is shorter than the clipping length, which happens + // on the degenerate slivers that offsetting leaves behind. Such a loop carries no extrusion. + if (loop_path.size() < 2) + continue; + + append_path(spiral, std::move(loop_path)); + innermost_loop = int(i); + current_pos = spiral.last_point(); + } + + flush_spiral(); + + // An outward fill order runs every spiral from its centre to its outer edge, innermost island first. + if (params.fill_order != SurfaceFillOrder::Inward) { + for (PathType& path : output) + path.reverse(); + std::reverse(output.begin(), output.end()); + } + + return output; +} + +void FillSpiralInset::_fill_surface_single(const FillParams& params, + unsigned int thickness_layers, + const std::pair& direction, + ExPolygon expolygon, + Polylines& polylines_out) +{ + BoundingBox bounding_box = expolygon.contour.bounding_box(); + + coord_t min_spacing = scale_(this->spacing); + coord_t distance = coord_t(min_spacing / params.density); + + if (params.density > 0.9999f && !params.dont_adjust) { + distance = this->_adjust_solid_spacing(bounding_box.size()(0), distance); + this->spacing = unscale(distance); + } + + Polygons loops = to_polygons(expolygon); + + ExPolygons last{std::move(expolygon)}; + while (!last.empty()) { + last = offset2_ex(last, -(distance + min_spacing / 2), +min_spacing / 2); + append(loops, to_polygons(last)); + } + + // Orders the loops outside in, depth first, which is the order the chaining below expects. + loops = union_pt_chained_outside_in(loops); + + std::vector loop_refs; + loop_refs.reserve(loops.size()); + for (const Polygon& loop : loops) + loop_refs.emplace_back(&loop); + + Polylines spiral_result = generate_spiral_insets(params, loop_refs, loops, distance, expolygon); + + append(polylines_out, spiral_result); +} + +void FillSpiralInset::_fill_surface_single(const FillParams& params, + unsigned int thickness_layers, + const std::pair& direction, + ExPolygon expolygon, + ThickPolylines& thick_polylines_out) +{ + assert(params.use_arachne); + assert(this->print_config != nullptr && this->print_object_config != nullptr); + + // Only a solid surface is worth the variable width walls; a sparse one falls back to plain loops. + if (params.density <= 0.9999f || params.dont_adjust) { + Polylines polylines; + this->_fill_surface_single(params, thickness_layers, direction, expolygon, polylines); + append(thick_polylines_out, to_thick_polylines(std::move(polylines), scaled(this->spacing))); + return; + } + + // no rotation is supported for this infill pattern + Point bbox_size = expolygon.contour.bounding_box().size(); + coord_t min_spacing = scaled(this->spacing); + + coord_t loops_count = std::max(bbox_size.x(), bbox_size.y()) / min_spacing + 1; + Polygons polygons = offset(expolygon, float(min_spacing) / 2.f); + + double min_nozzle_diameter = *std::min_element(print_config->nozzle_diameter.values.begin(), print_config->nozzle_diameter.values.end()); + Arachne::WallToolPathsParams input_params; + input_params.min_bead_width = 0.85 * min_nozzle_diameter; + input_params.min_feature_size = 0.25 * min_nozzle_diameter; + input_params.wall_transition_length = 1.0 * min_nozzle_diameter; + input_params.wall_transition_angle = 10; + input_params.wall_transition_filter_deviation = 0.25 * min_nozzle_diameter; + input_params.wall_distribution_count = 1; + + Arachne::WallToolPaths wallToolPaths(polygons, min_spacing, min_spacing, loops_count, 0, params.layer_height, input_params); + std::vector walls_by_inset = wallToolPaths.getToolPaths(); + + // Open walls are the thin features Arachne fits between the closed ones. They cannot join a + // spiral, so they go out as they are; leaving them behind is what would put the gaps back. + std::vector walls; + Polygons wall_outlines; + ThickPolylines open_walls; + for (const Arachne::VariableWidthLines& inset : walls_by_inset) + for (const Arachne::ExtrusionLine& wall : inset) { + if (wall.empty()) + continue; + if (wall.is_closed) { + walls.emplace_back(&wall); + wall_outlines.emplace_back(wall.toPolygon()); + } else { + open_walls.emplace_back(Arachne::to_thick_polyline(wall)); + } + } + + // Arachne hands the walls back grouped by inset, which is not their nesting: around a hole the + // wall of a given inset lies inside the wall of that same inset around the contour. Nest them by + // containment instead, so the spiral follows one island all the way in before starting the next, + // the same order union_pt_chained_outside_in gives the classic path above. + const size_t wall_count = walls.size(); + std::vector nesting_depth(wall_count, 0), parent(wall_count, -1); + std::vector inside(wall_count * wall_count, 0); + for (size_t i = 0; i < wall_count; ++i) + for (size_t j = 0; j < wall_count; ++j) + if (i != j && wall_outlines[j].contains(walls[i]->junctions.front().p)) { + inside[i * wall_count + j] = 1; + ++nesting_depth[i]; + } + // The innermost of the walls containing this one, which is the deepest of them, is its parent. + for (size_t i = 0; i < wall_count; ++i) + for (size_t j = 0; j < wall_count; ++j) + if (inside[i * wall_count + j] && (parent[i] < 0 || nesting_depth[parent[i]] < nesting_depth[j])) + parent[i] = int(j); + + std::vector ordered; + Polygons outlines; + ordered.reserve(wall_count); + outlines.reserve(wall_count); + std::function descend = [&](int idx) { + ordered.emplace_back(walls[idx]); + outlines.emplace_back(wall_outlines[idx]); + for (size_t k = 0; k < wall_count; ++k) + if (parent[k] == idx) + descend(int(k)); + }; + for (size_t i = 0; i < wall_count; ++i) + if (parent[i] < 0) + descend(int(i)); + + ThickPolylines spiral_result = + generate_spiral_insets(params, ordered, outlines, min_spacing, expolygon); + + append(thick_polylines_out, std::move(spiral_result)); + append(thick_polylines_out, std::move(open_walls)); +} + +} // namespace Slic3r diff --git a/src/libslic3r/Fill/FillSpiralInset.hpp b/src/libslic3r/Fill/FillSpiralInset.hpp new file mode 100644 index 0000000000..eb799ec2f3 --- /dev/null +++ b/src/libslic3r/Fill/FillSpiralInset.hpp @@ -0,0 +1,37 @@ +#ifndef slic3r_FillSpiralInset_hpp_ +#define slic3r_FillSpiralInset_hpp_ + +#include "FillBase.hpp" + +namespace Slic3r { + +class FillSpiralInset : public Fill +{ +public: + ~FillSpiralInset() override = default; + bool is_self_crossing() override { return false; } + +protected: + Fill* clone() const override { return new FillSpiralInset(*this); }; + void _fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon expolygon, + Polylines &polylines_out) override; + + // Orca: solid surfaces are filled with Arachne's variable width walls, which widen to take up + // whatever the fixed width loops above would have left over as gaps. + void _fill_surface_single( + const FillParams ¶ms, + unsigned int thickness_layers, + const std::pair &direction, + ExPolygon expolygon, + ThickPolylines &thick_polylines_out) override; + + bool no_sort() const override { return true; } +}; + +} // namespace Slic3r + +#endif // slic3r_FillSpiralInset_hpp_ diff --git a/src/libslic3r/Int128.hpp b/src/libslic3r/Int128.hpp index f8951f20f9..e7238ca745 100644 --- a/src/libslic3r/Int128.hpp +++ b/src/libslic3r/Int128.hpp @@ -125,7 +125,6 @@ public: /******************************************** Splitting the 128bit number into two 64bit words *********************************************/ Int128(int64_t lo = 0) : m_lo((uint64_t)lo), m_hi((lo < 0) ? -1 : 0) {} - Int128(const Int128 &val) : m_lo(val.m_lo), m_hi(val.m_hi) {} Int128(const int64_t& hi, const uint64_t& lo) : m_lo(lo), m_hi(hi) {} Int128& operator = (const int64_t &val) diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 87ad11bcf8..1d6c2b0703 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -419,6 +419,7 @@ coordf_t Layer::get_sparse_infill_max_void_area() double spacing = flow.scaled_spacing() * (100 - density) / density; switch (pattern) { case ipConcentric: + case ipSpiralInset: case ipRectilinear: case ipLine: case ipGyroid: diff --git a/src/libslic3r/MultiPoint.hpp b/src/libslic3r/MultiPoint.hpp index de386b501c..096230d22c 100644 --- a/src/libslic3r/MultiPoint.hpp +++ b/src/libslic3r/MultiPoint.hpp @@ -24,6 +24,7 @@ public: explicit MultiPoint(const Points &_points) : points(_points) {} MultiPoint& operator=(const MultiPoint &other) { points = other.points; return *this; } MultiPoint& operator=(MultiPoint &&other) { points = std::move(other.points); return *this; } + virtual ~MultiPoint() = default; void scale(double factor); void scale(double factor_x, double factor_y); void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); } diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index e96304e874..aee50db534 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1,3 +1,8 @@ +#ifdef _WIN32 +// Keep this first. A header below reaches boost/regex, whose w32_regex_traits +// needs the Win32 types declared already. +#include +#endif #include "Config.hpp" #include "Exception.hpp" #include "Print.hpp" diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 100089fd24..1db476eede 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -275,6 +275,7 @@ static t_config_enum_values s_keys_map_InfillPattern { { "tpmsfk", ipTpmsFK }, { "gyroid", ipGyroid }, { "concentric", ipConcentric }, + { "spiralinset", ipSpiralInset }, { "hilbertcurve", ipHilbertCurve }, { "archimedeanchords", ipArchimedeanChords }, { "octagramspiral", ipOctagramSpiral } @@ -371,6 +372,7 @@ static t_config_enum_values s_keys_map_SupportMaterialInterfacePattern { { "auto", smipAuto }, { "rectilinear", smipRectilinear }, { "concentric", smipConcentric }, + { "spiralinset", smipSpiralInset }, { "rectilinear_interlaced", smipRectilinearInterlaced}, { "grid", smipGrid } }; @@ -2292,6 +2294,7 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("rectilinear"); def->enum_values.push_back("alignedrectilinear"); def->enum_values.push_back("concentric"); + def->enum_values.push_back("spiralinset"); def->enum_values.push_back("hilbertcurve"); def->enum_values.push_back("archimedeanchords"); def->enum_values.push_back("octagramspiral"); @@ -2300,6 +2303,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Rectilinear")); def->enum_labels.push_back(L("Aligned Rectilinear")); def->enum_labels.push_back(L("Concentric")); + def->enum_labels.push_back(L("Spiral Inset")); def->enum_labels.push_back(L("Hilbert Curve")); def->enum_labels.push_back(L("Archimedean Chords")); def->enum_labels.push_back(L("Octagram Spiral")); @@ -2382,7 +2386,7 @@ void PrintConfigDef::init_fff_params() def->label = L("Top surface fill order"); def->category = L("Strength"); def->tooltip = L("Direction in which top surfaces are filled when using a center-based pattern " - "(Concentric, Archimedean Chords, Octagram Spiral).\n" + "(Concentric, Spiral Inset, Archimedean Chords, Octagram Spiral).\n" "Outward starts at the center of the surface, so any excess material is pushed " "towards the edge where it is least visible. Inward starts at the edge and ends " "with the tight curves at the center.\n" @@ -2401,7 +2405,7 @@ void PrintConfigDef::init_fff_params() def->label = L("Bottom surface fill order"); def->category = L("Strength"); def->tooltip = L("Direction in which bottom surfaces are filled when using a center-based pattern " - "(Concentric, Archimedean Chords, Octagram Spiral).\n" + "(Concentric, Spiral Inset, Archimedean Chords, Octagram Spiral).\n" "Inward starts each surface with the wider outer curves, which improves first layer " "adhesion on build plates where the tight curves at the center may not stick. " "Outward starts at the center, pushing any excess material towards the edge.\n" @@ -6963,11 +6967,13 @@ void PrintConfigDef::init_fff_params() def->enum_values.push_back("auto"); def->enum_values.push_back("rectilinear"); def->enum_values.push_back("concentric"); + def->enum_values.push_back("spiralinset"); def->enum_values.push_back("rectilinear_interlaced"); def->enum_values.push_back("grid"); def->enum_labels.push_back(L("Default")); def->enum_labels.push_back(L("Rectilinear")); def->enum_labels.push_back(L("Concentric")); + def->enum_labels.push_back(L("Spiral Inset")); def->enum_labels.push_back(L("Rectilinear Interlaced")); def->enum_labels.push_back(L("Grid")); def->mode = comAdvanced; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 330151c4d3..f7cbe8b2e5 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -113,7 +113,7 @@ enum InfillPattern : int { ipCubic, ipAdaptiveCubic, ipQuarterCubic, ipSupportCubic, ipLightning, ipHoneycomb, ip3DHoneycomb, ipLateralHoneycomb, ipLateralLattice, ipCrossHatch, ipTpmsD, ipTpmsFK, ipGyroid, - ipConcentric, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, + ipConcentric, ipSpiralInset, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipSupportBase, ipConcentricInternal, ipCount, }; @@ -271,7 +271,7 @@ enum LongRectrationLevel }; enum SupportMaterialInterfacePattern { - smipAuto, smipRectilinear, smipConcentric, smipRectilinearInterlaced, smipGrid + smipAuto, smipRectilinear, smipConcentric, smipSpiralInset, smipRectilinearInterlaced, smipGrid }; // BBS diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp index df62127770..f50cdbced2 100644 --- a/src/libslic3r/SLA/Hollowing.cpp +++ b/src/libslic3r/SLA/Hollowing.cpp @@ -1,4 +1,5 @@ #include +#include #include #include diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index dc0e94ed03..8d43edb444 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -141,6 +141,8 @@ struct SupportParameters { this->contact_fill_pattern = ipGrid; else if (object_config.support_interface_pattern == smipRectilinearInterlaced) this->contact_fill_pattern = ipRectilinear; + else if (object_config.support_interface_pattern == smipSpiralInset) + this->contact_fill_pattern = ipSpiralInset; else this->contact_fill_pattern = (object_config.support_interface_pattern == smipAuto && zero_gap_contact_interface) || diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index 1e39e30b0d..7c25792bb3 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -916,6 +916,9 @@ endif () if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE) +elseif (MSVC) + # Puts the Windows headers first when the PCH is off. + target_compile_options(libslic3r_gui PRIVATE "/FIslic3r/win_platform.hpp") endif () if (APPLE) @@ -981,10 +984,6 @@ endif () # Add a definition so that we can tell we are compiling slic3r. target_compile_definitions(libslic3r_gui PRIVATE SLIC3R_CURRENTLY_COMPILING_GUI_MODULE) -if(ORCA_BUNDLED_UV_EXECUTABLE_CONFIG) - target_compile_definitions(libslic3r_gui PRIVATE "ORCA_BUNDLED_UV_EXECUTABLE=\"${ORCA_BUNDLED_UV_EXECUTABLE_CONFIG}\"") -endif() - if (ORCA_BUILD_PYTHON_STUBGEN_MODULE) add_library(orca_stubgen MODULE plugin/PythonPluginBridge.cpp diff --git a/src/slic3r/GUI/AMSMaterialsSetting.cpp b/src/slic3r/GUI/AMSMaterialsSetting.cpp index 1748617789..68f1b44212 100644 --- a/src/slic3r/GUI/AMSMaterialsSetting.cpp +++ b/src/slic3r/GUI/AMSMaterialsSetting.cpp @@ -1915,11 +1915,6 @@ void ColorPickerPopup::paintEvent(wxPaintEvent& evt) void ColorPickerPopup::OnDismiss() {} -void ColorPickerPopup::Popup() -{ - PopupWindow::Popup(); -} - bool ColorPickerPopup::ProcessLeftDown(wxMouseEvent& event) { return PopupWindow::ProcessLeftDown(event); } diff --git a/src/slic3r/GUI/AMSMaterialsSetting.hpp b/src/slic3r/GUI/AMSMaterialsSetting.hpp index 81167884a7..2cbd58e9a9 100644 --- a/src/slic3r/GUI/AMSMaterialsSetting.hpp +++ b/src/slic3r/GUI/AMSMaterialsSetting.hpp @@ -85,7 +85,6 @@ public: void set_ams_colours(std::vector ams); void set_def_colour(wxColour col); void paintEvent(wxPaintEvent& evt); - void Popup(); virtual void OnDismiss() wxOVERRIDE; virtual bool ProcessLeftDown(wxMouseEvent& event) wxOVERRIDE; diff --git a/src/slic3r/GUI/AMSSetting.cpp b/src/slic3r/GUI/AMSSetting.cpp index d8760cfcf6..9ba9031f19 100644 --- a/src/slic3r/GUI/AMSSetting.cpp +++ b/src/slic3r/GUI/AMSSetting.cpp @@ -292,7 +292,7 @@ void AMSSetting::UpdateByObj(MachineObject* obj) update_ams_img(obj); - m_ams_type->Update(obj); + m_ams_type->UpdateInfo(obj); //m_ams_arrange_order->Update(obj); update_insert_material_read_mode(obj); m_sizer_remain_block->Show(obj->is_support_update_remain); @@ -624,7 +624,7 @@ void AMSSettingTypePanel::CreateGui() Fit(); } -void AMSSettingTypePanel::Update(const MachineObject* obj) +void AMSSettingTypePanel::UpdateInfo(const MachineObject* obj) { if (!obj) { Show(false); diff --git a/src/slic3r/GUI/AMSSetting.hpp b/src/slic3r/GUI/AMSSetting.hpp index 69630c59d2..e157c947ae 100644 --- a/src/slic3r/GUI/AMSSetting.hpp +++ b/src/slic3r/GUI/AMSSetting.hpp @@ -110,7 +110,7 @@ public: ~AMSSettingTypePanel(); public: - void Update(const MachineObject* obj); + void UpdateInfo(const MachineObject* obj); private: void CreateGui(); diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 09b7fcfad4..8eab1c785c 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -741,14 +741,21 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in bool have_infill = config->option("sparse_infill_density")->value > 0; // sparse_infill_filament_id uses the same logic as in Print::extruders() for (auto el : { "sparse_infill_pattern", "infill_combination", "fill_multiline","infill_direction", - "minimum_sparse_infill_area", "sparse_infill_filament_id", "infill_anchor", "infill_anchor_max","infill_shift_step","sparse_infill_rotate_template","symmetric_infill_y_axis"}) + "minimum_sparse_infill_area", "sparse_infill_filament_id","infill_shift_step","sparse_infill_rotate_template","symmetric_infill_y_axis"}) toggle_line(el, have_infill); + InfillPattern pattern = config->opt_enum("sparse_infill_pattern"); + + // Orca: the concentric patterns follow the surface outline instead of crossing it, so there is + // nothing for an infill anchor to attach to. Hide the anchor settings for them. + bool have_infill_anchor = have_infill && pattern != ipConcentric && pattern != ipSpiralInset; + toggle_line("infill_anchor", have_infill_anchor); + toggle_line("infill_anchor_max", have_infill_anchor); + bool have_combined_infill = config->opt_bool("infill_combination") && have_infill; toggle_line("infill_combination_max_layer_height", have_combined_infill); // Infill patterns that support multiline infill. - InfillPattern pattern = config->opt_enum("sparse_infill_pattern"); bool have_multiline_infill_pattern = pattern == ipGyroid || pattern == ipGrid || pattern == ipRectilinear || pattern == ipTpmsD || pattern == ipTpmsFK || pattern == ipCrossHatch || pattern == ipHoneycomb || pattern == ipLateralLattice || pattern == ipLateralHoneycomb || pattern == ipConcentric || pattern == ipCubic || pattern == ipStars || pattern == ipAlignedRectilinear || pattern == ipLightning || pattern == ip3DHoneycomb || pattern == ipAdaptiveCubic || pattern == ipSupportCubic|| pattern == ipTriangles || pattern == ipQuarterCubic|| pattern == ipArchimedeanChords || pattern == ipHilbertCurve || pattern == ipOctagramSpiral; @@ -831,7 +838,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in toggle_line("separated_infills", is_internal_infill_separable); // Fill order is only meaningful for the center-based surface fill patterns; hide it otherwise. - auto is_centered_fill = [](InfillPattern p) { return p == ipConcentric || p == ipArchimedeanChords || p == ipOctagramSpiral; }; + auto is_centered_fill = [](InfillPattern p) { return p == ipConcentric || p == ipSpiralInset || p == ipArchimedeanChords || p == ipOctagramSpiral; }; toggle_line("top_surface_fill_order", has_top_shell && is_centered_fill(config->opt_enum("top_surface_pattern"))); toggle_line("bottom_surface_fill_order", has_bottom_shell && is_centered_fill(config->opt_enum("bottom_surface_pattern"))); diff --git a/src/slic3r/GUI/DeviceTab/uiAMSBestPositionPopup.cpp b/src/slic3r/GUI/DeviceTab/uiAMSBestPositionPopup.cpp index a258a2178a..8505a559d6 100644 --- a/src/slic3r/GUI/DeviceTab/uiAMSBestPositionPopup.cpp +++ b/src/slic3r/GUI/DeviceTab/uiAMSBestPositionPopup.cpp @@ -705,7 +705,7 @@ ReselectMachineDialog::ReselectMachineDialog(wxWindow* parent) Centre(); } -void ReselectMachineDialog::Update(MachineObject* obj, const std::map& best_pos_map, const std::vector& ams_mapping, wxString save_time) +void ReselectMachineDialog::UpdateInfo(MachineObject* obj, const std::map& best_pos_map, const std::vector& ams_mapping, wxString save_time) { if (suggestText) diff --git a/src/slic3r/GUI/DeviceTab/uiAMSBestPositionPopup.hpp b/src/slic3r/GUI/DeviceTab/uiAMSBestPositionPopup.hpp index be8f957c94..265666bb12 100644 --- a/src/slic3r/GUI/DeviceTab/uiAMSBestPositionPopup.hpp +++ b/src/slic3r/GUI/DeviceTab/uiAMSBestPositionPopup.hpp @@ -188,7 +188,7 @@ class ReselectMachineDialog : public wxDialog public: ReselectMachineDialog(wxWindow* parent); ~ReselectMachineDialog(); - void Update(MachineObject* obj, + void UpdateInfo(MachineObject* obj, const std::map& best_pos_map, const std::vector& ams_mapping, wxString save_time); diff --git a/src/slic3r/GUI/DeviceTab/uiAmsHumidityPopup.cpp b/src/slic3r/GUI/DeviceTab/uiAmsHumidityPopup.cpp index 259506612d..e33987af33 100644 --- a/src/slic3r/GUI/DeviceTab/uiAmsHumidityPopup.cpp +++ b/src/slic3r/GUI/DeviceTab/uiAmsHumidityPopup.cpp @@ -98,7 +98,7 @@ void uiAmsPercentHumidityDryPopup::Create() Refresh(); } -void uiAmsPercentHumidityDryPopup::Update(int humidiy_level, int humidity_percent, int left_dry_time, float current_temperature) +void uiAmsPercentHumidityDryPopup::UpdateInfo(int humidiy_level, int humidity_percent, int left_dry_time, float current_temperature) { if (m_humidity_level != humidiy_level || m_humidity_percent != humidity_percent || m_left_dry_time != left_dry_time || m_current_temperature != current_temperature) diff --git a/src/slic3r/GUI/DeviceTab/uiAmsHumidityPopup.h b/src/slic3r/GUI/DeviceTab/uiAmsHumidityPopup.h index a109381c86..867e6ac118 100644 --- a/src/slic3r/GUI/DeviceTab/uiAmsHumidityPopup.h +++ b/src/slic3r/GUI/DeviceTab/uiAmsHumidityPopup.h @@ -38,14 +38,14 @@ public: ~uiAmsPercentHumidityDryPopup() = default; public: - void Update(uiAmsHumidityInfo *info) { m_ams_id = info->ams_id; Update(info->humidity_display_idx, info->humidity_percent, info->left_dry_time, info->current_temperature); }; + void UpdateInfo(uiAmsHumidityInfo *info) { m_ams_id = info->ams_id; UpdateInfo(info->humidity_display_idx, info->humidity_percent, info->left_dry_time, info->current_temperature); }; std::string get_owner_ams_id() const { return m_ams_id; } void msw_rescale(); private: - void Update(int humidiy_level, int humidity_percent, int left_dry_time, float current_temperature); + void UpdateInfo(int humidiy_level, int humidity_percent, int left_dry_time, float current_temperature); void UpdateContents(); void Create(); diff --git a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.cpp b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.cpp index 1e40a71150..44394866c8 100644 --- a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.cpp +++ b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.cpp @@ -399,7 +399,7 @@ void wgtDeviceNozzleRackArea::UpdateNozzleItems(const std::unordered_mapUpdate(nozzle_rack); + iter.second->UpdateInfo(nozzle_rack); } /*update nozzle possition and background*/ @@ -837,7 +837,7 @@ void wgtDeviceNozzleRackNozzleItem::SetSelected(bool selected) } } -void wgtDeviceNozzleRackNozzleItem::Update(const std::shared_ptr rack, bool on_rack /*= true*/) +void wgtDeviceNozzleRackNozzleItem::UpdateInfo(const std::shared_ptr rack, bool on_rack /*= true*/) { m_rack = rack; diff --git a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.h b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.h index 385fa6be48..16cd311120 100644 --- a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.h +++ b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.h @@ -200,7 +200,7 @@ public: wgtDeviceNozzleRackNozzleItem(wxWindow* parent, int nozzle_id); public: - void Update(const std::shared_ptr rack, bool on_rack = true); // on_rack is false means extruder nozzle + void UpdateInfo(const std::shared_ptr rack, bool on_rack = true); // on_rack is false means extruder nozzle int GetNozzleId() const { return m_nozzle_id; } void SetDisplayIdText(const wxString& text) { m_nozzle_label_id->SetLabel(text);}; diff --git a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleSelect.cpp b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleSelect.cpp index 4dc072d721..f351e7af2b 100644 --- a/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleSelect.cpp +++ b/src/slic3r/GUI/DeviceTab/wgtDeviceNozzleSelect.cpp @@ -114,7 +114,7 @@ static void s_update_nozzle_info(wgtDeviceNozzleRackNozzleItem* item, std::shared_ptr rack, const DevNozzle& nozzle_info) { - item->Update(rack, nozzle_info.IsOnRack()); + item->UpdateInfo(rack, nozzle_info.IsOnRack()); if (nozzle_info.IsUnknown()) { if (item->GetToolTipText() != _L("Nozzle information needs to be read")) { item->SetToolTip(_L("Nozzle information needs to be read")); diff --git a/src/slic3r/GUI/DragCanvas.hpp b/src/slic3r/GUI/DragCanvas.hpp index aa8d8ddfa8..f39e836673 100644 --- a/src/slic3r/GUI/DragCanvas.hpp +++ b/src/slic3r/GUI/DragCanvas.hpp @@ -3,6 +3,7 @@ #include "wx/bitmap.h" #include "wx/dragimag.h" +#include "wx/panel.h" namespace Slic3r { namespace GUI { diff --git a/src/slic3r/GUI/FilamentMapPanel.cpp b/src/slic3r/GUI/FilamentMapPanel.cpp index ed63016438..81117bc2a6 100644 --- a/src/slic3r/GUI/FilamentMapPanel.cpp +++ b/src/slic3r/GUI/FilamentMapPanel.cpp @@ -642,19 +642,12 @@ void FilamentMapBtnPanel::Select(bool selected) Refresh(); } -void GUI::FilamentMapBtnPanel::Hide() +bool GUI::FilamentMapBtnPanel::Show(bool show) { - m_btn->Hide(); - m_label->Hide(); - m_detail->Hide(); - wxPanel::Hide(); -} -void GUI::FilamentMapBtnPanel::Show() -{ - m_btn->Show(); - m_label->Show(); - m_detail->Show(); - wxPanel::Show(); + m_btn->Show(show); + m_label->Show(show); + m_detail->Show(show); + return wxPanel::Show(show); } FilamentMapAutoPanel::FilamentMapAutoPanel(wxWindow *parent, FilamentMapMode mode, bool machine_synced) : wxPanel(parent) @@ -694,18 +687,11 @@ FilamentMapAutoPanel::FilamentMapAutoPanel(wxWindow *parent, FilamentMapMode mod Layout(); GUI::wxGetApp().UpdateDarkUIWin(this); } -void FilamentMapAutoPanel::Hide() +bool FilamentMapAutoPanel::Show(bool show) { - m_flush_panel->Hide(); - m_match_panel->Hide(); - wxPanel::Hide(); -} - -void FilamentMapAutoPanel::Show() -{ - m_flush_panel->Show(); - m_match_panel->Show(); - wxPanel::Show(); + m_flush_panel->Show(show); + m_match_panel->Show(show); + return wxPanel::Show(show); } void FilamentMapAutoPanel::UpdateStatus() @@ -743,16 +729,10 @@ FilamentMapDefaultPanel::FilamentMapDefaultPanel(wxWindow *parent) : wxPanel(par GUI::wxGetApp().UpdateDarkUIWin(this); } -void FilamentMapDefaultPanel::Hide() +bool FilamentMapDefaultPanel::Show(bool show) { - m_label->Hide(); - wxPanel::Hide(); -} - -void FilamentMapDefaultPanel::Show() -{ - m_label->Show(); - wxPanel::Show(); + m_label->Show(show); + return wxPanel::Show(show); } }} // namespace Slic3r::GUI diff --git a/src/slic3r/GUI/FilamentMapPanel.hpp b/src/slic3r/GUI/FilamentMapPanel.hpp index a0caf8e78b..ce7c8010a3 100644 --- a/src/slic3r/GUI/FilamentMapPanel.hpp +++ b/src/slic3r/GUI/FilamentMapPanel.hpp @@ -69,10 +69,9 @@ class FilamentMapBtnPanel : public wxPanel { public: FilamentMapBtnPanel(wxWindow *parent, const wxString &label, const wxString &detail, const std::string &icon_path); - void Hide(); - void Show(); + bool Show(bool show = true) override; void Select(bool selected); - bool Enable(bool enable); + bool Enable(bool enable) override; bool IsEnabled() const { return m_enabled; } protected: void OnPaint(wxPaintEvent &event); @@ -99,8 +98,7 @@ class FilamentMapAutoPanel : public wxPanel { public: FilamentMapAutoPanel(wxWindow *parent, FilamentMapMode mode, bool machine_synced); - void Hide(); - void Show(); + bool Show(bool show = true) override; FilamentMapMode GetMode() const { return m_mode; } private: @@ -116,8 +114,7 @@ class FilamentMapDefaultPanel : public wxPanel { public: FilamentMapDefaultPanel(wxWindow *parent); - void Hide(); - void Show(); + bool Show(bool show = true) override; private: Label *m_label; diff --git a/src/slic3r/GUI/GCodeViewer.cpp b/src/slic3r/GUI/GCodeViewer.cpp index e5a88e4ea4..49477ac7dd 100644 --- a/src/slic3r/GUI/GCodeViewer.cpp +++ b/src/slic3r/GUI/GCodeViewer.cpp @@ -785,6 +785,19 @@ void GCodeViewer::SequentialView::GCodeWindow::load_gcode(const std::string& fil } } +// Byte offset just past the first count characters of str, or its length if it is shorter. +static size_t utf8_offset(const std::string& str, size_t count) +{ + const char* const begin = str.c_str(); + const char* const end = begin + str.size(); + const char* pos = begin; + for (size_t i = 0; i < count && pos < end; ++i) { + unsigned int codepoint = 0; + pos += ImTextCharFromUtf8(&codepoint, pos, end); + } + return pos - begin; +} + //BBS: GUI refactor: move to right void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, float right, uint64_t curr_line_id) const { @@ -796,23 +809,27 @@ void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, f // read line from file const size_t start = id == 1 ? 0 : m_lines_ends[id - 2]; const size_t original_len = m_lines_ends[id - 1] - start; - const size_t len = std::min(original_len, (size_t) 55); + // A character is four bytes at most, so 55 of them always fit in 220. + const size_t len = std::min(original_len, (size_t) 55 * 4); std::string gline(m_file.data() + start, len); - // If original line is longer than 55 characters, truncate and append "..." - if (original_len > 55) - gline = gline.substr(0, 52) + "..."; + // If original line is longer than 55 characters, truncate and append "...". + // The cut must land on a character boundary or it leaves half a character behind. + if (len < original_len || utf8_offset(gline, 55) < gline.size()) + gline = gline.substr(0, utf8_offset(gline, 52)) + "..."; std::string command, parameters, comment; - // extract comment - std::vector tokens; - boost::split(tokens, gline, boost::is_any_of(";"), boost::token_compress_on); - command = tokens.front(); - if (tokens.size() > 1) - comment = ";" + tokens.back(); + const size_t comment_start = gline.find(';'); + if (comment_start == std::string::npos) + command = gline; + else { + command = gline.substr(0, comment_start); + comment = gline.substr(comment_start); + } // extract gcode command and parameters if (!command.empty()) { + std::vector tokens; boost::split(tokens, command, boost::is_any_of(" "), boost::token_compress_on); command = tokens.front(); if (tokens.size() > 1) { diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 5b80a866ca..3500493329 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -12,6 +12,7 @@ #include "BuildCommit.hpp" #include "Downloader.hpp" #include +#include #include #include #include diff --git a/src/slic3r/GUI/OptionsGroup.hpp b/src/slic3r/GUI/OptionsGroup.hpp index c808545145..b20d16aca9 100644 --- a/src/slic3r/GUI/OptionsGroup.hpp +++ b/src/slic3r/GUI/OptionsGroup.hpp @@ -199,7 +199,7 @@ public: OptionsGroup(wxWindow *_parent, const wxString &title, const wxString &icon, bool is_tab_opt = false, column_t extra_clmn = nullptr); - ~OptionsGroup() { clear(true); } + virtual ~OptionsGroup() { clear(true); } wxGridSizer* get_grid_sizer() { return m_grid_sizer; } const std::vector& get_lines() { return m_lines; } diff --git a/src/slic3r/GUI/SelectMachine.cpp b/src/slic3r/GUI/SelectMachine.cpp index e70ba84225..41ee523fbe 100644 --- a/src/slic3r/GUI/SelectMachine.cpp +++ b/src/slic3r/GUI/SelectMachine.cpp @@ -2176,7 +2176,7 @@ void SelectMachineDialog::on_reselect_dialog_btn_clicked(wxMouseEvent&) best_pos_map[slot.id] = pos.value(); } } - m_best_pos_dialog->Update(obj, best_pos_map, m_ams_mapping_result, text); + m_best_pos_dialog->UpdateInfo(obj, best_pos_map, m_ams_mapping_result, text); m_best_pos_dialog->ShowModal(); } @@ -2201,7 +2201,7 @@ void SelectMachineDialog::update_best_pos_dialog(wxCommandEvent& evt) best_pos_map[slot.id] = pos.value(); } } - m_best_pos_dialog->Update(obj_, best_pos_map, m_ams_mapping_result, text); + m_best_pos_dialog->UpdateInfo(obj_, best_pos_map, m_ams_mapping_result, text); } void SelectMachineDialog::show_status(PrintDialogStatus status, std::vector params, wxString wiki_url) diff --git a/src/slic3r/GUI/SendToPrinter.cpp b/src/slic3r/GUI/SendToPrinter.cpp index a63d96ffce..c4db0423ab 100644 --- a/src/slic3r/GUI/SendToPrinter.cpp +++ b/src/slic3r/GUI/SendToPrinter.cpp @@ -1391,7 +1391,7 @@ void SendToPrinterDialog::show_status(PrintDialogStatus status, std::vectorShow(); m_animaicon->Stop(); - m_animaicon->Enable(); + m_animaicon->ShowEnabledIcon(); Layout(); Enable_Send_Button(false); diff --git a/src/slic3r/GUI/SlicingProgressNotification.cpp b/src/slic3r/GUI/SlicingProgressNotification.cpp index 6d7c6d7ecd..539da41024 100644 --- a/src/slic3r/GUI/SlicingProgressNotification.cpp +++ b/src/slic3r/GUI/SlicingProgressNotification.cpp @@ -259,8 +259,8 @@ void NotificationManager::SlicingProgressNotification::render(GLCanvas3D& canvas // ImVec2 view_dailytips_text_pos = m_window_pos + ImVec2(text_left_margin_x, m_window_height / 2.0f + m_line_height * 0.2f); // bbl_render_left_sign(imgui, m_window_width, m_window_height, m_window_pos.x + m_window_width, m_window_pos.y); - // render_text(text_pos); - // render_close_button(button_pos, button_size); + // render_progress_text(text_pos); + // render_progress_close_button(button_pos, button_size); // render_show_dailytips(view_dailytips_text_pos); //} @@ -278,8 +278,8 @@ void NotificationManager::SlicingProgressNotification::render(GLCanvas3D& canvas ImVec2 button_pos = child_window_pos + ImVec2(progress_panel_width - button_size.x, progress_panel_height - text_bottom - button_size.y / 2.0f); ImVec2 text_pos = ImVec2(progress_bar_pos.x, progress_bar_pos.y - m_line_height * (1.2f + m_lines_count - 1)); - render_text(text_pos); - render_close_button(button_pos, button_size); + render_progress_text(text_pos); + render_progress_close_button(button_pos, button_size); if (m_sp_state == SlicingProgressState::SP_PROGRESS) { render_bar(progress_bar_pos, progress_bar_size); render_cancel_button(button_pos, button_size); @@ -319,7 +319,7 @@ void NotificationManager::SlicingProgressNotification::render(GLCanvas3D& canvas ImGui::PopStyleColor(3); } -void Slic3r::GUI::NotificationManager::SlicingProgressNotification::render_text(const ImVec2& pos) +void Slic3r::GUI::NotificationManager::SlicingProgressNotification::render_progress_text(const ImVec2& pos) { ImGuiWrapper& imgui = *wxGetApp().imgui(); float scale = imgui.get_font_size() / 15.0f; @@ -462,7 +462,7 @@ void Slic3r::GUI::NotificationManager::SlicingProgressNotification::render_cance } } -void NotificationManager::SlicingProgressNotification::render_close_button(const ImVec2& pos, const ImVec2& size) +void NotificationManager::SlicingProgressNotification::render_progress_close_button(const ImVec2& pos, const ImVec2& size) { if (m_sp_state == SlicingProgressState::SP_CANCELLED || m_sp_state == SlicingProgressState::SP_COMPLETED) { ImGuiWrapper& imgui = *wxGetApp().imgui(); diff --git a/src/slic3r/GUI/SlicingProgressNotification.hpp b/src/slic3r/GUI/SlicingProgressNotification.hpp index 44f2bbd32b..6a7ede1e2e 100644 --- a/src/slic3r/GUI/SlicingProgressNotification.hpp +++ b/src/slic3r/GUI/SlicingProgressNotification.hpp @@ -53,10 +53,10 @@ protected: void init() override; void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width, float right_margin) override; /* PARAMS: pos is relative to screen */ - void render_text(const ImVec2& pos); + void render_progress_text(const ImVec2& pos); void render_bar(const ImVec2& pos, const ImVec2& size); void render_cancel_button(const ImVec2& pos, const ImVec2& size); - void render_close_button(const ImVec2& pos, const ImVec2& size); + void render_progress_close_button(const ImVec2& pos, const ImVec2& size); void render_dailytips_panel(const ImVec2& pos, const ImVec2& size); void render_show_dailytips(const ImVec2& pos); diff --git a/src/slic3r/GUI/Widgets/AMSControl.cpp b/src/slic3r/GUI/Widgets/AMSControl.cpp index 41f14b6a11..7ba24fd991 100644 --- a/src/slic3r/GUI/Widgets/AMSControl.cpp +++ b/src/slic3r/GUI/Widgets/AMSControl.cpp @@ -278,7 +278,7 @@ AMSControl::AMSControl(wxWindow *parent, wxWindowID id, const wxPoint &pos, cons m_ams_dry_ctr_win->Move(popup_pos); m_ams_dry_ctr_win->ShowModal(); } else { - m_percent_humidity_dry_popup->Update(info); + m_percent_humidity_dry_popup->UpdateInfo(info); wxPoint img_pos = ClientToScreen(wxPoint(0, 0)); wxPoint popup_pos(img_pos.x - m_percent_humidity_dry_popup->GetSize().GetWidth() + FromDIP(150), img_pos.y - FromDIP(80)); @@ -1034,7 +1034,7 @@ void AMSControl::UpdateAms(const std::string &series_name, humidity_info.humidity_percent = the_info.humidity_raw; humidity_info.left_dry_time = the_info.left_dray_time; humidity_info.current_temperature = the_info.current_temperature; - m_percent_humidity_dry_popup->Update(&humidity_info); + m_percent_humidity_dry_popup->UpdateInfo(&humidity_info); break; } } diff --git a/src/slic3r/GUI/Widgets/AnimaController.cpp b/src/slic3r/GUI/Widgets/AnimaController.cpp index acf78c01f5..d5f665cde1 100644 --- a/src/slic3r/GUI/Widgets/AnimaController.cpp +++ b/src/slic3r/GUI/Widgets/AnimaController.cpp @@ -78,7 +78,7 @@ void AnimaIcon::Stop() m_timer->Stop(); } -void AnimaIcon::Enable() +void AnimaIcon::ShowEnabledIcon() { if (m_bitmap) { m_bitmap->SetBitmap(m_image_enable); } } diff --git a/src/slic3r/GUI/Widgets/AnimaController.hpp b/src/slic3r/GUI/Widgets/AnimaController.hpp index e2f67ff8a6..daf8f95314 100644 --- a/src/slic3r/GUI/Widgets/AnimaController.hpp +++ b/src/slic3r/GUI/Widgets/AnimaController.hpp @@ -13,7 +13,7 @@ public: void Play(); void Stop(); - void Enable(); + void ShowEnabledIcon(); bool IsPlaying() const { return IsRunning(); }; bool IsRunning() const; diff --git a/src/slic3r/GUI/Widgets/ErrorMsgStaticText.hpp b/src/slic3r/GUI/Widgets/ErrorMsgStaticText.hpp index 735a118a20..63968a57c5 100644 --- a/src/slic3r/GUI/Widgets/ErrorMsgStaticText.hpp +++ b/src/slic3r/GUI/Widgets/ErrorMsgStaticText.hpp @@ -17,7 +17,7 @@ public: void paintEvent(wxPaintEvent &evt); - void SetLabel(wxString msg){m_msg = msg;}; + void SetLabel(const wxString &msg) override { m_msg = msg; } }; #endif diff --git a/src/slic3r/GUI/Widgets/MultiNozzleSync.cpp b/src/slic3r/GUI/Widgets/MultiNozzleSync.cpp index 518bda1d19..20944df553 100644 --- a/src/slic3r/GUI/Widgets/MultiNozzleSync.cpp +++ b/src/slic3r/GUI/Widgets/MultiNozzleSync.cpp @@ -600,7 +600,7 @@ StaticBox* HotEndTable::CreateNozzleBox(const std::vector& nozzle_indices) void HotEndTable::UpdateNozzleItems(const std::unordered_map& nozzle_items, std::shared_ptr nozzle_rack) { for (auto& item : nozzle_items) - item.second->Update(nozzle_rack); + item.second->UpdateInfo(nozzle_rack); } void HotEndTable::OnPaint(wxPaintEvent& evt) diff --git a/src/slic3r/GUI/Widgets/RadioBox.cpp b/src/slic3r/GUI/Widgets/RadioBox.cpp index 7f20b8724c..f00447e0ec 100644 --- a/src/slic3r/GUI/Widgets/RadioBox.cpp +++ b/src/slic3r/GUI/Widgets/RadioBox.cpp @@ -36,11 +36,6 @@ void RadioBox::SetValue(bool value) update(); } -bool RadioBox::GetValue() -{ - return wxBitmapToggleButton::GetValue(); -} - void RadioBox::Rescale() { diff --git a/src/slic3r/GUI/Widgets/RadioBox.hpp b/src/slic3r/GUI/Widgets/RadioBox.hpp index b2c03aae15..a34bd594dd 100644 --- a/src/slic3r/GUI/Widgets/RadioBox.hpp +++ b/src/slic3r/GUI/Widgets/RadioBox.hpp @@ -15,14 +15,10 @@ public: public: void SetValue(bool value) override; - bool GetValue(); void Rescale(); bool Disable() { return wxBitmapToggleButton::Disable(); } - bool Enable() { - return wxBitmapToggleButton::Enable(); - } private: void update(); diff --git a/src/slic3r/GUI/wxMediaCtrl3.h b/src/slic3r/GUI/wxMediaCtrl3.h index 1d64955ffd..ff4595ad32 100644 --- a/src/slic3r/GUI/wxMediaCtrl3.h +++ b/src/slic3r/GUI/wxMediaCtrl3.h @@ -8,6 +8,9 @@ #ifndef wxMediaCtrl3_h #define wxMediaCtrl3_h +#include +#include "wx/window.h" +#include "wx/bitmap.h" #include "wx/uri.h" #include "wx/mediactrl.h" diff --git a/src/slic3r/Utils/Bonjour.hpp b/src/slic3r/Utils/Bonjour.hpp index 50b71791fa..3ef8f0e1a2 100644 --- a/src/slic3r/Utils/Bonjour.hpp +++ b/src/slic3r/Utils/Bonjour.hpp @@ -115,6 +115,7 @@ class UdpSession { public: UdpSession(Bonjour::ReplyFn rfn); + virtual ~UdpSession() = default; virtual void handle_receive(const boost::system::error_code& error, size_t bytes) = 0; std::vector buffer; boost::asio::ip::udp::endpoint remote_endpoint; diff --git a/src/slic3r/win_platform.hpp b/src/slic3r/win_platform.hpp new file mode 100644 index 0000000000..0c5a8e81b2 --- /dev/null +++ b/src/slic3r/win_platform.hpp @@ -0,0 +1,19 @@ +#pragma once + +// Force-included into libslic3r_gui when SLIC3R_PCH is OFF, standing in for +// pchheader.hpp, which includes before anything else. Arriving +// late and transitively, rpcndr.h defines a global `byte` that collides with +// std::byte under `using namespace std`, and the control and URL moniker +// types the GUI uses are never declared. +#ifdef _WIN32 + #ifndef WIN32_LEAN_AND_MEAN + #define WIN32_LEAN_AND_MEAN + #endif + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #include + #include +#endif // _WIN32 + diff --git a/tests/fff_print/test_fill.cpp b/tests/fff_print/test_fill.cpp index d26639c659..04e5b61831 100644 --- a/tests/fff_print/test_fill.cpp +++ b/tests/fff_print/test_fill.cpp @@ -699,6 +699,213 @@ TEST_CASE("Solid infill direction offsets every layer when no template is set", } } +// Orca: the spiral inset pattern chains the concentric loops into a single continuous path per +// island, so it has to cope with the degenerate loops offsetting leaves behind and it must not join +// loops that only look adjacent. +namespace { + +Slic3r::Polylines spiral_inset_fill(const Slic3r::ExPolygon &surface_shape, double spacing) +{ + std::unique_ptr filler(Slic3r::Fill::new_from_type("spiralinset")); + filler->spacing = spacing; + // Cancel the half-spacing contraction fill_surface() applies, so the filler sees the shape as given. + filler->overlap = 0.5 * spacing; + + Slic3r::FillParams fill_params; + fill_params.density = 1.f; + fill_params.dont_adjust = true; + + Slic3r::Surface surface(Slic3r::stBottom, surface_shape); + return filler->fill_surface(&surface, fill_params); +} + +Slic3r::ExPolygon rectangle(double x, double y, double w, double h) +{ + return Slic3r::ExPolygon({Slic3r::Point::new_scale(x, y), Slic3r::Point::new_scale(x + w, y), + Slic3r::Point::new_scale(x + w, y + h), Slic3r::Point::new_scale(x, y + h)}); +} + +// Area of the surface the toolpaths fail to cover, and the largest single patch of it, in mm2. Each +// bead is measured at its own width so the variable width walls are not sold short. +std::pair uncovered_area(const Slic3r::ExPolygon &surface_shape, const Slic3r::Polygons &covered) +{ + double total = 0, biggest = 0; + for (const Slic3r::ExPolygon &gap : Slic3r::diff_ex(Slic3r::ExPolygons{surface_shape}, Slic3r::union_(covered))) { + const double area = unscale(unscale(gap.area())); + total += area; + biggest = std::max(biggest, area); + } + return {total, biggest}; +} + +Slic3r::Polygons beads_of(const Slic3r::Polylines &paths, double width) +{ + return Slic3r::offset(paths, float(scale_(0.5 * width))); +} + +Slic3r::Polygons beads_of(const Slic3r::ThickPolylines &paths) +{ + Slic3r::Polygons covered; + for (const Slic3r::ThickPolyline &path : paths) + for (size_t i = 0; i + 1 < path.points.size(); ++i) { + Slic3r::Polyline segment; + segment.points = {path.points[i], path.points[i + 1]}; + Slic3r::append(covered, Slic3r::offset(Slic3r::Polylines{segment}, + float(0.5 * std::max(path.width[2 * i], path.width[2 * i + 1])))); + } + return covered; +} + +} // namespace + +TEST_CASE("Spiral inset fill drops loops shorter than the end clipping", "[Fill][Regression]") +{ + // A sliver whose whole perimeter is shorter than the length clipped off the end of a loop, so the + // clipping consumes the path entirely. Such a loop carries no extrusion and must be dropped + // rather than kept as an empty path and read back from. + const double spacing = 0.45; + + Slic3r::Polylines paths; + REQUIRE_NOTHROW(paths = spiral_inset_fill(rectangle(0, 0, 0.05, 0.05), spacing)); + for (const Slic3r::Polyline &path : paths) + CHECK(path.size() >= 2); + + // The same surface at a size the clipping cannot swallow still gets filled. + REQUIRE_NOTHROW(paths = spiral_inset_fill(rectangle(0, 0, 5, 5), spacing)); + REQUIRE(paths.size() == 1); + CHECK(paths.front().size() >= 2); +} + +TEST_CASE("Spiral inset fill keeps separate islands on separate paths", "[Fill]") +{ + // Two lobes joined by a neck narrower than the loop spacing: the inward offsets break the surface + // into two islands, which cannot share one spiral, and no path may leave the surface. + const double spacing = 0.45; + Slic3r::ExPolygon dumbbell = rectangle(0, 0, 6, 6); + dumbbell = Slic3r::union_ex(Slic3r::ExPolygons{dumbbell, rectangle(6, 2.9, 4, 0.2), rectangle(10, 0, 6, 6)}).front(); + + const Slic3r::Polylines paths = spiral_inset_fill(dumbbell, spacing); + REQUIRE(paths.size() >= 2); + + // Inflate by a hair so that loops sitting exactly on the outline still count as contained. + const Slic3r::ExPolygons within = Slic3r::offset_ex(dumbbell, float(SCALED_EPSILON)); + REQUIRE(within.size() == 1); + for (const Slic3r::Polyline &path : paths) { + CHECK(path.size() >= 2); + CHECK(within.front().contains(path)); + } +} + + +TEST_CASE("Spiral inset fill stays connected across sharp corners", "[Fill][Regression]") +{ + // At a corner of half-angle a, the next ring inward retreats along the bisector by spacing/sin(a), + // which leaves it several spacings from the end of the ring it continues. Judging the break by + // distance broke the spiral into loose rings at every spike; nesting is what decides the island. + const double spacing = 0.45; + const Slic3r::ExPolygon spike({Slic3r::Point::new_scale(0, 0), Slic3r::Point::new_scale(30, 0), + Slic3r::Point::new_scale(15, 4)}); + + const Slic3r::Polylines paths = spiral_inset_fill(spike, spacing); + CHECK(paths.size() == 1); + + const Slic3r::ExPolygons within = Slic3r::offset_ex(spike, float(SCALED_EPSILON)); + REQUIRE(within.size() == 1); + for (const Slic3r::Polyline &path : paths) + CHECK(within.front().contains(path)); +} + +TEST_CASE("Spiral inset fill starts on a convex corner", "[Fill][Regression]") +{ + // The only right angle on this outline is the reflex one: the two edges meeting at the origin + // span 90 degrees exactly as a square corner would, but the material lies outside them. The next + // ring in steps away from a reflex corner along the bisector instead of hugging it, so starting + // the spiral there sent it across a long diagonal on every single ring. + const double spacing = 0.45; + const Slic3r::ExPolygon notched({Slic3r::Point::new_scale(0, 0), Slic3r::Point::new_scale(0, 10), + Slic3r::Point::new_scale(-16, 18), Slic3r::Point::new_scale(-16, -2), + Slic3r::Point::new_scale(-8, -16), Slic3r::Point::new_scale(18, -16), + Slic3r::Point::new_scale(10, 0)}); + + const Slic3r::Polylines paths = spiral_inset_fill(notched, spacing); + REQUIRE(paths.size() >= 1); + + // Every edge of the outline is at least 45 degrees off the bisector of that reflex corner, and + // so is every ring offset from it. A long segment running along the bisector can therefore only + // be the spiral striking out across the rings to reach the next one. + for (const Slic3r::Polyline &path : paths) + for (const Slic3r::Line &segment : path.lines()) { + const Vec2d v = (segment.b - segment.a).cast(); + const double direction = std::fmod(std::atan2(v.y(), v.x()) * 180.0 / M_PI + 180.0, 180.0); + if (std::abs(direction - 45.0) > 25.0) + continue; + CAPTURE(direction, unscale(segment.length())); + CHECK(segment.length() <= scale_(1.5 * spacing)); + } +} + +TEST_CASE("Spiral inset fill closes the gaps with variable width walls", "[Fill]") +{ + // Fixed width loops cannot fill a region that is not a whole number of lines across and leave the + // remainder open, which on a ring shows up as a wedge several lines wide. Plain concentric avoids + // that by building solid surfaces out of Arachne's variable width walls, and so must this pattern. + const double spacing = 0.45; + Slic3r::ExPolygon ring = rectangle(0, 0, 24, 24); + Slic3r::Polygon hole; + for (int i = 0; i < 64; ++i) { + const double angle = -2.0 * PI * i / 64.0; // clockwise, so it reads as a hole + hole.points.emplace_back(Slic3r::Point::new_scale(12 + 7.3 * std::cos(angle), 12 + 7.3 * std::sin(angle))); + } + ring.holes.emplace_back(hole); + + Slic3r::PrintConfig print_config; + Slic3r::PrintObjectConfig object_config; + auto make_filler = [&]() { + std::unique_ptr filler(Slic3r::Fill::new_from_type("spiralinset")); + filler->spacing = spacing; + filler->overlap = 0.5 * spacing; // cancel the contraction, so both see the same surface + filler->print_config = &print_config; + filler->print_object_config = &object_config; + return filler; + }; + + Slic3r::FillParams params; + params.density = 1.f; + params.dont_adjust = false; + params.layer_height = 0.2; + + const Slic3r::Surface surface(Slic3r::stTop, ring); + + std::unique_ptr fixed = make_filler(); + const Slic3r::Polylines fixed_width = fixed->fill_surface(&surface, params); + REQUIRE(!fixed_width.empty()); + const auto fixed_gaps = uncovered_area(ring, beads_of(fixed_width, fixed->spacing)); + + params.use_arachne = true; + std::unique_ptr variable = make_filler(); + const Slic3r::ThickPolylines variable_width = variable->fill_surface_arachne(&surface, params); + REQUIRE(!variable_width.empty()); + const auto variable_gaps = uncovered_area(ring, beads_of(variable_width)); + + CAPTURE(fixed_gaps.first, fixed_gaps.second, variable_gaps.first, variable_gaps.second); + // The wedges the fixed width loops leave behind are what the variable width walls take up. + CHECK(variable_gaps.second < 0.5 * fixed_gaps.second); + CHECK(variable_gaps.first < fixed_gaps.first); + + // And it is still a spiral: far fewer paths than the ring has loops. + // And the walls are still chained into spirals rather than printed one path per wall. The ring is + // at its narrowest (12 - 7.3) mm across and is filled from both sides, so it is at least this many + // walls thick there and thicker elsewhere. Arachne's short thin feature walls cannot join a spiral, + // so only the substantial paths count towards this. + const size_t walls_across = size_t(2.0 * (12.0 - 7.3) / spacing); + size_t spirals = 0; + for (const Slic3r::ThickPolyline &path : variable_width) + if (path.length() > scale_(10.0 * spacing)) + ++spirals; + CAPTURE(spirals, walls_across, variable_width.size(), fixed_width.size()); + CHECK(2 * spirals < walls_across); +} + TEST_CASE("Honeycomb infill rounds its cell corners with the smooth factor", "[Fill]") { // A cell whose sides are several times the line width, so that the corners have room to be rounded. diff --git a/tests/libslic3r/test_config_variant_expansion.cpp b/tests/libslic3r/test_config_variant_expansion.cpp index 28901702cc..2469789d5b 100644 --- a/tests/libslic3r/test_config_variant_expansion.cpp +++ b/tests/libslic3r/test_config_variant_expansion.cpp @@ -130,6 +130,12 @@ TEST_CASE("get_config_index_base resolves (volume type, extruder type, id) to a } } +TEST_CASE("support interface pattern registry includes spiral inset", "[Config]") +{ + const auto &values = ConfigOptionEnum::get_enum_values(); + REQUIRE(values.at("spiralinset") == SupportMaterialInterfacePattern::smipSpiralInset); +} + TEST_CASE("get_extruder_nozzle_volume_count reads the per-extruder volume-type layout", "[Config]") { std::vector> nozzle_volume_types;