Merge branch 'main' into feature/filament_id

This commit is contained in:
SoftFever
2026-09-07 11:53:50 +08:00
52 changed files with 939 additions and 111 deletions
+64 -2
View File
@@ -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 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 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 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_section "Toolchain"
call :add_arg use_clang_cl bool l clang-cl "Use clang-cl as the compiler" 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 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 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 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_section "How much gets rebuilt"
call :add_arg slicer_target string "" slicer-target "Build one slicer target instead of all, e.g. libslic3r" 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 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 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 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 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" for %%p in ("!build_dir!") do set "build_full=%%~fp"
echo Configuration: %build_type%, %arch% echo Configuration: %build_type%, %arch%
if not "%clang_exe%" == "" echo Compiler: %clang_exe% 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=" set "SIG_FLAG="
if defined ORCA_UPDATER_SIG_KEY set "SIG_FLAG=-DORCA_UPDATER_SIG_KEY=%ORCA_UPDATER_SIG_KEY%" 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% %error_check%
) )
if not "!cache_args!" == "" (
set "deps_args=!deps_args! !cache_args!"
)
if not "%no_configure%" == "ON" ( 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% 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% %error_check%
@@ -641,6 +692,16 @@ if "%build_slicer%" == "ON" (
set "slicer_args=!slicer_args! -DSLIC3R_ASAN=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 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 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 REM build it in this same run, and under a dry run, which configures
@@ -870,6 +931,7 @@ REM get_str_len <string> -> length in %ret%
echo %script_name% -s --no-configure -j 8 Rebuild quickly while iterating 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% -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% -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.
echo Environment: echo Environment:
echo ORCA_DEPS_CMAKE_ARGS Extra arguments for the deps configure echo ORCA_DEPS_CMAKE_ARGS Extra arguments for the deps configure
@@ -879,8 +941,8 @@ REM get_str_len <string> -> length in %ret%
echo NINJA_STATUS Ninja progress format, if you want your own echo NINJA_STATUS Ninja progress format, if you want your own
echo debugscript Set to ON to trace this script echo debugscript Set to ON to trace this script
echo. echo.
echo set ORCA_SLICER_CMAKE_ARGS=-DSLIC3R_PCH=OFF -DSLIC3R_MSVC_PDB=OFF echo set ORCA_SLICER_CMAKE_ARGS=-DSLIC3R_BUILD_SANDBOXES=ON -DSLIC3R_WARNINGS=OFF
echo $env:ORCA_SLICER_CMAKE_ARGS = '-DSLIC3R_PCH=OFF' (PowerShell) echo $env:ORCA_SLICER_CMAKE_ARGS = '-DSLIC3R_BUILD_SANDBOXES=ON' (PowerShell)
echo. echo.
echo --deps-args and --slicer-args cannot carry a value with spaces; use echo --deps-args and --slicer-args cannot carry a value with spaces; use
echo these instead. Neither form supports a value containing an ampersand. echo these instead. Neither form supports a value containing an ampersand.
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M12.00,12.00 L12.00,11.96 L12.01,11.92 L12.03,11.89 L12.05,11.85 L12.08,11.82 L12.12,11.80 L12.16,11.78 L12.20,11.76 L12.25,11.75 L12.30,11.75 L12.35,11.75 L12.40,11.77 L12.46,11.79 L12.51,11.81 L12.56,11.85 L12.61,11.89 L12.66,11.94 L12.70,12.00 L12.74,12.06 L12.77,12.14 L12.79,12.21 L12.80,12.29 L12.81,12.38 L12.81,12.47 L12.80,12.56 L12.77,12.65 L12.74,12.74 L12.70,12.83 L12.65,12.92 L12.58,13.01 L12.51,13.09 L12.43,13.17 L12.33,13.24 L12.23,13.30 L12.12,13.36 L12.00,13.40 L11.87,13.43 L11.74,13.46 L11.61,13.46 L11.47,13.46 L11.33,13.45 L11.18,13.41 L11.04,13.37 L10.90,13.31 L10.76,13.24 L10.63,13.15 L10.50,13.05 L10.38,12.93 L10.27,12.81 L10.17,12.67 L10.08,12.51 L10.01,12.35 L9.95,12.18 L9.90,12.00 L9.87,11.81 L9.86,11.62 L9.86,11.43 L9.88,11.23 L9.92,11.03 L9.98,10.83 L10.06,10.64 L10.15,10.45 L10.27,10.27 L10.40,10.09 L10.55,9.93 L10.72,9.78 L10.90,9.64 L11.10,9.52 L11.31,9.41 L11.53,9.32 L11.76,9.25 L12.00,9.20 L12.25,9.17 L12.50,9.17 L12.75,9.18 L13.01,9.22 L13.27,9.29 L13.52,9.37 L13.76,9.48 L14.00,9.62 L14.23,9.77 L14.44,9.95 L14.64,10.15 L14.83,10.37 L15.00,10.60 L15.14,10.86 L15.27,11.12 L15.37,11.41 L15.45,11.70 L15.50,12.00 L15.53,12.31 L15.52,12.62 L15.49,12.94 L15.44,13.25 L15.35,13.56 L15.23,13.87 L15.09,14.16 L14.92,14.45 L14.72,14.72 L14.50,14.98 L14.25,15.22 L13.98,15.44 L13.69,15.63 L13.38,15.80 L13.06,15.94 L12.72,16.06 L12.36,16.15 L12.00,16.20 L11.63,16.22 L11.26,16.21 L10.88,16.17 L10.51,16.09 L10.14,15.98 L9.78,15.84 L9.43,15.66 L9.10,15.46 L8.78,15.22 L8.48,14.95 L8.21,14.65 L7.96,14.33 L7.74,13.99 L7.54,13.62 L7.38,13.24 L7.25,12.84 L7.16,12.42 L7.10,12.00 L7.08,11.57 L7.10,11.14 L7.15,10.70 L7.25,10.27 L7.38,9.85 L7.55,9.43 L7.76,9.03 L8.01,8.65 L8.29,8.29 L8.60,7.95 L8.94,7.64 L9.32,7.35 L9.72,7.10 L10.14,6.88 L10.58,6.70 L11.04,6.56 L11.52,6.46 L12.00,6.40 L12.49,6.38 L12.99,6.41 L13.48,6.48 L13.97,6.59 L14.45,6.75 L14.92,6.95 L15.37,7.19 L15.80,7.47 L16.21,7.79 L16.59,8.15 L16.94,8.54 L17.25,8.97 L17.53,9.42 L17.77,9.90 L17.97,10.40 L18.13,10.92 L18.24,11.45 L18.30,12.00 L18.31,12.55 L18.28,13.11 L18.20,13.66 L18.07,14.21 L17.89,14.74 L17.66,15.27 L17.38,15.77 L17.06,16.25 L16.70,16.70 L16.30,17.12 L15.86,17.51 L15.38,17.86 L14.88,18.17 L14.34,18.43 L13.78,18.65 L13.20,18.82 L12.61,18.93 L12.00,19.00 L11.39,19.01 L10.77,18.97 L10.16,18.87 L9.55,18.72 L8.96,18.52 L8.38,18.26 L7.83,17.96 L7.30,17.60 L6.80,17.20 L6.34,16.75 L5.92,16.26 L5.53,15.73 L5.20,15.17 L4.91,14.58 L4.68,13.96 L4.49,13.32 L4.37,12.67 L4.30,12.00 L4.29,11.33 L4.34,10.65 L4.45,9.98 L4.62,9.31 L4.85,8.66 L5.13,8.03 L5.47,7.43 L5.86,6.85 L6.31,6.31 L6.80,5.80 L7.34,5.34 L7.92,4.93 L8.53,4.56 L9.18,4.25 L9.86,4.00 L10.55,3.80 L11.27,3.67 L12.00,3.60" style="fill:none;stroke:#009688;stroke-linecap:round;stroke-linejoin:round"/><rect x="1.5" y="1.5" width="21" height="21" rx="2" style="fill:none;stroke:#949494;stroke-linecap:round;stroke-linejoin:round"/></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

+51 -1
View File
@@ -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 Copy-Item "$env:SystemRoot\System32\where.exe" (Join-Path $clangDir 'clang-cl.exe') -Force
$clangOnPath = "$clangDir;$env:PATH" $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 # 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. # stands in for a machine whose Visual Studio has no clang toolset.
$noVs = Join-Path $fixtures 'no-vs' $noVs = Join-Path $fixtures 'no-vs'
@@ -121,7 +127,7 @@ $cases = @(
'Examples:', 'Environment:') } 'Examples:', 'Environment:') }
@{ Name = 'the environment section shows what to set'; Args = @('--help'); DryRun = $false @{ 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', 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 @{ Name = 'section headers do not widen the flag column'; Args = @('--help'); DryRun = $false
Match = @('^ -d, --deps +Download') } Match = @('^ -d, --deps +Download') }
# Windows Terminal opens at 120 columns and wraps at 120, so 119 is the # Windows Terminal opens at 120 columns and wraps at 120, so 119 is the
@@ -294,6 +300,14 @@ $cases = @(
Contains = @('-DBUILD_TESTS=ON') } Contains = @('-DBUILD_TESTS=ON') }
@{ Name = '-a enables ASAN for the slicer'; Args = @('-s', '-a') @{ Name = '-a enables ASAN for the slicer'; Args = @('-s', '-a')
Contains = @('-DSLIC3R_ASAN=ON') } 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') @{ Name = 'the slicer build runs gettext'; Args = @('-s')
Contains = @('run_gettext.bat') } Contains = @('run_gettext.bat') }
# tools\7z.exe needs a 7z.dll beside it, which the repo does not carry, # 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') @{ Name = 'deps and slicer build in one invocation'; Args = @('-d', '-s', '-x', '-l')
Contains = @('cmake -S deps', 'cmake -B "build-clang" ') } 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' 'the developer loop'
@{ Name = '--slicer-target builds one target'; Args = @('-s', '--slicer-target', 'libslic3r') @{ Name = '--slicer-target builds one target'; Args = @('-s', '--slicer-target', 'libslic3r')
Contains = @('--config Release --target libslic3r') } Contains = @('--config Release --target libslic3r') }
@@ -1,6 +1,7 @@
#include "BlacklistedLibraryCheck.hpp" #include "BlacklistedLibraryCheck.hpp"
#include <cstdio> #include <cstdio>
#include <boost/filesystem/path.hpp>
#include <boost/nowide/convert.hpp> #include <boost/nowide/convert.hpp>
#ifdef WIN32 #ifdef WIN32
+9
View File
@@ -147,6 +147,8 @@ set(lisbslic3r_sources
Fill/FillBase.hpp Fill/FillBase.hpp
Fill/FillConcentric.cpp Fill/FillConcentric.cpp
Fill/FillConcentric.hpp Fill/FillConcentric.hpp
Fill/FillSpiralInset.cpp
Fill/FillSpiralInset.hpp
Fill/FillConcentricInternal.cpp Fill/FillConcentricInternal.cpp
Fill/FillConcentricInternal.hpp Fill/FillConcentricInternal.hpp
Fill/FillCornerSmoothing.cpp Fill/FillCornerSmoothing.cpp
@@ -663,6 +665,13 @@ if(SLIC3R_PROFILE)
target_link_libraries(libslic3r PRIVATE Shiny) target_link_libraries(libslic3r PRIVATE Shiny)
endif() 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) if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE) add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE)
endif () endif ()
+4 -2
View File
@@ -950,7 +950,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
params.extruder = region_config.internal_solid_filament_id; params.extruder = region_config.internal_solid_filament_id;
// Orca: forced fill order applies only to top/bottom surfaces filled with a // 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. // 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) if (params.extrusion_role == erTopSolidInfill)
params.fill_order = region_config.top_surface_fill_order.value; params.fill_order = region_config.top_surface_fill_order.value;
else if (params.extrusion_role == erBottomSurface) 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 = surface_fill.params.anchor_length;
params.anchor_length_max = surface_fill.params.anchor_length_max; params.anchor_length_max = surface_fill.params.anchor_length_max;
params.resolution = resolution; 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.layer_height = layerm->layer()->height;
params.lateral_lattice_angle_1 = surface_fill.params.lateral_lattice_angle_1; params.lateral_lattice_angle_1 = surface_fill.params.lateral_lattice_angle_1;
params.lateral_lattice_angle_2 = surface_fill.params.lateral_lattice_angle_2; 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 ipCubic:
case ipLine: case ipLine:
case ipConcentric: case ipConcentric:
case ipSpiralInset:
case ipHoneycomb: case ipHoneycomb:
case ipLateralHoneycomb: case ipLateralHoneycomb:
case ip3DHoneycomb: case ip3DHoneycomb:
+2
View File
@@ -15,6 +15,7 @@
#include "FillBase.hpp" #include "FillBase.hpp"
#include "FillConcentric.hpp" #include "FillConcentric.hpp"
#include "FillSpiralInset.hpp"
#include "FillHoneycomb.hpp" #include "FillHoneycomb.hpp"
#include "Fill3DHoneycomb.hpp" #include "Fill3DHoneycomb.hpp"
#include "FillGyroid.hpp" #include "FillGyroid.hpp"
@@ -41,6 +42,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
{ {
switch (type) { switch (type) {
case ipConcentric: return new FillConcentric(); case ipConcentric: return new FillConcentric();
case ipSpiralInset: return new FillSpiralInset();
case ipHoneycomb: return new FillHoneycomb(); case ipHoneycomb: return new FillHoneycomb();
case ipLateralHoneycomb: return new FillLateralHoneycomb(); case ipLateralHoneycomb: return new FillLateralHoneycomb();
case ip3DHoneycomb: return new Fill3DHoneycomb(); case ip3DHoneycomb: return new Fill3DHoneycomb();
+426
View File
@@ -0,0 +1,426 @@
#include "../ClipperUtils.hpp"
#include "../ExPolygon.hpp"
#include "../Surface.hpp"
#include "../VariableWidth.hpp"
#include "Arachne/WallToolPaths.hpp"
#include "FillSpiralInset.hpp"
#include <algorithm>
#include <cmath>
#include <functional>
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<double>();
Vec2d e_out = (p_next - p).cast<double>();
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<double>();
Vec2d v2 = (p_next - p_last).cast<double>();
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<double>();
const double len = v.norm();
if (len > distance) {
const double t = distance / len;
path.points.emplace_back((last.cast<double>() + v * t).cast<coord_t>());
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<class LoopType, class PathType>
static std::vector<PathType> generate_spiral_insets(const FillParams& params,
const std::vector<const LoopType*>& loops,
const Polygons& loop_outlines,
const coord_t distance,
const ExPolygon& original_expoly)
{
std::vector<PathType> 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<float, Point>& 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<double>(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<const Polygon*> loop_refs;
loop_refs.reserve(loops.size());
for (const Polygon& loop : loops)
loop_refs.emplace_back(&loop);
Polylines spiral_result = generate_spiral_insets<Polygon, Polyline>(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<float, Point>& 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<coord_t>(this->spacing)));
return;
}
// no rotation is supported for this infill pattern
Point bbox_size = expolygon.contour.bounding_box().size();
coord_t min_spacing = scaled<coord_t>(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<Arachne::VariableWidthLines> 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<const Arachne::ExtrusionLine*> 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<int> nesting_depth(wall_count, 0), parent(wall_count, -1);
std::vector<char> 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<const Arachne::ExtrusionLine*> ordered;
Polygons outlines;
ordered.reserve(wall_count);
outlines.reserve(wall_count);
std::function<void(int)> 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<Arachne::ExtrusionLine, ThickPolyline>(params, ordered, outlines, min_spacing, expolygon);
append(thick_polylines_out, std::move(spiral_result));
append(thick_polylines_out, std::move(open_walls));
}
} // namespace Slic3r
+37
View File
@@ -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 &params,
unsigned int thickness_layers,
const std::pair<float, Point> &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 &params,
unsigned int thickness_layers,
const std::pair<float, Point> &direction,
ExPolygon expolygon,
ThickPolylines &thick_polylines_out) override;
bool no_sort() const override { return true; }
};
} // namespace Slic3r
#endif // slic3r_FillSpiralInset_hpp_
-1
View File
@@ -125,7 +125,6 @@ public:
/******************************************** Splitting the 128bit number into two 64bit words *********************************************/ /******************************************** 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(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(const int64_t& hi, const uint64_t& lo) : m_lo(lo), m_hi(hi) {}
Int128& operator = (const int64_t &val) Int128& operator = (const int64_t &val)
+1
View File
@@ -419,6 +419,7 @@ coordf_t Layer::get_sparse_infill_max_void_area()
double spacing = flow.scaled_spacing() * (100 - density) / density; double spacing = flow.scaled_spacing() * (100 - density) / density;
switch (pattern) { switch (pattern) {
case ipConcentric: case ipConcentric:
case ipSpiralInset:
case ipRectilinear: case ipRectilinear:
case ipLine: case ipLine:
case ipGyroid: case ipGyroid:
+1
View File
@@ -24,6 +24,7 @@ public:
explicit MultiPoint(const Points &_points) : points(_points) {} explicit MultiPoint(const Points &_points) : points(_points) {}
MultiPoint& operator=(const MultiPoint &other) { points = other.points; return *this; } MultiPoint& operator=(const MultiPoint &other) { points = other.points; return *this; }
MultiPoint& operator=(MultiPoint &&other) { points = std::move(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);
void scale(double factor_x, double factor_y); void scale(double factor_x, double factor_y);
void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); } void translate(double x, double y) { this->translate(Point(coord_t(x), coord_t(y))); }
+5
View File
@@ -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 <Windows.h>
#endif
#include "Config.hpp" #include "Config.hpp"
#include "Exception.hpp" #include "Exception.hpp"
#include "Print.hpp" #include "Print.hpp"
+8 -2
View File
@@ -275,6 +275,7 @@ static t_config_enum_values s_keys_map_InfillPattern {
{ "tpmsfk", ipTpmsFK }, { "tpmsfk", ipTpmsFK },
{ "gyroid", ipGyroid }, { "gyroid", ipGyroid },
{ "concentric", ipConcentric }, { "concentric", ipConcentric },
{ "spiralinset", ipSpiralInset },
{ "hilbertcurve", ipHilbertCurve }, { "hilbertcurve", ipHilbertCurve },
{ "archimedeanchords", ipArchimedeanChords }, { "archimedeanchords", ipArchimedeanChords },
{ "octagramspiral", ipOctagramSpiral } { "octagramspiral", ipOctagramSpiral }
@@ -371,6 +372,7 @@ static t_config_enum_values s_keys_map_SupportMaterialInterfacePattern {
{ "auto", smipAuto }, { "auto", smipAuto },
{ "rectilinear", smipRectilinear }, { "rectilinear", smipRectilinear },
{ "concentric", smipConcentric }, { "concentric", smipConcentric },
{ "spiralinset", smipSpiralInset },
{ "rectilinear_interlaced", smipRectilinearInterlaced}, { "rectilinear_interlaced", smipRectilinearInterlaced},
{ "grid", smipGrid } { "grid", smipGrid }
}; };
@@ -2292,6 +2294,7 @@ void PrintConfigDef::init_fff_params()
def->enum_values.push_back("rectilinear"); def->enum_values.push_back("rectilinear");
def->enum_values.push_back("alignedrectilinear"); def->enum_values.push_back("alignedrectilinear");
def->enum_values.push_back("concentric"); def->enum_values.push_back("concentric");
def->enum_values.push_back("spiralinset");
def->enum_values.push_back("hilbertcurve"); def->enum_values.push_back("hilbertcurve");
def->enum_values.push_back("archimedeanchords"); def->enum_values.push_back("archimedeanchords");
def->enum_values.push_back("octagramspiral"); 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("Rectilinear"));
def->enum_labels.push_back(L("Aligned Rectilinear")); def->enum_labels.push_back(L("Aligned Rectilinear"));
def->enum_labels.push_back(L("Concentric")); 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("Hilbert Curve"));
def->enum_labels.push_back(L("Archimedean Chords")); def->enum_labels.push_back(L("Archimedean Chords"));
def->enum_labels.push_back(L("Octagram Spiral")); 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->label = L("Top surface fill order");
def->category = L("Strength"); def->category = L("Strength");
def->tooltip = L("Direction in which top surfaces are filled when using a center-based pattern " 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 " "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 " "towards the edge where it is least visible. Inward starts at the edge and ends "
"with the tight curves at the center.\n" "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->label = L("Bottom surface fill order");
def->category = L("Strength"); def->category = L("Strength");
def->tooltip = L("Direction in which bottom surfaces are filled when using a center-based pattern " 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 " "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. " "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" "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("auto");
def->enum_values.push_back("rectilinear"); def->enum_values.push_back("rectilinear");
def->enum_values.push_back("concentric"); def->enum_values.push_back("concentric");
def->enum_values.push_back("spiralinset");
def->enum_values.push_back("rectilinear_interlaced"); def->enum_values.push_back("rectilinear_interlaced");
def->enum_values.push_back("grid"); def->enum_values.push_back("grid");
def->enum_labels.push_back(L("Default")); def->enum_labels.push_back(L("Default"));
def->enum_labels.push_back(L("Rectilinear")); def->enum_labels.push_back(L("Rectilinear"));
def->enum_labels.push_back(L("Concentric")); 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("Rectilinear Interlaced"));
def->enum_labels.push_back(L("Grid")); def->enum_labels.push_back(L("Grid"));
def->mode = comAdvanced; def->mode = comAdvanced;
+2 -2
View File
@@ -113,7 +113,7 @@ enum InfillPattern : int {
ipCubic, ipAdaptiveCubic, ipQuarterCubic, ipSupportCubic, ipLightning, ipCubic, ipAdaptiveCubic, ipQuarterCubic, ipSupportCubic, ipLightning,
ipHoneycomb, ip3DHoneycomb, ipLateralHoneycomb, ipLateralLattice, ipHoneycomb, ip3DHoneycomb, ipLateralHoneycomb, ipLateralLattice,
ipCrossHatch, ipTpmsD, ipTpmsFK, ipGyroid, ipCrossHatch, ipTpmsD, ipTpmsFK, ipGyroid,
ipConcentric, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipConcentric, ipSpiralInset, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral,
ipSupportBase, ipConcentricInternal, ipSupportBase, ipConcentricInternal,
ipCount, ipCount,
}; };
@@ -271,7 +271,7 @@ enum LongRectrationLevel
}; };
enum SupportMaterialInterfacePattern { enum SupportMaterialInterfacePattern {
smipAuto, smipRectilinear, smipConcentric, smipRectilinearInterlaced, smipGrid smipAuto, smipRectilinear, smipConcentric, smipSpiralInset, smipRectilinearInterlaced, smipGrid
}; };
// BBS // BBS
+1
View File
@@ -1,4 +1,5 @@
#include <functional> #include <functional>
#include <numeric>
#include <optional> #include <optional>
#include <libslic3r/OpenVDBUtils.hpp> #include <libslic3r/OpenVDBUtils.hpp>
@@ -141,6 +141,8 @@ struct SupportParameters {
this->contact_fill_pattern = ipGrid; this->contact_fill_pattern = ipGrid;
else if (object_config.support_interface_pattern == smipRectilinearInterlaced) else if (object_config.support_interface_pattern == smipRectilinearInterlaced)
this->contact_fill_pattern = ipRectilinear; this->contact_fill_pattern = ipRectilinear;
else if (object_config.support_interface_pattern == smipSpiralInset)
this->contact_fill_pattern = ipSpiralInset;
else else
this->contact_fill_pattern = this->contact_fill_pattern =
(object_config.support_interface_pattern == smipAuto && zero_gap_contact_interface) || (object_config.support_interface_pattern == smipAuto && zero_gap_contact_interface) ||
+3 -4
View File
@@ -916,6 +916,9 @@ endif ()
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY) if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE) 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 () endif ()
if (APPLE) if (APPLE)
@@ -981,10 +984,6 @@ endif ()
# Add a definition so that we can tell we are compiling slic3r. # Add a definition so that we can tell we are compiling slic3r.
target_compile_definitions(libslic3r_gui PRIVATE SLIC3R_CURRENTLY_COMPILING_GUI_MODULE) 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) if (ORCA_BUILD_PYTHON_STUBGEN_MODULE)
add_library(orca_stubgen MODULE add_library(orca_stubgen MODULE
plugin/PythonPluginBridge.cpp plugin/PythonPluginBridge.cpp
-5
View File
@@ -1915,11 +1915,6 @@ void ColorPickerPopup::paintEvent(wxPaintEvent& evt)
void ColorPickerPopup::OnDismiss() {} void ColorPickerPopup::OnDismiss() {}
void ColorPickerPopup::Popup()
{
PopupWindow::Popup();
}
bool ColorPickerPopup::ProcessLeftDown(wxMouseEvent& event) { bool ColorPickerPopup::ProcessLeftDown(wxMouseEvent& event) {
return PopupWindow::ProcessLeftDown(event); return PopupWindow::ProcessLeftDown(event);
} }
-1
View File
@@ -85,7 +85,6 @@ public:
void set_ams_colours(std::vector<wxColour> ams); void set_ams_colours(std::vector<wxColour> ams);
void set_def_colour(wxColour col); void set_def_colour(wxColour col);
void paintEvent(wxPaintEvent& evt); void paintEvent(wxPaintEvent& evt);
void Popup();
virtual void OnDismiss() wxOVERRIDE; virtual void OnDismiss() wxOVERRIDE;
virtual bool ProcessLeftDown(wxMouseEvent& event) wxOVERRIDE; virtual bool ProcessLeftDown(wxMouseEvent& event) wxOVERRIDE;
+2 -2
View File
@@ -292,7 +292,7 @@ void AMSSetting::UpdateByObj(MachineObject* obj)
update_ams_img(obj); update_ams_img(obj);
m_ams_type->Update(obj); m_ams_type->UpdateInfo(obj);
//m_ams_arrange_order->Update(obj); //m_ams_arrange_order->Update(obj);
update_insert_material_read_mode(obj); update_insert_material_read_mode(obj);
m_sizer_remain_block->Show(obj->is_support_update_remain); m_sizer_remain_block->Show(obj->is_support_update_remain);
@@ -624,7 +624,7 @@ void AMSSettingTypePanel::CreateGui()
Fit(); Fit();
} }
void AMSSettingTypePanel::Update(const MachineObject* obj) void AMSSettingTypePanel::UpdateInfo(const MachineObject* obj)
{ {
if (!obj) { if (!obj) {
Show(false); Show(false);
+1 -1
View File
@@ -110,7 +110,7 @@ public:
~AMSSettingTypePanel(); ~AMSSettingTypePanel();
public: public:
void Update(const MachineObject* obj); void UpdateInfo(const MachineObject* obj);
private: private:
void CreateGui(); void CreateGui();
+10 -3
View File
@@ -741,14 +741,21 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
bool have_infill = config->option<ConfigOptionPercent>("sparse_infill_density")->value > 0; bool have_infill = config->option<ConfigOptionPercent>("sparse_infill_density")->value > 0;
// sparse_infill_filament_id uses the same logic as in Print::extruders() // sparse_infill_filament_id uses the same logic as in Print::extruders()
for (auto el : { "sparse_infill_pattern", "infill_combination", "fill_multiline","infill_direction", 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); toggle_line(el, have_infill);
InfillPattern pattern = config->opt_enum<InfillPattern>("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; bool have_combined_infill = config->opt_bool("infill_combination") && have_infill;
toggle_line("infill_combination_max_layer_height", have_combined_infill); toggle_line("infill_combination_max_layer_height", have_combined_infill);
// Infill patterns that support multiline infill. // Infill patterns that support multiline infill.
InfillPattern pattern = config->opt_enum<InfillPattern>("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 || 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; 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); toggle_line("separated_infills", is_internal_infill_separable);
// Fill order is only meaningful for the center-based surface fill patterns; hide it otherwise. // 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<InfillPattern>("top_surface_pattern"))); toggle_line("top_surface_fill_order", has_top_shell && is_centered_fill(config->opt_enum<InfillPattern>("top_surface_pattern")));
toggle_line("bottom_surface_fill_order", has_bottom_shell && is_centered_fill(config->opt_enum<InfillPattern>("bottom_surface_pattern"))); toggle_line("bottom_surface_fill_order", has_bottom_shell && is_centered_fill(config->opt_enum<InfillPattern>("bottom_surface_pattern")));
@@ -705,7 +705,7 @@ ReselectMachineDialog::ReselectMachineDialog(wxWindow* parent)
Centre(); Centre();
} }
void ReselectMachineDialog::Update(MachineObject* obj, const std::map<int, int>& best_pos_map, const std::vector<FilamentInfo>& ams_mapping, wxString save_time) void ReselectMachineDialog::UpdateInfo(MachineObject* obj, const std::map<int, int>& best_pos_map, const std::vector<FilamentInfo>& ams_mapping, wxString save_time)
{ {
if (suggestText) if (suggestText)
@@ -188,7 +188,7 @@ class ReselectMachineDialog : public wxDialog
public: public:
ReselectMachineDialog(wxWindow* parent); ReselectMachineDialog(wxWindow* parent);
~ReselectMachineDialog(); ~ReselectMachineDialog();
void Update(MachineObject* obj, void UpdateInfo(MachineObject* obj,
const std::map<int, int>& best_pos_map, const std::map<int, int>& best_pos_map,
const std::vector<FilamentInfo>& ams_mapping, const std::vector<FilamentInfo>& ams_mapping,
wxString save_time); wxString save_time);
@@ -98,7 +98,7 @@ void uiAmsPercentHumidityDryPopup::Create()
Refresh(); 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 || if (m_humidity_level != humidiy_level || m_humidity_percent != humidity_percent ||
m_left_dry_time != left_dry_time || m_current_temperature != current_temperature) m_left_dry_time != left_dry_time || m_current_temperature != current_temperature)
@@ -38,14 +38,14 @@ public:
~uiAmsPercentHumidityDryPopup() = default; ~uiAmsPercentHumidityDryPopup() = default;
public: 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; } std::string get_owner_ams_id() const { return m_ams_id; }
void msw_rescale(); void msw_rescale();
private: 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 UpdateContents();
void Create(); void Create();
@@ -399,7 +399,7 @@ void wgtDeviceNozzleRackArea::UpdateNozzleItems(const std::unordered_map<int, wg
{ {
for (auto iter : nozzle_items) for (auto iter : nozzle_items)
{ {
iter.second->Update(nozzle_rack); iter.second->UpdateInfo(nozzle_rack);
} }
/*update nozzle possition and background*/ /*update nozzle possition and background*/
@@ -837,7 +837,7 @@ void wgtDeviceNozzleRackNozzleItem::SetSelected(bool selected)
} }
} }
void wgtDeviceNozzleRackNozzleItem::Update(const std::shared_ptr<DevNozzleRack> rack, bool on_rack /*= true*/) void wgtDeviceNozzleRackNozzleItem::UpdateInfo(const std::shared_ptr<DevNozzleRack> rack, bool on_rack /*= true*/)
{ {
m_rack = rack; m_rack = rack;
@@ -200,7 +200,7 @@ public:
wgtDeviceNozzleRackNozzleItem(wxWindow* parent, int nozzle_id); wgtDeviceNozzleRackNozzleItem(wxWindow* parent, int nozzle_id);
public: public:
void Update(const std::shared_ptr<DevNozzleRack> rack, bool on_rack = true); // on_rack is false means extruder nozzle void UpdateInfo(const std::shared_ptr<DevNozzleRack> rack, bool on_rack = true); // on_rack is false means extruder nozzle
int GetNozzleId() const { return m_nozzle_id; } int GetNozzleId() const { return m_nozzle_id; }
void SetDisplayIdText(const wxString& text) { m_nozzle_label_id->SetLabel(text);}; void SetDisplayIdText(const wxString& text) { m_nozzle_label_id->SetLabel(text);};
@@ -114,7 +114,7 @@ static void s_update_nozzle_info(wgtDeviceNozzleRackNozzleItem* item,
std::shared_ptr<DevNozzleRack> rack, std::shared_ptr<DevNozzleRack> rack,
const DevNozzle& nozzle_info) const DevNozzle& nozzle_info)
{ {
item->Update(rack, nozzle_info.IsOnRack()); item->UpdateInfo(rack, nozzle_info.IsOnRack());
if (nozzle_info.IsUnknown()) { if (nozzle_info.IsUnknown()) {
if (item->GetToolTipText() != _L("Nozzle information needs to be read")) { if (item->GetToolTipText() != _L("Nozzle information needs to be read")) {
item->SetToolTip(_L("Nozzle information needs to be read")); item->SetToolTip(_L("Nozzle information needs to be read"));
+1
View File
@@ -3,6 +3,7 @@
#include "wx/bitmap.h" #include "wx/bitmap.h"
#include "wx/dragimag.h" #include "wx/dragimag.h"
#include "wx/panel.h"
namespace Slic3r { namespace GUI { namespace Slic3r { namespace GUI {
+12 -32
View File
@@ -642,19 +642,12 @@ void FilamentMapBtnPanel::Select(bool selected)
Refresh(); Refresh();
} }
void GUI::FilamentMapBtnPanel::Hide() bool GUI::FilamentMapBtnPanel::Show(bool show)
{ {
m_btn->Hide(); m_btn->Show(show);
m_label->Hide(); m_label->Show(show);
m_detail->Hide(); m_detail->Show(show);
wxPanel::Hide(); return wxPanel::Show(show);
}
void GUI::FilamentMapBtnPanel::Show()
{
m_btn->Show();
m_label->Show();
m_detail->Show();
wxPanel::Show();
} }
FilamentMapAutoPanel::FilamentMapAutoPanel(wxWindow *parent, FilamentMapMode mode, bool machine_synced) : wxPanel(parent) FilamentMapAutoPanel::FilamentMapAutoPanel(wxWindow *parent, FilamentMapMode mode, bool machine_synced) : wxPanel(parent)
@@ -694,18 +687,11 @@ FilamentMapAutoPanel::FilamentMapAutoPanel(wxWindow *parent, FilamentMapMode mod
Layout(); Layout();
GUI::wxGetApp().UpdateDarkUIWin(this); GUI::wxGetApp().UpdateDarkUIWin(this);
} }
void FilamentMapAutoPanel::Hide() bool FilamentMapAutoPanel::Show(bool show)
{ {
m_flush_panel->Hide(); m_flush_panel->Show(show);
m_match_panel->Hide(); m_match_panel->Show(show);
wxPanel::Hide(); return wxPanel::Show(show);
}
void FilamentMapAutoPanel::Show()
{
m_flush_panel->Show();
m_match_panel->Show();
wxPanel::Show();
} }
void FilamentMapAutoPanel::UpdateStatus() void FilamentMapAutoPanel::UpdateStatus()
@@ -743,16 +729,10 @@ FilamentMapDefaultPanel::FilamentMapDefaultPanel(wxWindow *parent) : wxPanel(par
GUI::wxGetApp().UpdateDarkUIWin(this); GUI::wxGetApp().UpdateDarkUIWin(this);
} }
void FilamentMapDefaultPanel::Hide() bool FilamentMapDefaultPanel::Show(bool show)
{ {
m_label->Hide(); m_label->Show(show);
wxPanel::Hide(); return wxPanel::Show(show);
}
void FilamentMapDefaultPanel::Show()
{
m_label->Show();
wxPanel::Show();
} }
}} // namespace Slic3r::GUI }} // namespace Slic3r::GUI
+4 -7
View File
@@ -69,10 +69,9 @@ class FilamentMapBtnPanel : public wxPanel
{ {
public: public:
FilamentMapBtnPanel(wxWindow *parent, const wxString &label, const wxString &detail, const std::string &icon_path); FilamentMapBtnPanel(wxWindow *parent, const wxString &label, const wxString &detail, const std::string &icon_path);
void Hide(); bool Show(bool show = true) override;
void Show();
void Select(bool selected); void Select(bool selected);
bool Enable(bool enable); bool Enable(bool enable) override;
bool IsEnabled() const { return m_enabled; } bool IsEnabled() const { return m_enabled; }
protected: protected:
void OnPaint(wxPaintEvent &event); void OnPaint(wxPaintEvent &event);
@@ -99,8 +98,7 @@ class FilamentMapAutoPanel : public wxPanel
{ {
public: public:
FilamentMapAutoPanel(wxWindow *parent, FilamentMapMode mode, bool machine_synced); FilamentMapAutoPanel(wxWindow *parent, FilamentMapMode mode, bool machine_synced);
void Hide(); bool Show(bool show = true) override;
void Show();
FilamentMapMode GetMode() const { return m_mode; } FilamentMapMode GetMode() const { return m_mode; }
private: private:
@@ -116,8 +114,7 @@ class FilamentMapDefaultPanel : public wxPanel
{ {
public: public:
FilamentMapDefaultPanel(wxWindow *parent); FilamentMapDefaultPanel(wxWindow *parent);
void Hide(); bool Show(bool show = true) override;
void Show();
private: private:
Label *m_label; Label *m_label;
+27 -10
View File
@@ -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 //BBS: GUI refactor: move to right
void GCodeViewer::SequentialView::GCodeWindow::render(float top, float bottom, float right, uint64_t curr_line_id) const 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 // read line from file
const size_t start = id == 1 ? 0 : m_lines_ends[id - 2]; 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 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); std::string gline(m_file.data() + start, len);
// If original line is longer than 55 characters, truncate and append "..." // If original line is longer than 55 characters, truncate and append "...".
if (original_len > 55) // The cut must land on a character boundary or it leaves half a character behind.
gline = gline.substr(0, 52) + "..."; if (len < original_len || utf8_offset(gline, 55) < gline.size())
gline = gline.substr(0, utf8_offset(gline, 52)) + "...";
std::string command, parameters, comment; std::string command, parameters, comment;
// extract comment const size_t comment_start = gline.find(';');
std::vector<std::string> tokens; if (comment_start == std::string::npos)
boost::split(tokens, gline, boost::is_any_of(";"), boost::token_compress_on); command = gline;
command = tokens.front(); else {
if (tokens.size() > 1) command = gline.substr(0, comment_start);
comment = ";" + tokens.back(); comment = gline.substr(comment_start);
}
// extract gcode command and parameters // extract gcode command and parameters
if (!command.empty()) { if (!command.empty()) {
std::vector<std::string> tokens;
boost::split(tokens, command, boost::is_any_of(" "), boost::token_compress_on); boost::split(tokens, command, boost::is_any_of(" "), boost::token_compress_on);
command = tokens.front(); command = tokens.front();
if (tokens.size() > 1) { if (tokens.size() > 1) {
+1
View File
@@ -12,6 +12,7 @@
#include "BuildCommit.hpp" #include "BuildCommit.hpp"
#include "Downloader.hpp" #include "Downloader.hpp"
#include <boost/chrono/duration.hpp> #include <boost/chrono/duration.hpp>
#include <boost/locale/encoding_utf.hpp>
#include <boost/log/detail/native_typeof.hpp> #include <boost/log/detail/native_typeof.hpp>
#include <libslic3r/Config.hpp> #include <libslic3r/Config.hpp>
#include <mutex> #include <mutex>
+1 -1
View File
@@ -199,7 +199,7 @@ public:
OptionsGroup(wxWindow *_parent, const wxString &title, const wxString &icon, bool is_tab_opt = false, OptionsGroup(wxWindow *_parent, const wxString &title, const wxString &icon, bool is_tab_opt = false,
column_t extra_clmn = nullptr); column_t extra_clmn = nullptr);
~OptionsGroup() { clear(true); } virtual ~OptionsGroup() { clear(true); }
wxGridSizer* get_grid_sizer() { return m_grid_sizer; } wxGridSizer* get_grid_sizer() { return m_grid_sizer; }
const std::vector<Line>& get_lines() { return m_lines; } const std::vector<Line>& get_lines() { return m_lines; }
+2 -2
View File
@@ -2176,7 +2176,7 @@ void SelectMachineDialog::on_reselect_dialog_btn_clicked(wxMouseEvent&)
best_pos_map[slot.id] = pos.value(); 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(); m_best_pos_dialog->ShowModal();
} }
@@ -2201,7 +2201,7 @@ void SelectMachineDialog::update_best_pos_dialog(wxCommandEvent& evt)
best_pos_map[slot.id] = pos.value(); 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<wxString> params, wxString wiki_url) void SelectMachineDialog::show_status(PrintDialogStatus status, std::vector<wxString> params, wxString wiki_url)
+1 -1
View File
@@ -1391,7 +1391,7 @@ void SendToPrinterDialog::show_status(PrintDialogStatus status, std::vector<wxSt
update_print_status_msg(wxEmptyString, true, true); update_print_status_msg(wxEmptyString, true, true);
m_connecting_panel->Show(); m_connecting_panel->Show();
m_animaicon->Stop(); m_animaicon->Stop();
m_animaicon->Enable(); m_animaicon->ShowEnabledIcon();
Layout(); Layout();
Enable_Send_Button(false); Enable_Send_Button(false);
@@ -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); // 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); // 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_progress_text(text_pos);
// render_close_button(button_pos, button_size); // render_progress_close_button(button_pos, button_size);
// render_show_dailytips(view_dailytips_text_pos); // 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 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)); 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_progress_text(text_pos);
render_close_button(button_pos, button_size); render_progress_close_button(button_pos, button_size);
if (m_sp_state == SlicingProgressState::SP_PROGRESS) { if (m_sp_state == SlicingProgressState::SP_PROGRESS) {
render_bar(progress_bar_pos, progress_bar_size); render_bar(progress_bar_pos, progress_bar_size);
render_cancel_button(button_pos, button_size); render_cancel_button(button_pos, button_size);
@@ -319,7 +319,7 @@ void NotificationManager::SlicingProgressNotification::render(GLCanvas3D& canvas
ImGui::PopStyleColor(3); 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(); ImGuiWrapper& imgui = *wxGetApp().imgui();
float scale = imgui.get_font_size() / 15.0f; 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) { if (m_sp_state == SlicingProgressState::SP_CANCELLED || m_sp_state == SlicingProgressState::SP_COMPLETED) {
ImGuiWrapper& imgui = *wxGetApp().imgui(); ImGuiWrapper& imgui = *wxGetApp().imgui();
@@ -53,10 +53,10 @@ protected:
void init() override; void init() override;
void render(GLCanvas3D& canvas, float initial_y, bool move_from_overlay, float overlay_width, float right_margin) 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 */ /* 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_bar(const ImVec2& pos, const ImVec2& size);
void render_cancel_button(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_dailytips_panel(const ImVec2& pos, const ImVec2& size);
void render_show_dailytips(const ImVec2& pos); void render_show_dailytips(const ImVec2& pos);
+2 -2
View File
@@ -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->Move(popup_pos);
m_ams_dry_ctr_win->ShowModal(); m_ams_dry_ctr_win->ShowModal();
} else { } else {
m_percent_humidity_dry_popup->Update(info); m_percent_humidity_dry_popup->UpdateInfo(info);
wxPoint img_pos = ClientToScreen(wxPoint(0, 0)); 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)); 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.humidity_percent = the_info.humidity_raw;
humidity_info.left_dry_time = the_info.left_dray_time; humidity_info.left_dry_time = the_info.left_dray_time;
humidity_info.current_temperature = the_info.current_temperature; 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; break;
} }
} }
+1 -1
View File
@@ -78,7 +78,7 @@ void AnimaIcon::Stop()
m_timer->Stop(); m_timer->Stop();
} }
void AnimaIcon::Enable() void AnimaIcon::ShowEnabledIcon()
{ {
if (m_bitmap) { m_bitmap->SetBitmap(m_image_enable); } if (m_bitmap) { m_bitmap->SetBitmap(m_image_enable); }
} }
+1 -1
View File
@@ -13,7 +13,7 @@ public:
void Play(); void Play();
void Stop(); void Stop();
void Enable(); void ShowEnabledIcon();
bool IsPlaying() const { return IsRunning(); }; bool IsPlaying() const { return IsRunning(); };
bool IsRunning() const; bool IsRunning() const;
@@ -17,7 +17,7 @@ public:
void paintEvent(wxPaintEvent &evt); void paintEvent(wxPaintEvent &evt);
void SetLabel(wxString msg){m_msg = msg;}; void SetLabel(const wxString &msg) override { m_msg = msg; }
}; };
#endif #endif
+1 -1
View File
@@ -600,7 +600,7 @@ StaticBox* HotEndTable::CreateNozzleBox(const std::vector<int>& nozzle_indices)
void HotEndTable::UpdateNozzleItems(const std::unordered_map<int, wgtDeviceNozzleRackNozzleItem*>& nozzle_items, std::shared_ptr<DevNozzleRack> nozzle_rack) void HotEndTable::UpdateNozzleItems(const std::unordered_map<int, wgtDeviceNozzleRackNozzleItem*>& nozzle_items, std::shared_ptr<DevNozzleRack> nozzle_rack)
{ {
for (auto& item : nozzle_items) for (auto& item : nozzle_items)
item.second->Update(nozzle_rack); item.second->UpdateInfo(nozzle_rack);
} }
void HotEndTable::OnPaint(wxPaintEvent& evt) void HotEndTable::OnPaint(wxPaintEvent& evt)
-5
View File
@@ -36,11 +36,6 @@ void RadioBox::SetValue(bool value)
update(); update();
} }
bool RadioBox::GetValue()
{
return wxBitmapToggleButton::GetValue();
}
void RadioBox::Rescale() void RadioBox::Rescale()
{ {
-4
View File
@@ -15,14 +15,10 @@ public:
public: public:
void SetValue(bool value) override; void SetValue(bool value) override;
bool GetValue();
void Rescale(); void Rescale();
bool Disable() { bool Disable() {
return wxBitmapToggleButton::Disable(); return wxBitmapToggleButton::Disable();
} }
bool Enable() {
return wxBitmapToggleButton::Enable();
}
private: private:
void update(); void update();
+3
View File
@@ -8,6 +8,9 @@
#ifndef wxMediaCtrl3_h #ifndef wxMediaCtrl3_h
#define wxMediaCtrl3_h #define wxMediaCtrl3_h
#include <chrono>
#include "wx/window.h"
#include "wx/bitmap.h"
#include "wx/uri.h" #include "wx/uri.h"
#include "wx/mediactrl.h" #include "wx/mediactrl.h"
+1
View File
@@ -115,6 +115,7 @@ class UdpSession
{ {
public: public:
UdpSession(Bonjour::ReplyFn rfn); UdpSession(Bonjour::ReplyFn rfn);
virtual ~UdpSession() = default;
virtual void handle_receive(const boost::system::error_code& error, size_t bytes) = 0; virtual void handle_receive(const boost::system::error_code& error, size_t bytes) = 0;
std::vector<char> buffer; std::vector<char> buffer;
boost::asio::ip::udp::endpoint remote_endpoint; boost::asio::ip::udp::endpoint remote_endpoint;
+19
View File
@@ -0,0 +1,19 @@
#pragma once
// Force-included into libslic3r_gui when SLIC3R_PCH is OFF, standing in for
// pchheader.hpp, which includes <Windows.h> 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 <Windows.h>
#include <CommCtrl.h>
#include <urlmon.h>
#endif // _WIN32
+207
View File
@@ -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<Slic3r::Fill> 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<double, double> 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<double>(unscale<double>(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<double>();
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<double>(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<Slic3r::Fill> 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<Slic3r::Fill> 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<Slic3r::Fill> 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]") 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. // A cell whose sides are several times the line width, so that the corners have room to be rounded.
@@ -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<SupportMaterialInterfacePattern>::get_enum_values();
REQUIRE(values.at("spiralinset") == SupportMaterialInterfacePattern::smipSpiralInset);
}
TEST_CASE("get_extruder_nozzle_volume_count reads the per-extruder volume-type layout", "[Config]") TEST_CASE("get_extruder_nozzle_volume_count reads the per-extruder volume-type layout", "[Config]")
{ {
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types; std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;