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 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 <string> -> 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 <string> -> 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.
+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
$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') }
@@ -1,6 +1,7 @@
#include "BlacklistedLibraryCheck.hpp"
#include <cstdio>
#include <boost/filesystem/path.hpp>
#include <boost/nowide/convert.hpp>
#ifdef WIN32
+9
View File
@@ -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 ()
+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;
// 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:
+2
View File
@@ -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();
+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 *********************************************/
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)
+1
View File
@@ -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:
+1
View File
@@ -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))); }
+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 "Exception.hpp"
#include "Print.hpp"
+8 -2
View File
@@ -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;
+2 -2
View File
@@ -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
+1
View File
@@ -1,4 +1,5 @@
#include <functional>
#include <numeric>
#include <optional>
#include <libslic3r/OpenVDBUtils.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) ||
+3 -4
View File
@@ -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
-5
View File
@@ -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);
}
-1
View File
@@ -85,7 +85,6 @@ public:
void set_ams_colours(std::vector<wxColour> ams);
void set_def_colour(wxColour col);
void paintEvent(wxPaintEvent& evt);
void Popup();
virtual void OnDismiss() wxOVERRIDE;
virtual bool ProcessLeftDown(wxMouseEvent& event) wxOVERRIDE;
+2 -2
View File
@@ -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);
+1 -1
View File
@@ -110,7 +110,7 @@ public:
~AMSSettingTypePanel();
public:
void Update(const MachineObject* obj);
void UpdateInfo(const MachineObject* obj);
private:
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;
// 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<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;
toggle_line("infill_combination_max_layer_height", have_combined_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 ||
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<InfillPattern>("top_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();
}
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)
@@ -188,7 +188,7 @@ class ReselectMachineDialog : public wxDialog
public:
ReselectMachineDialog(wxWindow* parent);
~ReselectMachineDialog();
void Update(MachineObject* obj,
void UpdateInfo(MachineObject* obj,
const std::map<int, int>& best_pos_map,
const std::vector<FilamentInfo>& ams_mapping,
wxString save_time);
@@ -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)
@@ -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();
@@ -399,7 +399,7 @@ void wgtDeviceNozzleRackArea::UpdateNozzleItems(const std::unordered_map<int, wg
{
for (auto iter : nozzle_items)
{
iter.second->Update(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<DevNozzleRack> rack, bool on_rack /*= true*/)
void wgtDeviceNozzleRackNozzleItem::UpdateInfo(const std::shared_ptr<DevNozzleRack> rack, bool on_rack /*= true*/)
{
m_rack = rack;
@@ -200,7 +200,7 @@ public:
wgtDeviceNozzleRackNozzleItem(wxWindow* parent, int nozzle_id);
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; }
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,
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"));
+1
View File
@@ -3,6 +3,7 @@
#include "wx/bitmap.h"
#include "wx/dragimag.h"
#include "wx/panel.h"
namespace Slic3r { namespace GUI {
+12 -32
View File
@@ -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
+4 -7
View File
@@ -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;
+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
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<std::string> 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<std::string> tokens;
boost::split(tokens, command, boost::is_any_of(" "), boost::token_compress_on);
command = tokens.front();
if (tokens.size() > 1) {
+1
View File
@@ -12,6 +12,7 @@
#include "BuildCommit.hpp"
#include "Downloader.hpp"
#include <boost/chrono/duration.hpp>
#include <boost/locale/encoding_utf.hpp>
#include <boost/log/detail/native_typeof.hpp>
#include <libslic3r/Config.hpp>
#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,
column_t extra_clmn = nullptr);
~OptionsGroup() { clear(true); }
virtual ~OptionsGroup() { clear(true); }
wxGridSizer* get_grid_sizer() { return m_grid_sizer; }
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();
}
}
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<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);
m_connecting_panel->Show();
m_animaicon->Stop();
m_animaicon->Enable();
m_animaicon->ShowEnabledIcon();
Layout();
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);
// 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();
@@ -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);
+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->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;
}
}
+1 -1
View File
@@ -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); }
}
+1 -1
View File
@@ -13,7 +13,7 @@ public:
void Play();
void Stop();
void Enable();
void ShowEnabledIcon();
bool IsPlaying() const { return IsRunning(); };
bool IsRunning() const;
@@ -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
+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)
{
for (auto& item : nozzle_items)
item.second->Update(nozzle_rack);
item.second->UpdateInfo(nozzle_rack);
}
void HotEndTable::OnPaint(wxPaintEvent& evt)
-5
View File
@@ -36,11 +36,6 @@ void RadioBox::SetValue(bool value)
update();
}
bool RadioBox::GetValue()
{
return wxBitmapToggleButton::GetValue();
}
void RadioBox::Rescale()
{
-4
View File
@@ -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();
+3
View File
@@ -8,6 +8,9 @@
#ifndef wxMediaCtrl3_h
#define wxMediaCtrl3_h
#include <chrono>
#include "wx/window.h"
#include "wx/bitmap.h"
#include "wx/uri.h"
#include "wx/mediactrl.h"
+1
View File
@@ -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<char> buffer;
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]")
{
// 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]")
{
std::vector<std::vector<NozzleVolumeType>> nozzle_volume_types;