Merge branch 'master' into fs_QuadricEdgeCollapse
This commit is contained in:
commit
86a3fd00a5
@ -33,6 +33,8 @@ option(SLIC3R_MSVC_PDB "Generate PDB files on MSVC in Release mode" 1)
|
||||
option(SLIC3R_PERL_XS "Compile XS Perl module and enable Perl unit and integration tests" 0)
|
||||
option(SLIC3R_ASAN "Enable ASan on Clang and GCC" 0)
|
||||
|
||||
set(OPENVDB_FIND_MODULE_PATH "" CACHE PATH "Path to OpenVDB installation's find modules.")
|
||||
|
||||
set(SLIC3R_GTK "2" CACHE STRING "GTK version to use with wxWidgets on Linux")
|
||||
|
||||
set(IS_CROSS_COMPILE FALSE)
|
||||
@ -492,13 +494,17 @@ find_package(NLopt 1.4 REQUIRED)
|
||||
if(SLIC3R_STATIC)
|
||||
set(OPENVDB_USE_STATIC_LIBS ON)
|
||||
set(USE_BLOSC TRUE)
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
find_package(OpenVDB 5.0 REQUIRED COMPONENTS openvdb)
|
||||
find_package(OpenVDB 5.0 COMPONENTS openvdb)
|
||||
if(OpenVDB_FOUND)
|
||||
slic3r_remap_configs(IlmBase::Half RelWithDebInfo Release)
|
||||
slic3r_remap_configs(Blosc::blosc RelWithDebInfo Release)
|
||||
endif()
|
||||
else ()
|
||||
message(FATAL_ERROR "OpenVDB could not be found with the bundled find module. "
|
||||
"You can try to specify the find module location of your "
|
||||
"OpenVDB installation with the OPENVDB_FIND_MODULE_PATH cache variable.")
|
||||
endif ()
|
||||
|
||||
set(TOP_LEVEL_PROJECT_DIR ${PROJECT_SOURCE_DIR})
|
||||
function(prusaslicer_copy_dlls target)
|
||||
@ -559,9 +565,20 @@ if (WIN32)
|
||||
elseif (SLIC3R_FHS)
|
||||
# CMAKE_INSTALL_FULL_DATAROOTDIR: read-only architecture-independent data root (share)
|
||||
set(SLIC3R_FHS_RESOURCES "${CMAKE_INSTALL_FULL_DATAROOTDIR}/PrusaSlicer")
|
||||
install(DIRECTORY "${SLIC3R_RESOURCES_DIR}/" DESTINATION "${SLIC3R_FHS_RESOURCES}")
|
||||
install(FILES src/platform/unix/PrusaSlicer.desktop DESTINATION ${SLIC3R_FHS_RESOURCES}/applications)
|
||||
install(FILES src/platform/unix/PrusaGcodeviewer.desktop DESTINATION ${SLIC3R_FHS_RESOURCES}/applications)
|
||||
install(DIRECTORY ${SLIC3R_RESOURCES_DIR}/ DESTINATION ${SLIC3R_FHS_RESOURCES}
|
||||
PATTERN "*/data" EXCLUDE PATTERN "*/udev" EXCLUDE
|
||||
)
|
||||
install(FILES src/platform/unix/PrusaSlicer.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications)
|
||||
install(FILES src/platform/unix/PrusaGcodeviewer.desktop DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/applications)
|
||||
foreach(SIZE 32 128 192)
|
||||
install(FILES ${SLIC3R_RESOURCES_DIR}/icons/PrusaSlicer_${SIZE}px.png
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}x${SIZE}/apps RENAME PrusaSlicer.png
|
||||
)
|
||||
install(FILES ${SLIC3R_RESOURCES_DIR}/icons/PrusaSlicer-gcodeviewer_${SIZE}px.png
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/${SIZE}x${SIZE}/apps RENAME PrusaSlicer-gcodeviewer.png
|
||||
)
|
||||
endforeach()
|
||||
install(DIRECTORY ${SLIC3R_RESOURCES_DIR}/udev/ DESTINATION lib/udev/rules.d)
|
||||
else ()
|
||||
install(FILES src/platform/unix/PrusaSlicer.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/resources/applications)
|
||||
install(FILES src/platform/unix/PrusaGcodeviewer.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/resources/applications)
|
||||
|
137
build_win.bat
137
build_win.bat
@ -1,6 +1,6 @@
|
||||
@setlocal disableDelayedExpansion enableExtensions
|
||||
@echo off
|
||||
GOTO :MAIN
|
||||
@IF "%PS_ECHO_ON%" NEQ "" (echo on) ELSE (echo off)
|
||||
@GOTO :MAIN
|
||||
:HELP
|
||||
@ECHO Performs initial build or rebuild of the app (build) and deps (build/deps).
|
||||
@ECHO Default options are determined from build directories and system state.
|
||||
@ -20,30 +20,33 @@ GOTO :MAIN
|
||||
@ECHO deps - clean and build deps
|
||||
@ECHO deps-dirty - build deps without cleaning
|
||||
@ECHO Default: %PS_STEPS_DEFAULT%
|
||||
@ECHO -d -DESTDIR Deps destination directory
|
||||
@ECHO -d -DESTDIR Deps destination directory (ignored on dirty builds)
|
||||
@ECHO %PS_DESTDIR_DEFAULT_MSG%
|
||||
@ECHO.
|
||||
@ECHO Example usage:
|
||||
@ECHO First build: build_win -d "c:\src\PrusaSlicer-deps"
|
||||
@ECHO Deps change: build_win -s all
|
||||
@ECHO App rebuild: build_win
|
||||
@ECHO.
|
||||
GOTO :END
|
||||
|
||||
:MAIN
|
||||
SET START_TIME=%TIME%
|
||||
SET PS_START_DIR=%CD%
|
||||
pushd %~dp0
|
||||
REM Probe build directories and sytem state for reasonable default arguments
|
||||
SET PS_CONFIG=RelWithDebInfo
|
||||
SET PS_ARCH=%PROCESSOR_ARCHITECTURE%
|
||||
CALL :TOLOWER PS_ARCH
|
||||
SET DEPS_PATH_FILE=%~dp0deps\build\.DEPS_PATH.txt
|
||||
SET PS_DEPS_PATH_FILE=%~dp0deps\build\.DEPS_PATH.txt
|
||||
SET PS_DESTDIR=
|
||||
IF EXIST %DEPS_PATH_FILE% (
|
||||
FOR /F "tokens=* USEBACKQ" %%I IN ("%DEPS_PATH_FILE%") DO SET PS_DESTDIR=%%I
|
||||
IF EXIST %PS_DEPS_PATH_FILE% (
|
||||
FOR /F "tokens=* USEBACKQ" %%I IN ("%PS_DEPS_PATH_FILE%") DO SET PS_DESTDIR=%%I
|
||||
IF EXIST build/ALL_BUILD.vcxproj (
|
||||
SET PS_STEPS=app-dirty
|
||||
) ELSE SET PS_STEPS=app
|
||||
) ELSE SET PS_STEPS=all
|
||||
SET PS_DESTDIR_CACHED=%PS_DESTDIR%
|
||||
|
||||
REM Set up parameters used by help menu
|
||||
SET PS_CONFIG_DEFAULT=%PS_CONFIG%
|
||||
@ -60,16 +63,40 @@ SET EXIT_STATUS=1
|
||||
SET PARSER_STATE=
|
||||
SET PARSER_FAIL=
|
||||
FOR %%I in (%*) DO CALL :PARSE_OPTION "ARCH CONFIG DESTDIR STEPS" PARSER_STATE "%%~I"
|
||||
IF "%PARSER_FAIL%%PARSER_STATE%" NEQ "" GOTO :HELP
|
||||
IF "%PARSER_FAIL%" NEQ "" (
|
||||
@ECHO ERROR: Invalid switch: %PARSER_FAIL% 1>&2
|
||||
GOTO :HELP
|
||||
)ELSE IF "%PARSER_STATE%" NEQ "" (
|
||||
@ECHO ERROR: Missing parameter for: %PARSER_STATE% 1>&2
|
||||
GOTO :HELP
|
||||
)
|
||||
|
||||
REM Validate arguments
|
||||
SET PS_STEPS_SAVED=%PS_STEPS%
|
||||
CALL :PARSE_OPTION_NAME "all all-dirty deps-dirty deps app-dirty app" PS_STEPS -%PS_STEPS%
|
||||
IF "%PS_STEPS%" EQU "" GOTO :HELP
|
||||
IF "%PS_STEPS%" EQU "" (
|
||||
@ECHO ERROR: Invalid parameter: steps=%PS_STEPS_SAVED% 1>&2
|
||||
GOTO :HELP
|
||||
) ELSE SET PS_STEPS_SAVED=
|
||||
(echo %PS_STEPS%)| findstr /I /C:"dirty">nul && SET PS_STEPS_DIRTY=1
|
||||
CALL :TOLOWER PS_STEPS
|
||||
CALL :TOLOWER PS_ARCH
|
||||
IF "%OPTION_NAME%" NEQ "" GOTO :HELP
|
||||
IF "%PS_STEPS_DIRTY%%PS_DESTDIR%" EQU "" GOTO :HELP
|
||||
CALL :CANONICALIZE_PATH PS_DESTDIR "%PS_START_DIR%"
|
||||
IF "%PS_DESTDIR%" EQU "" (
|
||||
IF "%PS_STEPS_DIRTY%" EQU "" (
|
||||
@ECHO ERROR: Parameter required: destdir 1>&2
|
||||
GOTO :HELP
|
||||
)
|
||||
) ELSE IF "%PS_DESTDIR%" NEQ "%PS_DESTDIR_CACHED%" (
|
||||
IF "%PS_STEPS_DIRTY%" NEQ "" (
|
||||
@ECHO WARNING: Parameter ignored: destdir
|
||||
) ELSE (echo "all deps")| findstr /I /C:"%PS_STEPS%">nul || (
|
||||
@ECHO WARNING: Conflict with cached parameter: 1>&2
|
||||
@ECHO WARNING: -destdir=%PS_DESTDIR% 1>&2
|
||||
@ECHO WARNING: cached=%PS_DESTDIR_CACHED% 1>&2
|
||||
)
|
||||
)
|
||||
SET PS_DESTDIR_DEFAULT_MSG=
|
||||
|
||||
REM Set up MSVC environment
|
||||
SET EXIT_STATUS=2
|
||||
@ -77,12 +104,18 @@ SET EXIT_STATUS=2
|
||||
@ECHO ** Build Config: %PS_CONFIG%
|
||||
@ECHO ** Target Arch: %PS_ARCH%
|
||||
@ECHO ** Build Steps: %PS_STEPS%
|
||||
@ECHO ** Deps path: %PS_DESTDIR%
|
||||
@ECHO ** Using Microsoft Visual Studio installation found at:
|
||||
SET VSWHERE="%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||
IF NOT EXIST %VSWHERE% SET VSWHERE="%ProgramFiles%\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||
FOR /F "tokens=* USEBACKQ" %%I IN (`%VSWHERE% -nologo -property installationPath`) DO SET MSVC_DIR=%%I
|
||||
@ECHO ** %MSVC_DIR%
|
||||
CALL "%MSVC_DIR%\Common7\Tools\vsdevcmd.bat" -arch=%PS_ARCH% -host_arch=%PS_ARCH_HOST% -app_platform=Desktop || GOTO :END
|
||||
CALL "%MSVC_DIR%\Common7\Tools\vsdevcmd.bat" -arch=%PS_ARCH% -host_arch=%PS_ARCH_HOST% -app_platform=Desktop
|
||||
IF "%ERRORLEVEL%" NEQ "0" GOTO :END
|
||||
IF "%PS_DRY_RUN_ONLY%" NEQ "" (
|
||||
@ECHO Script terminated early because PS_DRY_RUN_ONLY is set. 1>&2
|
||||
GOTO :END
|
||||
)
|
||||
IF /I "%PS_STEPS:~0,3%" EQU "app" GOTO :BUILD_APP
|
||||
|
||||
REM Build deps
|
||||
@ -91,7 +124,7 @@ SET EXIT_STATUS=3
|
||||
IF "%PS_STEPS_DIRTY%" EQU "" CALL :MAKE_OR_CLEAN_DIRECTORY deps\build
|
||||
cd deps\build || GOTO :END
|
||||
IF "%PS_STEPS_DIRTY%" EQU "" cmake.exe .. -DDESTDIR="%PS_DESTDIR%" || GOTO :END
|
||||
(echo %PS_DESTDIR%)> "%DEPS_PATH_FILE%"
|
||||
(echo %PS_DESTDIR%)> "%PS_DEPS_PATH_FILE%"
|
||||
msbuild /m ALL_BUILD.vcxproj /p:Configuration=%PS_CONFIG% || GOTO :END
|
||||
cd ..\..
|
||||
IF /I "%PS_STEPS:~0,4%" EQU "deps" GOTO :PROLOGUE
|
||||
@ -107,58 +140,67 @@ msbuild /m ALL_BUILD.vcxproj /p:Configuration=%PS_CONFIG% || GOTO :END
|
||||
:PROLOGUE
|
||||
SET EXIT_STATUS=%ERRORLEVEL%
|
||||
:END
|
||||
@IF "%PS_ECHO_ON%%PS_DRY_RUN_ONLY%" NEQ "" (
|
||||
@ECHO Script Parameters:
|
||||
@SET PS_
|
||||
)
|
||||
@ECHO Script started at %START_TIME% and completed at %TIME%.
|
||||
popd
|
||||
endlocal
|
||||
exit /B %EXIT_STATUS%
|
||||
|
||||
GOTO :EOF
|
||||
REM Functions and stubs start here.
|
||||
|
||||
:PARSE_OPTION
|
||||
REM Argument parser called for each argument
|
||||
REM %1 - Valid option list
|
||||
REM %2 - Variable name for parser state; must be unset when parsing finished
|
||||
REM %3 - Current argument value
|
||||
REM PARSER_FAIL will be set on an error
|
||||
REM Note: Must avoid delayed expansion since filenames may contain ! character
|
||||
@REM Argument parser called for each argument
|
||||
@REM %1 - Valid option list
|
||||
@REM %2 - Variable name for parser state; must be unset when parsing finished
|
||||
@REM %3 - Current argument value
|
||||
@REM PARSER_FAIL will be set on an error
|
||||
@REM Note: Must avoid delayed expansion since filenames may contain ! character
|
||||
setlocal disableDelayedExpansion
|
||||
IF "%PARSER_FAIL%" NEQ "" GOTO :EOF
|
||||
CALL SET LAST_ARG=%%%2%%
|
||||
IF "%LAST_ARG%" EQU "" (
|
||||
CALL :PARSE_OPTION_NAME %1 %~2 %~3 1
|
||||
SET ARG_TYPE=NAME
|
||||
) ELSE (
|
||||
SET PS_SET_COMMAND=^&SET PS_%LAST_ARG%=%~3
|
||||
SET PS_SET_COMMAND=SET PS_%LAST_ARG%=%~3
|
||||
SET ARG_TYPE=LAST_ARG
|
||||
SET %~2=
|
||||
)
|
||||
CALL SET LAST_ARG=%%%2%%
|
||||
IF "%LAST_ARG%" EQU "" IF "%ARG_TYPE%" EQU "NAME" SET PARSER_FAIL=1
|
||||
endlocal & (SET PARSER_FAIL=%PARSER_FAIL%) & (SET %~2=%LAST_ARG%) %PS_SET_COMMAND%
|
||||
IF "%LAST_ARG%%ARG_TYPE%" EQU "NAME" SET PARSER_FAIL=%~3
|
||||
(
|
||||
endlocal
|
||||
SET PARSER_FAIL=%PARSER_FAIL%
|
||||
SET %~2=%LAST_ARG%
|
||||
%PS_SET_COMMAND%
|
||||
)
|
||||
GOTO :EOF
|
||||
|
||||
:PARSE_OPTION_NAME
|
||||
REM Parses an option name
|
||||
REM %1 - Valid option list
|
||||
REM %2 - Out variable name; unset on error
|
||||
REM %3 - Current argument value
|
||||
REM $4 - Boolean indicating single character switches are valid
|
||||
REM Note: Delayed expansion safe because ! character is invalid in option name
|
||||
@REM Parses an option name
|
||||
@REM %1 - Valid option list
|
||||
@REM %2 - Out variable name; unset on error
|
||||
@REM %3 - Current argument value
|
||||
@REM $4 - Boolean indicating single character switches are valid
|
||||
@REM Note: Delayed expansion safe because ! character is invalid in option name
|
||||
setlocal enableDelayedExpansion
|
||||
IF "%4" NEQ "" FOR %%I IN (%~1) DO (
|
||||
IF "%4" NEQ "" FOR %%I IN (%~1) DO @(
|
||||
SET SHORT_NAME=%%~I
|
||||
SET SHORT_ARG_!SHORT_NAME:~0,1!=%%~I
|
||||
)
|
||||
SET OPTION_NAME=%~3
|
||||
(echo %OPTION_NAME%)| findstr /R /C:"[-/]..*">nul || GOTO :PARSE_OPTION_NAME_FAIL
|
||||
SET OPTION_NAME=%OPTION_NAME:~1%
|
||||
@SET OPTION_NAME=%~3
|
||||
@(echo %OPTION_NAME%)| findstr /R /C:"[-/]..*">nul || GOTO :PARSE_OPTION_NAME_FAIL
|
||||
@SET OPTION_NAME=%OPTION_NAME:~1%
|
||||
IF "%4" NEQ "" (
|
||||
IF "%OPTION_NAME%" EQU "%OPTION_NAME:~0,1%" (
|
||||
IF "!SHORT_ARG_%OPTION_NAME:~0,1%!" NEQ "" SET OPTION_NAME=!SHORT_ARG_%OPTION_NAME:~0,1%!
|
||||
)
|
||||
)
|
||||
(echo %OPTION_NAME%)| findstr /R /C:".[ ][ ]*.">nul && GOTO :PARSE_OPTION_NAME_FAIL
|
||||
(echo %~1 )| findstr /I /C:" %OPTION_NAME% ">nul || GOTO :PARSE_OPTION_NAME_FAIL
|
||||
@(echo %OPTION_NAME%)| findstr /R /C:".[ ][ ]*.">nul && GOTO :PARSE_OPTION_NAME_FAIL
|
||||
@(echo %~1 )| findstr /I /C:" %OPTION_NAME% ">nul || GOTO :PARSE_OPTION_NAME_FAIL
|
||||
endlocal & SET %~2=%OPTION_NAME%
|
||||
GOTO :EOF
|
||||
:PARSE_OPTION_NAME_FAIL
|
||||
@ -166,8 +208,8 @@ endlocal & SET %~2=
|
||||
GOTO :EOF
|
||||
|
||||
:MAKE_OR_CLEAN_DIRECTORY
|
||||
REM Create directory if it doesn't exist or clean it if it does
|
||||
REM %1 - Directory path to clean or create
|
||||
@REM Create directory if it doesn't exist or clean it if it does
|
||||
@REM %1 - Directory path to clean or create
|
||||
setlocal disableDelayedExpansion
|
||||
IF NOT EXIST "%~1" (
|
||||
ECHO Creating %~1
|
||||
@ -179,12 +221,25 @@ for /F "usebackq delims=" %%I in (`dir /a /b "%~1"`) do (
|
||||
GOTO :EOF
|
||||
|
||||
:TOLOWER
|
||||
REM Converts supplied environment variable to lowercase
|
||||
REM %1 - Input/output variable name
|
||||
REM Note: This is slow on very long strings, but is used only on very short ones
|
||||
@REM Converts supplied environment variable to lowercase
|
||||
@REM %1 - Input/output variable name
|
||||
@REM Note: This is slow on very long strings, but is used only on very short ones
|
||||
setlocal disableDelayedExpansion
|
||||
FOR %%b IN (a b c d e f g h i j k l m n o p q r s t u v w x y z) DO CALL set %~1=%%%1:%%b=%%b%%
|
||||
CALL SET OUTPUT=%%%~1%%
|
||||
@FOR %%b IN (a b c d e f g h i j k l m n o p q r s t u v w x y z) DO @CALL set %~1=%%%1:%%b=%%b%%
|
||||
@CALL SET OUTPUT=%%%~1%%
|
||||
endlocal & SET %~1=%OUTPUT%
|
||||
GOTO :EOF
|
||||
|
||||
:CANONICALIZE_PATH
|
||||
@REM Canonicalizes the path in the supplied variable
|
||||
@REM %1 - Input/output variable containing path to canonicalize
|
||||
@REM %2 - Optional base directory
|
||||
setlocal
|
||||
CALL :CANONICALIZE_PATH_INNER %1 %%%~1%% %2
|
||||
endlocal & SET %~1=%OUTPUT%
|
||||
GOTO :EOF
|
||||
:CANONICALIZE_PATH_INNER
|
||||
if "%~3" NEQ "" (pushd %~3 || GOTO :EOF)
|
||||
SET OUTPUT=%~f2
|
||||
if "%~3" NEQ "" popd %~3
|
||||
GOTO :EOF
|
||||
|
@ -102,6 +102,27 @@ may be provided to tell this module where to look.
|
||||
|
||||
#]=======================================================================]
|
||||
|
||||
# If an explicit openvdb module path was specified, that will be used
|
||||
if (OPENVDB_FIND_MODULE_PATH)
|
||||
set(_module_path_bak ${CMAKE_MODULE_PATH})
|
||||
set(CMAKE_MODULE_PATH ${OPENVDB_FIND_MODULE_PATH})
|
||||
find_package(
|
||||
OpenVDB ${OpenVDB_FIND_VERSION} QUIET
|
||||
COMPONENTS
|
||||
${OpenVDB_FIND_COMPONENTS}
|
||||
)
|
||||
|
||||
set(CMAKE_MODULE_PATH ${_module_path_bak})
|
||||
if (OpenVDB_FOUND)
|
||||
return()
|
||||
endif ()
|
||||
|
||||
if (NOT OpenVDB_FIND_QUIETLY)
|
||||
message(STATUS "Using bundled find module for OpenVDB")
|
||||
endif ()
|
||||
endif ()
|
||||
# ###########################################################################
|
||||
|
||||
cmake_minimum_required(VERSION 3.3)
|
||||
# Monitoring <PackageName>_ROOT variables
|
||||
if(POLICY CMP0074)
|
||||
|
10
resources/icons/lock.svg
Normal file
10
resources/icons/lock.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<g id="lock_x5F_closed">
|
||||
<path fill="none" stroke="#ED6B21" stroke-width="2" stroke-miterlimit="10" d="M4,8V4c0,0,0-2,2-2c1,0,3,0,4,0c2,0,2,2,2,2v4"/>
|
||||
<path fill="#ED6B21" d="M13,8H3C2.45,8,2,8.45,2,9v5c0,0.55,0.45,1,1,1h10c0.55,0,1-0.45,1-1V9C14,8.45,13.55,8,13,8z M10,12H8.91
|
||||
c-0.21,0.58-0.76,1-1.41,1C6.67,13,6,12.33,6,11.5S6.67,10,7.5,10c0.65,0,1.2,0.42,1.41,1H10V12z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 729 B |
@ -1,5 +1,19 @@
|
||||
#version 110
|
||||
|
||||
#define INTENSITY_CORRECTION 0.6
|
||||
|
||||
// normalized values for (-0.6/1.31, 0.6/1.31, 1./1.31)
|
||||
const vec3 LIGHT_TOP_DIR = vec3(-0.4574957, 0.4574957, 0.7624929);
|
||||
#define LIGHT_TOP_DIFFUSE (0.8 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SPECULAR (0.125 * INTENSITY_CORRECTION)
|
||||
#define LIGHT_TOP_SHININESS 20.0
|
||||
|
||||
// normalized values for (1./1.43, 0.2/1.43, 1./1.43)
|
||||
const vec3 LIGHT_FRONT_DIR = vec3(0.6985074, 0.1397015, 0.6985074);
|
||||
#define LIGHT_FRONT_DIFFUSE (0.3 * INTENSITY_CORRECTION)
|
||||
|
||||
#define INTENSITY_AMBIENT 0.3
|
||||
|
||||
const vec3 ZERO = vec3(0.0, 0.0, 0.0);
|
||||
const vec3 GREEN = vec3(0.0, 0.7, 0.0);
|
||||
const vec3 YELLOW = vec3(0.5, 0.7, 0.0);
|
||||
@ -42,14 +56,42 @@ vec3 sinking_color(vec3 color)
|
||||
return (mod(model_pos.x + model_pos.y + model_pos.z, BANDS_WIDTH) < (0.5 * BANDS_WIDTH)) ? mix(color, ZERO, 0.6666) : color;
|
||||
}
|
||||
|
||||
uniform bool compute_triangle_normals_in_fs;
|
||||
|
||||
void main()
|
||||
{
|
||||
if (any(lessThan(clipping_planes_dots, ZERO)))
|
||||
discard;
|
||||
vec3 color = uniform_color.rgb;
|
||||
vec3 color = uniform_color.rgb;
|
||||
float alpha = uniform_color.a;
|
||||
if (slope.actived && world_normal_z < slope.normal_z - EPSILON)
|
||||
{
|
||||
|
||||
vec2 intensity_fs = intensity;
|
||||
vec3 eye_normal_fs = eye_normal;
|
||||
float world_normal_z_fs = world_normal_z;
|
||||
if (compute_triangle_normals_in_fs) {
|
||||
vec3 triangle_normal = normalize(cross(dFdx(model_pos.xyz), dFdy(model_pos.xyz)));
|
||||
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
eye_normal_fs = normalize(gl_NormalMatrix * triangle_normal);
|
||||
|
||||
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
|
||||
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
|
||||
float NdotL = max(dot(eye_normal_fs, LIGHT_TOP_DIR), 0.0);
|
||||
|
||||
intensity_fs = vec2(0.0, 0.0);
|
||||
intensity_fs.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec3 position = (gl_ModelViewMatrix * model_pos).xyz;
|
||||
intensity_fs.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal_fs)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular applied).
|
||||
NdotL = max(dot(eye_normal_fs, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity_fs.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
|
||||
// z component of normal vector in world coordinate used for slope shading
|
||||
world_normal_z_fs = slope.actived ? (normalize(slope.volume_world_normal_matrix * triangle_normal)).z : 0.0;
|
||||
}
|
||||
|
||||
if (slope.actived && world_normal_z_fs < slope.normal_z - EPSILON) {
|
||||
color = vec3(0.7, 0.7, 1.0);
|
||||
alpha = 1.0;
|
||||
}
|
||||
@ -60,8 +102,8 @@ void main()
|
||||
color = (abs(world_pos_z) < 0.05) ? WHITE : sinking_color(color);
|
||||
#ifdef ENABLE_ENVIRONMENT_MAP
|
||||
if (use_environment_tex)
|
||||
gl_FragColor = vec4(0.45 * texture2D(environment_tex, normalize(eye_normal).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity.x, alpha);
|
||||
gl_FragColor = vec4(0.45 * texture2D(environment_tex, normalize(eye_normal_fs).xy * 0.5 + 0.5).xyz + 0.8 * color * intensity_fs.x, alpha);
|
||||
else
|
||||
#endif
|
||||
gl_FragColor = vec4(vec3(intensity.y) + color * intensity.x, alpha);
|
||||
gl_FragColor = vec4(vec3(intensity_fs.y) + color * intensity_fs.x, alpha);
|
||||
}
|
||||
|
@ -54,22 +54,26 @@ varying float world_pos_z;
|
||||
varying float world_normal_z;
|
||||
varying vec3 eye_normal;
|
||||
|
||||
uniform bool compute_triangle_normals_in_fs;
|
||||
|
||||
void main()
|
||||
{
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
eye_normal = normalize(gl_NormalMatrix * gl_Normal);
|
||||
|
||||
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
|
||||
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
|
||||
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
|
||||
if (!compute_triangle_normals_in_fs) {
|
||||
// First transform the normal into camera space and normalize the result.
|
||||
eye_normal = normalize(gl_NormalMatrix * gl_Normal);
|
||||
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz;
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
// Compute the cos of the angle between the normal and lights direction. The light is directional so the direction is constant for every vertex.
|
||||
// Since these two are normalized the cosine is the dot product. We also need to clamp the result to the [0,1] range.
|
||||
float NdotL = max(dot(eye_normal, LIGHT_TOP_DIR), 0.0);
|
||||
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular applied).
|
||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
intensity.x = INTENSITY_AMBIENT + NdotL * LIGHT_TOP_DIFFUSE;
|
||||
vec3 position = (gl_ModelViewMatrix * gl_Vertex).xyz;
|
||||
intensity.y = LIGHT_TOP_SPECULAR * pow(max(dot(-normalize(position), reflect(-LIGHT_TOP_DIR, eye_normal)), 0.0), LIGHT_TOP_SHININESS);
|
||||
|
||||
// Perform the same lighting calculation for the 2nd light source (no specular applied).
|
||||
NdotL = max(dot(eye_normal, LIGHT_FRONT_DIR), 0.0);
|
||||
intensity.x += NdotL * LIGHT_FRONT_DIFFUSE;
|
||||
}
|
||||
|
||||
model_pos = gl_Vertex;
|
||||
// Point in homogenous coordinates.
|
||||
@ -77,19 +81,17 @@ void main()
|
||||
world_pos_z = world_pos.z;
|
||||
|
||||
// compute deltas for out of print volume detection (world coordinates)
|
||||
if (print_box.actived)
|
||||
{
|
||||
if (print_box.actived) {
|
||||
delta_box_min = world_pos.xyz - print_box.min;
|
||||
delta_box_max = world_pos.xyz - print_box.max;
|
||||
}
|
||||
else
|
||||
{
|
||||
} else {
|
||||
delta_box_min = ZERO;
|
||||
delta_box_max = ZERO;
|
||||
}
|
||||
|
||||
// z component of normal vector in world coordinate used for slope shading
|
||||
world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * gl_Normal)).z : 0.0;
|
||||
if (!compute_triangle_normals_in_fs)
|
||||
world_normal_z = slope.actived ? (normalize(slope.volume_world_normal_matrix * gl_Normal)).z : 0.0;
|
||||
|
||||
gl_Position = ftransform();
|
||||
// Fill in the scalars for fragment shader clipping. Fragments with any of these components lower than zero are discarded.
|
||||
|
BIN
resources/shapes/OTHER_recycling_symbol.stl
Normal file
BIN
resources/shapes/OTHER_recycling_symbol.stl
Normal file
Binary file not shown.
BIN
resources/shapes/PETG_recycling_symbol.stl
Normal file
BIN
resources/shapes/PETG_recycling_symbol.stl
Normal file
Binary file not shown.
BIN
resources/shapes/box.stl
Normal file
BIN
resources/shapes/box.stl
Normal file
Binary file not shown.
BIN
resources/shapes/bunny.stl
Normal file
BIN
resources/shapes/bunny.stl
Normal file
Binary file not shown.
BIN
resources/shapes/cylinder.stl
Normal file
BIN
resources/shapes/cylinder.stl
Normal file
Binary file not shown.
BIN
resources/shapes/pyramid.stl
Normal file
BIN
resources/shapes/pyramid.stl
Normal file
Binary file not shown.
BIN
resources/shapes/sphere.stl
Normal file
BIN
resources/shapes/sphere.stl
Normal file
Binary file not shown.
@ -602,12 +602,12 @@ FaceNeighborIndex its_create_neighbors_index_8(const indexed_triangle_set &its)
|
||||
|
||||
std::vector<Vec3crd> its_create_neighbors_index_9(const indexed_triangle_set &its)
|
||||
{
|
||||
return create_neighbors_index(ex_seq, its);
|
||||
return create_face_neighbors_index(ex_seq, its);
|
||||
}
|
||||
|
||||
std::vector<Vec3i> its_create_neighbors_index_10(const indexed_triangle_set &its)
|
||||
{
|
||||
return create_neighbors_index(ex_tbb, its);
|
||||
return create_face_neighbors_index(ex_tbb, its);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -661,6 +661,7 @@ bool CLI::setup(int argc, char **argv)
|
||||
set_resources_dir(path_resources.string());
|
||||
set_var_dir((path_resources / "icons").string());
|
||||
set_local_dir((path_resources / "localization").string());
|
||||
set_sys_shapes_dir((path_resources / "shapes").string());
|
||||
|
||||
// Parse all command line options into a DynamicConfig.
|
||||
// If any option is unsupported, print usage and abort immediately.
|
||||
|
@ -8,6 +8,7 @@ add_library(imgui STATIC
|
||||
imstb_rectpack.h
|
||||
imstb_textedit.h
|
||||
imstb_truetype.h
|
||||
imgui_tables.cpp
|
||||
imgui.cpp
|
||||
imgui_demo.cpp
|
||||
imgui_draw.cpp
|
||||
|
@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2018 Omar Cornut
|
||||
Copyright (c) 2014-2021 Omar Cornut
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -2,11 +2,8 @@
|
||||
|
||||
For more information go to https://github.com/ocornut/imgui
|
||||
|
||||
THIS DIRECTORY CONTAINS THE imgui-1.75 58b3e02 SOURCE DISTRIBUTION.
|
||||
THIS DIRECTORY CONTAINS THE imgui-1.83 ad5d1a8 SOURCE DISTRIBUTION.
|
||||
|
||||
Customized with the following commits:
|
||||
042880ba2df913916b2cc77f7bb677e07bfd2c58
|
||||
67c55c74901f1d337ef08f2090a87cfb4263bb0f
|
||||
a94c952b40d36b1505fb77b87c0dd739e1034659
|
||||
3ca3a544a87cc569b69351a77996c287763388a5
|
||||
6586a46ea23e86d54d228c55c63ca55680d25d56
|
||||
f93d0001baa5443da2c6510d11b03c675e652418
|
||||
b71d787f695c779e571865d5214d4da8d50aa7c5
|
||||
|
@ -3,10 +3,11 @@
|
||||
// Runtime options (clipboard callbacks, enabling various features, etc.) can generally be set via the ImGuiIO structure.
|
||||
// You can use ImGui::SetAllocatorFunctions() before calling ImGui::CreateContext() to rewire memory allocation functions.
|
||||
//-----------------------------------------------------------------------------
|
||||
// A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/branch with your modifications to imconfig.h)
|
||||
// B) or add configuration directives in your own file and compile with #define IMGUI_USER_CONFIG "myfilename.h"
|
||||
// If you do so you need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include
|
||||
// the imgui*.cpp files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures.
|
||||
// A) You may edit imconfig.h (and not overwrite it when updating Dear ImGui, or maintain a patch/rebased branch with your modifications to it)
|
||||
// B) or '#define IMGUI_USER_CONFIG "my_imgui_config.h"' in your project and then add directives in your own file without touching this template.
|
||||
//-----------------------------------------------------------------------------
|
||||
// You need to make sure that configuration settings are defined consistently _everywhere_ Dear ImGui is used, which include the imgui*.cpp
|
||||
// files but also _any_ of your code that uses Dear ImGui. This is because some compile-time options have an affect on data structures.
|
||||
// Defining those options in imconfig.h will ensure every compilation unit gets to see the same data structure layouts.
|
||||
// Call IMGUI_CHECKVERSION() from your .cpp files to verify that the data structures your files are using are matching the ones imgui.cpp is using.
|
||||
//-----------------------------------------------------------------------------
|
||||
@ -19,27 +20,30 @@
|
||||
//#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts
|
||||
|
||||
//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows
|
||||
// Using dear imgui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility.
|
||||
// Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility.
|
||||
// DLL users: heaps and globals are not shared across DLL boundaries! You will need to call SetCurrentContext() + SetAllocatorFunctions()
|
||||
// for each static/DLL boundary you are calling from. Read "Context and Memory Allocators" section of imgui.cpp for more details.
|
||||
//#define IMGUI_API __declspec( dllexport )
|
||||
//#define IMGUI_API __declspec( dllimport )
|
||||
|
||||
//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to avoid using soon-to-be obsolete function/names.
|
||||
//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||
|
||||
//---- Disable all of Dear ImGui or don't implement standard windows.
|
||||
//---- Disable all of Dear ImGui or don't implement standard windows.
|
||||
// It is very strongly recommended to NOT disable the demo windows during development. Please read comments in imgui_demo.cpp.
|
||||
//#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty.
|
||||
//#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. Not recommended.
|
||||
//#define IMGUI_DISABLE_METRICS_WINDOW // Disable debug/metrics window: ShowMetricsWindow() will be empty.
|
||||
//#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty.
|
||||
//#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. Not recommended.
|
||||
//#define IMGUI_DISABLE_METRICS_WINDOW // Disable metrics/debugger window: ShowMetricsWindow() will be empty.
|
||||
|
||||
//---- Don't implement some functions to reduce linkage requirements.
|
||||
//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc.
|
||||
//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] Don't implement default IME handler. Won't use and link with ImmGetContext/ImmSetCompositionWindow.
|
||||
//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a)
|
||||
//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] Don't implement default IME handler. Won't use and link with ImmGetContext/ImmSetCompositionWindow. (imm32.lib/.a)
|
||||
//#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, ime).
|
||||
//#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default).
|
||||
//#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf)
|
||||
//#define IMGUI_DISABLE_DEFAULT_MATH_FUNCTIONS // Don't implement ImFabs/ImSqrt/ImPow/ImFmod/ImCos/ImSin/ImAcos/ImAtan2 so you can implement them yourself.
|
||||
//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function.
|
||||
//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)
|
||||
//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function.
|
||||
//#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().
|
||||
|
||||
//---- Include imgui_user.h at the end of imgui.h as a convenience
|
||||
@ -48,17 +52,29 @@
|
||||
//---- Pack colors to BGRA8 instead of RGBA8 (to avoid converting from one to another)
|
||||
//#define IMGUI_USE_BGRA_PACKED_COLOR
|
||||
|
||||
//---- Use 32-bit for ImWchar (default is 16-bit) to support unicode planes 1-16. (e.g. point beyond 0xFFFF like emoticons, dingbats, symbols, shapes, ancient languages, etc...)
|
||||
//#define IMGUI_USE_WCHAR32
|
||||
|
||||
//---- Avoid multiple STB libraries implementations, or redefine path/filenames to prioritize another version
|
||||
// By default the embedded implementations are declared static and not available outside of imgui cpp files.
|
||||
// By default the embedded implementations are declared static and not available outside of Dear ImGui sources files.
|
||||
//#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h"
|
||||
//#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h"
|
||||
//#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION
|
||||
//#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION
|
||||
|
||||
//---- Unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined, use the much faster STB sprintf library implementation of vsnprintf instead of the one from the default C library.
|
||||
// Note that stb_sprintf.h is meant to be provided by the user and available in the include path at compile time. Also, the compatibility checks of the arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by STB sprintf.
|
||||
//---- Use stb_printf's faster implementation of vsnprintf instead of the one from libc (unless IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS is defined)
|
||||
// Requires 'stb_sprintf.h' to be available in the include path. Compatibility checks of arguments and formats done by clang and GCC will be disabled in order to support the extra formats provided by STB sprintf.
|
||||
// #define IMGUI_USE_STB_SPRINTF
|
||||
|
||||
//---- Use FreeType to build and rasterize the font atlas (instead of stb_truetype which is embedded by default in Dear ImGui)
|
||||
// Requires FreeType headers to be available in the include path. Requires program to be compiled with 'misc/freetype/imgui_freetype.cpp' (in this repository) + the FreeType library (not provided).
|
||||
// On Windows you may use vcpkg with 'vcpkg install freetype' + 'vcpkg integrate install'.
|
||||
//#define IMGUI_ENABLE_FREETYPE
|
||||
|
||||
//---- Use stb_truetype to build and rasterize the font atlas (default)
|
||||
// The only purpose of this define is if you want force compilation of the stb_truetype backend ALONG with the FreeType backend.
|
||||
//#define IMGUI_ENABLE_STB_TRUETYPE
|
||||
|
||||
//---- Define constructor and implicit cast operators to convert back<>forth between your math types and ImVec2/ImVec4.
|
||||
// This will be inlined as part of ImVec2 and ImVec4 class declarations.
|
||||
/*
|
||||
@ -72,12 +88,12 @@
|
||||
*/
|
||||
|
||||
//---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices.
|
||||
// Your renderer back-end will need to support it (most example renderer back-ends support both 16/32-bit indices).
|
||||
// Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices).
|
||||
// Another way to allow large meshes while keeping 16-bit indices is to handle ImDrawCmd::VtxOffset in your renderer.
|
||||
// Read about ImGuiBackendFlags_RendererHasVtxOffset for details.
|
||||
//#define ImDrawIdx unsigned int
|
||||
|
||||
//---- Override ImDrawCallback signature (will need to modify renderer back-ends accordingly)
|
||||
//---- Override ImDrawCallback signature (will need to modify renderer backends accordingly)
|
||||
//struct ImDrawList;
|
||||
//struct ImDrawCmd;
|
||||
//typedef void (*MyImDrawCallback)(const ImDrawList* draw_list, const ImDrawCmd* cmd, void* my_renderer_user_data);
|
||||
@ -107,25 +123,24 @@ namespace ImGui
|
||||
const char ColorMarkerStart = 0x2; // STX
|
||||
const char ColorMarkerEnd = 0x3; // ETX
|
||||
|
||||
// Special ASCII characters are used here as an ikons markers
|
||||
const char PrintIconMarker = 0x4;
|
||||
const char PrinterIconMarker = 0x5;
|
||||
const char PrinterSlaIconMarker = 0x6;
|
||||
const char FilamentIconMarker = 0x7;
|
||||
// Special ASCII characters are used here as an ikons markers
|
||||
const char PrintIconMarker = 0x4;
|
||||
const char PrinterIconMarker = 0x5;
|
||||
const char PrinterSlaIconMarker = 0x6;
|
||||
const char FilamentIconMarker = 0x7;
|
||||
const char MaterialIconMarker = 0x8;
|
||||
const char CloseNotifButton = 0xB;
|
||||
const char CloseNotifHoverButton = 0xC;
|
||||
const char CloseNotifButton = 0xB;
|
||||
const char CloseNotifHoverButton = 0xC;
|
||||
// const char TimerDotMarker = 0xE;
|
||||
// const char TimerDotEmptyMarker = 0xF;
|
||||
const char MinimalizeButton = 0xE;
|
||||
const char MinimalizeHoverButton = 0xF;
|
||||
const char WarningMarker = 0x10;
|
||||
const char ErrorMarker = 0x11;
|
||||
const char WarningMarker = 0x10;
|
||||
const char ErrorMarker = 0x11;
|
||||
const char EjectButton = 0x12;
|
||||
const char EjectHoverButton = 0x13;
|
||||
const char CancelButton = 0x14;
|
||||
const char CancelHoverButton = 0x15;
|
||||
// void MyFunction(const char* name, const MyMatrix44& v);
|
||||
|
||||
}
|
||||
|
||||
|
6437
src/imgui/imgui.cpp
6437
src/imgui/imgui.cpp
File diff suppressed because it is too large
Load Diff
1507
src/imgui/imgui.h
1507
src/imgui/imgui.h
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
4028
src/imgui/imgui_tables.cpp
Normal file
4028
src/imgui/imgui_tables.cpp
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -148,6 +148,8 @@
|
||||
// STB_TEXTEDIT_K_RIGHT keyboard input to move cursor right
|
||||
// STB_TEXTEDIT_K_UP keyboard input to move cursor up
|
||||
// STB_TEXTEDIT_K_DOWN keyboard input to move cursor down
|
||||
// STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page
|
||||
// STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page
|
||||
// STB_TEXTEDIT_K_LINESTART keyboard input to move cursor to start of line // e.g. HOME
|
||||
// STB_TEXTEDIT_K_LINEEND keyboard input to move cursor to end of line // e.g. END
|
||||
// STB_TEXTEDIT_K_TEXTSTART keyboard input to move cursor to start of text // e.g. ctrl-HOME
|
||||
@ -170,14 +172,10 @@
|
||||
// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text
|
||||
// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text
|
||||
//
|
||||
// Todo:
|
||||
// STB_TEXTEDIT_K_PGUP keyboard input to move cursor up a page
|
||||
// STB_TEXTEDIT_K_PGDOWN keyboard input to move cursor down a page
|
||||
//
|
||||
// Keyboard input must be encoded as a single integer value; e.g. a character code
|
||||
// and some bitflags that represent shift states. to simplify the interface, SHIFT must
|
||||
// be a bitflag, so we can test the shifted state of cursor movements to allow selection,
|
||||
// i.e. (STB_TEXTED_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow.
|
||||
// i.e. (STB_TEXTEDIT_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow.
|
||||
//
|
||||
// You can encode other things, such as CONTROL or ALT, in additional bits, and
|
||||
// then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example,
|
||||
@ -337,6 +335,10 @@ typedef struct
|
||||
// each textfield keeps its own insert mode state. to keep an app-wide
|
||||
// insert mode, copy this value in/out of the app state
|
||||
|
||||
int row_count_per_page;
|
||||
// page size in number of row.
|
||||
// this value MUST be set to >0 for pageup or pagedown in multilines documents.
|
||||
|
||||
/////////////////////
|
||||
//
|
||||
// private data
|
||||
@ -855,12 +857,16 @@ retry:
|
||||
break;
|
||||
|
||||
case STB_TEXTEDIT_K_DOWN:
|
||||
case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT: {
|
||||
case STB_TEXTEDIT_K_DOWN | STB_TEXTEDIT_K_SHIFT:
|
||||
case STB_TEXTEDIT_K_PGDOWN:
|
||||
case STB_TEXTEDIT_K_PGDOWN | STB_TEXTEDIT_K_SHIFT: {
|
||||
StbFindState find;
|
||||
StbTexteditRow row;
|
||||
int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;
|
||||
int i, j, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;
|
||||
int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGDOWN;
|
||||
int row_count = is_page ? state->row_count_per_page : 1;
|
||||
|
||||
if (state->single_line) {
|
||||
if (!is_page && state->single_line) {
|
||||
// on windows, up&down in single-line behave like left&right
|
||||
key = STB_TEXTEDIT_K_RIGHT | (key & STB_TEXTEDIT_K_SHIFT);
|
||||
goto retry;
|
||||
@ -869,17 +875,25 @@ retry:
|
||||
if (sel)
|
||||
stb_textedit_prep_selection_at_cursor(state);
|
||||
else if (STB_TEXT_HAS_SELECTION(state))
|
||||
stb_textedit_move_to_last(str,state);
|
||||
stb_textedit_move_to_last(str, state);
|
||||
|
||||
// compute current position of cursor point
|
||||
stb_textedit_clamp(str, state);
|
||||
stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
|
||||
|
||||
// now find character position down a row
|
||||
if (find.length) {
|
||||
float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
|
||||
float x;
|
||||
for (j = 0; j < row_count; ++j) {
|
||||
float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x;
|
||||
int start = find.first_char + find.length;
|
||||
|
||||
if (find.length == 0)
|
||||
break;
|
||||
|
||||
// [DEAR IMGUI]
|
||||
// going down while being on the last line shouldn't bring us to that line end
|
||||
if (STB_TEXTEDIT_GETCHAR(str, find.first_char + find.length - 1) != STB_TEXTEDIT_NEWLINE)
|
||||
break;
|
||||
|
||||
// now find character position down a row
|
||||
state->cursor = start;
|
||||
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
|
||||
x = row.x0;
|
||||
@ -901,17 +915,25 @@ retry:
|
||||
|
||||
if (sel)
|
||||
state->select_end = state->cursor;
|
||||
|
||||
// go to next line
|
||||
find.first_char = find.first_char + find.length;
|
||||
find.length = row.num_chars;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case STB_TEXTEDIT_K_UP:
|
||||
case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: {
|
||||
case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT:
|
||||
case STB_TEXTEDIT_K_PGUP:
|
||||
case STB_TEXTEDIT_K_PGUP | STB_TEXTEDIT_K_SHIFT: {
|
||||
StbFindState find;
|
||||
StbTexteditRow row;
|
||||
int i, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;
|
||||
int i, j, prev_scan, sel = (key & STB_TEXTEDIT_K_SHIFT) != 0;
|
||||
int is_page = (key & ~STB_TEXTEDIT_K_SHIFT) == STB_TEXTEDIT_K_PGUP;
|
||||
int row_count = is_page ? state->row_count_per_page : 1;
|
||||
|
||||
if (state->single_line) {
|
||||
if (!is_page && state->single_line) {
|
||||
// on windows, up&down become left&right
|
||||
key = STB_TEXTEDIT_K_LEFT | (key & STB_TEXTEDIT_K_SHIFT);
|
||||
goto retry;
|
||||
@ -926,11 +948,14 @@ retry:
|
||||
stb_textedit_clamp(str, state);
|
||||
stb_textedit_find_charpos(&find, str, state->cursor, state->single_line);
|
||||
|
||||
// can only go up if there's a previous row
|
||||
if (find.prev_first != find.first_char) {
|
||||
for (j = 0; j < row_count; ++j) {
|
||||
float x, goal_x = state->has_preferred_x ? state->preferred_x : find.x;
|
||||
|
||||
// can only go up if there's a previous row
|
||||
if (find.prev_first == find.first_char)
|
||||
break;
|
||||
|
||||
// now find character position up a row
|
||||
float goal_x = state->has_preferred_x ? state->preferred_x : find.x;
|
||||
float x;
|
||||
state->cursor = find.prev_first;
|
||||
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
|
||||
x = row.x0;
|
||||
@ -952,6 +977,14 @@ retry:
|
||||
|
||||
if (sel)
|
||||
state->select_end = state->cursor;
|
||||
|
||||
// go to previous line
|
||||
// (we need to scan previous line the hard way. maybe we could expose this as a new API function?)
|
||||
prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0;
|
||||
while (prev_scan > 0 && STB_TEXTEDIT_GETCHAR(str, prev_scan - 1) != STB_TEXTEDIT_NEWLINE)
|
||||
--prev_scan;
|
||||
find.first_char = find.prev_first;
|
||||
find.prev_first = prev_scan;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -1075,10 +1108,6 @@ retry:
|
||||
state->has_preferred_x = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// @TODO:
|
||||
// STB_TEXTEDIT_K_PGUP - move cursor up a page
|
||||
// STB_TEXTEDIT_K_PGDOWN - move cursor down a page
|
||||
}
|
||||
}
|
||||
|
||||
@ -1134,7 +1163,7 @@ static void stb_textedit_discard_redo(StbUndoState *state)
|
||||
state->undo_rec[i].char_storage += n;
|
||||
}
|
||||
// now move all the redo records towards the end of the buffer; the first one is at 'redo_point'
|
||||
// {DEAR IMGUI]
|
||||
// [DEAR IMGUI]
|
||||
size_t move_size = (size_t)((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point - 1) * sizeof(state->undo_rec[0]));
|
||||
const char* buf_begin = (char*)state->undo_rec; (void)buf_begin;
|
||||
const char* buf_end = (char*)state->undo_rec + sizeof(state->undo_rec); (void)buf_end;
|
||||
@ -1350,6 +1379,7 @@ static void stb_textedit_clear_state(STB_TexteditState *state, int is_single_lin
|
||||
state->initialized = 1;
|
||||
state->single_line = (unsigned char) is_single_line;
|
||||
state->insert_mode = 0;
|
||||
state->row_count_per_page = 0;
|
||||
}
|
||||
|
||||
// API initialize
|
||||
|
@ -2538,11 +2538,11 @@ static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, i
|
||||
// There are no other cases.
|
||||
STBTT_assert(0);
|
||||
break;
|
||||
};
|
||||
} // [DEAR IMGUI] removed ;
|
||||
}
|
||||
}
|
||||
break;
|
||||
};
|
||||
} // [DEAR IMGUI] removed ;
|
||||
|
||||
default:
|
||||
// TODO: Implement other stuff.
|
||||
@ -4132,7 +4132,7 @@ STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect
|
||||
STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges)
|
||||
{
|
||||
stbtt_fontinfo info;
|
||||
int i,j,n, return_value = 1;
|
||||
int i,j,n, return_value; // [DEAR IMGUI] removed = 1
|
||||
//stbrp_context *context = (stbrp_context *) spc->pack_info;
|
||||
stbrp_rect *rects;
|
||||
|
||||
@ -4302,7 +4302,7 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex
|
||||
int winding = 0;
|
||||
|
||||
orig[0] = x;
|
||||
//orig[1] = y; // [DEAR IMGUI] commmented double assignment
|
||||
//orig[1] = y; // [DEAR IMGUI] commented double assignment
|
||||
|
||||
// make sure y never passes through a vertex of the shape
|
||||
y_frac = (float) STBTT_fmod(y, 1.0f);
|
||||
|
@ -64,10 +64,12 @@ public:
|
||||
void set(const std::string §ion, const std::string &key, const std::string &value)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
std::string key_trimmed = key;
|
||||
boost::trim_all(key_trimmed);
|
||||
assert(key_trimmed == key);
|
||||
assert(! key_trimmed.empty());
|
||||
{
|
||||
std::string key_trimmed = key;
|
||||
boost::trim_all(key_trimmed);
|
||||
assert(key_trimmed == key);
|
||||
assert(! key_trimmed.empty());
|
||||
}
|
||||
#endif // NDEBUG
|
||||
std::string &old = m_storage[section][key];
|
||||
if (old != value) {
|
||||
|
@ -402,9 +402,13 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
|
||||
const BrimType &bt = object->config().brim_type;
|
||||
return (bt == btOuterOnly || bt == btOuterAndInner) && print.config().skirt_distance.value < object->config().brim_width;
|
||||
});
|
||||
|
||||
const bool draft_shield = print.config().draft_shield != dsDisabled;
|
||||
|
||||
|
||||
// If there is a possibility that brim intersects skirt, go through loops and split those extrusions
|
||||
// The result is either the original Polygon or a list of Polylines
|
||||
if (! print.skirt().empty() && could_brim_intersects_skirt)
|
||||
if (draft_shield && ! print.skirt().empty() && could_brim_intersects_skirt)
|
||||
{
|
||||
// Find the bounding polygons of the skirt
|
||||
const Polygons skirt_inners = offset(dynamic_cast<ExtrusionLoop*>(print.skirt().entities.back())->polygon(),
|
||||
|
@ -1570,12 +1570,7 @@ namespace Slic3r {
|
||||
|
||||
m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR));
|
||||
m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
|
||||
// m_curr_object.geometry.mmu_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR));
|
||||
// FIXME Lukas H.: This is only for backward compatibility with older 3MF test files. Removes this when it is not necessary.
|
||||
if(get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR) != "")
|
||||
m_curr_object.geometry.mmu_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR));
|
||||
else
|
||||
m_curr_object.geometry.mmu_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, "slic3rpe:mmu_painting"));
|
||||
m_curr_object.geometry.mmu_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -857,6 +857,8 @@ bool GCodeProcessor::contains_reserved_tags(const std::string& gcode, unsigned i
|
||||
|
||||
bool ret = false;
|
||||
|
||||
CNumericLocalesSetter locales_setter;
|
||||
|
||||
GCodeReader parser;
|
||||
parser.parse_buffer(gcode, [&ret, &found_tag, max_count](GCodeReader& parser, const GCodeReader::GCodeLine& line) {
|
||||
std::string comment = line.raw();
|
||||
|
@ -201,34 +201,39 @@ void SeamPlacer::init(const Print& print)
|
||||
|
||||
std::vector<ExPolygons> temp_enf;
|
||||
std::vector<ExPolygons> temp_blk;
|
||||
std::vector<Polygons> temp_polygons;
|
||||
|
||||
for (const PrintObject* po : print.objects()) {
|
||||
temp_enf.clear();
|
||||
temp_blk.clear();
|
||||
po->project_and_append_custom_facets(true, EnforcerBlockerType::ENFORCER, temp_enf);
|
||||
po->project_and_append_custom_facets(true, EnforcerBlockerType::BLOCKER, temp_blk);
|
||||
|
||||
// Offset the triangles out slightly.
|
||||
for (auto* custom_per_object : {&temp_enf, &temp_blk}) {
|
||||
float offset = max_nozzle_dmr + po->config().elefant_foot_compensation;
|
||||
for (ExPolygons& explgs : *custom_per_object) {
|
||||
explgs = Slic3r::offset_ex(explgs, scale_(offset));
|
||||
offset = max_nozzle_dmr;
|
||||
auto merge_and_offset = [po, &temp_polygons, max_nozzle_dmr](EnforcerBlockerType type, std::vector<ExPolygons>& out) {
|
||||
// Offset the triangles out slightly.
|
||||
auto offset_out = [](Polygon& input, float offset) -> ExPolygons {
|
||||
ClipperLib::Paths out(1);
|
||||
std::vector<float> deltas(input.points.size(), offset);
|
||||
input.make_counter_clockwise();
|
||||
out.front() = mittered_offset_path_scaled(input.points, deltas, 3.);
|
||||
return ClipperPaths_to_Slic3rExPolygons(out);
|
||||
};
|
||||
|
||||
|
||||
temp_polygons.clear();
|
||||
po->project_and_append_custom_facets(true, type, temp_polygons);
|
||||
out.clear();
|
||||
out.reserve(temp_polygons.size());
|
||||
float offset = scale_(max_nozzle_dmr + po->config().elefant_foot_compensation);
|
||||
for (Polygons &src : temp_polygons) {
|
||||
out.emplace_back(ExPolygons());
|
||||
for (Polygon& plg : src) {
|
||||
ExPolygons offset_explg = offset_out(plg, offset);
|
||||
if (! offset_explg.empty())
|
||||
out.back().emplace_back(std::move(offset_explg.front()));
|
||||
}
|
||||
|
||||
offset = scale_(max_nozzle_dmr);
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: Offsetting should be done somehow cheaper, but following does not work
|
||||
// for (auto* custom_per_object : {&temp_enf, &temp_blk}) {
|
||||
// for (ExPolygons& plgs : *custom_per_object) {
|
||||
// for (ExPolygon& plg : plgs) {
|
||||
// auto out = Slic3r::offset_ex(plg, scale_(max_nozzle_dmr));
|
||||
// plg = out.empty() ? ExPolygon() : out.front();
|
||||
// assert(out.empty() || out.size() == 1);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
};
|
||||
merge_and_offset(EnforcerBlockerType::BLOCKER, temp_blk);
|
||||
merge_and_offset(EnforcerBlockerType::ENFORCER, temp_enf);
|
||||
|
||||
// Remember this PrintObject and initialize a store of enforcers and blockers for it.
|
||||
m_po_list.push_back(po);
|
||||
|
@ -201,6 +201,16 @@ protected:
|
||||
virtual ~SupportLayer() = default;
|
||||
};
|
||||
|
||||
template<typename LayerContainer>
|
||||
inline std::vector<float> zs_from_layers(const LayerContainer &layers)
|
||||
{
|
||||
std::vector<float> zs;
|
||||
zs.reserve(layers.size());
|
||||
for (const Layer *l : layers)
|
||||
zs.emplace_back((float)l->slice_z);
|
||||
return zs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -276,6 +276,11 @@ bool does_bound_a_volume(const CGALMesh &mesh)
|
||||
return CGALProc::does_bound_a_volume(mesh.m);
|
||||
}
|
||||
|
||||
bool empty(const CGALMesh &mesh)
|
||||
{
|
||||
return mesh.m.is_empty();
|
||||
}
|
||||
|
||||
} // namespace cgal
|
||||
|
||||
} // namespace MeshBoolean
|
||||
|
@ -55,6 +55,7 @@ bool does_self_intersect(const TriangleMesh &mesh);
|
||||
bool does_self_intersect(const CGALMesh &mesh);
|
||||
|
||||
bool does_bound_a_volume(const CGALMesh &mesh);
|
||||
bool empty(const CGALMesh &mesh);
|
||||
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
namespace Slic3r {
|
||||
|
||||
template<class ExPolicy>
|
||||
std::vector<Vec3i> create_neighbors_index(ExPolicy &&ex, const indexed_triangle_set &its);
|
||||
std::vector<Vec3i> create_face_neighbors_index(ExPolicy &&ex, const indexed_triangle_set &its);
|
||||
|
||||
namespace meshsplit_detail {
|
||||
|
||||
@ -24,7 +24,7 @@ template<> struct ItsWithNeighborsIndex_<indexed_triangle_set> {
|
||||
static const indexed_triangle_set &get_its(const indexed_triangle_set &its) noexcept { return its; }
|
||||
static Index get_index(const indexed_triangle_set &its) noexcept
|
||||
{
|
||||
return create_neighbors_index(ex_tbb, its);
|
||||
return create_face_neighbors_index(ex_tbb, its);
|
||||
}
|
||||
};
|
||||
|
||||
@ -162,28 +162,14 @@ template<class Its> bool its_is_splittable(const Its &m)
|
||||
return !faces.empty();
|
||||
}
|
||||
|
||||
inline int get_vertex_index(size_t vertex_index, const stl_triangle_vertex_indices &triangle_indices) {
|
||||
if (int(vertex_index) == triangle_indices[0]) return 0;
|
||||
if (int(vertex_index) == triangle_indices[1]) return 1;
|
||||
if (int(vertex_index) == triangle_indices[2]) return 2;
|
||||
return -1;
|
||||
}
|
||||
|
||||
inline Vec2crd get_edge_indices(int edge_index, const stl_triangle_vertex_indices &triangle_indices)
|
||||
{
|
||||
int next_edge_index = (edge_index == 2) ? 0 : edge_index + 1;
|
||||
int vi0 = triangle_indices[edge_index];
|
||||
int vi1 = triangle_indices[next_edge_index];
|
||||
return Vec2crd(vi0, vi1);
|
||||
}
|
||||
|
||||
template<class ExPolicy>
|
||||
std::vector<Vec3i> create_neighbors_index(ExPolicy &&ex, const indexed_triangle_set &its)
|
||||
std::vector<Vec3i> create_face_neighbors_index(ExPolicy &&ex, const indexed_triangle_set &its)
|
||||
{
|
||||
const std::vector<stl_triangle_vertex_indices> &indices = its.indices;
|
||||
size_t vertices_size = its.vertices.size();
|
||||
|
||||
if (indices.empty() || vertices_size == 0) return {};
|
||||
if (indices.empty()) return {};
|
||||
|
||||
assert(! its.vertices.empty());
|
||||
|
||||
auto vertex_triangles = VertexFaceIndex{its};
|
||||
static constexpr int no_value = -1;
|
||||
@ -192,27 +178,28 @@ std::vector<Vec3i> create_neighbors_index(ExPolicy &&ex, const indexed_triangle_
|
||||
|
||||
//for (const stl_triangle_vertex_indices& triangle_indices : indices) {
|
||||
execution::for_each(ex, size_t(0), indices.size(),
|
||||
[&neighbors, &indices, &vertex_triangles] (size_t index)
|
||||
[&neighbors, &indices, &vertex_triangles] (size_t face_idx)
|
||||
{
|
||||
Vec3i& neighbor = neighbors[index];
|
||||
const stl_triangle_vertex_indices & triangle_indices = indices[index];
|
||||
Vec3i& neighbor = neighbors[face_idx];
|
||||
const stl_triangle_vertex_indices & triangle_indices = indices[face_idx];
|
||||
for (int edge_index = 0; edge_index < 3; ++edge_index) {
|
||||
// check if done
|
||||
int& neighbor_edge = neighbor[edge_index];
|
||||
if (neighbor_edge != no_value) continue;
|
||||
Vec2crd edge_indices = get_edge_indices(edge_index, triangle_indices);
|
||||
if (neighbor_edge != no_value)
|
||||
// This edge already has a neighbor assigned.
|
||||
continue;
|
||||
Vec2i edge_indices = its_triangle_edge(triangle_indices, edge_index);
|
||||
// IMPROVE: use same vector for 2 sides of triangle
|
||||
const auto &faces_range = vertex_triangles[edge_indices[0]];
|
||||
for (const size_t &face : faces_range) {
|
||||
if (face <= index) continue;
|
||||
const stl_triangle_vertex_indices &face_indices = indices[face];
|
||||
int vertex_index = get_vertex_index(edge_indices[1], face_indices);
|
||||
for (const size_t other_face : vertex_triangles[edge_indices[0]]) {
|
||||
if (other_face <= face_idx) continue;
|
||||
const stl_triangle_vertex_indices &face_indices = indices[other_face];
|
||||
int vertex_index = its_triangle_vertex_index(face_indices, edge_indices[1]);
|
||||
// NOT Contain second vertex?
|
||||
if (vertex_index < 0) continue;
|
||||
// Has NOT oposit direction?
|
||||
// Has NOT oposite direction?
|
||||
if (edge_indices[0] != face_indices[(vertex_index + 1) % 3]) continue;
|
||||
neighbor_edge = face;
|
||||
neighbors[face][vertex_index] = index;
|
||||
neighbor_edge = other_face;
|
||||
neighbors[other_face][vertex_index] = face_idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1070,8 +1070,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con
|
||||
new_object->input_file.clear();
|
||||
|
||||
int vol_idx = 0;
|
||||
for (ModelVolume* volume : volumes)
|
||||
{
|
||||
for (ModelVolume* volume : volumes) {
|
||||
if (!volume->mesh().empty()) {
|
||||
TriangleMesh mesh(volume->mesh());
|
||||
mesh.require_shared_vertices();
|
||||
@ -1089,6 +1088,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con
|
||||
vol->source.volume_idx = vol_idx;
|
||||
vol->source.is_converted_from_inches = volume->source.is_converted_from_inches;
|
||||
vol->source.is_converted_from_meters = volume->source.is_converted_from_meters;
|
||||
vol->source.is_from_builtin_objects = volume->source.is_from_builtin_objects;
|
||||
|
||||
vol->supported_facets.assign(volume->supported_facets);
|
||||
vol->seam_facets.assign(volume->seam_facets);
|
||||
@ -1959,8 +1959,19 @@ indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, Enforce
|
||||
{
|
||||
TriangleSelector selector(mv.mesh());
|
||||
selector.deserialize(m_data);
|
||||
indexed_triangle_set out = selector.get_facets(type);
|
||||
return out;
|
||||
return selector.get_facets(type);
|
||||
}
|
||||
|
||||
indexed_triangle_set FacetsAnnotation::get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const
|
||||
{
|
||||
TriangleSelector selector(mv.mesh());
|
||||
selector.deserialize(m_data);
|
||||
return selector.get_facets_strict(type);
|
||||
}
|
||||
|
||||
bool FacetsAnnotation::has_facets(const ModelVolume& mv, EnforcerBlockerType type) const
|
||||
{
|
||||
return TriangleSelector::has_facets(m_data, type);
|
||||
}
|
||||
|
||||
bool FacetsAnnotation::set(const TriangleSelector& selector)
|
||||
|
@ -500,10 +500,26 @@ private:
|
||||
};
|
||||
|
||||
enum class EnforcerBlockerType : int8_t {
|
||||
// Maximum is 3. The value is serialized in TriangleSelector into 2 bits!
|
||||
// Maximum is 3. The value is serialized in TriangleSelector into 2 bits.
|
||||
NONE = 0,
|
||||
ENFORCER = 1,
|
||||
BLOCKER = 2
|
||||
BLOCKER = 2,
|
||||
// Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code.
|
||||
Extruder1 = ENFORCER,
|
||||
Extruder2 = BLOCKER,
|
||||
Extruder3,
|
||||
Extruder4,
|
||||
Extruder5,
|
||||
Extruder6,
|
||||
Extruder7,
|
||||
Extruder8,
|
||||
Extruder9,
|
||||
Extruder10,
|
||||
Extruder11,
|
||||
Extruder12,
|
||||
Extruder13,
|
||||
Extruder14,
|
||||
Extruder15,
|
||||
};
|
||||
|
||||
enum class ConversionType : int {
|
||||
@ -521,6 +537,8 @@ public:
|
||||
const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>>& get_data() const throw() { return m_data; }
|
||||
bool set(const TriangleSelector& selector);
|
||||
indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const;
|
||||
indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const;
|
||||
bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const;
|
||||
bool empty() const { return m_data.first.empty(); }
|
||||
void clear();
|
||||
|
||||
@ -578,13 +596,14 @@ public:
|
||||
int volume_idx{ -1 };
|
||||
Vec3d mesh_offset{ Vec3d::Zero() };
|
||||
Geometry::Transformation transform;
|
||||
bool is_converted_from_inches = false;
|
||||
bool is_converted_from_meters = false;
|
||||
bool is_converted_from_inches{ false };
|
||||
bool is_converted_from_meters{ false };
|
||||
bool is_from_builtin_objects{ false };
|
||||
|
||||
template<class Archive> void serialize(Archive& ar) {
|
||||
//FIXME Vojtech: Serialize / deserialize only if the Source is set.
|
||||
// likely testing input_file or object_idx would be sufficient.
|
||||
ar(input_file, object_idx, volume_idx, mesh_offset, transform, is_converted_from_inches, is_converted_from_meters);
|
||||
ar(input_file, object_idx, volume_idx, mesh_offset, transform, is_converted_from_inches, is_converted_from_meters, is_from_builtin_objects);
|
||||
}
|
||||
};
|
||||
Source source;
|
||||
|
@ -1145,68 +1145,18 @@ static void cut_segmented_layers(const std::vector<ExPolygons>
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - end";
|
||||
}
|
||||
|
||||
// #define MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
|
||||
|
||||
// Returns MMU segmentation of top and bottom layers based on painting in MMU segmentation gizmo
|
||||
static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bottom_layers(const PrintObject &print_object,
|
||||
const std::vector<ExPolygons> &input_expolygons,
|
||||
const std::function<void()> &throw_on_cancel_callback)
|
||||
{
|
||||
const size_t num_extruders = print_object.print()->config().nozzle_diameter.size() + 1;
|
||||
const size_t num_layers = input_expolygons.size();
|
||||
const ConstLayerPtrsAdaptor layers = print_object.layers();
|
||||
std::vector<std::vector<ExPolygons>> triangles_by_color(num_extruders);
|
||||
triangles_by_color.assign(num_extruders, std::vector<ExPolygons>(layers.size()));
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - top and bottom layers - projection of painted triangles - begin";
|
||||
for (const ModelVolume *mv : print_object.model_object()->volumes) {
|
||||
for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx) {
|
||||
throw_on_cancel_callback();
|
||||
const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx));
|
||||
if (!mv->is_model_part() || custom_facets.indices.empty())
|
||||
continue;
|
||||
|
||||
const Transform3f tr = print_object.trafo().cast<float>() * mv->get_matrix().cast<float>();
|
||||
for (size_t facet_idx = 0; facet_idx < custom_facets.indices.size(); ++facet_idx) {
|
||||
float min_z = std::numeric_limits<float>::max();
|
||||
float max_z = std::numeric_limits<float>::lowest();
|
||||
|
||||
std::array<Vec3f, 3> facet;
|
||||
Points projected_facet(3);
|
||||
for (int p_idx = 0; p_idx < 3; ++p_idx) {
|
||||
facet[p_idx] = tr * custom_facets.vertices[custom_facets.indices[facet_idx](p_idx)];
|
||||
max_z = std::max(max_z, facet[p_idx].z());
|
||||
min_z = std::min(min_z, facet[p_idx].z());
|
||||
}
|
||||
|
||||
// Sort the vertices by z-axis for simplification of projected_facet on slices
|
||||
std::sort(facet.begin(), facet.end(), [](const Vec3f &p1, const Vec3f &p2) { return p1.z() < p2.z(); });
|
||||
|
||||
for (int p_idx = 0; p_idx < 3; ++p_idx) {
|
||||
projected_facet[p_idx] = Point(scale_(facet[p_idx].x()), scale_(facet[p_idx].y()));
|
||||
projected_facet[p_idx] = projected_facet[p_idx] - print_object.center_offset();
|
||||
}
|
||||
|
||||
ExPolygon triangle = ExPolygon(projected_facet);
|
||||
|
||||
// Find lowest slice not below the triangle.
|
||||
auto first_layer = std::upper_bound(layers.begin(), layers.end(), float(min_z - EPSILON),
|
||||
[](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; });
|
||||
auto last_layer = std::upper_bound(layers.begin(), layers.end(), float(max_z - EPSILON),
|
||||
[](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; });
|
||||
|
||||
if (last_layer == layers.end())
|
||||
--last_layer;
|
||||
|
||||
if (first_layer == layers.end() || (first_layer != layers.begin() && facet[0].z() < (*first_layer)->print_z - EPSILON))
|
||||
--first_layer;
|
||||
|
||||
for (auto layer_it = first_layer; (layer_it != (last_layer + 1) && layer_it != layers.end()); ++layer_it) {
|
||||
size_t layer_idx = layer_it - layers.begin();
|
||||
triangles_by_color[extruder_idx][layer_idx].emplace_back(triangle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - top and bottom layers - projection of painted triangles - end";
|
||||
|
||||
#if 0
|
||||
auto get_extrusion_width = [&layers = std::as_const(layers)](const size_t layer_idx) -> float {
|
||||
auto extrusion_width_it = std::max_element(layers[layer_idx]->regions().begin(), layers[layer_idx]->regions().end(),
|
||||
[](const LayerRegion *l1, const LayerRegion *l2) {
|
||||
@ -1235,9 +1185,9 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
||||
return (*top_bottom_layer_it)->region().config().bottom_solid_layers;
|
||||
};
|
||||
|
||||
std::vector<ExPolygons> top_layers(input_expolygons.size());
|
||||
std::vector<ExPolygons> top_layers(num_layers);
|
||||
top_layers.back() = input_expolygons.back();
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(1, input_expolygons.size()), [&](const tbb::blocked_range<size_t> &range) {
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(1, num_layers), [&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
||||
throw_on_cancel_callback();
|
||||
float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx)));
|
||||
@ -1245,9 +1195,9 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
||||
}
|
||||
}); // end of parallel_for
|
||||
|
||||
std::vector<ExPolygons> bottom_layers(input_expolygons.size());
|
||||
std::vector<ExPolygons> bottom_layers(num_layers);
|
||||
bottom_layers.front() = input_expolygons.front();
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, input_expolygons.size() - 1), [&](const tbb::blocked_range<size_t> &range) {
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers - 1), [&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
||||
throw_on_cancel_callback();
|
||||
float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx)));
|
||||
@ -1255,28 +1205,84 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
||||
}
|
||||
}); // end of parallel_for
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, input_expolygons.size()), [&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
||||
throw_on_cancel_callback();
|
||||
float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx)));
|
||||
for (std::vector<ExPolygons> &triangles : triangles_by_color) {
|
||||
if (!triangles[layer_idx].empty() && (!top_layers[layer_idx].empty() || !bottom_layers[layer_idx].empty())) {
|
||||
ExPolygons connected = union_ex(offset_ex(triangles[layer_idx], float(10 * SCALED_EPSILON)));
|
||||
triangles[layer_idx] = union_ex(offset_ex(offset_ex(connected, -extrusion_width / 1), extrusion_width / 1));
|
||||
} else {
|
||||
triangles[layer_idx].clear();
|
||||
std::vector<std::vector<ClipperLib::Paths>> triangles_by_color_raw(num_extruders, std::vector<ClipperLib::Paths>(layers.size()));
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - top and bottom layers - projection of painted triangles - begin";
|
||||
{
|
||||
auto delta = float(10 * SCALED_EPSILON);
|
||||
std::vector<float> deltas { delta, delta, delta };
|
||||
Points projected_facet;
|
||||
for (const ModelVolume *mv : print_object.model_object()->volumes)
|
||||
if (mv->is_model_part()) {
|
||||
const Transform3f tr = print_object.trafo().cast<float>() * mv->get_matrix().cast<float>();
|
||||
|
||||
for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx) {
|
||||
const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx));
|
||||
if (custom_facets.indices.empty())
|
||||
continue;
|
||||
|
||||
throw_on_cancel_callback();
|
||||
for (size_t facet_idx = 0; facet_idx < custom_facets.indices.size(); ++facet_idx) {
|
||||
float min_z = std::numeric_limits<float>::max();
|
||||
float max_z = std::numeric_limits<float>::lowest();
|
||||
|
||||
std::array<Vec3f, 3> facet;
|
||||
for (int p_idx = 0; p_idx < 3; ++p_idx) {
|
||||
facet[p_idx] = tr * custom_facets.vertices[custom_facets.indices[facet_idx](p_idx)];
|
||||
max_z = std::max(max_z, facet[p_idx].z());
|
||||
min_z = std::min(min_z, facet[p_idx].z());
|
||||
}
|
||||
|
||||
// Sort the vertices by z-axis for simplification of projected_facet on slices
|
||||
std::sort(facet.begin(), facet.end(), [](const Vec3f &p1, const Vec3f &p2) { return p1.z() < p2.z(); });
|
||||
projected_facet.clear();
|
||||
projected_facet.reserve(3);
|
||||
for (int p_idx = 0; p_idx < 3; ++p_idx)
|
||||
projected_facet.emplace_back(Point(scale_(facet[p_idx].x()), scale_(facet[p_idx].y())) - print_object.center_offset());
|
||||
if (cross2((projected_facet[1] - projected_facet[0]).cast<int64_t>(), (projected_facet[2] - projected_facet[1]).cast<int64_t>()) < 0)
|
||||
// Make CCW.
|
||||
std::swap(projected_facet[1], projected_facet[2]);
|
||||
ClipperLib::Path offsetted = mittered_offset_path_scaled(projected_facet, deltas, 3.);
|
||||
|
||||
// Find lowest slice not below the triangle.
|
||||
auto first_layer = std::upper_bound(layers.begin(), layers.end(), float(min_z - EPSILON),
|
||||
[](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; });
|
||||
auto last_layer = std::upper_bound(layers.begin(), layers.end(), float(max_z - EPSILON),
|
||||
[](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; });
|
||||
|
||||
if (last_layer == layers.end())
|
||||
--last_layer;
|
||||
|
||||
if (first_layer == layers.end() || (first_layer != layers.begin() && facet[0].z() < (*first_layer)->print_z - EPSILON))
|
||||
--first_layer;
|
||||
|
||||
for (auto layer_it = first_layer; (layer_it != (last_layer + 1) && layer_it != layers.end()); ++layer_it)
|
||||
if (size_t layer_idx = layer_it - layers.begin(); ! top_layers[layer_idx].empty() || ! bottom_layers[layer_idx].empty())
|
||||
triangles_by_color_raw[extruder_idx][layer_idx].emplace_back(offsetted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - top and bottom layers - projection of painted triangles - end";
|
||||
|
||||
std::vector<std::vector<ExPolygons>> triangles_by_color(num_extruders, std::vector<ExPolygons>(layers.size()));
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
||||
throw_on_cancel_callback();
|
||||
float offset_factor = 0.1f * float(scale_(get_extrusion_width(layer_idx)));
|
||||
for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id)
|
||||
if (ClipperLib::Paths &src_paths = triangles_by_color_raw[extruder_id][layer_idx]; !src_paths.empty())
|
||||
triangles_by_color[extruder_id][layer_idx] = offset_ex(offset_ex(ClipperPaths_to_Slic3rExPolygons(src_paths), -offset_factor), offset_factor);
|
||||
}
|
||||
}); // end of parallel_for
|
||||
triangles_by_color_raw.clear();
|
||||
|
||||
std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_extruders);
|
||||
std::vector<std::vector<ExPolygons>> triangles_by_color_top(num_extruders);
|
||||
triangles_by_color_bottom.assign(num_extruders, std::vector<ExPolygons>(input_expolygons.size()));
|
||||
triangles_by_color_top.assign(num_extruders, std::vector<ExPolygons>(input_expolygons.size()));
|
||||
triangles_by_color_bottom.assign(num_extruders, std::vector<ExPolygons>(num_layers));
|
||||
triangles_by_color_top.assign(num_extruders, std::vector<ExPolygons>(num_layers));
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of top layer - begin";
|
||||
for (size_t layer_idx = 0; layer_idx < input_expolygons.size(); ++layer_idx) {
|
||||
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
|
||||
float extrusion_width = scale_(get_extrusion_width(layer_idx));
|
||||
int top_solid_layers = get_top_solid_layers(layer_idx);
|
||||
ExPolygons top_expolygon = top_layers[layer_idx];
|
||||
@ -1312,7 +1318,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of top layer - end";
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of bottom layer - begin";
|
||||
for (size_t layer_idx = 0; layer_idx < input_expolygons.size(); ++layer_idx) {
|
||||
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
|
||||
float extrusion_width = scale_(get_extrusion_width(layer_idx));
|
||||
int bottom_solid_layers = get_bottom_solid_layers(layer_idx);
|
||||
const ExPolygons &bottom_expolygon = bottom_layers[layer_idx];
|
||||
@ -1328,7 +1334,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
||||
if (!intersection_poly.empty()) {
|
||||
triangles_by_color_bottom[color_idx][layer_idx].insert(triangles_by_color_bottom[color_idx][layer_idx].end(), intersection_poly.begin(),
|
||||
intersection_poly.end());
|
||||
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + bottom_solid_layers, input_expolygons.size()); ++last_idx) {
|
||||
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + bottom_solid_layers, num_layers); ++last_idx) {
|
||||
float offset_value = float(last_idx - layer_idx) * (-1.0f) * extrusion_width;
|
||||
if (offset_ex(bottom_expolygon, offset_value).empty())
|
||||
continue;
|
||||
@ -1347,8 +1353,8 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
||||
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of bottom layer - end";
|
||||
|
||||
std::vector<std::vector<ExPolygons>> triangles_by_color_merged(num_extruders);
|
||||
triangles_by_color_merged.assign(num_extruders, std::vector<ExPolygons>(input_expolygons.size()));
|
||||
for (size_t layer_idx = 0; layer_idx < input_expolygons.size(); ++layer_idx) {
|
||||
triangles_by_color_merged.assign(num_extruders, std::vector<ExPolygons>(num_layers));
|
||||
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
|
||||
throw_on_cancel_callback();
|
||||
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
|
||||
auto &self = triangles_by_color_merged[color_idx][layer_idx];
|
||||
@ -1363,6 +1369,204 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
|
||||
triangles_by_color_merged[color_idx - 1][layer_idx]);
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
// Maximum number of top / bottom layers accounts for maximum overlap of one thread group into a neighbor thread group.
|
||||
int max_top_layers = 0;
|
||||
int max_bottom_layers = 0;
|
||||
int granularity = 1;
|
||||
for (size_t i = 0; i < print_object.num_printing_regions(); ++ i) {
|
||||
const PrintRegionConfig &config = print_object.printing_region(i).config();
|
||||
max_top_layers = std::max(max_top_layers, config.top_solid_layers.value);
|
||||
max_bottom_layers = std::max(max_bottom_layers, config.bottom_solid_layers.value);
|
||||
granularity = std::max(granularity, std::max(config.top_solid_layers.value, config.bottom_solid_layers.value) - 1);
|
||||
}
|
||||
|
||||
// Project upwards pointing painted triangles over top surfaces,
|
||||
// project downards pointing painted triangles over bottom surfaces.
|
||||
std::vector<std::vector<Polygons>> top_raw(num_extruders), bottom_raw(num_extruders);
|
||||
std::vector<float> zs = zs_from_layers(print_object.layers());
|
||||
Transform3d object_trafo = print_object.trafo();
|
||||
object_trafo.pretranslate(Vec3d(- unscale<double>(print_object.center_offset().x()), - unscale<double>(print_object.center_offset().y()), 0));
|
||||
|
||||
#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
|
||||
static int iRun = 0;
|
||||
#endif // NDEBUG
|
||||
|
||||
if (max_top_layers > 0 || max_bottom_layers > 0) {
|
||||
for (const ModelVolume *mv : print_object.model_object()->volumes)
|
||||
if (mv->is_model_part()) {
|
||||
const Transform3d volume_trafo = object_trafo * mv->get_matrix();
|
||||
for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) {
|
||||
const indexed_triangle_set painted = mv->mmu_segmentation_facets.get_facets_strict(*mv, EnforcerBlockerType(extruder_idx));
|
||||
#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
|
||||
{
|
||||
static int iRun = 0;
|
||||
its_write_obj(painted, debug_out_path("mm-painted-patch-%d-%d.obj", iRun ++, extruder_idx).c_str());
|
||||
}
|
||||
#endif // MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
|
||||
if (! painted.indices.empty()) {
|
||||
std::vector<Polygons> top, bottom;
|
||||
slice_mesh_slabs(painted, zs, volume_trafo, max_top_layers > 0 ? &top : nullptr, max_bottom_layers > 0 ? &bottom : nullptr, throw_on_cancel_callback);
|
||||
auto merge = [](std::vector<Polygons> &&src, std::vector<Polygons> &dst) {
|
||||
auto it_src = find_if(src.begin(), src.end(), [](const Polygons &p){ return ! p.empty(); });
|
||||
if (it_src != src.end()) {
|
||||
if (dst.empty()) {
|
||||
dst = std::move(src);
|
||||
} else {
|
||||
assert(src.size() == dst.size());
|
||||
auto it_dst = dst.begin() + (it_src - src.begin());
|
||||
for (; it_src != src.end(); ++ it_src, ++ it_dst)
|
||||
if (! it_src->empty()) {
|
||||
if (it_dst->empty())
|
||||
*it_dst = std::move(*it_src);
|
||||
else
|
||||
append(*it_dst, std::move(*it_src));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
merge(std::move(top), top_raw[extruder_idx]);
|
||||
merge(std::move(bottom), bottom_raw[extruder_idx]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
|
||||
{
|
||||
const char* colors[] = { "aqua", "black", "blue", "fuchsia", "gray", "green", "lime", "maroon", "navy", "olive", "purple", "red", "silver", "teal", "yellow" };
|
||||
static int iRun = 0;
|
||||
for (size_t layer_id = 0; layer_id < zs.size(); ++layer_id) {
|
||||
std::vector<std::pair<Slic3r::ExPolygons, SVG::ExPolygonAttributes>> svg;
|
||||
for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) {
|
||||
if (! top_raw[extruder_idx].empty() && ! top_raw[extruder_idx][layer_id].empty())
|
||||
if (ExPolygons expoly = union_ex(top_raw[extruder_idx][layer_id]); ! expoly.empty()) {
|
||||
const char *color = colors[extruder_idx];
|
||||
svg.emplace_back(expoly, SVG::ExPolygonAttributes{ format("top%d", extruder_idx), color, color, color });
|
||||
}
|
||||
if (! bottom_raw[extruder_idx].empty() && ! bottom_raw[extruder_idx][layer_id].empty())
|
||||
if (ExPolygons expoly = union_ex(bottom_raw[extruder_idx][layer_id]); ! expoly.empty()) {
|
||||
const char *color = colors[extruder_idx + 8];
|
||||
svg.emplace_back(expoly, SVG::ExPolygonAttributes{ format("bottom%d", extruder_idx), color, color, color });
|
||||
}
|
||||
}
|
||||
SVG::export_expolygons(debug_out_path("mm-segmentation-top-bottom-%d-%d-%lf.svg", iRun, layer_id, zs[layer_id]), svg);
|
||||
}
|
||||
++ iRun;
|
||||
}
|
||||
#endif // MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
|
||||
|
||||
std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_extruders);
|
||||
std::vector<std::vector<ExPolygons>> triangles_by_color_top(num_extruders);
|
||||
triangles_by_color_bottom.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
|
||||
triangles_by_color_top.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
|
||||
|
||||
struct LayerColorStat {
|
||||
// Number of regions for a queried color.
|
||||
int num_regions { 0 };
|
||||
// Maximum perimeter extrusion width for a queried color.
|
||||
float extrusion_width { 0.f };
|
||||
// Minimum radius of a region to be printable. Used to filter regions by morphological opening.
|
||||
float small_region_threshold { 0.f };
|
||||
// Maximum number of top layers for a queried color.
|
||||
int top_solid_layers { 0 };
|
||||
// Maximum number of bottom layers for a queried color.
|
||||
int bottom_solid_layers { 0 };
|
||||
};
|
||||
auto layer_color_stat = [&layers = std::as_const(layers)](const size_t layer_idx, const size_t color_idx) -> LayerColorStat {
|
||||
LayerColorStat out;
|
||||
const Layer &layer = *layers[layer_idx];
|
||||
for (const LayerRegion *region : layer.regions())
|
||||
if (const PrintRegionConfig &config = region->region().config();
|
||||
// color_idx == 0 means "don't know" extruder aka the underlying extruder.
|
||||
// As this region may split existing regions, we collect statistics over all regions for color_idx == 0.
|
||||
color_idx == 0 || config.perimeter_extruder == int(color_idx)) {
|
||||
out.extrusion_width = std::max<float>(out.extrusion_width, config.perimeter_extrusion_width);
|
||||
out.top_solid_layers = std::max<float>(out.top_solid_layers, config.top_solid_layers);
|
||||
out.bottom_solid_layers = std::max<float>(out.bottom_solid_layers, config.bottom_solid_layers);
|
||||
out.small_region_threshold = config.gap_fill_enabled.value && config.gap_fill_speed.value > 0 ?
|
||||
// Gap fill enabled. Enable a single line of 1/2 extrusion width.
|
||||
0.5 * config.perimeter_extrusion_width :
|
||||
// Gap fill disabled. Enable two lines slightly overlapping.
|
||||
config.perimeter_extrusion_width + 0.7f * Flow::rounded_rectangle_extrusion_spacing(config.perimeter_extrusion_width, layer.height);
|
||||
out.small_region_threshold = scaled<float>(out.small_region_threshold * 0.5f);
|
||||
++ out.num_regions;
|
||||
}
|
||||
assert(out.num_regions > 0);
|
||||
out.extrusion_width = scaled<float>(out.extrusion_width);
|
||||
return out;
|
||||
};
|
||||
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers, granularity), [&](const tbb::blocked_range<size_t> &range) {
|
||||
size_t group_idx = range.begin() / granularity;
|
||||
size_t layer_idx_offset = (group_idx & 1) * num_layers;
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||
for (size_t color_idx = 0; color_idx < num_extruders; ++ color_idx) {
|
||||
throw_on_cancel_callback();
|
||||
LayerColorStat stat = layer_color_stat(layer_idx, color_idx);
|
||||
if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty())
|
||||
if (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) {
|
||||
// Clean up thin projections. They are not printable anyways.
|
||||
top_ex = offset2_ex(top_ex, - stat.small_region_threshold, + stat.small_region_threshold);
|
||||
if (! top_ex.empty()) {
|
||||
append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex);
|
||||
float offset = 0.f;
|
||||
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
|
||||
for (int last_idx = int(layer_idx) - 1; last_idx >= std::max(int(layer_idx - stat.top_solid_layers), int(0)); --last_idx) {
|
||||
offset -= stat.extrusion_width;
|
||||
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
|
||||
ExPolygons last = offset2_ex(intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset)),
|
||||
- stat.small_region_threshold, + stat.small_region_threshold);
|
||||
if (last.empty())
|
||||
break;
|
||||
append(triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (std::vector<Polygons> &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty())
|
||||
if (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); ! bottom_ex.empty()) {
|
||||
// Clean up thin projections. They are not printable anyways.
|
||||
bottom_ex = offset2_ex(bottom_ex, - stat.small_region_threshold, + stat.small_region_threshold);
|
||||
if (! bottom_ex.empty()) {
|
||||
append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex);
|
||||
float offset = 0.f;
|
||||
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
|
||||
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + stat.bottom_solid_layers, num_layers); ++last_idx) {
|
||||
offset -= stat.extrusion_width;
|
||||
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
|
||||
ExPolygons last = offset2_ex(intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset)),
|
||||
- stat.small_region_threshold, + stat.small_region_threshold);
|
||||
if (last.empty())
|
||||
break;
|
||||
append(triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
std::vector<std::vector<ExPolygons>> triangles_by_color_merged(num_extruders);
|
||||
triangles_by_color_merged.assign(num_extruders, std::vector<ExPolygons>(num_layers));
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||
throw_on_cancel_callback();
|
||||
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
|
||||
auto &self = triangles_by_color_merged[color_idx][layer_idx];
|
||||
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx]));
|
||||
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx + num_layers]));
|
||||
append(self, std::move(triangles_by_color_top[color_idx][layer_idx]));
|
||||
append(self, std::move(triangles_by_color_top[color_idx][layer_idx + num_layers]));
|
||||
self = union_ex(self);
|
||||
}
|
||||
// Trim one region by the other if some of the regions overlap.
|
||||
for (size_t color_idx = 1; color_idx < triangles_by_color_merged.size(); ++ color_idx)
|
||||
triangles_by_color_merged[color_idx][layer_idx] = diff_ex(triangles_by_color_merged[color_idx][layer_idx],
|
||||
triangles_by_color_merged[color_idx - 1][layer_idx]);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
return triangles_by_color_merged;
|
||||
}
|
||||
|
@ -159,6 +159,7 @@ void PresetBundle::setup_directories()
|
||||
data_dir,
|
||||
data_dir / "vendor",
|
||||
data_dir / "cache",
|
||||
data_dir / "shapes",
|
||||
#ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR
|
||||
// Store the print/filament/printer presets into a "presets" directory.
|
||||
data_dir / "presets",
|
||||
|
@ -157,7 +157,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
|| opt_key == "wipe_tower_x"
|
||||
|| opt_key == "wipe_tower_y"
|
||||
|| opt_key == "wipe_tower_rotation_angle") {
|
||||
steps.emplace_back(psSkirt);
|
||||
steps.emplace_back(psSkirtBrim);
|
||||
} else if (
|
||||
opt_key == "nozzle_diameter"
|
||||
|| opt_key == "resolution"
|
||||
@ -201,7 +201,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
|| opt_key == "first_layer_speed"
|
||||
|| opt_key == "z_offset") {
|
||||
steps.emplace_back(psWipeTower);
|
||||
steps.emplace_back(psSkirt);
|
||||
steps.emplace_back(psSkirtBrim);
|
||||
} else if (opt_key == "filament_soluble") {
|
||||
steps.emplace_back(psWipeTower);
|
||||
// Soluble support interface / non-soluble base interface produces non-soluble interface layers below soluble interface layers.
|
||||
@ -215,8 +215,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
||||
osteps.emplace_back(posPerimeters);
|
||||
osteps.emplace_back(posInfill);
|
||||
osteps.emplace_back(posSupportMaterial);
|
||||
steps.emplace_back(psSkirt);
|
||||
steps.emplace_back(psBrim);
|
||||
steps.emplace_back(psSkirtBrim);
|
||||
} else {
|
||||
// for legacy, if we can't handle this option let's invalidate all steps
|
||||
//FIXME invalidate all steps of all objects as well?
|
||||
@ -239,8 +238,6 @@ bool Print::invalidate_step(PrintStep step)
|
||||
{
|
||||
bool invalidated = Inherited::invalidate_step(step);
|
||||
// Propagate to dependent steps.
|
||||
if (step == psSkirt)
|
||||
invalidated |= Inherited::invalidate_step(psBrim);
|
||||
if (step != psGCodeExport)
|
||||
invalidated |= Inherited::invalidate_step(psGCodeExport);
|
||||
return invalidated;
|
||||
@ -342,12 +339,12 @@ std::vector<ObjectID> Print::print_object_ids() const
|
||||
|
||||
bool Print::has_infinite_skirt() const
|
||||
{
|
||||
return (m_config.draft_shield && m_config.skirts > 0) || (m_config.ooze_prevention && this->extruders().size() > 1);
|
||||
return (m_config.draft_shield == dsEnabled && m_config.skirts > 0) || (m_config.ooze_prevention && this->extruders().size() > 1);
|
||||
}
|
||||
|
||||
bool Print::has_skirt() const
|
||||
{
|
||||
return (m_config.skirt_height > 0 && m_config.skirts > 0) || this->has_infinite_skirt();
|
||||
return (m_config.skirt_height > 0 && m_config.skirts > 0) || m_config.draft_shield != dsDisabled;
|
||||
}
|
||||
|
||||
bool Print::has_brim() const
|
||||
@ -655,10 +652,8 @@ std::string Print::validate(std::string* warning) const
|
||||
// Notify the user in that case.
|
||||
if (! object->has_support() && warning) {
|
||||
for (const ModelVolume* mv : object->model_object()->volumes) {
|
||||
bool has_enforcers = mv->is_support_enforcer()
|
||||
|| (mv->is_model_part()
|
||||
&& ! mv->supported_facets.empty()
|
||||
&& ! mv->supported_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER).indices.empty());
|
||||
bool has_enforcers = mv->is_support_enforcer() ||
|
||||
(mv->is_model_part() && mv->supported_facets.has_facets(*mv, EnforcerBlockerType::ENFORCER));
|
||||
if (has_enforcers) {
|
||||
*warning = "_SUPPORTS_OFF";
|
||||
break;
|
||||
@ -863,30 +858,39 @@ void Print::process()
|
||||
}
|
||||
this->set_done(psWipeTower);
|
||||
}
|
||||
if (this->set_started(psSkirt)) {
|
||||
if (this->set_started(psSkirtBrim)) {
|
||||
this->set_status(88, L("Generating skirt and brim"));
|
||||
|
||||
m_skirt.clear();
|
||||
m_skirt_convex_hull.clear();
|
||||
m_first_layer_convex_hull.points.clear();
|
||||
if (this->has_skirt()) {
|
||||
this->set_status(88, L("Generating skirt"));
|
||||
this->_make_skirt();
|
||||
const bool draft_shield = config().draft_shield != dsDisabled;
|
||||
|
||||
if (this->has_skirt() && draft_shield) {
|
||||
// In case that draft shield is active, generate skirt first so brim
|
||||
// can be trimmed to make room for it.
|
||||
_make_skirt();
|
||||
}
|
||||
this->set_done(psSkirt);
|
||||
}
|
||||
if (this->set_started(psBrim)) {
|
||||
|
||||
m_brim.clear();
|
||||
m_first_layer_convex_hull.points.clear();
|
||||
if (this->has_brim()) {
|
||||
this->set_status(88, L("Generating brim"));
|
||||
Polygons islands_area;
|
||||
m_brim = make_brim(*this, this->make_try_cancel(), islands_area);
|
||||
for (Polygon &poly : union_(this->first_layer_islands(), islands_area))
|
||||
append(m_first_layer_convex_hull.points, std::move(poly.points));
|
||||
}
|
||||
// Brim depends on skirt (brim lines are trimmed by the skirt lines), therefore if
|
||||
// the skirt gets invalidated, brim gets invalidated as well and the following line is called.
|
||||
|
||||
|
||||
if (has_skirt() && ! draft_shield) {
|
||||
// In case that draft shield is NOT active, generate skirt now.
|
||||
// It will be placed around the brim, so brim has to be ready.
|
||||
assert(m_skirt.empty());
|
||||
_make_skirt();
|
||||
}
|
||||
|
||||
this->finalize_first_layer_convex_hull();
|
||||
this->set_done(psBrim);
|
||||
this->set_done(psSkirtBrim);
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "Slicing process finished." << log_memory_info();
|
||||
}
|
||||
@ -966,6 +970,10 @@ void Print::_make_skirt()
|
||||
// Include the wipe tower.
|
||||
append(points, this->first_layer_wipe_tower_corners());
|
||||
|
||||
// Unless draft shield is enabled, include all brims as well.
|
||||
if (config().draft_shield == dsDisabled)
|
||||
append(points, m_first_layer_convex_hull.points);
|
||||
|
||||
if (points.size() < 3)
|
||||
// At least three points required for a convex hull.
|
||||
return;
|
||||
|
@ -45,11 +45,10 @@ enum PrintStep {
|
||||
// psToolOrdering is a synonym to psWipeTower, as the Wipe Tower calculates and modifies the ToolOrdering,
|
||||
// while if printing without the Wipe Tower, the ToolOrdering is calculated as well.
|
||||
psToolOrdering = psWipeTower,
|
||||
psSkirt,
|
||||
psBrim,
|
||||
psSkirtBrim,
|
||||
// Last step before G-code export, after this step is finished, the initial extrusion path preview
|
||||
// should be refreshed.
|
||||
psSlicingFinished = psBrim,
|
||||
psSlicingFinished = psSkirtBrim,
|
||||
psGCodeExport,
|
||||
psCount,
|
||||
};
|
||||
@ -326,12 +325,12 @@ public:
|
||||
void slice();
|
||||
|
||||
// Helpers to slice support enforcer / blocker meshes by the support generator.
|
||||
std::vector<ExPolygons> slice_support_volumes(const ModelVolumeType model_volume_type) const;
|
||||
std::vector<ExPolygons> slice_support_blockers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_BLOCKER); }
|
||||
std::vector<ExPolygons> slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); }
|
||||
std::vector<Polygons> slice_support_volumes(const ModelVolumeType model_volume_type) const;
|
||||
std::vector<Polygons> slice_support_blockers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_BLOCKER); }
|
||||
std::vector<Polygons> slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); }
|
||||
|
||||
// Helpers to project custom facets on slices
|
||||
void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector<ExPolygons>& expolys) const;
|
||||
void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector<Polygons>& expolys) const;
|
||||
|
||||
private:
|
||||
// to be called from Print only.
|
||||
|
@ -1240,7 +1240,7 @@ Print::ApplyStatus Print::apply(const Model &model, DynamicPrintConfig new_full_
|
||||
deleted_objects = true;
|
||||
}
|
||||
if (new_objects || deleted_objects)
|
||||
update_apply_status(this->invalidate_steps({ psSkirt, psBrim, psWipeTower, psGCodeExport }));
|
||||
update_apply_status(this->invalidate_steps({ psSkirtBrim, psWipeTower, psGCodeExport }));
|
||||
if (new_objects)
|
||||
update_apply_status(false);
|
||||
print_regions_reshuffled = true;
|
||||
|
@ -173,6 +173,13 @@ static const t_config_enum_values s_keys_map_BrimType = {
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(BrimType)
|
||||
|
||||
static const t_config_enum_values s_keys_map_DraftShield = {
|
||||
{ "disabled", dsDisabled },
|
||||
{ "limited", dsLimited },
|
||||
{ "enabled", dsEnabled }
|
||||
};
|
||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(DraftShield)
|
||||
|
||||
static const t_config_enum_values s_keys_map_ForwardCompatibilitySubstitutionRule = {
|
||||
{ "disable", ForwardCompatibilitySubstitutionRule::Disable },
|
||||
{ "enable", ForwardCompatibilitySubstitutionRule::Enable },
|
||||
@ -455,6 +462,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->tooltip = L("Horizontal width of the brim that will be printed around each object on the first layer.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->max = 200;
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionFloat(0));
|
||||
|
||||
@ -2147,27 +2155,34 @@ void PrintConfigDef::init_fff_params()
|
||||
#endif
|
||||
|
||||
def = this->add("skirt_distance", coFloat);
|
||||
def->label = L("Distance from object");
|
||||
def->tooltip = L("Distance between skirt and object(s). Set this to zero to attach the skirt "
|
||||
"to the object(s) and get a brim for better adhesion.");
|
||||
def->label = L("Distance from brim/object");
|
||||
def->tooltip = L("Distance between skirt and brim (when draft shield is not used) or objects.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->set_default_value(new ConfigOptionFloat(6));
|
||||
|
||||
def = this->add("skirt_height", coInt);
|
||||
def->label = L("Skirt height");
|
||||
def->tooltip = L("Height of skirt expressed in layers. Set this to a tall value to use skirt "
|
||||
"as a shield against drafts.");
|
||||
def->tooltip = L("Height of skirt expressed in layers.");
|
||||
def->sidetext = L("layers");
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionInt(1));
|
||||
|
||||
def = this->add("draft_shield", coBool);
|
||||
def = this->add("draft_shield", coEnum);
|
||||
def->label = L("Draft shield");
|
||||
def->tooltip = L("If enabled, the skirt will be as tall as a highest printed object. "
|
||||
def->tooltip = L("With draft shield active, the skirt will be printed skirt_distance from the object, possibly intersecting brim.\n"
|
||||
"Enabled = skirt is as tall as the highest printed object.\n"
|
||||
"Limited = skirt is as tall as specified by skirt_height.\n"
|
||||
"This is useful to protect an ABS or ASA print from warping and detaching from print bed due to wind draft.");
|
||||
def->enum_keys_map = &ConfigOptionEnum<DraftShield>::get_enum_values();
|
||||
def->enum_values.push_back("disabled");
|
||||
def->enum_values.push_back("limited");
|
||||
def->enum_values.push_back("enabled");
|
||||
def->enum_labels.push_back(L("Disabled"));
|
||||
def->enum_labels.push_back(L("Limited"));
|
||||
def->enum_labels.push_back(L("Enabled"));
|
||||
def->mode = comAdvanced;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
def->set_default_value(new ConfigOptionEnum<DraftShield>(dsDisabled));
|
||||
|
||||
def = this->add("skirts", coInt);
|
||||
def->label = L("Loops (minimum)");
|
||||
@ -3652,9 +3667,12 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va
|
||||
value = "rectilinear";
|
||||
} else if (opt_key == "skirt_height" && value == "-1") {
|
||||
// PrusaSlicer no more accepts skirt_height == -1 to print a draft shield to the top of the highest object.
|
||||
// A new "draft_shield" boolean config value is used instead.
|
||||
// A new "draft_shield" enum config value is used instead.
|
||||
opt_key = "draft_shield";
|
||||
value = "1";
|
||||
value = "enabled";
|
||||
} else if (opt_key == "draft_shield" && (value == "1" || value == "0")) {
|
||||
// draft_shield used to be a bool, it was turned into an enum in PrusaSlicer 2.4.0.
|
||||
value = value == "1" ? "enabled" : "disabled";
|
||||
} else if (opt_key == "octoprint_host") {
|
||||
opt_key = "print_host";
|
||||
} else if (opt_key == "octoprint_cafile") {
|
||||
|
@ -121,6 +121,10 @@ enum BrimType {
|
||||
btOuterAndInner,
|
||||
};
|
||||
|
||||
enum DraftShield {
|
||||
dsDisabled, dsLimited, dsEnabled
|
||||
};
|
||||
|
||||
#define CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(NAME) \
|
||||
template<> const t_config_enum_names& ConfigOptionEnum<NAME>::get_enum_names(); \
|
||||
template<> const t_config_enum_values& ConfigOptionEnum<NAME>::get_enum_values();
|
||||
@ -141,6 +145,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SeamPosition)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLADisplayOrientation)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLAPillarConnectionMode)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(DraftShield)
|
||||
CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ForwardCompatibilitySubstitutionRule)
|
||||
|
||||
#undef CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS
|
||||
@ -689,6 +694,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionBools, cooling))
|
||||
((ConfigOptionFloat, default_acceleration))
|
||||
((ConfigOptionInts, disable_fan_first_layers))
|
||||
((ConfigOptionEnum<DraftShield>, draft_shield))
|
||||
((ConfigOptionFloat, duplicate_distance))
|
||||
((ConfigOptionFloat, extruder_clearance_height))
|
||||
((ConfigOptionFloat, extruder_clearance_radius))
|
||||
@ -728,7 +734,6 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
||||
((ConfigOptionBools, retract_layer_change))
|
||||
((ConfigOptionFloat, skirt_distance))
|
||||
((ConfigOptionInt, skirt_height))
|
||||
((ConfigOptionBool, draft_shield))
|
||||
((ConfigOptionInt, skirts))
|
||||
((ConfigOptionInts, slowdown_below_layer_time))
|
||||
((ConfigOptionBool, spiral_vase))
|
||||
|
@ -91,7 +91,7 @@ PrintBase::ApplyStatus PrintObject::set_instances(PrintInstances &&instances)
|
||||
[](const PrintInstance& lhs, const PrintInstance& rhs) { return lhs.model_instance == rhs.model_instance && lhs.shift == rhs.shift; });
|
||||
if (! equal) {
|
||||
status = PrintBase::APPLY_STATUS_CHANGED;
|
||||
if (m_print->invalidate_steps({ psSkirt, psBrim, psGCodeExport }) ||
|
||||
if (m_print->invalidate_steps({ psSkirtBrim, psGCodeExport }) ||
|
||||
(! equal_length && m_print->invalidate_step(psWipeTower)))
|
||||
status = PrintBase::APPLY_STATUS_INVALIDATED;
|
||||
m_instances = std::move(instances);
|
||||
@ -402,10 +402,8 @@ void PrintObject::generate_support_material()
|
||||
// Notify the user in that case.
|
||||
if (! this->has_support()) {
|
||||
for (const ModelVolume* mv : this->model_object()->volumes) {
|
||||
bool has_enforcers = mv->is_support_enforcer()
|
||||
|| (mv->is_model_part()
|
||||
&& ! mv->supported_facets.empty()
|
||||
&& ! mv->supported_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER).indices.empty());
|
||||
bool has_enforcers = mv->is_support_enforcer() ||
|
||||
(mv->is_model_part() && mv->supported_facets.has_facets(*mv, EnforcerBlockerType::ENFORCER));
|
||||
if (has_enforcers) {
|
||||
this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
|
||||
L("An object has custom support enforcers which will not be used "
|
||||
@ -676,18 +674,18 @@ bool PrintObject::invalidate_step(PrintObjectStep step)
|
||||
// propagate to dependent steps
|
||||
if (step == posPerimeters) {
|
||||
invalidated |= this->invalidate_steps({ posPrepareInfill, posInfill, posIroning });
|
||||
invalidated |= m_print->invalidate_steps({ psSkirt, psBrim });
|
||||
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
||||
} else if (step == posPrepareInfill) {
|
||||
invalidated |= this->invalidate_steps({ posInfill, posIroning });
|
||||
} else if (step == posInfill) {
|
||||
invalidated |= this->invalidate_steps({ posIroning });
|
||||
invalidated |= m_print->invalidate_steps({ psSkirt, psBrim });
|
||||
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
||||
} else if (step == posSlice) {
|
||||
invalidated |= this->invalidate_steps({ posPerimeters, posPrepareInfill, posInfill, posIroning, posSupportMaterial });
|
||||
invalidated |= m_print->invalidate_steps({ psSkirt, psBrim });
|
||||
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
||||
m_slicing_params.valid = false;
|
||||
} else if (step == posSupportMaterial) {
|
||||
invalidated |= m_print->invalidate_steps({ psSkirt, psBrim });
|
||||
invalidated |= m_print->invalidate_steps({ psSkirtBrim });
|
||||
m_slicing_params.valid = false;
|
||||
}
|
||||
|
||||
@ -2101,206 +2099,206 @@ void PrintObject::_generate_support_material()
|
||||
support_material.generate(*this);
|
||||
}
|
||||
|
||||
|
||||
void PrintObject::project_and_append_custom_facets(
|
||||
bool seam, EnforcerBlockerType type, std::vector<ExPolygons>& expolys) const
|
||||
static void project_triangles_to_slabs(ConstLayerPtrsAdaptor layers, const indexed_triangle_set &custom_facets, const Transform3f &tr, bool seam, std::vector<Polygons> &out)
|
||||
{
|
||||
for (const ModelVolume* mv : this->model_object()->volumes) {
|
||||
const indexed_triangle_set custom_facets = seam
|
||||
? mv->seam_facets.get_facets(*mv, type)
|
||||
: mv->supported_facets.get_facets(*mv, type);
|
||||
if (! mv->is_model_part() || custom_facets.indices.empty())
|
||||
if (custom_facets.indices.empty())
|
||||
return;
|
||||
|
||||
const float tr_det_sign = (tr.matrix().determinant() > 0. ? 1.f : -1.f);
|
||||
|
||||
// The projection will be at most a pentagon. Let's minimize heap
|
||||
// reallocations by saving in in the following struct.
|
||||
// Points are used so that scaling can be done in parallel
|
||||
// and they can be moved from to create an ExPolygon later.
|
||||
struct LightPolygon {
|
||||
LightPolygon() { pts.reserve(5); }
|
||||
LightPolygon(const std::array<Vec2f, 3>& tri) {
|
||||
pts.reserve(3);
|
||||
pts.emplace_back(scaled<coord_t>(tri.front()));
|
||||
pts.emplace_back(scaled<coord_t>(tri[1]));
|
||||
pts.emplace_back(scaled<coord_t>(tri.back()));
|
||||
}
|
||||
|
||||
Points pts;
|
||||
|
||||
void add(const Vec2f& pt) {
|
||||
pts.emplace_back(scaled<coord_t>(pt));
|
||||
assert(pts.size() <= 5);
|
||||
}
|
||||
};
|
||||
|
||||
// Structure to collect projected polygons. One element for each triangle.
|
||||
// Saves vector of polygons and layer_id of the first one.
|
||||
struct TriangleProjections {
|
||||
size_t first_layer_id;
|
||||
std::vector<LightPolygon> polygons;
|
||||
};
|
||||
|
||||
// Vector to collect resulting projections from each triangle.
|
||||
std::vector<TriangleProjections> projections_of_triangles(custom_facets.indices.size());
|
||||
|
||||
// Iterate over all triangles.
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, custom_facets.indices.size()),
|
||||
[&custom_facets, &tr, tr_det_sign, seam, layers, &projections_of_triangles](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t idx = range.begin(); idx < range.end(); ++ idx) {
|
||||
|
||||
std::array<Vec3f, 3> facet;
|
||||
|
||||
// Transform the triangle into worlds coords.
|
||||
for (int i=0; i<3; ++i)
|
||||
facet[i] = tr * custom_facets.vertices[custom_facets.indices[idx](i)];
|
||||
|
||||
// Ignore triangles with upward-pointing normal. Don't forget about mirroring.
|
||||
float z_comp = (facet[1]-facet[0]).cross(facet[2]-facet[0]).z();
|
||||
if (! seam && tr_det_sign * z_comp > 0.)
|
||||
continue;
|
||||
|
||||
const Transform3f& tr1 = mv->get_matrix().cast<float>();
|
||||
const Transform3f& tr2 = this->trafo().cast<float>();
|
||||
const Transform3f tr = tr2 * tr1;
|
||||
const float tr_det_sign = (tr.matrix().determinant() > 0. ? 1.f : -1.f);
|
||||
const Vec2f center = unscaled<float>(this->center_offset());
|
||||
ConstLayerPtrsAdaptor layers = this->layers();
|
||||
// The algorithm does not process vertical triangles, but it should for seam.
|
||||
// In that case, tilt the triangle a bit so the projection does not degenerate.
|
||||
if (seam && z_comp == 0.f)
|
||||
facet[0].x() += float(EPSILON);
|
||||
|
||||
// The projection will be at most a pentagon. Let's minimize heap
|
||||
// reallocations by saving in in the following struct.
|
||||
// Points are used so that scaling can be done in parallel
|
||||
// and they can be moved from to create an ExPolygon later.
|
||||
struct LightPolygon {
|
||||
LightPolygon() { pts.reserve(5); }
|
||||
LightPolygon(const std::array<Vec2f, 3>& tri) {
|
||||
pts.reserve(3);
|
||||
pts.emplace_back(scaled<coord_t>(tri.front()));
|
||||
pts.emplace_back(scaled<coord_t>(tri[1]));
|
||||
pts.emplace_back(scaled<coord_t>(tri.back()));
|
||||
}
|
||||
// Sort the three vertices according to z-coordinate.
|
||||
std::sort(facet.begin(), facet.end(),
|
||||
[](const Vec3f& pt1, const Vec3f&pt2) {
|
||||
return pt1.z() < pt2.z();
|
||||
});
|
||||
|
||||
Points pts;
|
||||
std::array<Vec2f, 3> trianglef;
|
||||
for (int i=0; i<3; ++i)
|
||||
trianglef[i] = to_2d(facet[i]);
|
||||
|
||||
void add(const Vec2f& pt) {
|
||||
pts.emplace_back(scaled<coord_t>(pt));
|
||||
assert(pts.size() <= 5);
|
||||
}
|
||||
};
|
||||
|
||||
// Structure to collect projected polygons. One element for each triangle.
|
||||
// Saves vector of polygons and layer_id of the first one.
|
||||
struct TriangleProjections {
|
||||
size_t first_layer_id;
|
||||
std::vector<LightPolygon> polygons;
|
||||
};
|
||||
|
||||
// Vector to collect resulting projections from each triangle.
|
||||
std::vector<TriangleProjections> projections_of_triangles(custom_facets.indices.size());
|
||||
|
||||
// Iterate over all triangles.
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, custom_facets.indices.size()),
|
||||
[center, &custom_facets, &tr, tr_det_sign, seam, layers, &projections_of_triangles](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t idx = range.begin(); idx < range.end(); ++ idx) {
|
||||
|
||||
std::array<Vec3f, 3> facet;
|
||||
|
||||
// Transform the triangle into worlds coords.
|
||||
for (int i=0; i<3; ++i)
|
||||
facet[i] = tr * custom_facets.vertices[custom_facets.indices[idx](i)];
|
||||
|
||||
// Ignore triangles with upward-pointing normal. Don't forget about mirroring.
|
||||
float z_comp = (facet[1]-facet[0]).cross(facet[2]-facet[0]).z();
|
||||
if (! seam && tr_det_sign * z_comp > 0.)
|
||||
continue;
|
||||
|
||||
// The algorithm does not process vertical triangles, but it should for seam.
|
||||
// In that case, tilt the triangle a bit so the projection does not degenerate.
|
||||
if (seam && z_comp == 0.f)
|
||||
facet[0].x() += float(EPSILON);
|
||||
|
||||
// Sort the three vertices according to z-coordinate.
|
||||
std::sort(facet.begin(), facet.end(),
|
||||
[](const Vec3f& pt1, const Vec3f&pt2) {
|
||||
return pt1.z() < pt2.z();
|
||||
// Find lowest slice not below the triangle.
|
||||
auto it = std::lower_bound(layers.begin(), layers.end(), facet[0].z()+EPSILON,
|
||||
[](const Layer* l1, float z) {
|
||||
return l1->slice_z < z;
|
||||
});
|
||||
|
||||
std::array<Vec2f, 3> trianglef;
|
||||
for (int i=0; i<3; ++i)
|
||||
trianglef[i] = to_2d(facet[i]) - center;
|
||||
// Count how many projections will be generated for this triangle
|
||||
// and allocate respective amount in projections_of_triangles.
|
||||
size_t first_layer_id = projections_of_triangles[idx].first_layer_id = it - layers.begin();
|
||||
size_t last_layer_id = first_layer_id;
|
||||
// The cast in the condition below is important. The comparison must
|
||||
// be an exact opposite of the one lower in the code where
|
||||
// the polygons are appended. And that one is on floats.
|
||||
while (last_layer_id + 1 < layers.size()
|
||||
&& float(layers[last_layer_id]->slice_z) <= facet[2].z())
|
||||
++last_layer_id;
|
||||
|
||||
// Find lowest slice not below the triangle.
|
||||
auto it = std::lower_bound(layers.begin(), layers.end(), facet[0].z()+EPSILON,
|
||||
[](const Layer* l1, float z) {
|
||||
return l1->slice_z < z;
|
||||
});
|
||||
|
||||
// Count how many projections will be generated for this triangle
|
||||
// and allocate respective amount in projections_of_triangles.
|
||||
size_t first_layer_id = projections_of_triangles[idx].first_layer_id = it - layers.begin();
|
||||
size_t last_layer_id = first_layer_id;
|
||||
// The cast in the condition below is important. The comparison must
|
||||
// be an exact opposite of the one lower in the code where
|
||||
// the polygons are appended. And that one is on floats.
|
||||
while (last_layer_id + 1 < layers.size()
|
||||
&& float(layers[last_layer_id]->slice_z) <= facet[2].z())
|
||||
++last_layer_id;
|
||||
|
||||
if (first_layer_id == last_layer_id) {
|
||||
// The triangle fits just a single slab, just project it. This also avoids division by zero for horizontal triangles.
|
||||
float dz = facet[2].z() - facet[0].z();
|
||||
assert(dz >= 0);
|
||||
// The face is nearly horizontal and it crosses the slicing plane at first_layer_id - 1.
|
||||
// Rather add this face to both the planes.
|
||||
bool add_below = dz < float(2. * EPSILON) && first_layer_id > 0 && layers[first_layer_id - 1]->slice_z > facet[0].z() - EPSILON;
|
||||
projections_of_triangles[idx].polygons.reserve(add_below ? 2 : 1);
|
||||
if (first_layer_id == last_layer_id) {
|
||||
// The triangle fits just a single slab, just project it. This also avoids division by zero for horizontal triangles.
|
||||
float dz = facet[2].z() - facet[0].z();
|
||||
assert(dz >= 0);
|
||||
// The face is nearly horizontal and it crosses the slicing plane at first_layer_id - 1.
|
||||
// Rather add this face to both the planes.
|
||||
bool add_below = dz < float(2. * EPSILON) && first_layer_id > 0 && layers[first_layer_id - 1]->slice_z > facet[0].z() - EPSILON;
|
||||
projections_of_triangles[idx].polygons.reserve(add_below ? 2 : 1);
|
||||
projections_of_triangles[idx].polygons.emplace_back(trianglef);
|
||||
if (add_below) {
|
||||
-- projections_of_triangles[idx].first_layer_id;
|
||||
projections_of_triangles[idx].polygons.emplace_back(trianglef);
|
||||
if (add_below) {
|
||||
-- projections_of_triangles[idx].first_layer_id;
|
||||
projections_of_triangles[idx].polygons.emplace_back(trianglef);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
projections_of_triangles[idx].polygons.resize(last_layer_id - first_layer_id + 1);
|
||||
|
||||
// Calculate how to move points on triangle sides per unit z increment.
|
||||
Vec2f ta(trianglef[1] - trianglef[0]);
|
||||
Vec2f tb(trianglef[2] - trianglef[0]);
|
||||
ta *= 1.f/(facet[1].z() - facet[0].z());
|
||||
tb *= 1.f/(facet[2].z() - facet[0].z());
|
||||
|
||||
// Projection on current slice will be build directly in place.
|
||||
LightPolygon* proj = &projections_of_triangles[idx].polygons[0];
|
||||
proj->add(trianglef[0]);
|
||||
|
||||
bool passed_first = false;
|
||||
bool stop = false;
|
||||
|
||||
// Project a sub-polygon on all slices intersecting the triangle.
|
||||
while (it != layers.end()) {
|
||||
const float z = float((*it)->slice_z);
|
||||
|
||||
// Projections of triangle sides intersections with slices.
|
||||
// a moves along one side, b tracks the other.
|
||||
Vec2f a;
|
||||
Vec2f b;
|
||||
|
||||
// If the middle vertex was already passed, append the vertex
|
||||
// and use ta for tracking the remaining side.
|
||||
if (z > facet[1].z() && ! passed_first) {
|
||||
proj->add(trianglef[1]);
|
||||
ta = trianglef[2]-trianglef[1];
|
||||
ta *= 1.f/(facet[2].z() - facet[1].z());
|
||||
passed_first = true;
|
||||
}
|
||||
|
||||
// This slice is above the triangle already.
|
||||
if (z > facet[2].z() || it+1 == layers.end()) {
|
||||
proj->add(trianglef[2]);
|
||||
stop = true;
|
||||
}
|
||||
else {
|
||||
// Move a, b along the side it currently tracks to get
|
||||
// projected intersection with current slice.
|
||||
a = passed_first ? (trianglef[1]+ta*(z-facet[1].z()))
|
||||
: (trianglef[0]+ta*(z-facet[0].z()));
|
||||
b = trianglef[0]+tb*(z-facet[0].z());
|
||||
proj->add(a);
|
||||
proj->add(b);
|
||||
}
|
||||
|
||||
if (stop)
|
||||
break;
|
||||
|
||||
// Advance to the next layer.
|
||||
++it;
|
||||
++proj;
|
||||
assert(proj <= &projections_of_triangles[idx].polygons.back() );
|
||||
|
||||
// a, b are first two points of the polygon for the next layer.
|
||||
proj->add(b);
|
||||
proj->add(a);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}); // end of parallel_for
|
||||
|
||||
// Make sure that the output vector can be used.
|
||||
expolys.resize(layers.size());
|
||||
projections_of_triangles[idx].polygons.resize(last_layer_id - first_layer_id + 1);
|
||||
|
||||
// Now append the collected polygons to respective layers.
|
||||
for (auto& trg : projections_of_triangles) {
|
||||
int layer_id = int(trg.first_layer_id);
|
||||
for (LightPolygon &poly : trg.polygons) {
|
||||
if (layer_id >= int(expolys.size()))
|
||||
break; // part of triangle could be projected above top layer
|
||||
assert(! poly.pts.empty());
|
||||
// The resulting triangles are fed to the Clipper library, which seem to handle flipped triangles well.
|
||||
// Calculate how to move points on triangle sides per unit z increment.
|
||||
Vec2f ta(trianglef[1] - trianglef[0]);
|
||||
Vec2f tb(trianglef[2] - trianglef[0]);
|
||||
ta *= 1.f/(facet[1].z() - facet[0].z());
|
||||
tb *= 1.f/(facet[2].z() - facet[0].z());
|
||||
|
||||
// Projection on current slice will be built directly in place.
|
||||
LightPolygon* proj = &projections_of_triangles[idx].polygons[0];
|
||||
proj->add(trianglef[0]);
|
||||
|
||||
bool passed_first = false;
|
||||
bool stop = false;
|
||||
|
||||
// Project a sub-polygon on all slices intersecting the triangle.
|
||||
while (it != layers.end()) {
|
||||
const float z = float((*it)->slice_z);
|
||||
|
||||
// Projections of triangle sides intersections with slices.
|
||||
// a moves along one side, b tracks the other.
|
||||
Vec2f a;
|
||||
Vec2f b;
|
||||
|
||||
// If the middle vertex was already passed, append the vertex
|
||||
// and use ta for tracking the remaining side.
|
||||
if (z > facet[1].z() && ! passed_first) {
|
||||
proj->add(trianglef[1]);
|
||||
ta = trianglef[2]-trianglef[1];
|
||||
ta *= 1.f/(facet[2].z() - facet[1].z());
|
||||
passed_first = true;
|
||||
}
|
||||
|
||||
// This slice is above the triangle already.
|
||||
if (z > facet[2].z() || it+1 == layers.end()) {
|
||||
proj->add(trianglef[2]);
|
||||
stop = true;
|
||||
}
|
||||
else {
|
||||
// Move a, b along the side it currently tracks to get
|
||||
// projected intersection with current slice.
|
||||
a = passed_first ? (trianglef[1]+ta*(z-facet[1].z()))
|
||||
: (trianglef[0]+ta*(z-facet[0].z()));
|
||||
b = trianglef[0]+tb*(z-facet[0].z());
|
||||
proj->add(a);
|
||||
proj->add(b);
|
||||
}
|
||||
|
||||
if (stop)
|
||||
break;
|
||||
|
||||
// Advance to the next layer.
|
||||
++it;
|
||||
++proj;
|
||||
assert(proj <= &projections_of_triangles[idx].polygons.back() );
|
||||
|
||||
// a, b are first two points of the polygon for the next layer.
|
||||
proj->add(b);
|
||||
proj->add(a);
|
||||
}
|
||||
}
|
||||
}); // end of parallel_for
|
||||
|
||||
// Make sure that the output vector can be used.
|
||||
out.resize(layers.size());
|
||||
|
||||
// Now append the collected polygons to respective layers.
|
||||
for (auto& trg : projections_of_triangles) {
|
||||
int layer_id = int(trg.first_layer_id);
|
||||
for (LightPolygon &poly : trg.polygons) {
|
||||
if (layer_id >= int(out.size()))
|
||||
break; // part of triangle could be projected above top layer
|
||||
assert(! poly.pts.empty());
|
||||
// The resulting triangles are fed to the Clipper library, which seem to handle flipped triangles well.
|
||||
// if (cross2(Vec2d((poly.pts[1] - poly.pts[0]).cast<double>()), Vec2d((poly.pts[2] - poly.pts[1]).cast<double>())) < 0)
|
||||
// std::swap(poly.pts.front(), poly.pts.back());
|
||||
|
||||
expolys[layer_id].emplace_back(std::move(poly.pts));
|
||||
++layer_id;
|
||||
}
|
||||
|
||||
out[layer_id].emplace_back(std::move(poly.pts));
|
||||
++layer_id;
|
||||
}
|
||||
|
||||
} // loop over ModelVolumes
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void PrintObject::project_and_append_custom_facets(
|
||||
bool seam, EnforcerBlockerType type, std::vector<Polygons>& out) const
|
||||
{
|
||||
for (const ModelVolume* mv : this->model_object()->volumes)
|
||||
if (mv->is_model_part()) {
|
||||
const indexed_triangle_set custom_facets = seam
|
||||
? mv->seam_facets.get_facets_strict(*mv, type)
|
||||
: mv->supported_facets.get_facets_strict(*mv, type);
|
||||
if (! custom_facets.indices.empty())
|
||||
project_triangles_to_slabs(this->layers(), custom_facets,
|
||||
(Eigen::Translation3d(to_3d(unscaled<double>(this->center_offset()), 0.)) * this->trafo() * mv->get_matrix()).cast<float>(),
|
||||
seam, out);
|
||||
}
|
||||
}
|
||||
|
||||
const Layer* PrintObject::get_layer_at_printz(coordf_t print_z) const {
|
||||
auto it = Slic3r::lower_bound_by_predicate(m_layers.begin(), m_layers.end(), [print_z](const Layer *layer) { return layer->print_z < print_z; });
|
||||
|
@ -39,16 +39,6 @@ LayerPtrs new_layers(
|
||||
return out;
|
||||
}
|
||||
|
||||
template<typename LayerContainer>
|
||||
static inline std::vector<float> zs_from_layers(const LayerContainer &layers)
|
||||
{
|
||||
std::vector<float> zs;
|
||||
zs.reserve(layers.size());
|
||||
for (const Layer *l : layers)
|
||||
zs.emplace_back((float)l->slice_z);
|
||||
return zs;
|
||||
}
|
||||
|
||||
//FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it.
|
||||
// This function will go away once we get rid of admesh from ModelVolume.
|
||||
static indexed_triangle_set get_mesh_its_fix_mesh_connectivity(TriangleMesh mesh)
|
||||
@ -604,14 +594,18 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
|
||||
if (! layer_split)
|
||||
continue;
|
||||
// Split LayerRegions by by_extruder regions.
|
||||
// layer_range.painted_regions are sorted by extruder ID and parent PrintObject region ID.
|
||||
auto it_painted_region = layer_range.painted_regions.begin();
|
||||
for (int region_id = 0; region_id < int(layer->region_count()); ++ region_id)
|
||||
if (LayerRegion &layerm = *layer->get_region(region_id); ! layerm.slices.surfaces.empty()) {
|
||||
assert(layerm.region().print_object_region_id() == region_id);
|
||||
const BoundingBox bbox = get_extents(layerm.slices.surfaces);
|
||||
assert(it_painted_region < layer_range.painted_regions.end());
|
||||
// Find the first it_painted_region which overrides this region.
|
||||
for (; layer_range.volume_regions[it_painted_region->parent].region->print_object_region_id() < region_id; ++ it_painted_region)
|
||||
assert(it_painted_region < layer_range.painted_regions.end());
|
||||
assert(&layerm.region() == it_painted_region->region && layerm.region().print_object_region_id() == region_id);
|
||||
assert(it_painted_region != layer_range.painted_regions.end());
|
||||
assert(it_painted_region != layer_range.painted_regions.end());
|
||||
assert(layer_range.volume_regions[it_painted_region->parent].region == &layerm.region());
|
||||
// 1-based extruder ID
|
||||
bool self_trimmed = false;
|
||||
int self_extruder_id = -1;
|
||||
@ -619,7 +613,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
|
||||
if (ByExtruder &segmented = by_extruder[extruder_id - 1]; segmented.bbox.defined && bbox.overlap(segmented.bbox)) {
|
||||
// Find the target region.
|
||||
for (; int(it_painted_region->extruder_id) < extruder_id; ++ it_painted_region)
|
||||
assert(it_painted_region < layer_range.painted_regions.end());
|
||||
assert(it_painted_region != layer_range.painted_regions.end());
|
||||
assert(layer_range.volume_regions[it_painted_region->parent].region == &layerm.region() && int(it_painted_region->extruder_id) == extruder_id);
|
||||
//FIXME Don't trim by self, it is not reliable.
|
||||
if (&layerm.region() == it_painted_region->region) {
|
||||
@ -669,7 +663,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
|
||||
ByRegion &src = by_region[region_id];
|
||||
if (src.needs_merge)
|
||||
// Multiple regions were merged into one.
|
||||
src.expolygons = offset2_ex(src.expolygons, float(scale_(EPSILON)), - float(scale_(EPSILON)));
|
||||
src.expolygons = offset2_ex(src.expolygons, float(scale_(10 * EPSILON)), - float(scale_(10 * EPSILON)));
|
||||
layer->get_region(region_id)->slices.set(std::move(src.expolygons), stInternal);
|
||||
}
|
||||
}
|
||||
@ -701,7 +695,7 @@ void PrintObject::slice_volumes()
|
||||
|
||||
std::vector<float> slice_zs = zs_from_layers(m_layers);
|
||||
Transform3d trafo = this->trafo();
|
||||
trafo.pretranslate(Vec3d(- unscale<float>(m_center_offset.x()), - unscale<float>(m_center_offset.y()), 0));
|
||||
trafo.pretranslate(Vec3d(- unscale<double>(m_center_offset.x()), - unscale<double>(m_center_offset.y()), 0));
|
||||
std::vector<std::vector<ExPolygons>> region_slices = slices_to_regions(this->model_object()->volumes, *m_shared_regions, slice_zs,
|
||||
slice_volumes_inner(
|
||||
print->config(), this->config(), trafo,
|
||||
@ -812,12 +806,12 @@ void PrintObject::slice_volumes()
|
||||
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - end";
|
||||
}
|
||||
|
||||
std::vector<ExPolygons> PrintObject::slice_support_volumes(const ModelVolumeType model_volume_type) const
|
||||
std::vector<Polygons> PrintObject::slice_support_volumes(const ModelVolumeType model_volume_type) const
|
||||
{
|
||||
auto it_volume = this->model_object()->volumes.begin();
|
||||
auto it_volume_end = this->model_object()->volumes.end();
|
||||
for (; it_volume != it_volume_end && (*it_volume)->type() != model_volume_type; ++ it_volume) ;
|
||||
std::vector<ExPolygons> slices;
|
||||
std::vector<Polygons> slices;
|
||||
if (it_volume != it_volume_end) {
|
||||
// Found at least a single support volume of model_volume_type.
|
||||
std::vector<float> zs = zs_from_layers(this->layers());
|
||||
@ -831,16 +825,18 @@ std::vector<ExPolygons> PrintObject::slice_support_volumes(const ModelVolumeType
|
||||
for (; it_volume != it_volume_end; ++ it_volume)
|
||||
if ((*it_volume)->type() == model_volume_type) {
|
||||
std::vector<ExPolygons> slices2 = slice_volume(*(*it_volume), zs, params, throw_on_cancel_callback);
|
||||
if (slices.empty())
|
||||
slices = std::move(slices2);
|
||||
else if (! slices2.empty()) {
|
||||
if (slices.empty()) {
|
||||
slices.reserve(slices2.size());
|
||||
for (ExPolygons &src : slices2)
|
||||
slices.emplace_back(to_polygons(std::move(src)));
|
||||
} else if (!slices2.empty()) {
|
||||
if (merge_layers.empty())
|
||||
merge_layers.assign(zs.size(), false);
|
||||
for (size_t i = 0; i < zs.size(); ++ i) {
|
||||
if (slices[i].empty())
|
||||
slices[i] = std::move(slices2[i]);
|
||||
slices[i] = to_polygons(std::move(slices2[i]));
|
||||
else if (! slices2[i].empty()) {
|
||||
append(slices[i], std::move(slices2[i]));
|
||||
append(slices[i], to_polygons(std::move(slices2[i])));
|
||||
merge_layers[i] = true;
|
||||
merge = true;
|
||||
}
|
||||
@ -848,7 +844,7 @@ std::vector<ExPolygons> PrintObject::slice_support_volumes(const ModelVolumeType
|
||||
}
|
||||
}
|
||||
if (merge) {
|
||||
std::vector<ExPolygons*> to_merge;
|
||||
std::vector<Polygons*> to_merge;
|
||||
to_merge.reserve(zs.size());
|
||||
for (size_t i = 0; i < zs.size(); ++ i)
|
||||
if (merge_layers[i])
|
||||
@ -857,7 +853,7 @@ std::vector<ExPolygons> PrintObject::slice_support_volumes(const ModelVolumeType
|
||||
tbb::blocked_range<size_t>(0, to_merge.size()),
|
||||
[&to_merge](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t i = range.begin(); i < range.end(); ++ i)
|
||||
*to_merge[i] = union_ex(*to_merge[i]);
|
||||
*to_merge[i] = union_(*to_merge[i]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -391,11 +391,10 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
|
||||
holept.normal += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)};
|
||||
holept.normal.normalize();
|
||||
holept.pos += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)};
|
||||
TriangleMesh m{holept.to_mesh()};
|
||||
m.require_shared_vertices();
|
||||
indexed_triangle_set m = holept.to_mesh();
|
||||
|
||||
part_to_drill.indices.clear();
|
||||
auto bb = m.bounding_box();
|
||||
auto bb = bounding_box(m);
|
||||
Eigen::AlignedBox<float, 3> ebb{bb.min.cast<float>(),
|
||||
bb.max.cast<float>()};
|
||||
|
||||
@ -429,19 +428,23 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
|
||||
auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh);
|
||||
|
||||
if (!MeshBoolean::cgal::does_bound_a_volume(*hollowed_mesh_cgal)) {
|
||||
po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL,
|
||||
po.active_step_add_warning(
|
||||
PrintStateBase::WarningLevel::NON_CRITICAL,
|
||||
L("Mesh to be hollowed is not suitable for hollowing (does not "
|
||||
"bound a volume)."));
|
||||
}
|
||||
|
||||
if (!MeshBoolean::cgal::does_bound_a_volume(*holes_mesh_cgal)) {
|
||||
po.active_step_add_warning(PrintStateBase::WarningLevel::NON_CRITICAL,
|
||||
if (!MeshBoolean::cgal::empty(*holes_mesh_cgal)
|
||||
&& !MeshBoolean::cgal::does_bound_a_volume(*holes_mesh_cgal)) {
|
||||
po.active_step_add_warning(
|
||||
PrintStateBase::WarningLevel::NON_CRITICAL,
|
||||
L("Unable to drill the current configuration of holes into the "
|
||||
"model."));
|
||||
}
|
||||
|
||||
try {
|
||||
MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal);
|
||||
if (!MeshBoolean::cgal::empty(*holes_mesh_cgal))
|
||||
MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal);
|
||||
|
||||
hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal);
|
||||
mesh_view = hollowed_mesh;
|
||||
|
@ -1353,9 +1353,9 @@ struct SupportAnnotations
|
||||
object.project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers_layers);
|
||||
}
|
||||
|
||||
std::vector<ExPolygons> enforcers_layers;
|
||||
std::vector<ExPolygons> blockers_layers;
|
||||
const std::vector<Polygons>& buildplate_covered;
|
||||
std::vector<Polygons> enforcers_layers;
|
||||
std::vector<Polygons> blockers_layers;
|
||||
const std::vector<Polygons>& buildplate_covered;
|
||||
};
|
||||
|
||||
struct SlicesMarginCache
|
||||
|
@ -68,6 +68,8 @@
|
||||
#define DISABLE_ALLOW_NEGATIVE_Z_FOR_SLA (1 && ENABLE_ALLOW_NEGATIVE_Z)
|
||||
// Enable visualization of objects clearance for sequential prints
|
||||
#define ENABLE_SEQUENTIAL_LIMITS (1 && ENABLE_2_4_0_ALPHA0)
|
||||
// Enable delayed rendering of transparent volumes
|
||||
#define ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING (1 && ENABLE_2_4_0_ALPHA0)
|
||||
|
||||
|
||||
#endif // _prusaslicer_technologies_h_
|
||||
|
@ -728,7 +728,7 @@ static std::vector<EdgeToFace> create_edge_map(
|
||||
// Map from a face edge to a unique edge identifier or -1 if no neighbor exists.
|
||||
// Two neighbor faces share a unique edge identifier even if they are flipped.
|
||||
template<typename ThrowOnCancelCallback>
|
||||
static inline std::vector<Vec3i> create_face_neighbors_index_impl(const indexed_triangle_set &its, ThrowOnCancelCallback throw_on_cancel)
|
||||
static inline std::vector<Vec3i> its_face_edge_ids_impl(const indexed_triangle_set &its, ThrowOnCancelCallback throw_on_cancel)
|
||||
{
|
||||
std::vector<Vec3i> out(its.indices.size(), Vec3i(-1, -1, -1));
|
||||
|
||||
@ -778,14 +778,56 @@ static inline std::vector<Vec3i> create_face_neighbors_index_impl(const indexed_
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<Vec3i> create_face_neighbors_index(const indexed_triangle_set &its)
|
||||
std::vector<Vec3i> its_face_edge_ids(const indexed_triangle_set &its)
|
||||
{
|
||||
return create_face_neighbors_index_impl(its, [](){});
|
||||
return its_face_edge_ids_impl(its, [](){});
|
||||
}
|
||||
|
||||
std::vector<Vec3i> create_face_neighbors_index(const indexed_triangle_set &its, std::function<void()> throw_on_cancel_callback)
|
||||
std::vector<Vec3i> its_face_edge_ids(const indexed_triangle_set &its, std::function<void()> throw_on_cancel_callback)
|
||||
{
|
||||
return create_face_neighbors_index_impl(its, throw_on_cancel_callback);
|
||||
return its_face_edge_ids_impl(its, throw_on_cancel_callback);
|
||||
}
|
||||
|
||||
// Having the face neighbors available, assign unique edge IDs to face edges for chaining of polygons over slices.
|
||||
std::vector<Vec3i> its_face_edge_ids(const indexed_triangle_set &its, std::vector<Vec3i> &face_neighbors, bool assign_unbound_edges, int *num_edges)
|
||||
{
|
||||
// out elements are not initialized!
|
||||
std::vector<Vec3i> out(face_neighbors.size());
|
||||
int last_edge_id = 0;
|
||||
for (int i = 0; i < int(face_neighbors.size()); ++ i) {
|
||||
const stl_triangle_vertex_indices &triangle = its.indices[i];
|
||||
const Vec3i &neighbors = face_neighbors[i];
|
||||
for (int j = 0; j < 3; ++ j) {
|
||||
int n = neighbors[j];
|
||||
if (n > i) {
|
||||
const stl_triangle_vertex_indices &triangle2 = its.indices[n];
|
||||
int edge_id = last_edge_id ++;
|
||||
Vec2i edge = its_triangle_edge(triangle, j);
|
||||
// First find an edge with opposite orientation.
|
||||
std::swap(edge(0), edge(1));
|
||||
int k = its_triangle_edge_index(triangle2, edge);
|
||||
//FIXME is the following realistic? Could face_neighbors contain such faces?
|
||||
// And if it does, do we want to produce the same edge ID for those mutually incorrectly oriented edges?
|
||||
if (k == -1) {
|
||||
// Second find an edge with the same orientation (the neighbor triangle may be flipped).
|
||||
std::swap(edge(0), edge(1));
|
||||
k = its_triangle_edge_index(triangle2, edge);
|
||||
}
|
||||
assert(k >= 0);
|
||||
out[i](j) = edge_id;
|
||||
out[n](k) = edge_id;
|
||||
} else if (n == -1) {
|
||||
out[i](j) = assign_unbound_edges ? last_edge_id ++ : -1;
|
||||
} else {
|
||||
// Triangle shall never be neighbor of itself.
|
||||
assert(n < i);
|
||||
// Don't do anything, the neighbor will assign us an edge ID in later iterations.
|
||||
}
|
||||
}
|
||||
}
|
||||
if (num_edges)
|
||||
*num_edges = last_edge_id;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Merge duplicate vertices, return number of vertices removed.
|
||||
@ -1219,14 +1261,14 @@ void VertexFaceIndex::create(const indexed_triangle_set &its)
|
||||
m_vertex_to_face_start.front() = 0;
|
||||
}
|
||||
|
||||
std::vector<Vec3i> its_create_neighbors_index(const indexed_triangle_set &its)
|
||||
std::vector<Vec3i> its_face_neighbors(const indexed_triangle_set &its)
|
||||
{
|
||||
return create_neighbors_index(ex_seq, its);
|
||||
return create_face_neighbors_index(ex_seq, its);
|
||||
}
|
||||
|
||||
std::vector<Vec3i> its_create_neighbors_index_par(const indexed_triangle_set &its)
|
||||
std::vector<Vec3i> its_face_neighbors_par(const indexed_triangle_set &its)
|
||||
{
|
||||
return create_neighbors_index(ex_tbb, its);
|
||||
return create_face_neighbors_index(ex_tbb, its);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -116,13 +116,14 @@ private:
|
||||
// Map from a face edge to a unique edge identifier or -1 if no neighbor exists.
|
||||
// Two neighbor faces share a unique edge identifier even if they are flipped.
|
||||
// Used for chaining slice lines into polygons.
|
||||
std::vector<Vec3i> create_face_neighbors_index(const indexed_triangle_set &its);
|
||||
std::vector<Vec3i> create_face_neighbors_index(const indexed_triangle_set &its, std::function<void()> throw_on_cancel_callback);
|
||||
std::vector<Vec3i> its_face_edge_ids(const indexed_triangle_set &its);
|
||||
std::vector<Vec3i> its_face_edge_ids(const indexed_triangle_set &its, std::function<void()> throw_on_cancel_callback);
|
||||
// Having the face neighbors available, assign unique edge IDs to face edges for chaining of polygons over slices.
|
||||
std::vector<Vec3i> its_face_edge_ids(const indexed_triangle_set &its, std::vector<Vec3i> &face_neighbors, bool assign_unbound_edges = false, int *num_edges = nullptr);
|
||||
|
||||
// Create index that gives neighbor faces for each face. Ignores face orientations.
|
||||
// TODO: naming...
|
||||
std::vector<Vec3i> its_create_neighbors_index(const indexed_triangle_set &its);
|
||||
std::vector<Vec3i> its_create_neighbors_index_par(const indexed_triangle_set &its);
|
||||
std::vector<Vec3i> its_face_neighbors(const indexed_triangle_set &its);
|
||||
std::vector<Vec3i> its_face_neighbors_par(const indexed_triangle_set &its);
|
||||
|
||||
// After applying a transformation with negative determinant, flip the faces to keep the transformed mesh volume positive.
|
||||
void its_flip_triangles(indexed_triangle_set &its);
|
||||
@ -153,6 +154,28 @@ void its_collect_mesh_projection_points_above(const indexed_triangle_set &its, c
|
||||
Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const Matrix3f &m, const float z);
|
||||
Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const Transform3f &t, const float z);
|
||||
|
||||
// Index of a vertex inside triangle_indices.
|
||||
inline int its_triangle_vertex_index(const stl_triangle_vertex_indices &triangle_indices, int vertex_idx)
|
||||
{
|
||||
return vertex_idx == triangle_indices[0] ? 0 :
|
||||
vertex_idx == triangle_indices[1] ? 1 :
|
||||
vertex_idx == triangle_indices[2] ? 2 : -1;
|
||||
}
|
||||
|
||||
inline Vec2i its_triangle_edge(const stl_triangle_vertex_indices &triangle_indices, int edge_idx)
|
||||
{
|
||||
int next_edge_idx = (edge_idx == 2) ? 0 : edge_idx + 1;
|
||||
return { triangle_indices[edge_idx], triangle_indices[next_edge_idx] };
|
||||
}
|
||||
|
||||
// Index of an edge inside triangle.
|
||||
inline int its_triangle_edge_index(const stl_triangle_vertex_indices &triangle_indices, const Vec2i &triangle_edge)
|
||||
{
|
||||
return triangle_edge(0) == triangle_indices[0] && triangle_edge(1) == triangle_indices[1] ? 0 :
|
||||
triangle_edge(0) == triangle_indices[1] && triangle_edge(1) == triangle_indices[2] ? 1 :
|
||||
triangle_edge(0) == triangle_indices[2] && triangle_edge(1) == triangle_indices[0] ? 2 : -1;
|
||||
}
|
||||
|
||||
using its_triangle = std::array<stl_vertex, 3>;
|
||||
|
||||
inline its_triangle its_triangle_vertices(const indexed_triangle_set &its,
|
||||
|
@ -3,17 +3,23 @@
|
||||
#include "Tesselate.hpp"
|
||||
#include "TriangleMesh.hpp"
|
||||
#include "TriangleMeshSlicer.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <deque>
|
||||
#include <queue>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
#ifndef NDEBUG
|
||||
// #define EXPENSIVE_DEBUG_CHECKS
|
||||
#endif // NDEBUG
|
||||
|
||||
#if 0
|
||||
#define DEBUG
|
||||
#define _DEBUG
|
||||
@ -26,6 +32,8 @@
|
||||
#include <boost/thread/mutex.hpp>
|
||||
#include <boost/thread/lock_guard.hpp>
|
||||
|
||||
// #define SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
|
||||
#if defined(SLIC3R_DEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING)
|
||||
#include "SVG.hpp"
|
||||
#endif
|
||||
@ -64,6 +72,8 @@ public:
|
||||
|
||||
bool is_seed_candidate() const { return (this->flags & NO_SEED) == 0 && ! this->skip(); }
|
||||
void set_no_seed(bool set) { if (set) this->flags |= NO_SEED; else this->flags &= ~NO_SEED; }
|
||||
|
||||
void reverse() { std::swap(a, b); std::swap(a_id, b_id); std::swap(edge_a_id, edge_b_id); }
|
||||
|
||||
// Inherits Point a, b
|
||||
// For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1.
|
||||
@ -81,8 +91,14 @@ public:
|
||||
Top,
|
||||
// Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane.
|
||||
Bottom,
|
||||
// Two vertices are aligned with the cutting plane, the edge is shared by two triangles, where one
|
||||
// triangle is below or at the cutting plane and the other is above or at the cutting plane (only one
|
||||
// vertex may lie on the plane).
|
||||
TopBottom,
|
||||
// All three vertices of a face are aligned with the cutting plane.
|
||||
Horizontal
|
||||
Horizontal,
|
||||
// Edge
|
||||
Slab,
|
||||
};
|
||||
|
||||
// feGeneral, feTop, feBottom, feHorizontal
|
||||
@ -102,6 +118,15 @@ public:
|
||||
SKIP = 0x200,
|
||||
};
|
||||
uint32_t flags { 0 };
|
||||
|
||||
#ifndef NDEBUG
|
||||
enum class Source {
|
||||
BottomPlane,
|
||||
TopPlane,
|
||||
Slab,
|
||||
};
|
||||
Source source { Source::BottomPlane };
|
||||
#endif // NDEBUG
|
||||
};
|
||||
|
||||
using IntersectionLines = std::vector<IntersectionLine>;
|
||||
@ -119,7 +144,7 @@ static FacetSliceType slice_facet(
|
||||
// 3 vertices of the triangle, XY scaled. Z scaled or unscaled (same as slice_z).
|
||||
const stl_vertex *vertices,
|
||||
const stl_triangle_vertex_indices &indices,
|
||||
const Vec3i &edge_neighbor,
|
||||
const Vec3i &edge_ids,
|
||||
const int idx_vertex_lowest,
|
||||
const bool horizontal,
|
||||
IntersectionLine &line_out)
|
||||
@ -138,7 +163,7 @@ static FacetSliceType slice_facet(
|
||||
{
|
||||
int k = (idx_vertex_lowest + j) % 3;
|
||||
int l = (k + 1) % 3;
|
||||
edge_id = edge_neighbor(k);
|
||||
edge_id = edge_ids(k);
|
||||
a_id = indices[k];
|
||||
a = vertices + k;
|
||||
b_id = indices[l];
|
||||
@ -284,11 +309,11 @@ void slice_facet_at_zs(
|
||||
const std::vector<Vec3f> &mesh_vertices,
|
||||
const TransformVertex &transform_vertex_fn,
|
||||
const stl_triangle_vertex_indices &indices,
|
||||
const Vec3i &facet_neighbors,
|
||||
const Vec3i &edge_ids,
|
||||
// Scaled or unscaled zs. If vertices have their zs scaled or transform_vertex_fn scales them, then zs have to be scaled as well.
|
||||
const std::vector<float> &zs,
|
||||
std::vector<IntersectionLines> &lines,
|
||||
boost::mutex &lines_mutex)
|
||||
std::array<std::mutex, 64> &lines_mutex)
|
||||
{
|
||||
stl_vertex vertices[3] { transform_vertex_fn(mesh_vertices[indices(0)]), transform_vertex_fn(mesh_vertices[indices(1)]), transform_vertex_fn(mesh_vertices[indices(2)]) };
|
||||
|
||||
@ -299,43 +324,439 @@ void slice_facet_at_zs(
|
||||
// find layer extents
|
||||
auto min_layer = std::lower_bound(zs.begin(), zs.end(), min_z); // first layer whose slice_z is >= min_z
|
||||
auto max_layer = std::upper_bound(min_layer, zs.end(), max_z); // first layer whose slice_z is > max_z
|
||||
int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0);
|
||||
|
||||
for (auto it = min_layer; it != max_layer; ++ it) {
|
||||
IntersectionLine il;
|
||||
int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0);
|
||||
if (slice_facet(*it, vertices, indices, facet_neighbors, idx_vertex_lowest, min_z == max_z, il) == FacetSliceType::Slicing &&
|
||||
il.edge_type != IntersectionLine::FacetEdgeType::Horizontal) {
|
||||
// Ignore horizontal triangles. Any valid horizontal triangle must have a vertical triangle connected, otherwise the part has zero volume.
|
||||
boost::lock_guard<boost::mutex> l(lines_mutex);
|
||||
lines[it - zs.begin()].emplace_back(il);
|
||||
// Ignore horizontal triangles. Any valid horizontal triangle must have a vertical triangle connected, otherwise the part has zero volume.
|
||||
if (min_z != max_z && slice_facet(*it, vertices, indices, edge_ids, idx_vertex_lowest, false, il) == FacetSliceType::Slicing) {
|
||||
assert(il.edge_type != IntersectionLine::FacetEdgeType::Horizontal);
|
||||
size_t slice_id = it - zs.begin();
|
||||
boost::lock_guard<std::mutex> l(lines_mutex[slice_id >> 6]);
|
||||
lines[slice_id].emplace_back(il);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TransformVertex, typename ThrowOnCancel>
|
||||
inline std::vector<IntersectionLines> slice_make_lines(
|
||||
static inline std::vector<IntersectionLines> slice_make_lines(
|
||||
const std::vector<stl_vertex> &vertices,
|
||||
const TransformVertex &transform_vertex_fn,
|
||||
const std::vector<stl_triangle_vertex_indices> &indices,
|
||||
const std::vector<Vec3i> &face_neighbors,
|
||||
const std::vector<Vec3i> &face_edge_ids,
|
||||
const std::vector<float> &zs,
|
||||
const ThrowOnCancel throw_on_cancel_fn)
|
||||
{
|
||||
std::vector<IntersectionLines> lines(zs.size(), IntersectionLines());
|
||||
boost::mutex lines_mutex;
|
||||
std::array<std::mutex, 64> lines_mutex;
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<int>(0, int(indices.size())),
|
||||
[&vertices, &transform_vertex_fn, &indices, &face_neighbors, &zs, &lines, &lines_mutex, throw_on_cancel_fn](const tbb::blocked_range<int> &range) {
|
||||
[&vertices, &transform_vertex_fn, &indices, &face_edge_ids, &zs, &lines, &lines_mutex, throw_on_cancel_fn](const tbb::blocked_range<int> &range) {
|
||||
for (int face_idx = range.begin(); face_idx < range.end(); ++ face_idx) {
|
||||
if ((face_idx & 0x0ffff) == 0)
|
||||
throw_on_cancel_fn();
|
||||
slice_facet_at_zs(vertices, transform_vertex_fn, indices[face_idx], face_neighbors[face_idx], zs, lines, lines_mutex);
|
||||
slice_facet_at_zs(vertices, transform_vertex_fn, indices[face_idx], face_edge_ids[face_idx], zs, lines, lines_mutex);
|
||||
}
|
||||
}
|
||||
);
|
||||
return lines;
|
||||
}
|
||||
|
||||
// For projecting triangle sets onto slice slabs.
|
||||
struct SlabLines {
|
||||
// Intersection lines of a slice with a triangle set, CCW oriented.
|
||||
std::vector<IntersectionLines> at_slice;
|
||||
// Projections of triangle set boundary lines into layer below (for projection from the top)
|
||||
// or into layer above (for projection from the bottom).
|
||||
// In both cases the intersection liens are CCW oriented.
|
||||
std::vector<IntersectionLines> between_slices;
|
||||
};
|
||||
|
||||
// Orientation of the face normal in regard to a XY plane pointing upwards.
|
||||
enum class FaceOrientation : char {
|
||||
// Z component of the normal is positive.
|
||||
Up,
|
||||
// Z component of the normal is negative.
|
||||
Down,
|
||||
// Z component of the normal is zero.
|
||||
Vertical,
|
||||
// Triangle is degenerate, thus its normal is undefined. We may want to slice the degenerate triangles
|
||||
// because of the connectivity information they carry.
|
||||
Degenerate
|
||||
};
|
||||
|
||||
template<bool ProjectionFromTop>
|
||||
void slice_facet_with_slabs(
|
||||
// Scaled or unscaled vertices. transform_vertex_fn may scale zs.
|
||||
const std::vector<Vec3f> &mesh_vertices,
|
||||
const std::vector<stl_triangle_vertex_indices> &mesh_triangles,
|
||||
const size_t facet_idx,
|
||||
const Vec3i &facet_neighbors,
|
||||
const Vec3i &facet_edge_ids,
|
||||
// Increase edge_ids at the top plane of the slab edges by num_edges to allow chaining
|
||||
// from bottom plane of the slab to the top plane of the slab and vice versa.
|
||||
const int num_edges,
|
||||
const std::vector<float> &zs,
|
||||
SlabLines &lines,
|
||||
std::array<std::mutex, 64> &lines_mutex)
|
||||
{
|
||||
const stl_triangle_vertex_indices &indices = mesh_triangles[facet_idx];
|
||||
stl_vertex vertices[3] { mesh_vertices[indices(0)], mesh_vertices[indices(1)], mesh_vertices[indices(2)] };
|
||||
|
||||
// find facet extents
|
||||
const float min_z = fminf(vertices[0].z(), fminf(vertices[1].z(), vertices[2].z()));
|
||||
const float max_z = fmaxf(vertices[0].z(), fmaxf(vertices[1].z(), vertices[2].z()));
|
||||
const bool horizontal = min_z == max_z;
|
||||
|
||||
// find layer extents
|
||||
auto min_layer = std::lower_bound(zs.begin(), zs.end(), min_z); // first layer whose slice_z is >= min_z
|
||||
auto max_layer = std::upper_bound(min_layer, zs.end(), max_z); // first layer whose slice_z is > max_z
|
||||
assert(min_layer == zs.end() ? max_layer == zs.end() : *min_layer >= min_z);
|
||||
assert(max_layer == zs.end() || *max_layer > max_z);
|
||||
|
||||
auto emit_slab_edge = [&lines, &lines_mutex](IntersectionLine il, size_t slab_id, bool reverse) {
|
||||
if (reverse)
|
||||
il.reverse();
|
||||
boost::lock_guard<std::mutex> l(lines_mutex[(slab_id + 32) >> 6]);
|
||||
lines.between_slices[slab_id].emplace_back(il);
|
||||
};
|
||||
|
||||
if (min_layer == max_layer || horizontal) {
|
||||
// Horizontal face or a nearly horizontal face that fits between two layers or below the bottom most or above the top most layer.
|
||||
assert(horizontal || zs.empty() || max_z < zs.front() || min_z > zs.back() ||
|
||||
(min_layer == max_layer && min_layer != zs.end() && min_layer != zs.begin() && *(min_layer - 1) < min_z && *min_layer > max_z));
|
||||
if (horizontal && min_layer != zs.end() && *min_layer == min_z) {
|
||||
// Slicing a horizontal triangle with a slicing plane. The triangle has to be upwards facing for ProjectionFromTop
|
||||
// and downwards facing for ! ProjectionFromTop.
|
||||
assert(min_layer != max_layer);
|
||||
size_t line_id = min_layer - zs.begin();
|
||||
for (int iedge = 0; iedge < 3; ++ iedge)
|
||||
if (facet_neighbors(iedge) == -1) {
|
||||
int i = iedge;
|
||||
int j = next_idx_modulo(i, 3);
|
||||
assert(vertices[i].z() == zs[line_id]);
|
||||
assert(vertices[j].z() == zs[line_id]);
|
||||
IntersectionLine il {
|
||||
{ to_2d(vertices[i]).cast<coord_t>(), to_2d(vertices[j]).cast<coord_t>() },
|
||||
indices(i), indices(j), -1, -1,
|
||||
ProjectionFromTop ? IntersectionLine::FacetEdgeType::Bottom : IntersectionLine::FacetEdgeType::Top
|
||||
};
|
||||
// Don't flip the FacetEdgeType::Top edge, it will be flipped when chaining.
|
||||
// if (! ProjectionFromTop) il.reverse();
|
||||
boost::lock_guard<std::mutex> l(lines_mutex[line_id >> 6]);
|
||||
lines.at_slice[line_id].emplace_back(il);
|
||||
}
|
||||
} else {
|
||||
// Triangle is completely between two slicing planes, the triangle may or may not be horizontal, which
|
||||
// does not matter for the processing of such a triangle.
|
||||
size_t slab_id;
|
||||
if (ProjectionFromTop) {
|
||||
if (max_layer == zs.begin()) {
|
||||
// Not slicing the triangle and it is below the lowest layer.
|
||||
return;
|
||||
} else {
|
||||
// Not slicing the triangle and it could be projected into a slab.
|
||||
slab_id = max_layer - zs.begin();
|
||||
}
|
||||
} else {
|
||||
// projection from bottom
|
||||
if (min_layer == zs.end()) {
|
||||
// Not slicing the triangle and it is above the highest layer.
|
||||
return;
|
||||
} else {
|
||||
// Not slicing the triangle and it could be projected into a slab.
|
||||
slab_id = min_layer - zs.begin();
|
||||
}
|
||||
}
|
||||
if (ProjectionFromTop)
|
||||
-- slab_id;
|
||||
for (int iedge = 0; iedge < 3; ++ iedge)
|
||||
if (facet_neighbors(iedge) == -1) {
|
||||
int i = iedge;
|
||||
int j = next_idx_modulo(i, 3);
|
||||
assert(ProjectionFromTop ? vertices[i].z() >= zs[slab_id] : vertices[i].z() <= zs[slab_id]);
|
||||
assert(ProjectionFromTop ? vertices[j].z() >= zs[slab_id] : vertices[j].z() <= zs[slab_id]);
|
||||
emit_slab_edge(
|
||||
IntersectionLine {
|
||||
{ to_2d(vertices[i]).cast<coord_t>(), to_2d(vertices[j]).cast<coord_t>() },
|
||||
indices(i), indices(j), -1, -1, IntersectionLine::FacetEdgeType::Slab
|
||||
},
|
||||
slab_id, ! ProjectionFromTop);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// The triangle is not horizontal and at least a single slicing plane intersects the triangle.
|
||||
int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0);
|
||||
IntersectionLine il_prev;
|
||||
for (auto it = min_layer; it != max_layer; ++ it) {
|
||||
IntersectionLine il;
|
||||
auto type = slice_facet(*it, vertices, indices, facet_edge_ids, idx_vertex_lowest, false, il);
|
||||
if (type == FacetSliceType::NoSlice) {
|
||||
// One and exactly one vertex is touching the slicing plane.
|
||||
} else {
|
||||
if (il.edge_type == IntersectionLine::FacetEdgeType::Top || il.edge_type == IntersectionLine::FacetEdgeType::Bottom) {
|
||||
// The non-horizontal triangle is being sliced at one of its edges.
|
||||
// If the edge is open (it does not have a neighbor), add it.
|
||||
// If the edge has a neighbor, then add it as TopBottom, and do it just once.
|
||||
assert(il.a_id != -1 && il.b_id != -1);
|
||||
assert(il.edge_a_id == -1 && il.edge_b_id == -1);
|
||||
// Identify edge ID from the edge vertices.
|
||||
int edge_id;
|
||||
if (type == FacetSliceType::Cutting) {
|
||||
// The edge is oriented CCW along the face perimeter.
|
||||
assert(il.edge_type == IntersectionLine::FacetEdgeType::Bottom);
|
||||
edge_id = il.a_id == indices(0) ? 0 : il.a_id == indices(1) ? 1 : 2;
|
||||
assert(il.a_id == indices(edge_id));
|
||||
assert(il.b_id == indices(next_idx_modulo(edge_id, 3)));
|
||||
} else {
|
||||
// The edge is oriented CW along the face perimeter.
|
||||
assert(il.edge_type == IntersectionLine::FacetEdgeType::Top);
|
||||
edge_id = il.b_id == indices(0) ? 0 : il.b_id == indices(1) ? 1 : 2;
|
||||
assert(il.b_id == indices(edge_id));
|
||||
assert(il.a_id == indices(next_idx_modulo(edge_id, 3)));
|
||||
}
|
||||
int neighbor_idx = facet_neighbors(edge_id);
|
||||
if (neighbor_idx == -1) {
|
||||
// Save the open edge for sure.
|
||||
type = FacetSliceType::Slicing;
|
||||
} else {
|
||||
const stl_triangle_vertex_indices &neighbor = mesh_triangles[neighbor_idx];
|
||||
float z = *it;
|
||||
#ifndef NDEBUG
|
||||
int num_on_plane = (mesh_vertices[neighbor(0)].z() == z) + (mesh_vertices[neighbor(1)].z() == z) + (mesh_vertices[neighbor(2)].z() == z);
|
||||
assert(num_on_plane == 2 || num_on_plane == 3);
|
||||
#endif // NDEBUG
|
||||
if (mesh_vertices[neighbor(0)].z() == z && mesh_vertices[neighbor(1)].z() == z && mesh_vertices[neighbor(2)].z() == z) {
|
||||
// The neighbor triangle is horizontal.
|
||||
// Is the corner convex or concave?
|
||||
if (il.edge_type == (ProjectionFromTop ? IntersectionLine::FacetEdgeType::Top : IntersectionLine::FacetEdgeType::Bottom)) {
|
||||
// Convex corner. Add this edge to both slabs, the edge is a boundary edge of both the projection patch below and
|
||||
// above this slicing plane.
|
||||
type = FacetSliceType::Slicing;
|
||||
il.edge_type = IntersectionLine::FacetEdgeType::TopBottom;
|
||||
} else {
|
||||
// Concave corner. Ignore this edge, it is internal to the projection patch.
|
||||
type = FacetSliceType::Cutting;
|
||||
}
|
||||
} else if (il.edge_type == IntersectionLine::FacetEdgeType::Top) {
|
||||
// Indicate that the edge belongs to both the slab below and above the plane.
|
||||
assert(type == FacetSliceType::Slicing);
|
||||
il.edge_type = IntersectionLine::FacetEdgeType::TopBottom;
|
||||
} else {
|
||||
// Don't add this edge, as the neighbor triangle will add the same edge as FacetEdgeType::TopBottom.
|
||||
assert(type == FacetSliceType::Cutting);
|
||||
assert(il.edge_type == IntersectionLine::FacetEdgeType::Bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type == FacetSliceType::Slicing) {
|
||||
if (! ProjectionFromTop)
|
||||
il.reverse();
|
||||
size_t line_id = it - zs.begin();
|
||||
boost::lock_guard<std::mutex> l(lines_mutex[line_id >> 6]);
|
||||
lines.at_slice[line_id].emplace_back(il);
|
||||
}
|
||||
}
|
||||
if (! ProjectionFromTop || it != zs.begin()) {
|
||||
size_t slab_id = it - zs.begin();
|
||||
if (ProjectionFromTop)
|
||||
-- slab_id;
|
||||
// Try to project unbound edges.
|
||||
for (int iedge = 0; iedge < 3; ++ iedge)
|
||||
if (facet_neighbors(iedge) == -1) {
|
||||
// Unbound edge.
|
||||
int edge_id = facet_edge_ids(iedge);
|
||||
bool intersects_this = il.edge_a_id == edge_id || il.edge_b_id == edge_id;
|
||||
bool intersects_prev = il_prev.edge_a_id == edge_id || il_prev.edge_b_id == edge_id;
|
||||
int i = iedge;
|
||||
int j = next_idx_modulo(i, 3);
|
||||
assert((! intersects_this && ! intersects_prev) || vertices[j].z() != vertices[i].z());
|
||||
bool edge_up = vertices[j].z() > vertices[i].z();
|
||||
if (intersects_this && intersects_prev) {
|
||||
// Intersects both, emit the segment between these intersections.
|
||||
Line l(il_prev.edge_a_id == edge_id ? il_prev.a : il_prev.b,
|
||||
il.edge_a_id == edge_id ? il.a : il.b);
|
||||
emit_slab_edge(
|
||||
IntersectionLine { l, -1, -1, edge_id, edge_id + num_edges, IntersectionLine::FacetEdgeType::Slab },
|
||||
slab_id, ProjectionFromTop != edge_up);
|
||||
} else if (intersects_this) {
|
||||
// Intersects just the top plane, may touch the bottom plane.
|
||||
assert((vertices[i].z() > *it && vertices[j].z() < *it) || (vertices[i].z() < *it && vertices[j].z() > *it));
|
||||
assert(il.edge_a_id == edge_id || il.edge_b_id == edge_id);
|
||||
emit_slab_edge(
|
||||
IntersectionLine { {
|
||||
to_2d(edge_up ? vertices[i] : vertices[j]).cast<coord_t>(),
|
||||
il.edge_a_id == edge_id ? il.a : il.b
|
||||
},
|
||||
edge_up ? indices(i) : indices(j), -1, -1, edge_id + num_edges, IntersectionLine::FacetEdgeType::Slab
|
||||
},
|
||||
slab_id, ProjectionFromTop != edge_up);
|
||||
} else if (intersects_prev) {
|
||||
// Intersects just the bottom plane, may touch the top vertex.
|
||||
assert(*it <= max_z);
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
auto it_prev = it;
|
||||
-- it_prev;
|
||||
assert((vertices[i].z() > *it_prev && vertices[j].z() < *it_prev) || (vertices[i].z() < *it_prev && vertices[j].z() > *it_prev));
|
||||
}
|
||||
#endif // NDEBUG
|
||||
emit_slab_edge(
|
||||
IntersectionLine { {
|
||||
il_prev.edge_a_id == edge_id ? il_prev.a : il_prev.b,
|
||||
to_2d(edge_up ? vertices[j] : vertices[i]).cast<coord_t>()
|
||||
},
|
||||
-1, edge_up ? indices(j) : indices(i), edge_id, -1, IntersectionLine::FacetEdgeType::Slab
|
||||
},
|
||||
slab_id, ProjectionFromTop != edge_up);
|
||||
} else if (float zi = vertices[i].z(), zj = vertices[j].z(); zi < *it || zj < *it) {
|
||||
// The edge does not intersect the current plane and it does not intersect the previous plane either.
|
||||
// Both points have to be inside the slab.
|
||||
assert(zi <= *it && zj <= *it);
|
||||
#ifndef NDEBUG
|
||||
if (type == FacetSliceType::Slicing || type == FacetSliceType::Cutting) {
|
||||
// Such edge should already be processed in the code above, it shall be skipped here.
|
||||
assert(indices(i) != il.b_id || indices(j) != il.a_id);
|
||||
assert(indices(i) != il.a_id || indices(j) != il.b_id);
|
||||
}
|
||||
#endif // NDEBUG
|
||||
// Is it inside the slab?
|
||||
bool inside_slab = true;
|
||||
if (it != min_layer) {
|
||||
auto it_prev = it;
|
||||
-- it_prev;
|
||||
assert(*it_prev >= *min_layer && *it_prev < *it);
|
||||
// One point may touch the plane below, the other must not.
|
||||
inside_slab = zi > *it_prev || zj > *it_prev;
|
||||
// Both points have to be inside the slab.
|
||||
assert(! inside_slab || (zi >= *it_prev && zj >= *it_prev));
|
||||
}
|
||||
if (inside_slab) {
|
||||
assert(ProjectionFromTop ? vertices[i].z() >= zs[slab_id] : vertices[i].z() <= zs[slab_id]);
|
||||
assert(ProjectionFromTop ? vertices[j].z() >= zs[slab_id] : vertices[j].z() <= zs[slab_id]);
|
||||
emit_slab_edge(
|
||||
IntersectionLine {
|
||||
{ to_2d(vertices[i]).cast<coord_t>(), to_2d(vertices[j]).cast<coord_t>() },
|
||||
indices(i), indices(j), -1, -1, IntersectionLine::FacetEdgeType::Slab
|
||||
},
|
||||
slab_id, ! ProjectionFromTop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
il_prev = il;
|
||||
}
|
||||
if (ProjectionFromTop || max_layer != zs.end()) {
|
||||
// Try to project unbound edges above the last slicing plane to the last slab.
|
||||
// Last layer slicing this triangle.
|
||||
auto it = max_layer - 1;
|
||||
size_t slab_id = max_layer - zs.begin();
|
||||
if (ProjectionFromTop)
|
||||
-- slab_id;
|
||||
for (int iedge = 0; iedge < 3; ++ iedge)
|
||||
if (facet_neighbors(iedge) == -1) {
|
||||
// Unbound edge.
|
||||
int edge_id = facet_edge_ids(iedge);
|
||||
int i = iedge;
|
||||
int j = next_idx_modulo(i, 3);
|
||||
if (il_prev.edge_a_id == edge_id || il_prev.edge_b_id == edge_id) {
|
||||
// Intersects just the bottom plane, may touch the top vertex.
|
||||
assert((vertices[i].z() > *it && vertices[j].z() < *it) || (vertices[i].z() < *it && vertices[j].z() > *it));
|
||||
bool edge_up = vertices[j].z() > vertices[i].z();
|
||||
emit_slab_edge(
|
||||
IntersectionLine{ {
|
||||
il_prev.edge_a_id == edge_id ? il_prev.a : il_prev.b,
|
||||
to_2d(edge_up ? vertices[j] : vertices[i]).cast<coord_t>()
|
||||
},
|
||||
-1, edge_up ? indices(j) : indices(i), edge_id, -1, IntersectionLine::FacetEdgeType::Slab
|
||||
},
|
||||
slab_id, ProjectionFromTop != edge_up);
|
||||
} else if (float zi = vertices[i].z(), zj = vertices[j].z(); zi > *it || zj > *it) {
|
||||
// The edge does not intersect the current plane and it does not intersect the previous plane either.
|
||||
// Both points have to be inside the slab.
|
||||
assert(zi >= *it && zj >= *it);
|
||||
assert(max_layer == zs.end() || (zi < *max_layer && zj < *max_layer));
|
||||
emit_slab_edge(
|
||||
IntersectionLine{
|
||||
{ to_2d(vertices[i]).cast<coord_t>(), to_2d(vertices[j]).cast<coord_t>() },
|
||||
indices(i), indices(j), -1, -1, IntersectionLine::FacetEdgeType::Slab
|
||||
},
|
||||
slab_id, ! ProjectionFromTop);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// used by slice_mesh_slabs() to produce on-slice lines and between-slices lines.
|
||||
// Returning top / bottom SlabLines.
|
||||
template<typename ThrowOnCancel>
|
||||
inline std::pair<SlabLines, SlabLines> slice_slabs_make_lines(
|
||||
const std::vector<stl_vertex> &vertices,
|
||||
const std::vector<stl_triangle_vertex_indices> &indices,
|
||||
const std::vector<Vec3i> &face_neighbors,
|
||||
const std::vector<Vec3i> &face_edge_ids,
|
||||
// Total number of edges. All face_edge_ids are lower than num_edges.
|
||||
// num_edges will be used to distinguish between intersections with the top and bottom plane.
|
||||
const int num_edges,
|
||||
const std::vector<FaceOrientation> &face_orientation,
|
||||
const std::vector<float> &zs,
|
||||
bool top,
|
||||
bool bottom,
|
||||
const ThrowOnCancel throw_on_cancel_fn)
|
||||
{
|
||||
std::pair<SlabLines, SlabLines> out;
|
||||
SlabLines &lines_top = out.first;
|
||||
SlabLines &lines_bottom = out.second;
|
||||
std::array<std::mutex, 64> lines_mutex_top;
|
||||
std::array<std::mutex, 64> lines_mutex_bottom;
|
||||
|
||||
if (top) {
|
||||
lines_top.at_slice.assign(zs.size(), IntersectionLines());
|
||||
lines_top.between_slices.assign(zs.size(), IntersectionLines());
|
||||
}
|
||||
if (bottom) {
|
||||
lines_bottom.at_slice.assign(zs.size(), IntersectionLines());
|
||||
lines_bottom.between_slices.assign(zs.size(), IntersectionLines());
|
||||
}
|
||||
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<int>(0, int(indices.size())),
|
||||
[&vertices, &indices, &face_neighbors, &face_edge_ids, num_edges, &face_orientation, &zs, top, bottom, &lines_top, &lines_bottom, &lines_mutex_top, &lines_mutex_bottom, throw_on_cancel_fn]
|
||||
(const tbb::blocked_range<int> &range) {
|
||||
for (int face_idx = range.begin(); face_idx < range.end(); ++ face_idx) {
|
||||
if ((face_idx & 0x0ffff) == 0)
|
||||
throw_on_cancel_fn();
|
||||
FaceOrientation fo = face_orientation[face_idx];
|
||||
Vec3i edge_ids = face_edge_ids[face_idx];
|
||||
if (top && (fo == FaceOrientation::Up || fo == FaceOrientation::Degenerate)) {
|
||||
Vec3i neighbors = face_neighbors[face_idx];
|
||||
// Reset neighborship of this triangle in case the other triangle is oriented backwards from this one.
|
||||
for (int i = 0; i < 3; ++ i)
|
||||
if (neighbors(i) != -1) {
|
||||
FaceOrientation fo2 = face_orientation[neighbors(i)];
|
||||
if (fo2 != FaceOrientation::Up && fo2 != FaceOrientation::Degenerate)
|
||||
neighbors(i) = -1;
|
||||
}
|
||||
slice_facet_with_slabs<true>(vertices, indices, face_idx, neighbors, edge_ids, num_edges, zs, lines_top, lines_mutex_top);
|
||||
}
|
||||
if (bottom && (fo == FaceOrientation::Down || fo == FaceOrientation::Degenerate)) {
|
||||
Vec3i neighbors = face_neighbors[face_idx];
|
||||
// Reset neighborship of this triangle in case the other triangle is oriented backwards from this one.
|
||||
for (int i = 0; i < 3; ++ i)
|
||||
if (neighbors(i) != -1) {
|
||||
FaceOrientation fo2 = face_orientation[neighbors(i)];
|
||||
if (fo2 != FaceOrientation::Down && fo2 != FaceOrientation::Degenerate)
|
||||
neighbors(i) = -1;
|
||||
}
|
||||
slice_facet_with_slabs<false>(vertices, indices, face_idx, neighbors, edge_ids, num_edges, zs, lines_bottom, lines_mutex_bottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
return out;
|
||||
}
|
||||
|
||||
#if 0
|
||||
//FIXME Should this go away? For valid meshes the function slice_facet() returns Slicing
|
||||
// and sets edges of vertical triangles to produce only a single edge per pair of neighbor faces.
|
||||
@ -495,6 +916,7 @@ static void chain_lines_by_triangle_connectivity(IntersectionLines &lines, Polyg
|
||||
if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) ||
|
||||
(first_line->a_id != -1 && first_line->a_id == last_line->b_id)) {
|
||||
// The current loop is complete. Add it to the output.
|
||||
assert(first_line->a == last_line->b);
|
||||
loops.emplace_back(std::move(loop_pts));
|
||||
#ifdef SLIC3R_TRIANGLEMESH_DEBUG
|
||||
printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size());
|
||||
@ -513,6 +935,7 @@ static void chain_lines_by_triangle_connectivity(IntersectionLines &lines, Polyg
|
||||
next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id,
|
||||
next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y);
|
||||
*/
|
||||
assert(last_line->b == next_line->a);
|
||||
loop_pts.emplace_back(next_line->a);
|
||||
last_line = next_line;
|
||||
next_line->set_skip();
|
||||
@ -778,7 +1201,7 @@ static Polygons make_loops(
|
||||
{
|
||||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-%d.svg", iRun ++).c_str(), bbox_svg);
|
||||
svg.draw(union_ex(*loops));
|
||||
svg.draw(union_ex(loops));
|
||||
for (const OpenPolyline &pl : open_polylines)
|
||||
svg.draw(Polyline(pl.points), "red");
|
||||
svg.Close();
|
||||
@ -795,7 +1218,7 @@ static Polygons make_loops(
|
||||
{
|
||||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines2-%d.svg", iRun++).c_str(), bbox_svg);
|
||||
svg.draw(union_ex(*loops));
|
||||
svg.draw(union_ex(loops));
|
||||
for (const OpenPolyline &pl : open_polylines) {
|
||||
if (pl.points.empty())
|
||||
continue;
|
||||
@ -825,7 +1248,7 @@ static Polygons make_loops(
|
||||
{
|
||||
static int iRun = 0;
|
||||
SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-final-%d.svg", iRun++).c_str(), bbox_svg);
|
||||
svg.draw(union_ex(*loops));
|
||||
svg.draw(union_ex(loops));
|
||||
for (const OpenPolyline &pl : open_polylines) {
|
||||
if (pl.points.empty())
|
||||
continue;
|
||||
@ -892,6 +1315,134 @@ static std::vector<Polygons> make_loops(
|
||||
return layers;
|
||||
}
|
||||
|
||||
// used by slice_mesh_slabs() to produce loops from on-slice lines and between-slices lines.
|
||||
template<bool ProjectionFromTop, typename ThrowOnCancel>
|
||||
static std::vector<Polygons> make_slab_loops(
|
||||
// Lines will have their flags modified.
|
||||
SlabLines &lines,
|
||||
// To differentiate edge IDs of the top plane from the edge IDs of the bottom plane for chaining.
|
||||
int num_edges,
|
||||
ThrowOnCancel throw_on_cancel)
|
||||
{
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
static int iRun = 0;
|
||||
++ iRun;
|
||||
#endif // SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
|
||||
assert(! lines.at_slice.empty() && lines.at_slice.size() == lines.between_slices.size());
|
||||
std::vector<Polygons> layers;
|
||||
layers.resize(lines.at_slice.size());
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<int>(0, int(lines.at_slice.size())),
|
||||
[&lines, num_edges, &layers, throw_on_cancel](const tbb::blocked_range<int> &range) {
|
||||
for (int line_idx = range.begin(); line_idx < range.end(); ++ line_idx) {
|
||||
if ((line_idx & 0x0ffff) == 0)
|
||||
throw_on_cancel();
|
||||
IntersectionLines in;
|
||||
size_t nlines = lines.between_slices[line_idx].size();
|
||||
int slice_below = ProjectionFromTop ? line_idx : line_idx - 1;
|
||||
int slice_above = ProjectionFromTop ? line_idx + 1 : line_idx;
|
||||
bool has_slice_below = ProjectionFromTop || line_idx > 0;
|
||||
bool has_slice_above = ! ProjectionFromTop || line_idx + 1 < int(lines.at_slice.size());
|
||||
if (has_slice_below)
|
||||
nlines += lines.at_slice[slice_below].size();
|
||||
if (has_slice_above)
|
||||
nlines += lines.at_slice[slice_above].size();
|
||||
if (nlines) {
|
||||
in.reserve(nlines);
|
||||
if (has_slice_below) {
|
||||
for (const IntersectionLine &l : lines.at_slice[slice_below])
|
||||
if (l.edge_type != IntersectionLine::FacetEdgeType::Top) {
|
||||
in.emplace_back(l);
|
||||
#ifndef NDEBUG
|
||||
in.back().source = IntersectionLine::Source::BottomPlane;
|
||||
#endif // NDEBUG
|
||||
}
|
||||
}
|
||||
{
|
||||
// Edges in between slice_below and slice_above.
|
||||
#ifndef NDEBUG
|
||||
size_t old_size = in.size();
|
||||
#endif // NDEBUG
|
||||
// Edge IDs of end points on in-between lines that touch the layer above are already increased with num_edges.
|
||||
append(in, lines.between_slices[line_idx]);
|
||||
#ifndef NDEBUG
|
||||
for (auto it = in.begin() + old_size; it != in.end(); ++ it) {
|
||||
assert(it->edge_type == IntersectionLine::FacetEdgeType::Slab);
|
||||
it->source = IntersectionLine::Source::Slab;
|
||||
}
|
||||
#endif // NDEBUG
|
||||
}
|
||||
if (has_slice_above) {
|
||||
for (const IntersectionLine &lsrc : lines.at_slice[slice_above])
|
||||
if (lsrc.edge_type != IntersectionLine::FacetEdgeType::Bottom) {
|
||||
in.emplace_back(lsrc);
|
||||
auto &l = in.back();
|
||||
l.reverse();
|
||||
// Differentiate edge IDs of the top plane from the edge IDs of the bottom plane for chaining.
|
||||
if (l.edge_a_id >= 0)
|
||||
l.edge_a_id += num_edges;
|
||||
if (l.edge_b_id >= 0)
|
||||
l.edge_b_id += num_edges;
|
||||
#ifndef NDEBUG
|
||||
l.source = IntersectionLine::Source::TopPlane;
|
||||
#endif // NDEBUG
|
||||
}
|
||||
}
|
||||
if (! in.empty()) {
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
BoundingBox bbox_svg;
|
||||
coordf_t stroke_width = scale_(0.02);
|
||||
{
|
||||
for (const IntersectionLine &line : in) {
|
||||
bbox_svg.merge(line.a);
|
||||
bbox_svg.merge(line.b);
|
||||
}
|
||||
SVG svg(debug_out_path("make_slab_loops-in-%d-%d-%s.svg", iRun, line_idx, ProjectionFromTop ? "top" : "bottom").c_str(), bbox_svg);
|
||||
svg.arrows = true;
|
||||
for (const IntersectionLine& line : in) {
|
||||
const char* color = line.source == IntersectionLine::Source::BottomPlane ? "red" : line.source == IntersectionLine::Source::TopPlane ? "blue" : "green";
|
||||
svg.draw(line, color, stroke_width);
|
||||
}
|
||||
svg.Close();
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
Polygons &loops = layers[line_idx];
|
||||
std::vector<OpenPolyline> open_polylines;
|
||||
chain_lines_by_triangle_connectivity(in, loops, open_polylines);
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
{
|
||||
SVG svg(debug_out_path("make_slab_loops-out-%d-%d-%s.svg", iRun, line_idx, ProjectionFromTop ? "top" : "bottom").c_str(), bbox_svg);
|
||||
svg.arrows = true;
|
||||
for (const IntersectionLine& line : in) {
|
||||
const char* color = line.source == IntersectionLine::Source::BottomPlane ? "red" : line.source == IntersectionLine::Source::TopPlane ? "blue" : "green";
|
||||
svg.draw(line, color, stroke_width);
|
||||
}
|
||||
svg.draw(loops, "black");
|
||||
svg.Close();
|
||||
}
|
||||
{
|
||||
SVG svg(debug_out_path("make_slab_loops-open-polylines-%d-%d-%s.svg", iRun, line_idx, ProjectionFromTop ? "top" : "bottom").c_str(), bbox_svg);
|
||||
svg.draw(loops, "black");
|
||||
svg.arrows = true;
|
||||
for (const OpenPolyline &open_polyline : open_polylines)
|
||||
svg.draw(Polyline(open_polyline.points), "black", stroke_width);
|
||||
svg.Close();
|
||||
}
|
||||
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
|
||||
assert(! loops.empty());
|
||||
assert(open_polylines.empty());
|
||||
if (! open_polylines.empty())
|
||||
BOOST_LOG_TRIVIAL(trace) << "make_slab_loops - chaining failed. #" << open_polylines.size() << " open polylines";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return layers;
|
||||
}
|
||||
|
||||
// Used to cut the mesh into two halves.
|
||||
static ExPolygons make_expolygons_simple(std::vector<IntersectionLine> &lines)
|
||||
{
|
||||
@ -1055,6 +1606,44 @@ static void make_expolygons(const Polygons &loops, const float closing_radius, c
|
||||
union_ex(loops, fill_type));
|
||||
}
|
||||
|
||||
// Make a trafo for transforming the vertices. Scale up in XY, not in Z.
|
||||
static inline Transform3f make_trafo_for_slicing(const Transform3d &trafo)
|
||||
{
|
||||
auto t = trafo;
|
||||
static constexpr const double s = 1. / SCALING_FACTOR;
|
||||
t.prescale(Vec3d(s, s, 1.));
|
||||
return t.cast<float>();
|
||||
}
|
||||
|
||||
static inline bool is_identity(const Transform3d &trafo)
|
||||
{
|
||||
return trafo.matrix() == Transform3d::Identity().matrix();
|
||||
}
|
||||
|
||||
static std::vector<stl_vertex> transform_mesh_vertices_for_slicing(const indexed_triangle_set &mesh, const Transform3d &trafo)
|
||||
{
|
||||
// Copy and scale vertices in XY, don't scale in Z.
|
||||
// Possibly apply the transformation.
|
||||
static constexpr const double s = 1. / SCALING_FACTOR;
|
||||
std::vector<stl_vertex> out(mesh.vertices);
|
||||
if (is_identity(trafo)) {
|
||||
// Identity.
|
||||
for (stl_vertex &v : out) {
|
||||
// Scale just XY, leave Z unscaled.
|
||||
v.x() *= float(s);
|
||||
v.y() *= float(s);
|
||||
}
|
||||
} else {
|
||||
// Transform the vertices, scale up in XY, not in Y.
|
||||
auto t = trafo;
|
||||
t.prescale(Vec3d(s, s, 1.));
|
||||
auto tf = t.cast<float>();
|
||||
for (stl_vertex &v : out)
|
||||
v = tf * v;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<Polygons> slice_mesh(
|
||||
const indexed_triangle_set &mesh,
|
||||
// Unscaled Zs
|
||||
@ -1071,41 +1660,23 @@ std::vector<Polygons> slice_mesh(
|
||||
// Instead of edge identifiers, one shall use a sorted pair of edge vertex indices.
|
||||
// However facets_edges assigns a single edge ID to two triangles only, thus when factoring facets_edges out, one will have
|
||||
// to make sure that no code relies on it.
|
||||
std::vector<Vec3i> facets_edges = create_face_neighbors_index(mesh);
|
||||
const bool identity = params.trafo.matrix() == Transform3d::Identity().matrix();
|
||||
static constexpr const double s = 1. / SCALING_FACTOR;
|
||||
std::vector<Vec3i> face_edge_ids = its_face_edge_ids(mesh);
|
||||
if (zs.size() <= 1) {
|
||||
// It likely is not worthwile to copy the vertices. Apply the transformation in place.
|
||||
if (identity)
|
||||
if (is_identity(params.trafo)) {
|
||||
lines = slice_make_lines(
|
||||
mesh.vertices, [](const Vec3f &p) { return Vec3f(scaled<float>(p.x()), scaled<float>(p.y()), p.z()); },
|
||||
mesh.indices, facets_edges, zs, throw_on_cancel);
|
||||
else {
|
||||
// Transform the vertices, scale up in XY, not in Y.
|
||||
auto t = params.trafo;
|
||||
t.prescale(Vec3d(s, s, 1.));
|
||||
auto tf = t.cast<float>();
|
||||
lines = slice_make_lines(mesh.vertices, [tf](const Vec3f &p) { return tf * p; }, mesh.indices, facets_edges, zs, throw_on_cancel);
|
||||
mesh.indices, face_edge_ids, zs, throw_on_cancel);
|
||||
} else {
|
||||
// Transform the vertices, scale up in XY, not in Z.
|
||||
Transform3f tf = make_trafo_for_slicing(params.trafo);
|
||||
lines = slice_make_lines(mesh.vertices, [tf](const Vec3f &p) { return tf * p; }, mesh.indices, face_edge_ids, zs, throw_on_cancel);
|
||||
}
|
||||
} else {
|
||||
// Copy and scale vertices in XY, don't scale in Z.
|
||||
// Possibly apply the transformation.
|
||||
std::vector<stl_vertex> vertices(mesh.vertices);
|
||||
if (identity) {
|
||||
for (stl_vertex &v : vertices) {
|
||||
// Scale just XY, leave Z unscaled.
|
||||
v.x() *= float(s);
|
||||
v.y() *= float(s);
|
||||
}
|
||||
} else {
|
||||
// Transform the vertices, scale up in XY, not in Y.
|
||||
auto t = params.trafo;
|
||||
t.prescale(Vec3d(s, s, 1.));
|
||||
auto tf = t.cast<float>();
|
||||
for (stl_vertex &v : vertices)
|
||||
v = tf * v;
|
||||
}
|
||||
lines = slice_make_lines(vertices, [](const Vec3f &p) { return p; }, mesh.indices, facets_edges, zs, throw_on_cancel);
|
||||
// Copy and scale vertices in XY, don't scale in Z. Possibly apply the transformation.
|
||||
lines = slice_make_lines(
|
||||
transform_mesh_vertices_for_slicing(mesh, params.trafo),
|
||||
[](const Vec3f &p) { return p; }, mesh.indices, face_edge_ids, zs, throw_on_cancel);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1194,6 +1765,107 @@ std::vector<ExPolygons> slice_mesh_ex(
|
||||
return layers;
|
||||
}
|
||||
|
||||
// Slice a triangle set with a set of Z slabs (thick layers).
|
||||
// The effect is similar to producing the usual top / bottom layers from a sliced mesh by
|
||||
// subtracting layer[i] from layer[i - 1] for the top surfaces resp.
|
||||
// subtracting layer[i] from layer[i + 1] for the bottom surfaces,
|
||||
// with the exception that the triangle set this function processes may not cover the whole top resp. bottom surface.
|
||||
// top resp. bottom surfaces are calculated only if out_top resp. out_bottom is not null.
|
||||
void slice_mesh_slabs(
|
||||
const indexed_triangle_set &mesh,
|
||||
// Unscaled Zs
|
||||
const std::vector<float> &zs,
|
||||
const Transform3d &trafo,
|
||||
std::vector<Polygons> *out_top,
|
||||
std::vector<Polygons> *out_bottom,
|
||||
std::function<void()> throw_on_cancel)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(debug) << "slice_mesh_slabs to polygons";
|
||||
|
||||
#ifdef EXPENSIVE_DEBUG_CHECKS
|
||||
{
|
||||
// Verify that the vertices are unique.
|
||||
auto v = mesh.vertices;
|
||||
std::sort(v.begin(), v.end(), [](auto &l, auto &r) {
|
||||
return l.x() < r.x() || (l.x() == r.x() && (l.y() < r.y() || (l.y() == r.y() && l.z() < r.z())));
|
||||
});
|
||||
size_t num_duplicates = v.end() - std::unique(v.begin(), v.end());
|
||||
assert(num_duplicates == 0);
|
||||
}
|
||||
if (0)
|
||||
{
|
||||
// Verify that there are no T-joints.
|
||||
// The T-joints could likely be already part of the source mesh.
|
||||
for (const auto &tri : mesh.indices)
|
||||
for (int i = 0; i < 3; ++ i) {
|
||||
int j = next_idx_modulo(i, 3);
|
||||
int k = next_idx_modulo(j, 3);
|
||||
auto &v1 = mesh.vertices[tri(i)];
|
||||
auto &v2 = mesh.vertices[tri(j)];
|
||||
auto &v3 = mesh.vertices[tri(k)];
|
||||
for (auto &pt : mesh.vertices)
|
||||
if (&pt != &v1 && &pt != &v2) {
|
||||
assert(pt != v1 && pt != v2);
|
||||
assert((pt - v1).norm() > EPSILON);
|
||||
assert((pt - v2).norm() > EPSILON);
|
||||
auto l2 = (v2 - v1).squaredNorm();
|
||||
assert(l2 > 0);
|
||||
auto t = (pt - v1).dot(v2 - v1);
|
||||
if (t > 0 && t < l2) {
|
||||
auto d2 = (pt - v1).squaredNorm() - sqr(t) / l2;
|
||||
auto d = sqrt(std::max(d2, 0.f));
|
||||
if (&pt == &v3) {
|
||||
if (d < EPSILON)
|
||||
printf("Degenerate triangle!\n");
|
||||
} else {
|
||||
assert(d > EPSILON);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // EXPENSIVE_DEBUG_CHECKS
|
||||
|
||||
std::vector<stl_vertex> vertices_transformed = transform_mesh_vertices_for_slicing(mesh, trafo);
|
||||
|
||||
std::vector<FaceOrientation> face_orientation(mesh.indices.size(), FaceOrientation::Up);
|
||||
for (const stl_triangle_vertex_indices &tri : mesh.indices) {
|
||||
const Vec3f fa = vertices_transformed[tri(0)];
|
||||
const Vec3f fb = vertices_transformed[tri(1)];
|
||||
const Vec3f fc = vertices_transformed[tri(2)];
|
||||
assert(fa != fb && fa != fc && fb != fc);
|
||||
const Point a = to_2d(fa).cast<coord_t>();
|
||||
const Point b = to_2d(fb).cast<coord_t>();
|
||||
const Point c = to_2d(fc).cast<coord_t>();
|
||||
const int64_t d = cross2((b - a).cast<int64_t>(), (c - b).cast<int64_t>());
|
||||
FaceOrientation fo = FaceOrientation::Vertical;
|
||||
if (d > 0)
|
||||
fo = FaceOrientation::Up;
|
||||
else if (d < 0)
|
||||
fo = FaceOrientation::Down;
|
||||
else {
|
||||
// Is the triangle vertical or degenerate?
|
||||
assert(d == 0);
|
||||
fo = fa == fb || fa == fc || fb == fc ? FaceOrientation::Degenerate : FaceOrientation::Vertical;
|
||||
}
|
||||
face_orientation[&tri - mesh.indices.data()] = fo;
|
||||
}
|
||||
|
||||
std::vector<Vec3i> face_neighbors = its_face_neighbors_par(mesh);
|
||||
int num_edges;
|
||||
std::vector<Vec3i> face_edge_ids = its_face_edge_ids(mesh, face_neighbors, true, &num_edges);
|
||||
std::pair<SlabLines, SlabLines> lines = slice_slabs_make_lines(
|
||||
vertices_transformed, mesh.indices, face_neighbors, face_edge_ids, num_edges, face_orientation, zs,
|
||||
out_top != nullptr, out_bottom != nullptr, throw_on_cancel);
|
||||
|
||||
throw_on_cancel();
|
||||
|
||||
if (out_top)
|
||||
*out_top = make_slab_loops<true>(lines.first, num_edges, throw_on_cancel);
|
||||
if (out_bottom)
|
||||
*out_bottom = make_slab_loops<false>(lines.second, num_edges, throw_on_cancel);
|
||||
}
|
||||
|
||||
// Remove duplicates of slice_vertices, optionally triangulate the cut.
|
||||
static void triangulate_slice(
|
||||
indexed_triangle_set &its,
|
||||
@ -1308,7 +1980,7 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u
|
||||
// To triangulate the caps after slicing.
|
||||
IntersectionLines upper_lines, lower_lines;
|
||||
std::vector<int> upper_slice_vertices, lower_slice_vertices;
|
||||
std::vector<Vec3i> facets_edges = create_face_neighbors_index(mesh);
|
||||
std::vector<Vec3i> facets_edge_ids = its_face_edge_ids(mesh);
|
||||
|
||||
for (int facet_idx = 0; facet_idx < int(mesh.indices.size()); ++ facet_idx) {
|
||||
const stl_triangle_vertex_indices &facet = mesh.indices[facet_idx];
|
||||
@ -1329,7 +2001,7 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u
|
||||
dst.y() = scale_(src.y());
|
||||
dst.z() = src.z();
|
||||
}
|
||||
slice_type = slice_facet(z, vertices_scaled, mesh.indices[facet_idx], facets_edges[facet_idx], idx_vertex_lowest, min_z == max_z, line);
|
||||
slice_type = slice_facet(z, vertices_scaled, mesh.indices[facet_idx], facets_edge_ids[facet_idx], idx_vertex_lowest, min_z == max_z, line);
|
||||
}
|
||||
|
||||
if (slice_type != FacetSliceType::NoSlice) {
|
||||
@ -1371,8 +2043,8 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u
|
||||
// get vertices starting from the isolated one
|
||||
int iv = isolated_vertex;
|
||||
stl_vertex v0v1, v2v0;
|
||||
assert(facets_edges[facet_idx](iv) == line.edge_a_id ||facets_edges[facet_idx](iv) == line.edge_b_id);
|
||||
if (facets_edges[facet_idx](iv) == line.edge_a_id) {
|
||||
assert(facets_edge_ids[facet_idx](iv) == line.edge_a_id ||facets_edge_ids[facet_idx](iv) == line.edge_b_id);
|
||||
if (facets_edge_ids[facet_idx](iv) == line.edge_a_id) {
|
||||
v0v1 = to_3d(unscaled<float>(line.a), z);
|
||||
v2v0 = to_3d(unscaled<float>(line.b), z);
|
||||
} else {
|
||||
|
@ -77,6 +77,21 @@ inline std::vector<ExPolygons> slice_mesh_ex(
|
||||
return slice_mesh_ex(mesh, zs, params, throw_on_cancel);
|
||||
}
|
||||
|
||||
// Slice a triangle set with a set of Z slabs (thick layers).
|
||||
// The effect is similar to producing the usual top / bottom layers from a sliced mesh by
|
||||
// subtracting layer[i] from layer[i - 1] for the top surfaces resp.
|
||||
// subtracting layer[i] from layer[i + 1] for the bottom surfaces,
|
||||
// with the exception that the triangle set this function processes may not cover the whole top resp. bottom surface.
|
||||
// top resp. bottom surfaces are calculated only if out_top resp. out_bottom is not null.
|
||||
void slice_mesh_slabs(
|
||||
const indexed_triangle_set &mesh,
|
||||
// Unscaled Zs
|
||||
const std::vector<float> &zs,
|
||||
const Transform3d &trafo,
|
||||
std::vector<Polygons> *out_top,
|
||||
std::vector<Polygons> *out_bottom,
|
||||
std::function<void()> throw_on_cancel);
|
||||
|
||||
void cut_mesh(
|
||||
const indexed_triangle_set &mesh,
|
||||
float z,
|
||||
|
@ -69,7 +69,70 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si
|
||||
this->special_side_idx = special_side_idx;
|
||||
}
|
||||
|
||||
inline bool is_point_inside_triangle(const Vec3f &pt, const Vec3f &p1, const Vec3f &p2, const Vec3f &p3)
|
||||
{
|
||||
// Real-time collision detection, Ericson, Chapter 3.4
|
||||
auto barycentric = [&pt, &p1, &p2, &p3]() -> Vec3f {
|
||||
std::array<Vec3f, 3> v = {p2 - p1, p3 - p1, pt - p1};
|
||||
float d00 = v[0].dot(v[0]);
|
||||
float d01 = v[0].dot(v[1]);
|
||||
float d11 = v[1].dot(v[1]);
|
||||
float d20 = v[2].dot(v[0]);
|
||||
float d21 = v[2].dot(v[1]);
|
||||
float denom = d00 * d11 - d01 * d01;
|
||||
|
||||
Vec3f barycentric_cords(1.f, (d11 * d20 - d01 * d21) / denom, (d00 * d21 - d01 * d20) / denom);
|
||||
barycentric_cords.x() = barycentric_cords.x() - barycentric_cords.y() - barycentric_cords.z();
|
||||
return barycentric_cords;
|
||||
};
|
||||
|
||||
Vec3f barycentric_cords = barycentric();
|
||||
return std::all_of(begin(barycentric_cords), end(barycentric_cords), [](float cord) { return 0.f <= cord && cord <= 1.0; });
|
||||
}
|
||||
|
||||
int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx, const Vec3i &neighbors) const
|
||||
{
|
||||
assert(facet_idx < int(m_triangles.size()));
|
||||
const Triangle *tr = &m_triangles[facet_idx];
|
||||
if (!tr->valid())
|
||||
return -1;
|
||||
|
||||
if (!tr->is_split()) {
|
||||
if (const std::array<int, 3> &t_vert = m_triangles[facet_idx].verts_idxs; is_point_inside_triangle(hit, m_vertices[t_vert[0]].v, m_vertices[t_vert[1]].v, m_vertices[t_vert[2]].v))
|
||||
return facet_idx;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
assert(this->verify_triangle_neighbors(*tr, neighbors));
|
||||
|
||||
int num_of_children = tr->number_of_split_sides() + 1;
|
||||
if (num_of_children != 1) {
|
||||
for (int i = 0; i < num_of_children; ++i) {
|
||||
assert(i < int(tr->children.size()));
|
||||
assert(tr->children[i] < int(m_triangles.size()));
|
||||
// Recursion, deep first search over the children of this triangle.
|
||||
// All children of this triangle were created by splitting a single source triangle of the original mesh.
|
||||
|
||||
const std::array<int, 3> &t_vert = m_triangles[tr->children[i]].verts_idxs;
|
||||
if (is_point_inside_triangle(hit, m_vertices[t_vert[0]].v, m_vertices[t_vert[1]].v, m_vertices[t_vert[2]].v))
|
||||
return this->select_unsplit_triangle(hit, tr->children[i], this->child_neighbors(*tr, neighbors, i));
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) const
|
||||
{
|
||||
assert(facet_idx < int(m_triangles.size()));
|
||||
if (!m_triangles[facet_idx].valid())
|
||||
return -1;
|
||||
|
||||
Vec3i neighbors = root_neighbors(*m_mesh, facet_idx);
|
||||
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
|
||||
return this->select_unsplit_triangle(hit, facet_idx, neighbors);
|
||||
}
|
||||
|
||||
void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
|
||||
const Vec3f& source, float radius,
|
||||
@ -116,19 +179,19 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
|
||||
}
|
||||
}
|
||||
|
||||
void TriangleSelector::seed_fill_select_triangles(const Vec3f& hit, int facet_start, float seed_fill_angle)
|
||||
void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, float seed_fill_angle)
|
||||
{
|
||||
assert(facet_start < m_orig_size_indices);
|
||||
this->seed_fill_unselect_all_triangles();
|
||||
|
||||
std::vector<bool> visited(m_triangles.size(), false);
|
||||
std::queue<int> facet_queue;
|
||||
std::queue<int> facet_queue;
|
||||
facet_queue.push(facet_start);
|
||||
|
||||
const double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle)) - EPSILON;
|
||||
|
||||
// Depth-first traversal of neighbors of the face hit by the ray thrown from the mouse cursor.
|
||||
while(!facet_queue.empty()) {
|
||||
while (!facet_queue.empty()) {
|
||||
int current_facet = facet_queue.front();
|
||||
facet_queue.pop();
|
||||
|
||||
@ -161,6 +224,129 @@ void TriangleSelector::seed_fill_select_triangles(const Vec3f& hit, int facet_st
|
||||
}
|
||||
}
|
||||
|
||||
void TriangleSelector::precompute_all_level_neighbors_recursive(const int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec3i> &neighbors_out) const
|
||||
{
|
||||
assert(facet_idx < int(m_triangles.size()));
|
||||
|
||||
const Triangle *tr = &m_triangles[facet_idx];
|
||||
if (!tr->valid())
|
||||
return;
|
||||
|
||||
neighbors_out[facet_idx] = neighbors_propagated;
|
||||
if (tr->is_split()) {
|
||||
assert(this->verify_triangle_neighbors(*tr, neighbors));
|
||||
|
||||
int num_of_children = tr->number_of_split_sides() + 1;
|
||||
if (num_of_children != 1) {
|
||||
for (int i = 0; i < num_of_children; ++i) {
|
||||
assert(i < int(tr->children.size()));
|
||||
assert(tr->children[i] < int(m_triangles.size()));
|
||||
// Recursion, deep first search over the children of this triangle.
|
||||
// All children of this triangle were created by splitting a single source triangle of the original mesh.
|
||||
this->precompute_all_level_neighbors_recursive(tr->children[i], this->child_neighbors(*tr, neighbors, i), this->child_neighbors_propagated(*tr, neighbors_propagated, i), neighbors_out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Vec3i> TriangleSelector::precompute_all_level_neighbors() const
|
||||
{
|
||||
std::vector<Vec3i> neighbors(m_triangles.size(), Vec3i(-1, -1, -1));
|
||||
for (int facet_idx = 0; facet_idx < this->m_orig_size_indices; ++facet_idx) {
|
||||
neighbors[facet_idx] = root_neighbors(*m_mesh, facet_idx);
|
||||
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors[facet_idx]));
|
||||
if (m_triangles[facet_idx].is_split())
|
||||
this->precompute_all_level_neighbors_recursive(facet_idx, neighbors[facet_idx], neighbors[facet_idx], neighbors);
|
||||
}
|
||||
return neighbors;
|
||||
}
|
||||
|
||||
bool TriangleSelector::are_triangles_touching(const int first_facet_idx, const int second_facet_idx) const
|
||||
{
|
||||
std::array<Linef3, 3> sides_facet = {Linef3(m_vertices[m_triangles[first_facet_idx].verts_idxs[0]].v.cast<double>(), m_vertices[m_triangles[first_facet_idx].verts_idxs[1]].v.cast<double>()),
|
||||
Linef3(m_vertices[m_triangles[first_facet_idx].verts_idxs[1]].v.cast<double>(), m_vertices[m_triangles[first_facet_idx].verts_idxs[2]].v.cast<double>()),
|
||||
Linef3(m_vertices[m_triangles[first_facet_idx].verts_idxs[2]].v.cast<double>(), m_vertices[m_triangles[first_facet_idx].verts_idxs[0]].v.cast<double>())};
|
||||
|
||||
const Vec3d p0 = m_vertices[m_triangles[second_facet_idx].verts_idxs[0]].v.cast<double>();
|
||||
const Vec3d p1 = m_vertices[m_triangles[second_facet_idx].verts_idxs[1]].v.cast<double>();
|
||||
const Vec3d p2 = m_vertices[m_triangles[second_facet_idx].verts_idxs[2]].v.cast<double>();
|
||||
|
||||
for (size_t idx = 0; idx < 3; ++idx)
|
||||
if (line_alg::distance_to_squared(sides_facet[idx], p0) <= EPSILON && (line_alg::distance_to_squared(sides_facet[idx], p1) <= EPSILON || line_alg::distance_to_squared(sides_facet[idx], p2) <= EPSILON))
|
||||
return true;
|
||||
else if (line_alg::distance_to_squared(sides_facet[idx], p1) <= EPSILON && line_alg::distance_to_squared(sides_facet[idx], p2) <= EPSILON)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<int> TriangleSelector::neighboring_triangles(const int first_facet_idx, const int second_facet_idx, EnforcerBlockerType second_facet_state) const
|
||||
{
|
||||
assert(first_facet_idx < int(m_triangles.size()));
|
||||
|
||||
const Triangle *tr = &m_triangles[first_facet_idx];
|
||||
if (!tr->valid())
|
||||
return {};
|
||||
|
||||
if (!tr->is_split() && tr->get_state() == second_facet_state && (are_triangles_touching(second_facet_idx, first_facet_idx) || are_triangles_touching(first_facet_idx, second_facet_idx)))
|
||||
return {first_facet_idx};
|
||||
|
||||
std::vector<int> neighbor_facets_out;
|
||||
int num_of_children = tr->number_of_split_sides() + 1;
|
||||
if (num_of_children != 1) {
|
||||
for (int i = 0; i < num_of_children; ++i) {
|
||||
assert(i < int(tr->children.size()));
|
||||
assert(tr->children[i] < int(m_triangles.size()));
|
||||
|
||||
if (std::vector<int> neighbor_facets = neighboring_triangles(tr->children[i], second_facet_idx, second_facet_state); !neighbor_facets.empty())
|
||||
Slic3r::append(neighbor_facets_out, std::move(neighbor_facets));
|
||||
}
|
||||
}
|
||||
|
||||
return neighbor_facets_out;
|
||||
}
|
||||
|
||||
void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, bool propagate)
|
||||
{
|
||||
this->seed_fill_unselect_all_triangles();
|
||||
|
||||
int start_facet_idx = select_unsplit_triangle(hit, facet_start);
|
||||
EnforcerBlockerType start_facet_state = m_triangles[start_facet_idx].get_state();
|
||||
if (start_facet_idx == -1)
|
||||
return;
|
||||
|
||||
if (!propagate) {
|
||||
m_triangles[start_facet_idx].select_by_seed_fill();
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Vec3i> all_level_neighbors = this->precompute_all_level_neighbors();
|
||||
std::vector<bool> visited(m_triangles.size(), false);
|
||||
std::queue<int> facet_queue;
|
||||
|
||||
facet_queue.push(start_facet_idx);
|
||||
while (!facet_queue.empty()) {
|
||||
int current_facet = facet_queue.front();
|
||||
facet_queue.pop();
|
||||
assert(!m_triangles[current_facet].is_split());
|
||||
|
||||
if (!visited[current_facet]) {
|
||||
m_triangles[current_facet].select_by_seed_fill();
|
||||
for(int neighbor_idx : all_level_neighbors[current_facet]) {
|
||||
if(!m_triangles[neighbor_idx].is_split()) {
|
||||
if(m_triangles[neighbor_idx].get_state() == start_facet_state)
|
||||
facet_queue.push(neighbor_idx);
|
||||
} else {
|
||||
for(int neighbor_facet_idx : neighboring_triangles(neighbor_idx, current_facet, start_facet_state))
|
||||
facet_queue.push(neighbor_facet_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visited[current_facet] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Selects either the whole triangle (discarding any children it had), or divides
|
||||
// the triangle recursively, selecting just subtriangles truly inside the circle.
|
||||
// This is done by an actual recursive call. Returns false if the triangle is
|
||||
@ -412,6 +598,89 @@ Vec3i TriangleSelector::child_neighbors(const Triangle &tr, const Vec3i &neighbo
|
||||
return out;
|
||||
}
|
||||
|
||||
// Return neighbors of the ith child of a triangle given neighbors of the triangle.
|
||||
// If such a neighbor doesn't exist, return the neighbor from the previous depth.
|
||||
Vec3i TriangleSelector::child_neighbors_propagated(const Triangle &tr, const Vec3i &neighbors, int child_idx) const
|
||||
{
|
||||
int i = tr.special_side();
|
||||
int j = i + 1;
|
||||
if (j >= 3) j = 0;
|
||||
int k = j + 1;
|
||||
if (k >= 3) k = 0;
|
||||
|
||||
Vec3i out;
|
||||
switch (tr.number_of_split_sides()) {
|
||||
case 1:
|
||||
switch (child_idx) {
|
||||
case 0:
|
||||
out(0) = neighbors(i);
|
||||
out(1) = neighbors(j);
|
||||
out(2) = tr.children[1];
|
||||
break;
|
||||
default:
|
||||
assert(child_idx == 1);
|
||||
out(0) = neighbors(j);
|
||||
out(1) = neighbors(k);
|
||||
out(2) = tr.children[0];
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
switch (child_idx) {
|
||||
case 0:
|
||||
out(0) = neighbors(i);
|
||||
out(1) = tr.children[1];
|
||||
out(2) = neighbors(k);
|
||||
break;
|
||||
case 1:
|
||||
assert(child_idx == 1);
|
||||
out(0) = neighbors(i);
|
||||
out(1) = tr.children[2];
|
||||
out(2) = tr.children[0];
|
||||
break;
|
||||
default:
|
||||
assert(child_idx == 2);
|
||||
out(0) = neighbors(j);
|
||||
out(1) = neighbors(k);
|
||||
out(2) = tr.children[1];
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
assert(tr.special_side() == 0);
|
||||
switch (child_idx) {
|
||||
case 0:
|
||||
out(0) = neighbors(0);
|
||||
out(1) = tr.children[3];
|
||||
out(2) = neighbors(2);
|
||||
break;
|
||||
case 1:
|
||||
out(0) = neighbors(0);
|
||||
out(1) = neighbors(1);
|
||||
out(2) = tr.children[3];
|
||||
break;
|
||||
case 2:
|
||||
out(0) = neighbors(1);
|
||||
out(1) = neighbors(2);
|
||||
out(2) = tr.children[3];
|
||||
break;
|
||||
default:
|
||||
assert(child_idx == 3);
|
||||
out(0) = tr.children[1];
|
||||
out(1) = tr.children[2];
|
||||
out(2) = tr.children[0];
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default: assert(false);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType type, bool triangle_splitting)
|
||||
{
|
||||
assert(facet_idx < int(m_triangles.size()));
|
||||
@ -810,7 +1079,11 @@ int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, co
|
||||
void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType old_state)
|
||||
{
|
||||
// Reserve space for the new triangles upfront, so that the reference to this triangle will not change.
|
||||
m_triangles.reserve(m_triangles.size() + m_triangles[facet_idx].number_of_split_sides() + 1);
|
||||
{
|
||||
size_t num_triangles_new = m_triangles.size() + m_triangles[facet_idx].number_of_split_sides() + 1;
|
||||
if (m_triangles.capacity() < num_triangles_new)
|
||||
m_triangles.reserve(next_highest_power_of_2(num_triangles_new));
|
||||
}
|
||||
|
||||
Triangle &tr = m_triangles[facet_idx];
|
||||
assert(tr.is_split());
|
||||
@ -918,7 +1191,7 @@ indexed_triangle_set TriangleSelector::get_facets_strict(EnforcerBlockerType sta
|
||||
++ num_vertices;
|
||||
out.vertices.reserve(num_vertices);
|
||||
std::vector<int> vertex_map(m_vertices.size(), -1);
|
||||
for (int i = 0; i < m_vertices.size(); ++ i)
|
||||
for (size_t i = 0; i < m_vertices.size(); ++ i)
|
||||
if (const Vertex &v = m_vertices[i]; v.ref_cnt > 0) {
|
||||
vertex_map[i] = int(out.vertices.size());
|
||||
out.vertices.emplace_back(v.v);
|
||||
@ -958,10 +1231,13 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i vertices, const V
|
||||
this->triangle_midpoint(neighbors(1), vertices(2), vertices(1)),
|
||||
this->triangle_midpoint(neighbors(2), vertices(0), vertices(2)));
|
||||
int splits = (midpoints(0) != -1) + (midpoints(1) != -1) + (midpoints(2) != -1);
|
||||
if (splits == 0) {
|
||||
switch (splits) {
|
||||
case 0:
|
||||
// Just emit this triangle.
|
||||
out_triangles.emplace_back(vertices(0), midpoints(0), midpoints(2));
|
||||
} else if (splits == 1) {
|
||||
out_triangles.emplace_back(vertices(0), vertices(1), vertices(2));
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
// Split to two triangles
|
||||
int i = midpoints(0) != -1 ? 2 : midpoints(1) != -1 ? 0 : 1;
|
||||
int j = next_idx_modulo(i, 3);
|
||||
@ -969,16 +1245,19 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i vertices, const V
|
||||
this->get_facets_split_by_tjoints(
|
||||
{ vertices(i), vertices(j), midpoints(j) },
|
||||
{ neighbors(i),
|
||||
this->neighbor_child(neighbors(j), vertices(j), vertices(k), Partition::Second),
|
||||
this->neighbor_child(neighbors(j), vertices(k), vertices(j), Partition::Second),
|
||||
-1 },
|
||||
out_triangles);
|
||||
this->get_facets_split_by_tjoints(
|
||||
{ midpoints(j), vertices(j), vertices(k) },
|
||||
{ this->neighbor_child(neighbors(j), vertices(j), vertices(k), Partition::First),
|
||||
{ midpoints(j), vertices(k), vertices(i) },
|
||||
{ this->neighbor_child(neighbors(j), vertices(k), vertices(j), Partition::First),
|
||||
neighbors(k),
|
||||
-1 },
|
||||
out_triangles);
|
||||
} else if (splits == 2) {
|
||||
break;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
// Split to three triangles.
|
||||
int i = midpoints(0) == -1 ? 2 : midpoints(1) == -1 ? 0 : 1;
|
||||
int j = next_idx_modulo(i, 3);
|
||||
@ -1000,7 +1279,10 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i vertices, const V
|
||||
this->neighbor_child(neighbors(k), vertices(i), vertices(k), Partition::Second),
|
||||
-1 },
|
||||
out_triangles);
|
||||
} else if (splits == 4) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
assert(splits == 3);
|
||||
// Split to 4 triangles.
|
||||
this->get_facets_split_by_tjoints(
|
||||
{ vertices(0), midpoints(0), midpoints(2) },
|
||||
@ -1021,6 +1303,7 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i vertices, const V
|
||||
-1 },
|
||||
out_triangles);
|
||||
out_triangles.emplace_back(midpoints);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1106,6 +1389,13 @@ void TriangleSelector::deserialize(const std::pair<std::vector<std::pair<int, in
|
||||
{
|
||||
reset(); // dump any current state
|
||||
|
||||
// Reserve number of triangles as if each triangle was saved with 4 bits.
|
||||
// With MMU painting this estimate may be somehow low, but better than nothing.
|
||||
m_triangles.reserve(std::max(m_mesh->its.indices.size(), data.second.size() / 4));
|
||||
// Number of triangles is twice the number of vertices on a large manifold mesh of genus zero.
|
||||
// Here the triangles count account for both the nodes and leaves, thus the following line may overestimate.
|
||||
m_vertices.reserve(std::max(m_mesh->its.vertices.size(), m_triangles.size() / 2));
|
||||
|
||||
// Vector to store all parents that have offsprings.
|
||||
struct ProcessingInfo {
|
||||
int facet_id = 0;
|
||||
@ -1203,7 +1493,8 @@ bool TriangleSelector::has_facets(const std::pair<std::vector<std::pair<int, int
|
||||
std::vector<int> parents_children;
|
||||
parents_children.reserve(64);
|
||||
|
||||
for (auto [triangle_id, ibit] : data.first) {
|
||||
for (const std::pair<int, int> &triangle_id_and_ibit : data.first) {
|
||||
int ibit = triangle_id_and_ibit.second;
|
||||
assert(ibit < data.second.size());
|
||||
auto next_nibble = [&data, &ibit = ibit]() {
|
||||
int n = 0;
|
||||
|
@ -18,9 +18,13 @@ class TriangleSelector {
|
||||
public:
|
||||
enum CursorType {
|
||||
CIRCLE,
|
||||
SPHERE
|
||||
SPHERE,
|
||||
POINTER
|
||||
};
|
||||
|
||||
[[nodiscard]] std::vector<Vec3i> precompute_all_level_neighbors() const;
|
||||
void precompute_all_level_neighbors_recursive(const int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec3i> &neighbors_out) const;
|
||||
|
||||
// Set a limit to the edge length, below which the edge will not be split by select_patch().
|
||||
// Called by select_patch() internally. Made public for debugging purposes, see TriangleSelectorGUI::render_debug().
|
||||
void set_edge_limit(float edge_limit);
|
||||
@ -29,6 +33,14 @@ public:
|
||||
// stay valid, a ptr to it is saved and used.
|
||||
explicit TriangleSelector(const TriangleMesh& mesh);
|
||||
|
||||
// Returns the facet_idx of the unsplit triangle containing the "hit". Returns -1 if the triangle isn't found.
|
||||
[[nodiscard]] int select_unsplit_triangle(const Vec3f &hit, int facet_idx) const;
|
||||
[[nodiscard]] int select_unsplit_triangle(const Vec3f &hit, int facet_idx, const Vec3i &neighbors) const;
|
||||
|
||||
[[nodiscard]] bool are_triangles_touching(int first_facet_idx, int second_facet_idx) const;
|
||||
|
||||
[[nodiscard]] std::vector<int> neighboring_triangles(int first_facet_idx, int second_facet_idx, EnforcerBlockerType second_facet_state) const;
|
||||
|
||||
// Select all triangles fully inside the circle, subdivide where needed.
|
||||
void select_patch(const Vec3f &hit, // point where to start
|
||||
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
|
||||
@ -43,6 +55,10 @@ public:
|
||||
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
|
||||
float seed_fill_angle); // the maximal angle between two facets to be painted by the same color
|
||||
|
||||
void bucket_fill_select_triangles(const Vec3f &hit, // point where to start
|
||||
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
|
||||
bool propagate); // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to.
|
||||
|
||||
bool has_facets(EnforcerBlockerType state) const;
|
||||
static bool has_facets(const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> &data, const EnforcerBlockerType test_state);
|
||||
int num_facets(EnforcerBlockerType state) const;
|
||||
@ -192,6 +208,7 @@ private:
|
||||
int push_triangle(int a, int b, int c, int source_triangle, const EnforcerBlockerType state = EnforcerBlockerType{0});
|
||||
void perform_split(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType old_state);
|
||||
Vec3i child_neighbors(const Triangle &tr, const Vec3i &neighbors, int child_idx) const;
|
||||
Vec3i child_neighbors_propagated(const Triangle &tr, const Vec3i &neighbors, int child_idx) const;
|
||||
// Return child of itriangle at a CCW oriented side (vertexi, vertexj), either first or 2nd part.
|
||||
// If itriangle == -1 or if the side sharing (vertexi, vertexj) is not split, return -1.
|
||||
enum class Partition {
|
||||
|
@ -45,6 +45,11 @@ void set_local_dir(const std::string &path);
|
||||
// Return a full path to the localization directory.
|
||||
const std::string& localization_dir();
|
||||
|
||||
// Set a path with shapes gallery files.
|
||||
void set_sys_shapes_dir(const std::string &path);
|
||||
// Return a full path to the system shapes gallery directory.
|
||||
const std::string& sys_shapes_dir();
|
||||
|
||||
// Set a path with preset files.
|
||||
void set_data_dir(const std::string &path);
|
||||
// Return a full path to the GUI resource files.
|
||||
@ -91,6 +96,9 @@ extern bool is_plain_file(const boost::filesystem::directory_entry &path);
|
||||
extern bool is_ini_file(const boost::filesystem::directory_entry &path);
|
||||
extern bool is_idx_file(const boost::filesystem::directory_entry &path);
|
||||
extern bool is_gcode_file(const std::string &path);
|
||||
extern bool is_img_file(const std::string& path);
|
||||
extern bool is_stl_file(const boost::filesystem::directory_entry& path);
|
||||
extern bool is_stl_file(const std::string& path);
|
||||
|
||||
// File path / name / extension splitting utilities, working with UTF-8,
|
||||
// to be published to Perl.
|
||||
|
@ -175,6 +175,18 @@ const std::string& localization_dir()
|
||||
return g_local_dir;
|
||||
}
|
||||
|
||||
static std::string g_sys_shapes_dir;
|
||||
|
||||
void set_sys_shapes_dir(const std::string &dir)
|
||||
{
|
||||
g_sys_shapes_dir = dir;
|
||||
}
|
||||
|
||||
const std::string& sys_shapes_dir()
|
||||
{
|
||||
return g_sys_shapes_dir;
|
||||
}
|
||||
|
||||
// Translate function callback, to call wxWidgets translate function to convert non-localized UTF8 string to a localized one.
|
||||
Slic3r::I18N::translate_fn_type Slic3r::I18N::translate_fn = nullptr;
|
||||
|
||||
@ -744,6 +756,21 @@ bool is_gcode_file(const std::string &path)
|
||||
boost::iends_with(path, ".g") || boost::iends_with(path, ".ngc");
|
||||
}
|
||||
|
||||
bool is_img_file(const std::string &path)
|
||||
{
|
||||
return boost::iends_with(path, ".png") || boost::iends_with(path, ".svg");
|
||||
}
|
||||
|
||||
bool is_stl_file(const boost::filesystem::directory_entry& dir_entry)
|
||||
{
|
||||
return is_plain_file(dir_entry) && strcasecmp(dir_entry.path().extension().string().c_str(), ".stl") == 0;
|
||||
}
|
||||
|
||||
bool is_stl_file(const std::string &path)
|
||||
{
|
||||
return boost::iends_with(path, ".stl");
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#ifdef WIN32
|
||||
|
@ -103,6 +103,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/GUI_Factories.hpp
|
||||
GUI/GUI_ObjectList.cpp
|
||||
GUI/GUI_ObjectList.hpp
|
||||
GUI/GalleryDialog.cpp
|
||||
GUI/GalleryDialog.hpp
|
||||
GUI/GUI_ObjectManipulation.cpp
|
||||
GUI/GUI_ObjectManipulation.hpp
|
||||
GUI/GUI_ObjectSettings.cpp
|
||||
|
@ -748,21 +748,18 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo
|
||||
GLVolumeWithIdAndZList list;
|
||||
list.reserve(volumes.size());
|
||||
|
||||
for (unsigned int i = 0; i < (unsigned int)volumes.size(); ++i)
|
||||
{
|
||||
for (unsigned int i = 0; i < (unsigned int)volumes.size(); ++i) {
|
||||
GLVolume* volume = volumes[i];
|
||||
bool is_transparent = (volume->render_color[3] < 1.0f);
|
||||
if ((((type == GLVolumeCollection::Opaque) && !is_transparent) ||
|
||||
((type == GLVolumeCollection::Transparent) && is_transparent) ||
|
||||
(type == GLVolumeCollection::All)) &&
|
||||
if (((type == GLVolumeCollection::ERenderType::Opaque && !is_transparent) ||
|
||||
(type == GLVolumeCollection::ERenderType::Transparent && is_transparent) ||
|
||||
type == GLVolumeCollection::ERenderType::All) &&
|
||||
(! filter_func || filter_func(*volume)))
|
||||
list.emplace_back(std::make_pair(volume, std::make_pair(i, 0.0)));
|
||||
}
|
||||
|
||||
if ((type == GLVolumeCollection::Transparent) && (list.size() > 1))
|
||||
{
|
||||
for (GLVolumeWithIdAndZ& volume : list)
|
||||
{
|
||||
if (type == GLVolumeCollection::ERenderType::Transparent && list.size() > 1) {
|
||||
for (GLVolumeWithIdAndZ& volume : list) {
|
||||
volume.second.second = volume.first->bounding_box().transformed(view_matrix * volume.first->world_matrix()).max(2);
|
||||
}
|
||||
|
||||
@ -770,8 +767,7 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo
|
||||
[](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool { return v1.second.second < v2.second.second; }
|
||||
);
|
||||
}
|
||||
else if ((type == GLVolumeCollection::Opaque) && (list.size() > 1))
|
||||
{
|
||||
else if (type == GLVolumeCollection::ERenderType::Opaque && list.size() > 1) {
|
||||
std::sort(list.begin(), list.end(),
|
||||
[](const GLVolumeWithIdAndZ& v1, const GLVolumeWithIdAndZ& v2) -> bool { return v1.first->selected && !v2.first->selected; }
|
||||
);
|
||||
@ -786,8 +782,10 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
|
||||
if (shader == nullptr)
|
||||
return;
|
||||
|
||||
glsafe(::glEnable(GL_BLEND));
|
||||
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
|
||||
if (type == ERenderType::Transparent) {
|
||||
glsafe(::glEnable(GL_BLEND));
|
||||
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
|
||||
}
|
||||
|
||||
glsafe(::glCullFace(GL_BACK));
|
||||
if (disable_cullface)
|
||||
@ -840,7 +838,8 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
|
||||
if (disable_cullface)
|
||||
glsafe(::glEnable(GL_CULL_FACE));
|
||||
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
if (type == ERenderType::Transparent)
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
}
|
||||
|
||||
bool GLVolumeCollection::check_outside_state(const DynamicPrintConfig* config, ModelInstanceEPrintVolumeState* out_state) const
|
||||
|
@ -39,26 +39,22 @@ enum ModelInstanceEPrintVolumeState : unsigned char;
|
||||
// possibly indexed by triangles and / or quads.
|
||||
class GLIndexedVertexArray {
|
||||
public:
|
||||
GLIndexedVertexArray() :
|
||||
vertices_and_normals_interleaved_VBO_id(0),
|
||||
triangle_indices_VBO_id(0),
|
||||
quad_indices_VBO_id(0)
|
||||
{}
|
||||
// Only Eigen types of Nx16 size are vectorized. This bounding box will not be vectorized.
|
||||
static_assert(sizeof(Eigen::AlignedBox<float, 3>) == 24, "Eigen::AlignedBox<float, 3> is not being vectorized, thus it does not need to be aligned");
|
||||
using BoundingBox = Eigen::AlignedBox<float, 3>;
|
||||
|
||||
GLIndexedVertexArray() { m_bounding_box.setEmpty(); }
|
||||
GLIndexedVertexArray(const GLIndexedVertexArray &rhs) :
|
||||
vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved),
|
||||
triangle_indices(rhs.triangle_indices),
|
||||
quad_indices(rhs.quad_indices),
|
||||
vertices_and_normals_interleaved_VBO_id(0),
|
||||
triangle_indices_VBO_id(0),
|
||||
quad_indices_VBO_id(0)
|
||||
{ assert(! rhs.has_VBOs()); }
|
||||
m_bounding_box(rhs.m_bounding_box)
|
||||
{ assert(! rhs.has_VBOs()); m_bounding_box.setEmpty(); }
|
||||
GLIndexedVertexArray(GLIndexedVertexArray &&rhs) :
|
||||
vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)),
|
||||
triangle_indices(std::move(rhs.triangle_indices)),
|
||||
quad_indices(std::move(rhs.quad_indices)),
|
||||
vertices_and_normals_interleaved_VBO_id(0),
|
||||
triangle_indices_VBO_id(0),
|
||||
quad_indices_VBO_id(0)
|
||||
m_bounding_box(rhs.m_bounding_box)
|
||||
{ assert(! rhs.has_VBOs()); }
|
||||
|
||||
~GLIndexedVertexArray() { release_geometry(); }
|
||||
@ -92,7 +88,7 @@ public:
|
||||
this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved);
|
||||
this->triangle_indices = std::move(rhs.triangle_indices);
|
||||
this->quad_indices = std::move(rhs.quad_indices);
|
||||
this->m_bounding_box = std::move(rhs.m_bounding_box);
|
||||
this->m_bounding_box = rhs.m_bounding_box;
|
||||
this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size;
|
||||
this->triangle_indices_size = rhs.triangle_indices_size;
|
||||
this->quad_indices_size = rhs.quad_indices_size;
|
||||
@ -147,7 +143,7 @@ public:
|
||||
this->vertices_and_normals_interleaved.emplace_back(z);
|
||||
|
||||
this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size();
|
||||
m_bounding_box.merge(Vec3f(x, y, z).cast<double>());
|
||||
m_bounding_box.extend(Vec3f(x, y, z));
|
||||
};
|
||||
|
||||
inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) {
|
||||
@ -203,10 +199,10 @@ public:
|
||||
this->vertices_and_normals_interleaved.clear();
|
||||
this->triangle_indices.clear();
|
||||
this->quad_indices.clear();
|
||||
this->m_bounding_box.reset();
|
||||
vertices_and_normals_interleaved_size = 0;
|
||||
triangle_indices_size = 0;
|
||||
quad_indices_size = 0;
|
||||
m_bounding_box.setEmpty();
|
||||
}
|
||||
|
||||
// Shrink the internal storage to tighly fit the data stored.
|
||||
@ -216,7 +212,7 @@ public:
|
||||
this->quad_indices.shrink_to_fit();
|
||||
}
|
||||
|
||||
const BoundingBoxf3& bounding_box() const { return m_bounding_box; }
|
||||
const BoundingBox& bounding_box() const { return m_bounding_box; }
|
||||
|
||||
// Return an estimate of the memory consumed by this class.
|
||||
size_t cpu_memory_used() const { return sizeof(*this) + vertices_and_normals_interleaved.capacity() * sizeof(float) + triangle_indices.capacity() * sizeof(int) + quad_indices.capacity() * sizeof(int); }
|
||||
@ -235,7 +231,7 @@ public:
|
||||
size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); }
|
||||
|
||||
private:
|
||||
BoundingBoxf3 m_bounding_box;
|
||||
BoundingBox m_bounding_box;
|
||||
};
|
||||
|
||||
class GLVolume {
|
||||
@ -355,7 +351,15 @@ public:
|
||||
std::vector<size_t> offsets;
|
||||
|
||||
// Bounding box of this volume, in unscaled coordinates.
|
||||
const BoundingBoxf3& bounding_box() const { return this->indexed_vertex_array.bounding_box(); }
|
||||
BoundingBoxf3 bounding_box() const {
|
||||
BoundingBoxf3 out;
|
||||
if (! this->indexed_vertex_array.bounding_box().isEmpty()) {
|
||||
out.min = this->indexed_vertex_array.bounding_box().min().cast<double>();
|
||||
out.max = this->indexed_vertex_array.bounding_box().max().cast<double>();
|
||||
out.defined = true;
|
||||
};
|
||||
return out;
|
||||
}
|
||||
|
||||
void set_render_color(float r, float g, float b, float a);
|
||||
void set_render_color(const float* rgba, unsigned int size);
|
||||
@ -476,7 +480,7 @@ typedef std::vector<GLVolumeWithIdAndZ> GLVolumeWithIdAndZList;
|
||||
class GLVolumeCollection
|
||||
{
|
||||
public:
|
||||
enum ERenderType : unsigned char
|
||||
enum class ERenderType : unsigned char
|
||||
{
|
||||
Opaque,
|
||||
Transparent,
|
||||
|
@ -72,8 +72,7 @@ CopyrightsDialog::CopyrightsDialog()
|
||||
m_html->Bind(wxEVT_HTML_LINK_CLICKED, &CopyrightsDialog::onLinkClicked, this);
|
||||
|
||||
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE);
|
||||
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_CLOSE, this)));
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this, true);
|
||||
this->SetEscapeId(wxID_CLOSE);
|
||||
this->Bind(wxEVT_BUTTON, &CopyrightsDialog::onCloseDialog, this, wxID_CLOSE);
|
||||
sizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
|
||||
@ -297,20 +296,18 @@ AboutDialog::AboutDialog()
|
||||
|
||||
|
||||
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxCLOSE);
|
||||
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_CLOSE, this)));
|
||||
|
||||
m_copy_rights_btn_id = NewControlId();
|
||||
auto copy_rights_btn = new wxButton(this, m_copy_rights_btn_id, _L("Portions copyright")+dots);
|
||||
buttons->Insert(0, copy_rights_btn, 0, wxLEFT, 5);
|
||||
buttons->Insert(0, copy_rights_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
|
||||
copy_rights_btn->Bind(wxEVT_BUTTON, &AboutDialog::onCopyrightBtn, this);
|
||||
|
||||
m_copy_version_btn_id = NewControlId();
|
||||
auto copy_version_btn = new wxButton(this, m_copy_version_btn_id, _L("Copy Version Info"));
|
||||
buttons->Insert(1, copy_version_btn, 0, wxLEFT, 5);
|
||||
buttons->Insert(1, copy_version_btn, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
|
||||
copy_version_btn->Bind(wxEVT_BUTTON, &AboutDialog::onCopyToClipboard, this);
|
||||
|
||||
wxGetApp().UpdateDarkUI(copy_rights_btn);
|
||||
wxGetApp().UpdateDarkUI(copy_version_btn);
|
||||
wxGetApp().UpdateDlgDarkUI(this, true);
|
||||
|
||||
this->SetEscapeId(wxID_CLOSE);
|
||||
this->Bind(wxEVT_BUTTON, &AboutDialog::onCloseDialog, this, wxID_CLOSE);
|
||||
|
@ -177,7 +177,6 @@ void BedShape::apply_optgroup_values(ConfigOptionsGroupShp optgroup)
|
||||
void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model)
|
||||
{
|
||||
SetFont(wxGetApp().normal_font());
|
||||
// wxGetApp().UpdateDarkUI(this);
|
||||
|
||||
m_panel = new BedShapePanel(this);
|
||||
m_panel->build_panel(default_pt, custom_texture, custom_model);
|
||||
@ -186,8 +185,7 @@ void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt, const Co
|
||||
main_sizer->Add(m_panel, 1, wxEXPAND);
|
||||
main_sizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
|
||||
|
||||
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_OK, this)), true);
|
||||
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_CANCEL, this)), true);
|
||||
wxGetApp().UpdateDlgDarkUI(this, true);
|
||||
|
||||
SetSizer(main_sizer);
|
||||
SetMinSize(GetSize());
|
||||
@ -249,7 +247,6 @@ void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const Conf
|
||||
line.full_width = 1;
|
||||
line.widget = [this](wxWindow* parent) {
|
||||
wxButton* shape_btn = new wxButton(parent, wxID_ANY, _L("Load shape from STL..."));
|
||||
wxGetApp().UpdateDarkUI(shape_btn, true);
|
||||
wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
shape_sizer->Add(shape_btn, 1, wxEXPAND);
|
||||
|
||||
@ -333,7 +330,6 @@ wxPanel* BedShapePanel::init_texture_panel()
|
||||
line.full_width = 1;
|
||||
line.widget = [this](wxWindow* parent) {
|
||||
wxButton* load_btn = new wxButton(parent, wxID_ANY, _(L("Load...")));
|
||||
wxGetApp().UpdateDarkUI(load_btn, true);
|
||||
wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
load_sizer->Add(load_btn, 1, wxEXPAND);
|
||||
|
||||
@ -343,7 +339,6 @@ wxPanel* BedShapePanel::init_texture_panel()
|
||||
filename_sizer->Add(filename_lbl, 1, wxEXPAND);
|
||||
|
||||
wxButton* remove_btn = new wxButton(parent, wxID_ANY, _(L("Remove")));
|
||||
wxGetApp().UpdateDarkUI(remove_btn, true);
|
||||
wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
remove_sizer->Add(remove_btn, 1, wxEXPAND);
|
||||
|
||||
@ -417,7 +412,6 @@ wxPanel* BedShapePanel::init_model_panel()
|
||||
line.full_width = 1;
|
||||
line.widget = [this](wxWindow* parent) {
|
||||
wxButton* load_btn = new wxButton(parent, wxID_ANY, _(L("Load...")));
|
||||
wxGetApp().UpdateDarkUI(load_btn, true);
|
||||
wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
load_sizer->Add(load_btn, 1, wxEXPAND);
|
||||
|
||||
@ -426,7 +420,6 @@ wxPanel* BedShapePanel::init_model_panel()
|
||||
filename_sizer->Add(filename_lbl, 1, wxEXPAND);
|
||||
|
||||
wxButton* remove_btn = new wxButton(parent, wxID_ANY, _(L("Remove")));
|
||||
wxGetApp().UpdateDarkUI(remove_btn, true);
|
||||
wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
remove_sizer->Add(remove_btn, 1, wxEXPAND);
|
||||
|
||||
|
@ -70,9 +70,9 @@ ButtonsDescription::ButtonsDescription(wxWindow* parent, const std::vector<Entry
|
||||
auto buttons = CreateStdDialogButtonSizer(wxOK|wxCANCEL);
|
||||
main_sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM, 10);
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this, true);
|
||||
|
||||
wxButton* btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
|
||||
wxGetApp().UpdateDarkUI(btn);
|
||||
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_CANCEL, this)));
|
||||
btn->Bind(wxEVT_BUTTON, [sys_colour, mod_colour, this](wxCommandEvent&) {
|
||||
wxGetApp().set_label_clr_sys(sys_colour->GetColour());
|
||||
wxGetApp().set_label_clr_modified(mod_colour->GetColour());
|
||||
|
@ -25,19 +25,19 @@ std::string Camera::get_type_as_string() const
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
case Unknown: return "unknown";
|
||||
case Perspective: return "perspective";
|
||||
case EType::Unknown: return "unknown";
|
||||
case EType::Perspective: return "perspective";
|
||||
default:
|
||||
case Ortho: return "orthographic";
|
||||
case EType::Ortho: return "orthographic";
|
||||
};
|
||||
}
|
||||
|
||||
void Camera::set_type(EType type)
|
||||
{
|
||||
if (m_type != type) {
|
||||
if (m_type != type && (type == EType::Ortho || type == EType::Perspective)) {
|
||||
m_type = type;
|
||||
if (m_update_config_on_type_change_enabled) {
|
||||
wxGetApp().app_config->set("use_perspective_camera", (m_type == Perspective) ? "1" : "0");
|
||||
wxGetApp().app_config->set("use_perspective_camera", (m_type == EType::Perspective) ? "1" : "0");
|
||||
wxGetApp().app_config->save();
|
||||
}
|
||||
}
|
||||
@ -46,7 +46,7 @@ void Camera::set_type(EType type)
|
||||
void Camera::select_next_type()
|
||||
{
|
||||
unsigned char next = (unsigned char)m_type + 1;
|
||||
if (next == (unsigned char)Num_types)
|
||||
if (next == (unsigned char)EType::Num_types)
|
||||
next = 1;
|
||||
|
||||
set_type((EType)next);
|
||||
@ -95,10 +95,10 @@ double Camera::get_fov() const
|
||||
{
|
||||
switch (m_type)
|
||||
{
|
||||
case Perspective:
|
||||
case EType::Perspective:
|
||||
return 2.0 * Geometry::rad2deg(std::atan(1.0 / m_projection_matrix.matrix()(1, 1)));
|
||||
default:
|
||||
case Ortho:
|
||||
case EType::Ortho:
|
||||
return 0.0;
|
||||
};
|
||||
}
|
||||
@ -143,12 +143,12 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa
|
||||
switch (m_type)
|
||||
{
|
||||
default:
|
||||
case Ortho:
|
||||
case EType::Ortho:
|
||||
{
|
||||
m_gui_scale = 1.0;
|
||||
break;
|
||||
}
|
||||
case Perspective:
|
||||
case EType::Perspective:
|
||||
{
|
||||
// scale near plane to keep w and h constant on the plane at z = m_distance
|
||||
const double scale = m_frustrum_zs.first / m_distance;
|
||||
@ -165,12 +165,12 @@ void Camera::apply_projection(const BoundingBoxf3& box, double near_z, double fa
|
||||
switch (m_type)
|
||||
{
|
||||
default:
|
||||
case Ortho:
|
||||
case EType::Ortho:
|
||||
{
|
||||
glsafe(::glOrtho(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second));
|
||||
break;
|
||||
}
|
||||
case Perspective:
|
||||
case EType::Perspective:
|
||||
{
|
||||
glsafe(::glFrustum(-w, w, -h, h, m_frustrum_zs.first, m_frustrum_zs.second));
|
||||
break;
|
||||
@ -501,7 +501,7 @@ void Camera::set_default_orientation()
|
||||
const Vec3d camera_pos = m_target + m_distance * Vec3d(sin_theta * ::sin(phi_rad), sin_theta * ::cos(phi_rad), ::cos(theta_rad));
|
||||
m_view_rotation = Eigen::AngleAxisd(theta_rad, Vec3d::UnitX()) * Eigen::AngleAxisd(phi_rad, Vec3d::UnitZ());
|
||||
m_view_rotation.normalize();
|
||||
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (- camera_pos), m_view_rotation, Vec3d(1., 1., 1.));
|
||||
m_view_matrix.fromPositionOrientationScale(m_view_rotation * (-camera_pos), m_view_rotation, Vec3d::Ones());
|
||||
}
|
||||
|
||||
Vec3d Camera::validate_target(const Vec3d& target) const
|
||||
|
@ -18,7 +18,7 @@ struct Camera
|
||||
static double FrustrumZMargin;
|
||||
static double MaxFovDeg;
|
||||
|
||||
enum EType : unsigned char
|
||||
enum class EType : unsigned char
|
||||
{
|
||||
Unknown,
|
||||
Ortho,
|
||||
@ -29,7 +29,7 @@ struct Camera
|
||||
bool requires_zoom_to_bed{ false };
|
||||
|
||||
private:
|
||||
EType m_type{ Perspective };
|
||||
EType m_type{ EType::Perspective };
|
||||
bool m_update_config_on_type_change_enabled{ false };
|
||||
Vec3d m_target{ Vec3d::Zero() };
|
||||
float m_zenit{ 45.0f };
|
||||
@ -54,7 +54,7 @@ public:
|
||||
std::string get_type_as_string() const;
|
||||
void set_type(EType type);
|
||||
// valid values for type: "0" -> ortho, "1" -> perspective
|
||||
void set_type(const std::string& type) { set_type((type == "1") ? Perspective : Ortho); }
|
||||
void set_type(const std::string& type) { set_type((type == "1") ? EType::Perspective : EType::Ortho); }
|
||||
void select_next_type();
|
||||
|
||||
void enable_update_config_on_type_change(bool enable) { m_update_config_on_type_change_enabled = enable; }
|
||||
|
@ -276,7 +276,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config)
|
||||
toggle_field(el, have_default_acceleration);
|
||||
|
||||
bool have_skirt = config->opt_int("skirts") > 0;
|
||||
toggle_field("skirt_height", have_skirt && !config->opt_bool("draft_shield"));
|
||||
toggle_field("skirt_height", have_skirt && config->opt_enum<DraftShield>("draft_shield") != dsEnabled);
|
||||
for (auto el : { "skirt_distance", "draft_shield", "min_skirt_length" })
|
||||
toggle_field(el, have_skirt);
|
||||
|
||||
|
@ -992,22 +992,20 @@ void PageMaterials::update_lists(int sel_type, int sel_vendor, int last_selected
|
||||
}
|
||||
|
||||
materials->filter_presets(printer, type, vendor, [this, &to_list](const Preset* p) {
|
||||
bool was_checked = false;
|
||||
//size_t printer_counter = materials->get_printer_counter(p);
|
||||
int cur_i = list_profile->find(p->alias);
|
||||
bool emplace_to_to_list = false;
|
||||
if (cur_i == wxNOT_FOUND) {
|
||||
cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias);
|
||||
emplace_to_to_list = true;
|
||||
} else
|
||||
was_checked = list_profile->IsChecked(cur_i);
|
||||
|
||||
const std::string& section = materials->appconfig_section();
|
||||
bool checked = wizard_p()->appconfig_new.has(section, p->name);
|
||||
bool was_checked = false;
|
||||
|
||||
const bool checked = wizard_p()->appconfig_new.has(section, p->name);
|
||||
list_profile->Check(cur_i, checked || was_checked);
|
||||
if (emplace_to_to_list)
|
||||
to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked || was_checked);
|
||||
int cur_i = list_profile->find(p->alias);
|
||||
if (cur_i == wxNOT_FOUND) {
|
||||
cur_i = list_profile->append(p->alias + (materials->get_omnipresent(p) ? "" : " *"), &p->alias);
|
||||
to_list.emplace_back(p->alias, materials->get_omnipresent(p), checked);
|
||||
}
|
||||
else {
|
||||
was_checked = list_profile->IsChecked(cur_i);
|
||||
to_list[cur_i].checked = checked || was_checked;
|
||||
}
|
||||
list_profile->Check(cur_i, checked || was_checked);
|
||||
|
||||
/* Update preset selection in config.
|
||||
* If one preset from aliases bundle is selected,
|
||||
@ -2335,6 +2333,7 @@ void ConfigWizard::priv::on_3rdparty_install(const VendorProfile *vendor, bool i
|
||||
|
||||
bool ConfigWizard::priv::on_bnt_finish()
|
||||
{
|
||||
wxBusyCursor wait;
|
||||
/* When Filaments or Sla Materials pages are activated,
|
||||
* materials for this pages are automaticaly updated and presets are reloaded.
|
||||
*
|
||||
|
@ -2417,7 +2417,7 @@ void GCodeViewer::render_toolpaths() const
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
double zoom = camera.get_zoom();
|
||||
const std::array<int, 4>& viewport = camera.get_viewport();
|
||||
float near_plane_height = camera.get_type() == Camera::Perspective ? static_cast<float>(viewport[3]) / (2.0f * static_cast<float>(2.0 * std::tan(0.5 * Geometry::deg2rad(camera.get_fov())))) :
|
||||
float near_plane_height = camera.get_type() == Camera::EType::Perspective ? static_cast<float>(viewport[3]) / (2.0f * static_cast<float>(2.0 * std::tan(0.5 * Geometry::deg2rad(camera.get_fov())))) :
|
||||
static_cast<float>(viewport[3]) * 0.0005;
|
||||
|
||||
auto set_uniform_color = [](const std::array<float, 3>& color, GLShaderProgram& shader) {
|
||||
@ -2593,7 +2593,7 @@ void GCodeViewer::render_shells() const
|
||||
// glsafe(::glDepthMask(GL_FALSE));
|
||||
|
||||
shader->start_using();
|
||||
m_shells.volumes.render(GLVolumeCollection::Transparent, true, wxGetApp().plater()->get_camera().get_view_matrix());
|
||||
m_shells.volumes.render(GLVolumeCollection::ERenderType::Transparent, true, wxGetApp().plater()->get_camera().get_view_matrix());
|
||||
shader->stop_using();
|
||||
|
||||
// glsafe(::glDepthMask(GL_TRUE));
|
||||
|
@ -21,7 +21,6 @@
|
||||
#include "slic3r/GUI/GUI_Preview.hpp"
|
||||
#include "slic3r/GUI/OpenGLManager.hpp"
|
||||
#include "slic3r/GUI/3DBed.hpp"
|
||||
#include "slic3r/GUI/Camera.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/MainFrame.hpp"
|
||||
|
||||
@ -697,7 +696,7 @@ void GLCanvas3D::Labels::render(const std::vector<const ModelInstance*>& sorted_
|
||||
Vec3d screen_box_center = world_to_screen * owner.world_box.center();
|
||||
float x = 0.0f;
|
||||
float y = 0.0f;
|
||||
if (camera.get_type() == Camera::Perspective) {
|
||||
if (camera.get_type() == Camera::EType::Perspective) {
|
||||
x = (0.5f + 0.001f * 0.5f * (float)screen_box_center(0)) * viewport[2];
|
||||
y = (0.5f - 0.001f * 0.5f * (float)screen_box_center(1)) * viewport[3];
|
||||
} else {
|
||||
@ -729,7 +728,7 @@ void GLCanvas3D::Labels::render(const std::vector<const ModelInstance*>& sorted_
|
||||
}
|
||||
|
||||
// force re-render while the windows gets to its final size (it takes several frames)
|
||||
if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowExpectedSize(ImGui::GetCurrentWindow()).x)
|
||||
if (ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x)
|
||||
m_canvas.request_extra_frame();
|
||||
|
||||
imgui.end();
|
||||
@ -780,7 +779,7 @@ void GLCanvas3D::Tooltip::render(const Vec2d& mouse_position, GLCanvas3D& canvas
|
||||
ImGui::TextUnformatted(m_text.c_str());
|
||||
|
||||
// force re-render while the windows gets to its final size (it may take several frames) or while hidden
|
||||
if (alpha < 1.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowExpectedSize(ImGui::GetCurrentWindow()).x)
|
||||
if (alpha < 1.0f || ImGui::GetWindowContentRegionWidth() + 2.0f * ImGui::GetStyle().WindowPadding.x != ImGui::CalcWindowNextAutoFitSize(ImGui::GetCurrentWindow()).x)
|
||||
canvas.request_extra_frame();
|
||||
|
||||
size = ImGui::GetWindowSize();
|
||||
@ -1476,12 +1475,20 @@ void GLCanvas3D::render()
|
||||
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
|
||||
_render_background();
|
||||
|
||||
#if ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
|
||||
_render_objects(GLVolumeCollection::ERenderType::Opaque);
|
||||
#else
|
||||
_render_objects();
|
||||
#endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
|
||||
if (!m_main_toolbar.is_enabled())
|
||||
_render_gcode();
|
||||
_render_sla_slices();
|
||||
_render_selection();
|
||||
_render_bed(!camera.is_looking_downward(), true);
|
||||
#if ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
|
||||
_render_objects(GLVolumeCollection::ERenderType::Transparent);
|
||||
#endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
|
||||
|
||||
#if ENABLE_SEQUENTIAL_LIMITS
|
||||
_render_sequential_clearance();
|
||||
#endif // ENABLE_SEQUENTIAL_LIMITS
|
||||
@ -1589,13 +1596,18 @@ void GLCanvas3D::render()
|
||||
#endif // ENABLE_RENDER_STATISTICS
|
||||
}
|
||||
|
||||
void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
|
||||
void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type)
|
||||
{
|
||||
render_thumbnail(thumbnail_data, w, h, thumbnail_params, m_volumes, camera_type);
|
||||
}
|
||||
|
||||
void GLCanvas3D::render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type)
|
||||
{
|
||||
switch (OpenGLManager::get_framebuffers_type())
|
||||
{
|
||||
case OpenGLManager::EFramebufferType::Arb: { _render_thumbnail_framebuffer(thumbnail_data, w, h, printable_only, parts_only, show_bed, transparent_background); break; }
|
||||
case OpenGLManager::EFramebufferType::Ext: { _render_thumbnail_framebuffer_ext(thumbnail_data, w, h, printable_only, parts_only, show_bed, transparent_background); break; }
|
||||
default: { _render_thumbnail_legacy(thumbnail_data, w, h, printable_only, parts_only, show_bed, transparent_background); break; }
|
||||
case OpenGLManager::EFramebufferType::Arb: { _render_thumbnail_framebuffer(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; }
|
||||
case OpenGLManager::EFramebufferType::Ext: { _render_thumbnail_framebuffer_ext(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; }
|
||||
default: { _render_thumbnail_legacy(thumbnail_data, w, h, thumbnail_params, volumes, camera_type); break; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -2910,9 +2922,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
#ifdef SLIC3R_DEBUG_MOUSE_EVENTS
|
||||
printf((format_mouse_event_debug_message(evt) + " - Consumed by ImGUI\n").c_str());
|
||||
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
|
||||
m_dirty = true;
|
||||
m_dirty = true;
|
||||
// do not return if dragging or tooltip not empty to allow for tooltip update
|
||||
if (!m_mouse.dragging && m_tooltip.is_empty())
|
||||
// also, do not return if the mouse is moving and also is inside MM gizmo to allow update seed fill selection
|
||||
if (!m_mouse.dragging && m_tooltip.is_empty() && (m_gizmos.get_current_type() != GLGizmosManager::MmuSegmentation || !evt.Moving()))
|
||||
return;
|
||||
}
|
||||
|
||||
@ -4085,7 +4098,7 @@ static void debug_output_thumbnail(const ThumbnailData& thumbnail_data)
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
||||
|
||||
void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
|
||||
void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type)
|
||||
{
|
||||
auto is_visible = [](const GLVolume& v) {
|
||||
bool ret = v.printable;
|
||||
@ -4098,9 +4111,9 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
|
||||
|
||||
GLVolumePtrs visible_volumes;
|
||||
|
||||
for (GLVolume* vol : m_volumes.volumes) {
|
||||
if (!vol->is_modifier && !vol->is_wipe_tower && (!parts_only || (vol->composite_id.volume_id >= 0))) {
|
||||
if (!printable_only || is_visible(*vol))
|
||||
for (GLVolume* vol : volumes.volumes) {
|
||||
if (!vol->is_modifier && !vol->is_wipe_tower && (!thumbnail_params.parts_only || vol->composite_id.volume_id >= 0)) {
|
||||
if (!thumbnail_params.printable_only || is_visible(*vol))
|
||||
visible_volumes.emplace_back(vol);
|
||||
}
|
||||
}
|
||||
@ -4108,37 +4121,37 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
|
||||
if (visible_volumes.empty())
|
||||
return;
|
||||
|
||||
BoundingBoxf3 box;
|
||||
BoundingBoxf3 volumes_box;
|
||||
for (const GLVolume* vol : visible_volumes) {
|
||||
box.merge(vol->transformed_bounding_box());
|
||||
volumes_box.merge(vol->transformed_bounding_box());
|
||||
}
|
||||
|
||||
Camera camera;
|
||||
camera.set_type(Camera::Ortho);
|
||||
camera.set_type(camera_type);
|
||||
camera.set_scene_box(scene_bounding_box());
|
||||
camera.apply_viewport(0, 0, thumbnail_data.width, thumbnail_data.height);
|
||||
camera.zoom_to_volumes(visible_volumes);
|
||||
camera.zoom_to_box(volumes_box);
|
||||
camera.apply_view_matrix();
|
||||
|
||||
double near_z = -1.0;
|
||||
double far_z = -1.0;
|
||||
|
||||
if (show_bed) {
|
||||
if (thumbnail_params.show_bed) {
|
||||
// extends the near and far z of the frustrum to avoid the bed being clipped
|
||||
|
||||
// box in eye space
|
||||
BoundingBoxf3 t_bed_box = wxGetApp().plater()->get_bed().get_bounding_box(true).transformed(camera.get_view_matrix());
|
||||
near_z = -t_bed_box.max(2);
|
||||
far_z = -t_bed_box.min(2);
|
||||
near_z = -t_bed_box.max.z();
|
||||
far_z = -t_bed_box.min.z();
|
||||
}
|
||||
|
||||
camera.apply_projection(box, near_z, far_z);
|
||||
camera.apply_projection(volumes_box, near_z, far_z);
|
||||
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("gouraud_light");
|
||||
if (shader == nullptr)
|
||||
return;
|
||||
|
||||
if (transparent_background)
|
||||
if (thumbnail_params.transparent_background)
|
||||
glsafe(::glClearColor(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
|
||||
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
|
||||
@ -4160,15 +4173,15 @@ void GLCanvas3D::_render_thumbnail_internal(ThumbnailData& thumbnail_data, bool
|
||||
|
||||
glsafe(::glDisable(GL_DEPTH_TEST));
|
||||
|
||||
if (show_bed)
|
||||
if (thumbnail_params.show_bed)
|
||||
_render_bed(!camera.is_looking_downward(), false);
|
||||
|
||||
// restore background color
|
||||
if (transparent_background)
|
||||
if (thumbnail_params.transparent_background)
|
||||
glsafe(::glClearColor(1.0f, 1.0f, 1.0f, 1.0f));
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
|
||||
void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type)
|
||||
{
|
||||
thumbnail_data.set(w, h);
|
||||
if (!thumbnail_data.is_valid())
|
||||
@ -4218,7 +4231,7 @@ void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, un
|
||||
glsafe(::glDrawBuffers(1, drawBufs));
|
||||
|
||||
if (::glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) {
|
||||
_render_thumbnail_internal(thumbnail_data, printable_only, parts_only, show_bed, transparent_background);
|
||||
_render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type);
|
||||
|
||||
if (multisample) {
|
||||
GLuint resolve_fbo;
|
||||
@ -4267,7 +4280,7 @@ void GLCanvas3D::_render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, un
|
||||
glsafe(::glDisable(GL_MULTISAMPLE));
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
|
||||
void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type)
|
||||
{
|
||||
thumbnail_data.set(w, h);
|
||||
if (!thumbnail_data.is_valid())
|
||||
@ -4317,7 +4330,7 @@ void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data
|
||||
glsafe(::glDrawBuffers(1, drawBufs));
|
||||
|
||||
if (::glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT) == GL_FRAMEBUFFER_COMPLETE_EXT) {
|
||||
_render_thumbnail_internal(thumbnail_data, printable_only, parts_only, show_bed, transparent_background);
|
||||
_render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type);
|
||||
|
||||
if (multisample) {
|
||||
GLuint resolve_fbo;
|
||||
@ -4366,7 +4379,7 @@ void GLCanvas3D::_render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data
|
||||
glsafe(::glDisable(GL_MULTISAMPLE));
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
|
||||
void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type)
|
||||
{
|
||||
// check that thumbnail size does not exceed the default framebuffer size
|
||||
const Size& cnv_size = get_canvas_size();
|
||||
@ -4382,7 +4395,7 @@ void GLCanvas3D::_render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigne
|
||||
if (!thumbnail_data.is_valid())
|
||||
return;
|
||||
|
||||
_render_thumbnail_internal(thumbnail_data, printable_only, parts_only, show_bed, transparent_background);
|
||||
_render_thumbnail_internal(thumbnail_data, thumbnail_params, volumes, camera_type);
|
||||
|
||||
glsafe(::glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, (void*)thumbnail_data.pixels.data()));
|
||||
#if ENABLE_THUMBNAIL_GENERATOR_DEBUG_OUTPUT
|
||||
@ -5031,7 +5044,11 @@ void GLCanvas3D::_render_bed(bool bottom, bool show_axes)
|
||||
wxGetApp().plater()->get_bed().render(*this, bottom, scale_factor, show_axes, show_texture);
|
||||
}
|
||||
|
||||
#if ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
|
||||
void GLCanvas3D::_render_objects(GLVolumeCollection::ERenderType type)
|
||||
#else
|
||||
void GLCanvas3D::_render_objects()
|
||||
#endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
|
||||
{
|
||||
if (m_volumes.empty())
|
||||
return;
|
||||
@ -5062,37 +5079,63 @@ void GLCanvas3D::_render_objects()
|
||||
if (shader != nullptr) {
|
||||
shader->start_using();
|
||||
|
||||
if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) {
|
||||
int object_id = m_layers_editing.last_object_id;
|
||||
m_volumes.render(GLVolumeCollection::Opaque, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) {
|
||||
// Which volume to paint without the layer height profile shader?
|
||||
return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id);
|
||||
});
|
||||
// Let LayersEditing handle rendering of the active object using the layer height profile shader.
|
||||
m_layers_editing.render_volumes(*this, m_volumes);
|
||||
} else {
|
||||
// do not cull backfaces to show broken geometry, if any
|
||||
m_volumes.render(GLVolumeCollection::Opaque, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this](const GLVolume& volume) {
|
||||
return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0);
|
||||
});
|
||||
}
|
||||
|
||||
// In case a painting gizmo is open, it should render the painted triangles
|
||||
// before transparent objects are rendered. Otherwise they would not be
|
||||
// visible when inside modifier meshes etc.
|
||||
#if ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
|
||||
switch (type)
|
||||
{
|
||||
const GLGizmosManager& gm = get_gizmos_manager();
|
||||
GLGizmosManager::EType type = gm.get_current_type();
|
||||
if (type == GLGizmosManager::FdmSupports
|
||||
|| type == GLGizmosManager::Seam
|
||||
|| type == GLGizmosManager::MmuSegmentation) {
|
||||
shader->stop_using();
|
||||
gm.render_painter_gizmo();
|
||||
shader->start_using();
|
||||
default:
|
||||
case GLVolumeCollection::ERenderType::Opaque:
|
||||
{
|
||||
#endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
|
||||
if (m_picking_enabled && !m_gizmos.is_dragging() && m_layers_editing.is_enabled() && (m_layers_editing.last_object_id != -1) && (m_layers_editing.object_max_z() > 0.0f)) {
|
||||
int object_id = m_layers_editing.last_object_id;
|
||||
#if ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
|
||||
m_volumes.render(type, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) {
|
||||
#else
|
||||
m_volumes.render(GLVolumeCollection::ERenderType::Opaque, false, wxGetApp().plater()->get_camera().get_view_matrix(), [object_id](const GLVolume& volume) {
|
||||
#endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
|
||||
// Which volume to paint without the layer height profile shader?
|
||||
return volume.is_active && (volume.is_modifier || volume.composite_id.object_id != object_id);
|
||||
});
|
||||
// Let LayersEditing handle rendering of the active object using the layer height profile shader.
|
||||
m_layers_editing.render_volumes(*this, m_volumes);
|
||||
}
|
||||
else {
|
||||
// do not cull backfaces to show broken geometry, if any
|
||||
#if ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
|
||||
m_volumes.render(type, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this](const GLVolume& volume) {
|
||||
#else
|
||||
m_volumes.render(GLVolumeCollection::ERenderType::Opaque, m_picking_enabled, wxGetApp().plater()->get_camera().get_view_matrix(), [this](const GLVolume& volume) {
|
||||
#endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
|
||||
return (m_render_sla_auxiliaries || volume.composite_id.volume_id >= 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
m_volumes.render(GLVolumeCollection::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix());
|
||||
// In case a painting gizmo is open, it should render the painted triangles
|
||||
// before transparent objects are rendered. Otherwise they would not be
|
||||
// visible when inside modifier meshes etc.
|
||||
{
|
||||
const GLGizmosManager& gm = get_gizmos_manager();
|
||||
GLGizmosManager::EType type = gm.get_current_type();
|
||||
if (type == GLGizmosManager::FdmSupports
|
||||
|| type == GLGizmosManager::Seam
|
||||
|| type == GLGizmosManager::MmuSegmentation) {
|
||||
shader->stop_using();
|
||||
gm.render_painter_gizmo();
|
||||
shader->start_using();
|
||||
}
|
||||
}
|
||||
#if ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
|
||||
break;
|
||||
}
|
||||
case GLVolumeCollection::ERenderType::Transparent:
|
||||
{
|
||||
m_volumes.render(type, false, wxGetApp().plater()->get_camera().get_view_matrix());
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
m_volumes.render(GLVolumeCollection::ERenderType::Transparent, false, wxGetApp().plater()->get_camera().get_view_matrix());
|
||||
#endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
|
||||
shader->stop_using();
|
||||
}
|
||||
|
||||
@ -5253,8 +5296,8 @@ void GLCanvas3D::_render_volumes_for_picking() const
|
||||
|
||||
const Transform3d& view_matrix = wxGetApp().plater()->get_camera().get_view_matrix();
|
||||
for (size_t type = 0; type < 2; ++ type) {
|
||||
GLVolumeWithIdAndZList to_render = volumes_to_render(m_volumes.volumes, (type == 0) ? GLVolumeCollection::Opaque : GLVolumeCollection::Transparent, view_matrix);
|
||||
for (const GLVolumeWithIdAndZ& volume : to_render)
|
||||
GLVolumeWithIdAndZList to_render = volumes_to_render(m_volumes.volumes, (type == 0) ? GLVolumeCollection::ERenderType::Opaque : GLVolumeCollection::ERenderType::Transparent, view_matrix);
|
||||
for (const GLVolumeWithIdAndZ& volume : to_render)
|
||||
if (!volume.first->disabled && ((volume.first->composite_id.volume_id >= 0) || m_render_sla_auxiliaries)) {
|
||||
// Object picking mode. Render the object with a color encoding the object index.
|
||||
unsigned int id = volume.second.first;
|
||||
@ -5661,7 +5704,7 @@ void GLCanvas3D::_load_print_toolpaths()
|
||||
if (print == nullptr)
|
||||
return;
|
||||
|
||||
if (!print->is_step_done(psSkirt) || !print->is_step_done(psBrim))
|
||||
if (! print->is_step_done(psSkirtBrim))
|
||||
return;
|
||||
|
||||
if (!print->has_skirt() && !print->has_brim())
|
||||
|
@ -15,6 +15,7 @@
|
||||
#include "MeshUtils.hpp"
|
||||
#include "libslic3r/GCode/GCodeProcessor.hpp"
|
||||
#include "GCodeViewer.hpp"
|
||||
#include "Camera.hpp"
|
||||
|
||||
#include "libslic3r/Slicing.hpp"
|
||||
|
||||
@ -37,9 +38,9 @@ class wxGLContext;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
struct Camera;
|
||||
class BackgroundSlicingProcess;
|
||||
struct ThumbnailData;
|
||||
struct ThumbnailsParams;
|
||||
class ModelObject;
|
||||
class ModelInstance;
|
||||
class PrintObject;
|
||||
@ -622,7 +623,8 @@ public:
|
||||
void render();
|
||||
// printable_only == false -> render also non printable volumes as grayed
|
||||
// parts_only == false -> render also sla support and pad
|
||||
void render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
|
||||
void render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type);
|
||||
void render_thumbnail(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type);
|
||||
|
||||
void select_all();
|
||||
void deselect_all();
|
||||
@ -820,7 +822,11 @@ private:
|
||||
void _rectangular_selection_picking_pass();
|
||||
void _render_background() const;
|
||||
void _render_bed(bool bottom, bool show_axes);
|
||||
#if ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
|
||||
void _render_objects(GLVolumeCollection::ERenderType type);
|
||||
#else
|
||||
void _render_objects();
|
||||
#endif // ENABLE_DELAYED_TRANSPARENT_VOLUMES_RENDERING
|
||||
void _render_gcode() const;
|
||||
void _render_selection() const;
|
||||
#if ENABLE_SEQUENTIAL_LIMITS
|
||||
@ -846,13 +852,13 @@ private:
|
||||
bool _render_undo_redo_stack(const bool is_undo, float pos_x);
|
||||
bool _render_search_list(float pos_x);
|
||||
bool _render_arrange_menu(float pos_x);
|
||||
void _render_thumbnail_internal(ThumbnailData& thumbnail_data, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
|
||||
void _render_thumbnail_internal(ThumbnailData& thumbnail_data, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type);
|
||||
// render thumbnail using an off-screen framebuffer
|
||||
void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
|
||||
void _render_thumbnail_framebuffer(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type);
|
||||
// render thumbnail using an off-screen framebuffer when GLEW_EXT_framebuffer_object is supported
|
||||
void _render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
|
||||
void _render_thumbnail_framebuffer_ext(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type);
|
||||
// render thumbnail using the default framebuffer
|
||||
void _render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
|
||||
void _render_thumbnail_legacy(ThumbnailData& thumbnail_data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, const GLVolumeCollection& volumes, Camera::EType camera_type);
|
||||
|
||||
void _update_volumes_hover_state();
|
||||
|
||||
|
@ -608,7 +608,7 @@ static void generic_exception_handle()
|
||||
std::terminate();
|
||||
throw;
|
||||
} catch (const std::exception& ex) {
|
||||
wxLogError("Internal error: %s", ex.what());
|
||||
wxLogError(format_wxstr(_L("Internal error: %1%"), ex.what()));
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Uncaught exception: %1%") % ex.what();
|
||||
throw;
|
||||
}
|
||||
@ -1115,10 +1115,11 @@ void GUI_App::UpdateDarkUI(wxWindow* window, bool highlited/* = false*/, bool ju
|
||||
|
||||
// recursive function for scaling fonts for all controls in Window
|
||||
#ifdef _WIN32
|
||||
static void update_dark_children_ui(wxWindow* window)
|
||||
static void update_dark_children_ui(wxWindow* window, bool just_buttons_update = false)
|
||||
{
|
||||
bool highlight_btn = dynamic_cast<wxButton*>(window) != nullptr;
|
||||
wxGetApp().UpdateDarkUI(window, highlight_btn);
|
||||
bool is_btn = dynamic_cast<wxButton*>(window) != nullptr;
|
||||
if (!(just_buttons_update && !is_btn))
|
||||
wxGetApp().UpdateDarkUI(window, is_btn);
|
||||
|
||||
auto children = window->GetChildren();
|
||||
for (auto child : children) {
|
||||
@ -1127,16 +1128,17 @@ static void update_dark_children_ui(wxWindow* window)
|
||||
}
|
||||
#endif
|
||||
|
||||
void GUI_App::UpdateDlgDarkUI(wxDialog* dlg)
|
||||
// Note: Don't use this function for Dialog contains ScalableButtons
|
||||
void GUI_App::UpdateDlgDarkUI(wxDialog* dlg, bool just_buttons_update/* = false*/)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
update_dark_children_ui(dlg);
|
||||
update_dark_children_ui(dlg, just_buttons_update);
|
||||
#endif
|
||||
}
|
||||
void GUI_App::UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited/* = false*/)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
UpdateDarkUI(dvc, highlited);
|
||||
UpdateDarkUI(dvc, highlited ? dark_mode() : false);
|
||||
wxItemAttr attr(dark_mode() ? m_color_highlight_default : m_color_label_default,
|
||||
m_color_window_default,
|
||||
m_normal_font);
|
||||
|
@ -182,7 +182,7 @@ public:
|
||||
// update color mode for window
|
||||
void UpdateDarkUI(wxWindow *window, bool highlited = false, bool just_font = false);
|
||||
// update color mode for whole dialog including all children
|
||||
void UpdateDlgDarkUI(wxDialog* dlg);
|
||||
void UpdateDlgDarkUI(wxDialog* dlg, bool just_buttons_update = false);
|
||||
// update color mode for DataViewControl
|
||||
void UpdateDVCDarkUI(wxDataViewCtrl* dvc, bool highlited = false);
|
||||
// update color mode for panel including all static texts controls
|
||||
|
@ -433,6 +433,12 @@ wxMenu* MenuFactory::append_submenu_add_generic(wxMenu* menu, ModelVolumeType ty
|
||||
[type, item](wxCommandEvent&) { obj_list()->load_generic_subobject(item, type); }, "", menu);
|
||||
}
|
||||
|
||||
if (wxGetApp().get_mode() == comExpert) {
|
||||
sub_menu->AppendSeparator();
|
||||
append_menu_item(sub_menu, wxID_ANY, _L("Gallery"), "",
|
||||
[type](wxCommandEvent&) { obj_list()->load_subobject(type, true); }, "", menu);
|
||||
}
|
||||
|
||||
return sub_menu;
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "I18N.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "BitmapComboBox.hpp"
|
||||
#include "GalleryDialog.hpp"
|
||||
#if ENABLE_PROJECT_DIRTY_STATE
|
||||
#include "MainFrame.hpp"
|
||||
#endif // ENABLE_PROJECT_DIRTY_STATE
|
||||
@ -594,13 +595,31 @@ void ObjectList::update_name_in_model(const wxDataViewItem& item) const
|
||||
|
||||
take_snapshot(volume_id < 0 ? _(L("Rename Object")) : _(L("Rename Sub-object")));
|
||||
|
||||
ModelObject* obj = object(obj_idx);
|
||||
if (m_objects_model->GetItemType(item) & itObject) {
|
||||
(*m_objects)[obj_idx]->name = m_objects_model->GetName(item).ToUTF8().data();
|
||||
obj->name = m_objects_model->GetName(item).ToUTF8().data();
|
||||
// if object has just one volume, rename this volume too
|
||||
if (obj->volumes.size() == 1)
|
||||
obj->volumes[0]->name = obj->name;
|
||||
return;
|
||||
}
|
||||
|
||||
if (volume_id < 0) return;
|
||||
(*m_objects)[obj_idx]->volumes[volume_id]->name = m_objects_model->GetName(item).ToUTF8().data();
|
||||
obj->volumes[volume_id]->name = m_objects_model->GetName(item).ToUTF8().data();
|
||||
}
|
||||
|
||||
void ObjectList::update_name_in_list(int obj_idx, int vol_idx) const
|
||||
{
|
||||
if (obj_idx < 0) return;
|
||||
wxDataViewItem item = GetSelection();
|
||||
if (!item || !(m_objects_model->GetItemType(item) & (itVolume | itObject)))
|
||||
return;
|
||||
|
||||
wxString new_name = from_u8(object(obj_idx)->volumes[vol_idx]->name);
|
||||
if (new_name.IsEmpty() || m_objects_model->GetName(item) == new_name)
|
||||
return;
|
||||
|
||||
m_objects_model->SetName(new_name, item);
|
||||
}
|
||||
|
||||
void ObjectList::selection_changed()
|
||||
@ -1337,8 +1356,13 @@ bool ObjectList::is_instance_or_object_selected()
|
||||
return selection.is_single_full_instance() || selection.is_single_full_object();
|
||||
}
|
||||
|
||||
void ObjectList::load_subobject(ModelVolumeType type)
|
||||
void ObjectList::load_subobject(ModelVolumeType type, bool from_galery/* = false*/)
|
||||
{
|
||||
if (type == ModelVolumeType::INVALID && from_galery) {
|
||||
load_shape_object_from_gallery();
|
||||
return;
|
||||
}
|
||||
|
||||
wxDataViewItem item = GetSelection();
|
||||
// we can add volumes for Object or Instance
|
||||
if (!item || !(m_objects_model->GetItemType(item)&(itObject|itInstance)))
|
||||
@ -1351,10 +1375,17 @@ void ObjectList::load_subobject(ModelVolumeType type)
|
||||
if (m_objects_model->GetItemType(item)&itInstance)
|
||||
item = m_objects_model->GetItemById(obj_idx);
|
||||
|
||||
take_snapshot(_L("Load Part"));
|
||||
|
||||
std::vector<ModelVolume*> volumes;
|
||||
load_part((*m_objects)[obj_idx], volumes, type);
|
||||
if (type == ModelVolumeType::MODEL_PART)
|
||||
load_part(*(*m_objects)[obj_idx], volumes, type, from_galery);
|
||||
else
|
||||
load_modifier(*(*m_objects)[obj_idx], volumes, type, from_galery);
|
||||
|
||||
if (volumes.empty())
|
||||
return;
|
||||
|
||||
take_snapshot((type == ModelVolumeType::MODEL_PART) ? _L("Load Part") : _L("Load Modifier"));
|
||||
|
||||
wxDataViewItemArray items = reorder_volumes_and_get_selection(obj_idx, [volumes](const ModelVolume* volume) {
|
||||
return std::find(volumes.begin(), volumes.end(), volume) != volumes.end(); });
|
||||
|
||||
@ -1371,12 +1402,25 @@ void ObjectList::load_subobject(ModelVolumeType type)
|
||||
selection_changed();
|
||||
}
|
||||
|
||||
void ObjectList::load_part(ModelObject* model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type)
|
||||
void ObjectList::load_part(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery/* = false*/)
|
||||
{
|
||||
if (type != ModelVolumeType::MODEL_PART)
|
||||
return;
|
||||
|
||||
wxWindow* parent = wxGetApp().tab_panel()->GetPage(0);
|
||||
|
||||
wxArrayString input_files;
|
||||
wxGetApp().import_model(parent, input_files);
|
||||
|
||||
if (from_galery) {
|
||||
GalleryDialog dlg(this);
|
||||
if (dlg.ShowModal() == wxID_CANCEL)
|
||||
return;
|
||||
dlg.get_input_files(input_files);
|
||||
if (input_files.IsEmpty())
|
||||
return;
|
||||
}
|
||||
else
|
||||
wxGetApp().import_model(parent, input_files);
|
||||
|
||||
wxProgressDialog dlg(_L("Loading") + dots, "", 100, wxGetApp().plater(), wxPD_AUTO_HIDE);
|
||||
wxBusyCursor busy;
|
||||
@ -1400,13 +1444,13 @@ void ObjectList::load_part(ModelObject* model_object, std::vector<ModelVolume*>&
|
||||
|
||||
for (auto object : model.objects) {
|
||||
Vec3d delta = Vec3d::Zero();
|
||||
if (model_object->origin_translation != Vec3d::Zero()) {
|
||||
if (model_object.origin_translation != Vec3d::Zero()) {
|
||||
object->center_around_origin();
|
||||
delta = model_object->origin_translation - object->origin_translation;
|
||||
delta = model_object.origin_translation - object->origin_translation;
|
||||
}
|
||||
for (auto volume : object->volumes) {
|
||||
volume->translate(delta);
|
||||
auto new_volume = model_object->add_volume(*volume, type);
|
||||
auto new_volume = model_object.add_volume(*volume, type);
|
||||
new_volume->name = boost::filesystem::path(input_file).filename().string();
|
||||
// set a default extruder value, since user can't add it manually
|
||||
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
|
||||
@ -1415,7 +1459,106 @@ void ObjectList::load_part(ModelObject* model_object, std::vector<ModelVolume*>&
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectList::load_modifier(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery)
|
||||
{
|
||||
if (type == ModelVolumeType::MODEL_PART)
|
||||
return;
|
||||
|
||||
wxWindow* parent = wxGetApp().tab_panel()->GetPage(0);
|
||||
|
||||
wxArrayString input_files;
|
||||
|
||||
if (from_galery) {
|
||||
GalleryDialog dlg(this);
|
||||
if (dlg.ShowModal() == wxID_CANCEL)
|
||||
return;
|
||||
dlg.get_input_files(input_files);
|
||||
if (input_files.IsEmpty())
|
||||
return;
|
||||
}
|
||||
else
|
||||
wxGetApp().import_model(parent, input_files);
|
||||
|
||||
wxProgressDialog dlg(_L("Loading") + dots, "", 100, wxGetApp().plater(), wxPD_AUTO_HIDE);
|
||||
wxBusyCursor busy;
|
||||
|
||||
const int obj_idx = get_selected_obj_idx();
|
||||
if (obj_idx < 0)
|
||||
return;
|
||||
|
||||
const Selection& selection = scene_selection();
|
||||
assert(obj_idx == selection.get_object_idx());
|
||||
|
||||
/** Any changes of the Object's composition is duplicated for all Object's Instances
|
||||
* So, It's enough to take a bounding box of a first selected Instance and calculate Part(generic_subobject) position
|
||||
*/
|
||||
int instance_idx = *selection.get_instance_idxs().begin();
|
||||
assert(instance_idx != -1);
|
||||
if (instance_idx == -1)
|
||||
return;
|
||||
|
||||
// Bounding box of the selected instance in world coordinate system including the translation, without modifiers.
|
||||
const BoundingBoxf3 instance_bb = model_object.instance_bounding_box(instance_idx);
|
||||
|
||||
// First (any) GLVolume of the selected instance. They all share the same instance matrix.
|
||||
const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||
const Geometry::Transformation inst_transform = v->get_instance_transformation();
|
||||
const Transform3d inv_inst_transform = inst_transform.get_matrix(true).inverse();
|
||||
const Vec3d instance_offset = v->get_instance_offset();
|
||||
|
||||
for (size_t i = 0; i < input_files.size(); ++i) {
|
||||
const std::string input_file = input_files.Item(i).ToUTF8().data();
|
||||
|
||||
dlg.Update(static_cast<int>(100.0f * static_cast<float>(i) / static_cast<float>(input_files.size())),
|
||||
_L("Loading file") + ": " + from_path(boost::filesystem::path(input_file).filename()));
|
||||
dlg.Fit();
|
||||
|
||||
Model model;
|
||||
try {
|
||||
model = Model::read_from_file(input_file);
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
auto msg = _L("Error!") + " " + input_file + " : " + e.what() + ".";
|
||||
show_error(parent, msg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (from_galery)
|
||||
model.center_instances_around_point(Vec2d::Zero());
|
||||
else {
|
||||
for (auto object : model.objects) {
|
||||
if (model_object.origin_translation != Vec3d::Zero()) {
|
||||
object->center_around_origin();
|
||||
Vec3d delta = model_object.origin_translation - object->origin_translation;
|
||||
for (auto volume : object->volumes) {
|
||||
volume->translate(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TriangleMesh mesh = model.mesh();
|
||||
mesh.repair();
|
||||
// Mesh will be centered when loading.
|
||||
ModelVolume* new_volume = model_object.add_volume(std::move(mesh), type);
|
||||
new_volume->name = boost::filesystem::path(input_file).filename().string();
|
||||
// set a default extruder value, since user can't add it manually
|
||||
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
|
||||
|
||||
if (from_galery) {
|
||||
// Transform the new modifier to be aligned with the print bed.
|
||||
const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box();
|
||||
new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(inst_transform, mesh_bb));
|
||||
// Set the modifier position.
|
||||
// Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed.
|
||||
const Vec3d offset = Vec3d(instance_bb.max.x(), instance_bb.min.y(), instance_bb.min.z()) + 0.5 * mesh_bb.size() - instance_offset;
|
||||
new_volume->set_offset(inv_inst_transform * offset);
|
||||
}
|
||||
|
||||
added_volumes.push_back(new_volume);
|
||||
}
|
||||
}
|
||||
|
||||
static TriangleMesh create_mesh(const std::string& type_name, const BoundingBoxf3& bb)
|
||||
@ -1465,7 +1608,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
|
||||
if (instance_idx == -1)
|
||||
return;
|
||||
|
||||
take_snapshot(_(L("Add Generic Subobject")));
|
||||
take_snapshot(_L("Add Generic Subobject"));
|
||||
|
||||
// Selected object
|
||||
ModelObject &model_object = *(*m_objects)[obj_idx];
|
||||
@ -1477,26 +1620,24 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
|
||||
// Mesh will be centered when loading.
|
||||
ModelVolume *new_volume = model_object.add_volume(std::move(mesh), type);
|
||||
|
||||
if (instance_idx != -1)
|
||||
{
|
||||
// First (any) GLVolume of the selected instance. They all share the same instance matrix.
|
||||
const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||
// Transform the new modifier to be aligned with the print bed.
|
||||
const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box();
|
||||
new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb));
|
||||
// Set the modifier position.
|
||||
auto offset = (type_name == "Slab") ?
|
||||
// Slab: Lift to print bed
|
||||
Vec3d(0., 0., 0.5 * mesh_bb.size().z() + instance_bb.min.z() - v->get_instance_offset().z()) :
|
||||
// Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed.
|
||||
Vec3d(instance_bb.max(0), instance_bb.min(1), instance_bb.min(2)) + 0.5 * mesh_bb.size() - v->get_instance_offset();
|
||||
new_volume->set_offset(v->get_instance_transformation().get_matrix(true).inverse() * offset);
|
||||
}
|
||||
// First (any) GLVolume of the selected instance. They all share the same instance matrix.
|
||||
const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||
// Transform the new modifier to be aligned with the print bed.
|
||||
const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box();
|
||||
new_volume->set_transformation(Geometry::Transformation::volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb));
|
||||
// Set the modifier position.
|
||||
auto offset = (type_name == "Slab") ?
|
||||
// Slab: Lift to print bed
|
||||
Vec3d(0., 0., 0.5 * mesh_bb.size().z() + instance_bb.min.z() - v->get_instance_offset().z()) :
|
||||
// Translate the new modifier to be pickable: move to the left front corner of the instance's bounding box, lift to print bed.
|
||||
Vec3d(instance_bb.max.x(), instance_bb.min.y(), instance_bb.min.z()) + 0.5 * mesh_bb.size() - v->get_instance_offset();
|
||||
new_volume->set_offset(v->get_instance_transformation().get_matrix(true).inverse() * offset);
|
||||
|
||||
const wxString name = _(L("Generic")) + "-" + _(type_name);
|
||||
const wxString name = _L("Generic") + "-" + _(type_name);
|
||||
new_volume->name = into_u8(name);
|
||||
// set a default extruder value, since user can't add it manually
|
||||
new_volume->config.set_key_value("extruder", new ConfigOptionInt(0));
|
||||
new_volume->source.is_from_builtin_objects = true;
|
||||
|
||||
select_item([this, obj_idx, new_volume]() {
|
||||
wxDataViewItem sel_item;
|
||||
@ -1536,6 +1677,39 @@ void ObjectList::load_shape_object(const std::string& type_name)
|
||||
#endif // ENABLE_PROJECT_DIRTY_STATE
|
||||
}
|
||||
|
||||
void ObjectList::load_shape_object_from_gallery()
|
||||
{
|
||||
if (wxGetApp().plater()->canvas3D()->get_selection().get_object_idx() != -1)
|
||||
return;// Add nothing if something is selected on 3DScene
|
||||
|
||||
wxArrayString input_files;
|
||||
GalleryDialog gallery_dlg(this);
|
||||
if (gallery_dlg.ShowModal() == wxID_CANCEL)
|
||||
return;
|
||||
gallery_dlg.get_input_files(input_files);
|
||||
if (input_files.IsEmpty())
|
||||
return;
|
||||
|
||||
std::vector<boost::filesystem::path> paths;
|
||||
for (const auto& file : input_files)
|
||||
paths.push_back(into_path(file));
|
||||
|
||||
assert(!paths.empty());
|
||||
wxString snapshot_label = (paths.size() == 1 ? _L("Add Shape") : _L("Add Shapes")) + ": " +
|
||||
wxString::FromUTF8(paths.front().filename().string().c_str());
|
||||
for (size_t i = 1; i < paths.size(); ++i)
|
||||
snapshot_label += ", " + wxString::FromUTF8(paths[i].filename().string().c_str());
|
||||
|
||||
take_snapshot(snapshot_label);
|
||||
#if ENABLE_PROJECT_DIRTY_STATE
|
||||
std::vector<size_t> res = wxGetApp().plater()->load_files(paths, true, false);
|
||||
if (!res.empty())
|
||||
wxGetApp().mainframe->update_title();
|
||||
#else
|
||||
load_files(paths, true, false);
|
||||
#endif // ENABLE_PROJECT_DIRTY_STATE
|
||||
}
|
||||
|
||||
void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center)
|
||||
{
|
||||
// Add mesh to model as a new object
|
||||
@ -1561,7 +1735,7 @@ void ObjectList::load_mesh_object(const TriangleMesh &mesh, const wxString &name
|
||||
|
||||
if (center) {
|
||||
const BoundingBoxf bed_shape = wxGetApp().plater()->bed_shape_bb();
|
||||
new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -new_object->origin_translation(2)));
|
||||
new_object->instances[0]->set_offset(Slic3r::to_3d(bed_shape.center().cast<double>(), -new_object->origin_translation.z()));
|
||||
} else {
|
||||
new_object->instances[0]->set_offset(bb.center());
|
||||
}
|
||||
@ -3731,22 +3905,8 @@ void ObjectList::rename_item()
|
||||
return;
|
||||
}
|
||||
|
||||
// The icon can't be edited so get its old value and reuse it.
|
||||
wxVariant valueOld;
|
||||
m_objects_model->GetValue(valueOld, item, colName);
|
||||
|
||||
DataViewBitmapText bmpText;
|
||||
bmpText << valueOld;
|
||||
|
||||
// But replace the text with the value entered by user.
|
||||
bmpText.SetText(new_name);
|
||||
|
||||
wxVariant value;
|
||||
value << bmpText;
|
||||
m_objects_model->SetValue(value, item, colName);
|
||||
m_objects_model->ItemChanged(item);
|
||||
|
||||
update_name_in_model(item);
|
||||
if (m_objects_model->SetName(new_name, item))
|
||||
update_name_in_model(item);
|
||||
}
|
||||
|
||||
void ObjectList::fix_through_netfabb()
|
||||
|
@ -202,6 +202,7 @@ public:
|
||||
void update_extruder_in_config(const wxDataViewItem& item);
|
||||
// update changed name in the object model
|
||||
void update_name_in_model(const wxDataViewItem& item) const;
|
||||
void update_name_in_list(int obj_idx, int vol_idx) const;
|
||||
void update_extruder_values_for_items(const size_t max_extruder);
|
||||
|
||||
// Get obj_idx and vol_idx values for the selected (by default) or an adjusted item
|
||||
@ -238,10 +239,12 @@ public:
|
||||
void show_settings(const wxDataViewItem settings_item);
|
||||
bool is_instance_or_object_selected();
|
||||
|
||||
void load_subobject(ModelVolumeType type);
|
||||
void load_part(ModelObject* model_object, std::vector<ModelVolume*> &added_volumes, ModelVolumeType type);
|
||||
void load_generic_subobject(const std::string& type_name, const ModelVolumeType type);
|
||||
void load_subobject(ModelVolumeType type, bool from_galery = false);
|
||||
void load_part(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery = false);
|
||||
void load_modifier(ModelObject& model_object, std::vector<ModelVolume*>& added_volumes, ModelVolumeType type, bool from_galery = false);
|
||||
void load_generic_subobject(const std::string& type_name, const ModelVolumeType type);
|
||||
void load_shape_object(const std::string &type_name);
|
||||
void load_shape_object_from_gallery();
|
||||
void load_mesh_object(const TriangleMesh &mesh, const wxString &name, bool center = true);
|
||||
void del_object(const int obj_idx);
|
||||
void del_subobject_item(wxDataViewItem& item);
|
||||
|
539
src/slic3r/GUI/GalleryDialog.cpp
Normal file
539
src/slic3r/GUI/GalleryDialog.cpp
Normal file
@ -0,0 +1,539 @@
|
||||
#include "GalleryDialog.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
#include <wx/textctrl.h>
|
||||
#include <wx/button.h>
|
||||
#include <wx/statbox.h>
|
||||
#include <wx/wupdlock.h>
|
||||
#include <wx/notebook.h>
|
||||
#include <wx/listctrl.h>
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "format.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "Notebook.hpp"
|
||||
#include "3DScene.hpp"
|
||||
#include "GLCanvas3D.hpp"
|
||||
#include "Plater.hpp"
|
||||
#include "3DBed.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||
#include "../Utils/MacDarkMode.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
#define BORDER_W 10
|
||||
#define IMG_PX_CNT 64
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
// Gallery::DropTarget
|
||||
class GalleryDropTarget : public wxFileDropTarget
|
||||
{
|
||||
public:
|
||||
GalleryDropTarget(GalleryDialog* gallery_dlg) : gallery_dlg(gallery_dlg) { this->SetDefaultAction(wxDragCopy); }
|
||||
|
||||
bool OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) override;
|
||||
|
||||
private:
|
||||
GalleryDialog* gallery_dlg {nullptr};
|
||||
};
|
||||
|
||||
bool GalleryDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames)
|
||||
{
|
||||
#ifdef WIN32
|
||||
// hides the system icon
|
||||
this->MSWUpdateDragImageOnLeave();
|
||||
#endif // WIN32
|
||||
return gallery_dlg ? gallery_dlg->load_files(filenames) : false;
|
||||
}
|
||||
|
||||
|
||||
GalleryDialog::GalleryDialog(wxWindow* parent) :
|
||||
DPIDialog(parent, wxID_ANY, _L("Shapes Gallery"), wxDefaultPosition, wxSize(45 * wxGetApp().em_unit(), -1), wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
#endif
|
||||
SetFont(wxGetApp().normal_font());
|
||||
|
||||
wxStaticText* label_top = new wxStaticText(this, wxID_ANY, _L("Select shape from the gallery") + ":");
|
||||
|
||||
m_list_ctrl = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxSize(55 * wxGetApp().em_unit(), 35 * wxGetApp().em_unit()),
|
||||
wxLC_ICON | wxSIMPLE_BORDER);
|
||||
m_list_ctrl->Bind(wxEVT_LIST_ITEM_SELECTED, &GalleryDialog::select, this);
|
||||
m_list_ctrl->Bind(wxEVT_LIST_ITEM_DESELECTED, &GalleryDialog::deselect, this);
|
||||
m_list_ctrl->Bind(wxEVT_LIST_ITEM_ACTIVATED, [this](wxListEvent& event) {
|
||||
m_selected_items.clear();
|
||||
select(event);
|
||||
this->EndModal(wxID_OK);
|
||||
});
|
||||
#ifdef _WIN32
|
||||
this->Bind(wxEVT_SIZE, [this](wxSizeEvent& event) {
|
||||
event.Skip();
|
||||
m_list_ctrl->Arrange();
|
||||
});
|
||||
#endif
|
||||
|
||||
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK | wxCANCEL);
|
||||
wxButton* ok_btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
|
||||
ok_btn->Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(!m_selected_items.empty()); });
|
||||
|
||||
auto add_btn = [this, buttons]( size_t pos, int& ID, wxString title, wxString tooltip,
|
||||
void (GalleryDialog::* method)(wxEvent&),
|
||||
std::function<bool()> enable_fn = []() {return true; }) {
|
||||
ID = NewControlId();
|
||||
wxButton* btn = new wxButton(this, ID, title);
|
||||
btn->SetToolTip(tooltip);
|
||||
btn->Bind(wxEVT_UPDATE_UI, [enable_fn](wxUpdateUIEvent& evt) { evt.Enable(enable_fn()); });
|
||||
buttons->Insert(pos, btn, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, BORDER_W);
|
||||
this->Bind(wxEVT_BUTTON, method, this, ID);
|
||||
};
|
||||
|
||||
auto enable_del_fn = [this]() {
|
||||
if (m_selected_items.empty())
|
||||
return false;
|
||||
for (const Item& item : m_selected_items)
|
||||
if (item.is_system)
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
add_btn(0, ID_BTN_ADD_CUSTOM_SHAPE, _L("Add"), _L("Add one or more custom shapes"), &GalleryDialog::add_custom_shapes);
|
||||
add_btn(1, ID_BTN_DEL_CUSTOM_SHAPE, _L("Delete"), _L("Delete one or more custom shape. You can't delete system shapes"), &GalleryDialog::del_custom_shapes, enable_del_fn);
|
||||
add_btn(2, ID_BTN_REPLACE_CUSTOM_PNG, _L("Replace PNG"), _L("Replace PNG for custom shape. You can't raplace PNG for system shape"),&GalleryDialog::replace_custom_png, [this]() { return (m_selected_items.size() == 1 && !m_selected_items[0].is_system); });
|
||||
buttons->InsertStretchSpacer(3, 2* BORDER_W);
|
||||
|
||||
load_label_icon_list();
|
||||
|
||||
wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
topSizer->Add(label_top , 0, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W);
|
||||
topSizer->Add(m_list_ctrl, 1, wxEXPAND | wxLEFT | wxTOP | wxRIGHT, BORDER_W);
|
||||
topSizer->Add(buttons , 0, wxEXPAND | wxALL, BORDER_W);
|
||||
|
||||
SetSizer(topSizer);
|
||||
topSizer->SetSizeHints(this);
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
this->CenterOnScreen();
|
||||
|
||||
this->SetDropTarget(new GalleryDropTarget(this));
|
||||
}
|
||||
|
||||
GalleryDialog::~GalleryDialog()
|
||||
{
|
||||
}
|
||||
|
||||
void GalleryDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
{
|
||||
const int& em = em_unit();
|
||||
|
||||
msw_buttons_rescale(this, em, { ID_BTN_ADD_CUSTOM_SHAPE, ID_BTN_DEL_CUSTOM_SHAPE, ID_BTN_REPLACE_CUSTOM_PNG, wxID_OK, wxID_CANCEL });
|
||||
|
||||
wxSize size = wxSize(55 * em, 35 * em);
|
||||
m_list_ctrl->SetMinSize(size);
|
||||
m_list_ctrl->SetSize(size);
|
||||
|
||||
Fit();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
static void add_lock(wxImage& image)
|
||||
{
|
||||
int lock_sz = 22;
|
||||
#ifdef __APPLE__
|
||||
lock_sz /= mac_max_scaling_factor();
|
||||
#endif
|
||||
wxBitmap bmp = create_scaled_bitmap("lock", nullptr, lock_sz);
|
||||
|
||||
wxImage lock_image = bmp.ConvertToImage();
|
||||
if (!lock_image.IsOk() || lock_image.GetWidth() == 0 || lock_image.GetHeight() == 0)
|
||||
return;
|
||||
|
||||
auto lock_px_data = (uint8_t*)lock_image.GetData();
|
||||
auto lock_a_data = (uint8_t*)lock_image.GetAlpha();
|
||||
int lock_width = lock_image.GetWidth();
|
||||
int lock_height = lock_image.GetHeight();
|
||||
|
||||
auto px_data = (uint8_t*)image.GetData();
|
||||
auto a_data = (uint8_t*)image.GetAlpha();
|
||||
|
||||
int width = image.GetWidth();
|
||||
int height = image.GetHeight();
|
||||
|
||||
size_t beg_x = width - lock_width;
|
||||
size_t beg_y = height - lock_height;
|
||||
for (size_t x = 0; x < lock_width; ++x) {
|
||||
for (size_t y = 0; y < lock_height; ++y) {
|
||||
const size_t lock_idx = (x + y * lock_width);
|
||||
if (lock_a_data && lock_a_data[lock_idx] == 0)
|
||||
continue;
|
||||
|
||||
const size_t idx = (beg_x + x + (beg_y + y) * width);
|
||||
if (a_data)
|
||||
a_data[idx] = lock_a_data[lock_idx];
|
||||
|
||||
const size_t idx_rgb = (beg_x + x + (beg_y + y) * width) * 3;
|
||||
const size_t lock_idx_rgb = (x + y * lock_width) * 3;
|
||||
px_data[idx_rgb] = lock_px_data[lock_idx_rgb];
|
||||
px_data[idx_rgb + 1] = lock_px_data[lock_idx_rgb + 1];
|
||||
px_data[idx_rgb + 2] = lock_px_data[lock_idx_rgb + 2];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void add_default_image(wxImageList* img_list, bool is_system)
|
||||
{
|
||||
wxBitmap bmp = create_scaled_bitmap("cog", nullptr, IMG_PX_CNT, true);
|
||||
|
||||
if (is_system) {
|
||||
wxImage image = bmp.ConvertToImage();
|
||||
if (image.IsOk() && image.GetWidth() != 0 && image.GetHeight() != 0) {
|
||||
add_lock(image);
|
||||
bmp = wxBitmap(std::move(image));
|
||||
}
|
||||
}
|
||||
img_list->Add(bmp);
|
||||
};
|
||||
|
||||
static fs::path get_dir(bool sys_dir)
|
||||
{
|
||||
if (sys_dir)
|
||||
return fs::absolute(fs::path(sys_shapes_dir())).make_preferred();
|
||||
return fs::absolute(fs::path(data_dir()) / "shapes").make_preferred();
|
||||
}
|
||||
|
||||
static std::string get_dir_path(bool sys_dir)
|
||||
{
|
||||
fs::path dir = get_dir(sys_dir);
|
||||
#ifdef __WXMSW__
|
||||
return dir.string() + "\\";
|
||||
#else
|
||||
return dir.string() + "/";
|
||||
#endif
|
||||
}
|
||||
|
||||
static void generate_thumbnail_from_stl(const std::string& filename)
|
||||
{
|
||||
if (!boost::algorithm::iends_with(filename, ".stl")) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Found invalid file type in generate_thumbnail_from_stl() [" << filename << "]";
|
||||
return;
|
||||
}
|
||||
|
||||
Model model;
|
||||
try {
|
||||
model = Model::read_from_file(filename);
|
||||
}
|
||||
catch (std::exception&) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Error loading model from " << filename << " in generate_thumbnail_from_stl()";
|
||||
return;
|
||||
}
|
||||
|
||||
assert(model.objects.size() == 1);
|
||||
assert(model.objects[0]->volumes.size() == 1);
|
||||
assert(model.objects[0]->instances.size() == 1);
|
||||
|
||||
model.objects[0]->center_around_origin(false);
|
||||
model.objects[0]->ensure_on_bed(false);
|
||||
|
||||
const Vec3d bed_center_3d = wxGetApp().plater()->get_bed().get_bounding_box(false).center();
|
||||
const Vec2d bed_center_2d = { bed_center_3d.x(), bed_center_3d.y()};
|
||||
model.center_instances_around_point(bed_center_2d);
|
||||
|
||||
GLVolumeCollection volumes;
|
||||
volumes.volumes.push_back(new GLVolume());
|
||||
GLVolume* volume = volumes.volumes[0];
|
||||
volume->indexed_vertex_array.load_mesh(model.mesh());
|
||||
volume->indexed_vertex_array.finalize_geometry(true);
|
||||
volume->set_instance_transformation(model.objects[0]->instances[0]->get_transformation());
|
||||
volume->set_volume_transformation(model.objects[0]->volumes[0]->get_transformation());
|
||||
|
||||
ThumbnailData thumbnail_data;
|
||||
const ThumbnailsParams thumbnail_params = { {}, false, false, false, true };
|
||||
wxGetApp().plater()->canvas3D()->render_thumbnail(thumbnail_data, 256, 256, thumbnail_params, volumes, Camera::EType::Perspective);
|
||||
|
||||
if (thumbnail_data.width == 0 || thumbnail_data.height == 0)
|
||||
return;
|
||||
|
||||
wxImage image(thumbnail_data.width, thumbnail_data.height);
|
||||
image.InitAlpha();
|
||||
|
||||
for (unsigned int r = 0; r < thumbnail_data.height; ++r) {
|
||||
unsigned int rr = (thumbnail_data.height - 1 - r) * thumbnail_data.width;
|
||||
for (unsigned int c = 0; c < thumbnail_data.width; ++c) {
|
||||
unsigned char* px = (unsigned char*)thumbnail_data.pixels.data() + 4 * (rr + c);
|
||||
image.SetRGB((int)c, (int)r, px[0], px[1], px[2]);
|
||||
image.SetAlpha((int)c, (int)r, px[3]);
|
||||
}
|
||||
}
|
||||
|
||||
fs::path out_path = fs::path(filename);
|
||||
out_path.replace_extension("png");
|
||||
image.SaveFile(out_path.string(), wxBITMAP_TYPE_PNG);
|
||||
}
|
||||
|
||||
void GalleryDialog::load_label_icon_list()
|
||||
{
|
||||
// load names from files
|
||||
auto add_files_from_gallery = [](std::vector<Item>& items, bool sys_dir, std::string& dir_path)
|
||||
{
|
||||
fs::path dir = get_dir(sys_dir);
|
||||
if (!fs::exists(dir))
|
||||
return;
|
||||
|
||||
dir_path = get_dir_path(sys_dir);
|
||||
|
||||
std::vector<std::string> sorted_names;
|
||||
for (auto& dir_entry : fs::directory_iterator(dir))
|
||||
if (TriangleMesh mesh; is_stl_file(dir_entry) && mesh.ReadSTLFile(dir_entry.path().string().c_str()))
|
||||
sorted_names.push_back(dir_entry.path().stem().string());
|
||||
|
||||
// sort the filename case insensitive
|
||||
std::sort(sorted_names.begin(), sorted_names.end(), [](const std::string& a, const std::string& b)
|
||||
{ return boost::algorithm::to_lower_copy(a) < boost::algorithm::to_lower_copy(b); });
|
||||
|
||||
for (const std::string& name : sorted_names)
|
||||
items.push_back(Item{ name, sys_dir });
|
||||
};
|
||||
|
||||
wxBusyCursor busy;
|
||||
|
||||
std::string m_sys_dir_path, m_cust_dir_path;
|
||||
std::vector<Item> list_items;
|
||||
add_files_from_gallery(list_items, true, m_sys_dir_path);
|
||||
add_files_from_gallery(list_items, false, m_cust_dir_path);
|
||||
|
||||
// Make an image list containing large icons
|
||||
|
||||
int px_cnt = (int)(em_unit() * IMG_PX_CNT * 0.1f + 0.5f);
|
||||
m_image_list = new wxImageList(px_cnt, px_cnt);
|
||||
|
||||
std::string ext = ".png";
|
||||
|
||||
for (const auto& item : list_items) {
|
||||
std::string img_name = (item.is_system ? m_sys_dir_path : m_cust_dir_path) + item.name + ext;
|
||||
std::string stl_name = (item.is_system ? m_sys_dir_path : m_cust_dir_path) + item.name + ".stl";
|
||||
if (!fs::exists(img_name))
|
||||
generate_thumbnail_from_stl(stl_name);
|
||||
|
||||
wxImage image;
|
||||
if (!image.CanRead(from_u8(img_name)) ||
|
||||
!image.LoadFile(from_u8(img_name), wxBITMAP_TYPE_PNG) ||
|
||||
image.GetWidth() == 0 || image.GetHeight() == 0) {
|
||||
add_default_image(m_image_list, item.is_system);
|
||||
continue;
|
||||
}
|
||||
image.Rescale(px_cnt, px_cnt, wxIMAGE_QUALITY_BILINEAR);
|
||||
|
||||
if (item.is_system)
|
||||
add_lock(image);
|
||||
wxBitmap bmp = wxBitmap(std::move(image));
|
||||
m_image_list->Add(bmp);
|
||||
}
|
||||
|
||||
m_list_ctrl->SetImageList(m_image_list, wxIMAGE_LIST_NORMAL);
|
||||
|
||||
int img_cnt = m_image_list->GetImageCount();
|
||||
for (int i = 0; i < img_cnt; i++) {
|
||||
m_list_ctrl->InsertItem(i, from_u8(list_items[i].name), i);
|
||||
if (list_items[i].is_system)
|
||||
m_list_ctrl->SetItemFont(i, wxGetApp().bold_font());
|
||||
}
|
||||
}
|
||||
|
||||
void GalleryDialog::get_input_files(wxArrayString& input_files)
|
||||
{
|
||||
for (const Item& item : m_selected_items)
|
||||
input_files.Add(from_u8(get_dir_path(item.is_system) + item.name + ".stl"));
|
||||
}
|
||||
|
||||
void GalleryDialog::add_custom_shapes(wxEvent& event)
|
||||
{
|
||||
wxArrayString input_files;
|
||||
wxFileDialog dialog(this, _L("Choose one or more files (STL):"),
|
||||
from_u8(wxGetApp().app_config->get_last_dir()), "",
|
||||
file_wildcards(FT_STL), wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
|
||||
|
||||
if (dialog.ShowModal() == wxID_OK)
|
||||
dialog.GetPaths(input_files);
|
||||
|
||||
if (input_files.IsEmpty())
|
||||
return;
|
||||
|
||||
load_files(input_files);
|
||||
}
|
||||
|
||||
void GalleryDialog::del_custom_shapes(wxEvent& event)
|
||||
{
|
||||
auto custom_dir = get_dir(false);
|
||||
|
||||
auto remove_file = [custom_dir](const std::string& name) {
|
||||
if (!fs::exists(custom_dir / name))
|
||||
return;
|
||||
try {
|
||||
fs::remove(custom_dir / name);
|
||||
}
|
||||
catch (fs::filesystem_error const& e) {
|
||||
std::cerr << e.what() << '\n';
|
||||
}
|
||||
};
|
||||
|
||||
for (const Item& item : m_selected_items) {
|
||||
remove_file(item.name + ".stl");
|
||||
remove_file(item.name + ".png");
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
static void show_warning(const wxString& title, const std::string& error_file_type)
|
||||
{
|
||||
const wxString msg_text = format_wxstr(_L("It looks like selected %1%-file has an error or is destructed.\n"
|
||||
"We can't load this file"), error_file_type);
|
||||
MessageDialog dialog(nullptr, msg_text, title, wxICON_WARNING | wxOK);
|
||||
dialog.ShowModal();
|
||||
}
|
||||
|
||||
void GalleryDialog::replace_custom_png(wxEvent& event)
|
||||
{
|
||||
if (m_selected_items.size() != 1 || m_selected_items[0].is_system)
|
||||
return;
|
||||
|
||||
wxFileDialog dialog(this, _L("Choose one PNG file:"),
|
||||
from_u8(wxGetApp().app_config->get_last_dir()), "",
|
||||
"PNG files (*.png)|*.png;*.PNG", wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dialog.ShowModal() != wxID_OK)
|
||||
return;
|
||||
|
||||
wxArrayString input_files;
|
||||
dialog.GetPaths(input_files);
|
||||
|
||||
if (input_files.IsEmpty())
|
||||
return;
|
||||
|
||||
if (wxImage image; !image.CanRead(input_files.Item(0))) {
|
||||
show_warning(_L("Replacing of the PNG"), "PNG");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
fs::path current = fs::path(into_u8(input_files.Item(0)));
|
||||
fs::copy_file(current, get_dir(false) / (m_selected_items[0].name + ".png"), fs::copy_option::overwrite_if_exists);
|
||||
}
|
||||
catch (fs::filesystem_error const& e) {
|
||||
std::cerr << e.what() << '\n';
|
||||
return;
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void GalleryDialog::select(wxListEvent& event)
|
||||
{
|
||||
int idx = event.GetIndex();
|
||||
Item item { into_u8(m_list_ctrl->GetItemText(idx)), m_list_ctrl->GetItemFont(idx).GetWeight() == wxFONTWEIGHT_BOLD };
|
||||
|
||||
m_selected_items.push_back(item);
|
||||
}
|
||||
|
||||
void GalleryDialog::deselect(wxListEvent& event)
|
||||
{
|
||||
if (m_list_ctrl->GetSelectedItemCount() == 0) {
|
||||
m_selected_items.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string name = into_u8(m_list_ctrl->GetItemText(event.GetIndex()));
|
||||
m_selected_items.erase(std::remove_if(m_selected_items.begin(), m_selected_items.end(), [name](Item item) { return item.name == name; }));
|
||||
}
|
||||
|
||||
void GalleryDialog::update()
|
||||
{
|
||||
m_selected_items.clear();
|
||||
m_image_list->RemoveAll();
|
||||
m_list_ctrl->ClearAll();
|
||||
load_label_icon_list();
|
||||
}
|
||||
|
||||
bool GalleryDialog::load_files(const wxArrayString& input_files)
|
||||
{
|
||||
auto dest_dir = get_dir(false);
|
||||
|
||||
try {
|
||||
if (!fs::exists(dest_dir))
|
||||
if (!fs::create_directory(dest_dir)) {
|
||||
std::cerr << "Unable to create destination directory" << dest_dir.string() << '\n' ;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (fs::filesystem_error const& e) {
|
||||
std::cerr << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
// Iterate through the source directory
|
||||
for (size_t i = 0; i < input_files.size(); ++i) {
|
||||
std::string input_file = into_u8(input_files.Item(i));
|
||||
|
||||
if (TriangleMesh mesh; !mesh.ReadSTLFile(input_file.c_str())) {
|
||||
show_warning(format_wxstr(_L("Loading of the \"%1%\""), input_file), "STL");
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
fs::path current = fs::path(input_file);
|
||||
if (!fs::exists(dest_dir / current.filename()))
|
||||
fs::copy_file(current, dest_dir / current.filename());
|
||||
else {
|
||||
std::string filename = current.stem().string();
|
||||
|
||||
int file_idx = 0;
|
||||
for (auto& dir_entry : fs::directory_iterator(dest_dir))
|
||||
if (is_stl_file(dir_entry)) {
|
||||
std::string name = dir_entry.path().stem().string();
|
||||
if (filename == name) {
|
||||
if (file_idx == 0)
|
||||
file_idx++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (name.find(filename) != 0 ||
|
||||
name[filename.size()] != ' ' || name[filename.size()+1] != '(' || name[name.size()-1] != ')')
|
||||
continue;
|
||||
std::string idx_str = name.substr(filename.size() + 2, name.size() - filename.size() - 3);
|
||||
if (int cur_idx = atoi(idx_str.c_str()); file_idx <= cur_idx)
|
||||
file_idx = cur_idx+1;
|
||||
}
|
||||
if (file_idx > 0) {
|
||||
filename += " (" + std::to_string(file_idx) + ").stl";
|
||||
fs::copy_file(current, dest_dir / filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (fs::filesystem_error const& e) {
|
||||
std::cerr << e.what() << '\n';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
update();
|
||||
return true;
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GUI
|
59
src/slic3r/GUI/GalleryDialog.hpp
Normal file
59
src/slic3r/GUI/GalleryDialog.hpp
Normal file
@ -0,0 +1,59 @@
|
||||
#ifndef slic3r_GalleryDialog_hpp_
|
||||
#define slic3r_GalleryDialog_hpp_
|
||||
|
||||
#include "GUI_Utils.hpp"
|
||||
|
||||
class wxListCtrl;
|
||||
class wxImageList;
|
||||
class wxListEvent;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace GUI {
|
||||
|
||||
|
||||
//------------------------------------------
|
||||
// GalleryDialog
|
||||
//------------------------------------------
|
||||
|
||||
class GalleryDialog : public DPIDialog
|
||||
{
|
||||
wxListCtrl* m_list_ctrl { nullptr };
|
||||
wxImageList* m_image_list { nullptr };
|
||||
|
||||
struct Item {
|
||||
std::string name;
|
||||
bool is_system;
|
||||
};
|
||||
std::vector<Item> m_selected_items;
|
||||
|
||||
int ID_BTN_ADD_CUSTOM_SHAPE;
|
||||
int ID_BTN_DEL_CUSTOM_SHAPE;
|
||||
int ID_BTN_REPLACE_CUSTOM_PNG;
|
||||
|
||||
void load_label_icon_list();
|
||||
void add_custom_shapes(wxEvent& event);
|
||||
void del_custom_shapes(wxEvent& event);
|
||||
void replace_custom_png(wxEvent& event);
|
||||
void select(wxListEvent& event);
|
||||
void deselect(wxListEvent& event);
|
||||
|
||||
void update();
|
||||
|
||||
public:
|
||||
GalleryDialog(wxWindow* parent);
|
||||
~GalleryDialog();
|
||||
|
||||
void get_input_files(wxArrayString& input_files);
|
||||
bool load_files(const wxArrayString& input_files);
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
void on_sys_color_changed() override {};
|
||||
};
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif //slic3r_GalleryDialog_hpp_
|
@ -27,11 +27,6 @@ const std::array<float, 4> GLGizmoCut::GrabberColor = { 1.0, 0.5, 0.0, 1.0 };
|
||||
|
||||
GLGizmoCut::GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
|
||||
: GLGizmoBase(parent, icon_filename, sprite_id)
|
||||
, m_cut_z(0.0)
|
||||
, m_max_z(0.0)
|
||||
, m_keep_upper(true)
|
||||
, m_keep_lower(true)
|
||||
, m_rotate_lower(false)
|
||||
{}
|
||||
|
||||
std::string GLGizmoCut::get_tooltip() const
|
||||
@ -58,9 +53,8 @@ std::string GLGizmoCut::on_get_name() const
|
||||
void GLGizmoCut::on_set_state()
|
||||
{
|
||||
// Reset m_cut_z on gizmo activation
|
||||
if (get_state() == On) {
|
||||
m_cut_z = m_parent.get_selection().get_bounding_box().size()(2) / 2.0;
|
||||
}
|
||||
if (get_state() == On)
|
||||
m_cut_z = bounding_box().center().z();
|
||||
}
|
||||
|
||||
bool GLGizmoCut::on_is_activable() const
|
||||
@ -74,13 +68,12 @@ void GLGizmoCut::on_start_dragging()
|
||||
if (m_hover_id == -1)
|
||||
return;
|
||||
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
const BoundingBoxf3& box = selection.get_bounding_box();
|
||||
const BoundingBoxf3 box = bounding_box();
|
||||
m_max_z = box.max.z();
|
||||
m_start_z = m_cut_z;
|
||||
update_max_z(selection);
|
||||
m_drag_pos = m_grabbers[m_hover_id].center;
|
||||
m_drag_center = box.center();
|
||||
m_drag_center(2) = m_cut_z;
|
||||
m_drag_center.z() = m_cut_z;
|
||||
}
|
||||
|
||||
void GLGizmoCut::on_update(const UpdateData& data)
|
||||
@ -91,13 +84,11 @@ void GLGizmoCut::on_update(const UpdateData& data)
|
||||
|
||||
void GLGizmoCut::on_render() const
|
||||
{
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
|
||||
update_max_z(selection);
|
||||
|
||||
const BoundingBoxf3& box = selection.get_bounding_box();
|
||||
BoundingBoxf3 box = bounding_box();
|
||||
Vec3d plane_center = box.center();
|
||||
plane_center.z() = m_cut_z;
|
||||
m_max_z = box.max.z();
|
||||
set_cut_z(m_cut_z);
|
||||
|
||||
const float min_x = box.min.x() - Margin;
|
||||
const float max_x = box.max.x() + Margin;
|
||||
@ -160,10 +151,10 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
|
||||
|
||||
m_imgui->begin(_L("Cut"), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
|
||||
const bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
|
||||
|
||||
// adjust window position to avoid overlap the view toolbar
|
||||
float win_h = ImGui::GetWindowHeight();
|
||||
const float win_h = ImGui::GetWindowHeight();
|
||||
y = std::min(y, bottom_limit - win_h);
|
||||
ImGui::SetWindowPos(ImVec2(x, y), ImGuiCond_Always);
|
||||
if (last_h != win_h || last_y != y) {
|
||||
@ -198,7 +189,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || m_cut_z <= 0.0 || m_max_z < m_cut_z);
|
||||
m_imgui->disabled_begin((!m_keep_upper && !m_keep_lower) || m_cut_z <= 0.0 || m_max_z <= m_cut_z);
|
||||
const bool cut_clicked = m_imgui->button(_L("Perform cut"));
|
||||
m_imgui->disabled_end();
|
||||
|
||||
@ -208,12 +199,6 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
|
||||
perform_cut(m_parent.get_selection());
|
||||
}
|
||||
|
||||
void GLGizmoCut::update_max_z(const Selection& selection) const
|
||||
{
|
||||
m_max_z = selection.get_bounding_box().size()(2);
|
||||
set_cut_z(m_cut_z);
|
||||
}
|
||||
|
||||
void GLGizmoCut::set_cut_z(double cut_z) const
|
||||
{
|
||||
// Clamp the plane to the object's bounding box
|
||||
@ -229,10 +214,10 @@ void GLGizmoCut::perform_cut(const Selection& selection)
|
||||
|
||||
// m_cut_z is the distance from the bed. Subtract possible SLA elevation.
|
||||
const GLVolume* first_glvolume = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||
coordf_t object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z();
|
||||
const double object_cut_z = m_cut_z - first_glvolume->get_sla_shift_z();
|
||||
|
||||
if (object_cut_z > 0.)
|
||||
wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z,
|
||||
if (0.0 < object_cut_z && object_cut_z < m_max_z)
|
||||
wxGetApp().plater()->cut(object_idx, instance_idx, object_cut_z,
|
||||
only_if(m_keep_upper, ModelObjectCutAttribute::KeepUpper) |
|
||||
only_if(m_keep_lower, ModelObjectCutAttribute::KeepLower) |
|
||||
only_if(m_rotate_lower, ModelObjectCutAttribute::FlipLower));
|
||||
@ -247,16 +232,15 @@ double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const
|
||||
|
||||
const Vec3d starting_vec = m_drag_pos - m_drag_center;
|
||||
const double len_starting_vec = starting_vec.norm();
|
||||
if (len_starting_vec != 0.0)
|
||||
{
|
||||
Vec3d mouse_dir = mouse_ray.unit_vector();
|
||||
if (len_starting_vec != 0.0) {
|
||||
const Vec3d mouse_dir = mouse_ray.unit_vector();
|
||||
// finds the intersection of the mouse ray with the plane parallel to the camera viewport and passing throught the starting position
|
||||
// use ray-plane intersection see i.e. https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection algebric form
|
||||
// in our case plane normal and ray direction are the same (orthogonal view)
|
||||
// when moving to perspective camera the negative z unit axis of the camera needs to be transformed in world space and used as plane normal
|
||||
Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
|
||||
const Vec3d inters = mouse_ray.a + (m_drag_pos - mouse_ray.a).dot(mouse_dir) / mouse_dir.squaredNorm() * mouse_dir;
|
||||
// vector from the starting position to the found intersection
|
||||
Vec3d inters_vec = inters - m_drag_pos;
|
||||
const Vec3d inters_vec = inters - m_drag_pos;
|
||||
|
||||
// finds projection of the vector along the staring direction
|
||||
projection = inters_vec.dot(starting_vec.normalized());
|
||||
@ -264,6 +248,18 @@ double GLGizmoCut::calc_projection(const Linef3& mouse_ray) const
|
||||
return projection;
|
||||
}
|
||||
|
||||
BoundingBoxf3 GLGizmoCut::bounding_box() const
|
||||
{
|
||||
BoundingBoxf3 ret;
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
const Selection::IndicesList& idxs = selection.get_volume_idxs();
|
||||
for (unsigned int i : idxs) {
|
||||
const GLVolume* volume = selection.get_volume(i);
|
||||
if (!volume->is_modifier)
|
||||
ret.merge(volume->transformed_convex_hull_bounding_box());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
@ -13,14 +13,14 @@ class GLGizmoCut : public GLGizmoBase
|
||||
static const double Margin;
|
||||
static const std::array<float, 4> GrabberColor;
|
||||
|
||||
mutable double m_cut_z;
|
||||
double m_start_z;
|
||||
mutable double m_max_z;
|
||||
mutable double m_cut_z{ 0.0 };
|
||||
mutable double m_max_z{ 0.0 };
|
||||
double m_start_z{ 0.0 };
|
||||
Vec3d m_drag_pos;
|
||||
Vec3d m_drag_center;
|
||||
bool m_keep_upper;
|
||||
bool m_keep_lower;
|
||||
bool m_rotate_lower;
|
||||
bool m_keep_upper{ true };
|
||||
bool m_keep_lower{ true };
|
||||
bool m_rotate_lower{ false };
|
||||
|
||||
public:
|
||||
GLGizmoCut(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
@ -32,7 +32,7 @@ public:
|
||||
|
||||
protected:
|
||||
virtual bool on_init() override;
|
||||
virtual void on_load(cereal::BinaryInputArchive& ar) override{ ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
|
||||
virtual void on_load(cereal::BinaryInputArchive& ar) override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
|
||||
virtual void on_save(cereal::BinaryOutputArchive& ar) const override { ar(m_cut_z, m_keep_upper, m_keep_lower, m_rotate_lower); }
|
||||
virtual std::string on_get_name() const override;
|
||||
virtual void on_set_state() override;
|
||||
@ -44,9 +44,9 @@ protected:
|
||||
virtual void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
|
||||
private:
|
||||
void update_max_z(const Selection& selection) const;
|
||||
void perform_cut(const Selection& selection);
|
||||
double calc_projection(const Linef3& mouse_ray) const;
|
||||
BoundingBoxf3 bounding_box() const;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
|
@ -170,6 +170,7 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||
if (mv->is_model_part()) {
|
||||
++idx;
|
||||
m_triangle_selectors[idx]->reset();
|
||||
m_triangle_selectors[idx]->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,6 +193,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
// Manually inserted values aren't clamped by ImGui. Zero cursor size results in a crash.
|
||||
m_cursor_radius = std::clamp(m_cursor_radius, CursorRadiusMin, CursorRadiusMax);
|
||||
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
@ -285,13 +288,12 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool block)
|
||||
|
||||
// Now calculate dot product of vert_direction and facets' normals.
|
||||
int idx = -1;
|
||||
for (const stl_facet& facet : mv->mesh().stl.facet_start) {
|
||||
for (const stl_facet &facet : mv->mesh().stl.facet_start) {
|
||||
++idx;
|
||||
if (facet.normal.dot(down) > dot_limit)
|
||||
m_triangle_selectors[mesh_id]->set_facet(idx,
|
||||
block
|
||||
? EnforcerBlockerType::BLOCKER
|
||||
: EnforcerBlockerType::ENFORCER);
|
||||
if (facet.normal.dot(down) > dot_limit) {
|
||||
m_triangle_selectors[mesh_id]->set_facet(idx, block ? EnforcerBlockerType::BLOCKER : EnforcerBlockerType::ENFORCER);
|
||||
m_triangle_selectors.back()->request_update_render_data();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -346,6 +348,7 @@ void GLGizmoFdmSupports::update_from_model_object()
|
||||
|
||||
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh));
|
||||
m_triangle_selectors.back()->deserialize(mv->supported_facets.get_data());
|
||||
m_triangle_selectors.back()->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,16 +107,24 @@ bool GLGizmoMmuSegmentation::on_init()
|
||||
m_desc["reset_direction"] = _L("Reset direction");
|
||||
m_desc["clipping_of_view"] = _L("Clipping of view") + ": ";
|
||||
m_desc["cursor_size"] = _L("Brush size") + ": ";
|
||||
m_desc["cursor_type"] = _L("Brush shape") + ": ";
|
||||
m_desc["cursor_type"] = _L("Brush shape");
|
||||
m_desc["first_color_caption"] = _L("Left mouse button") + ": ";
|
||||
m_desc["first_color"] = _L("First color");
|
||||
m_desc["second_color_caption"] = _L("Right mouse button") + ": ";
|
||||
m_desc["second_color"] = _L("Second color");
|
||||
m_desc["remove_caption"] = _L("Shift + Left mouse button") + ": ";
|
||||
m_desc["remove"] = _L("Remove painted color");
|
||||
m_desc["remove_all"] = _L("Remove all painted colors");
|
||||
m_desc["remove_all"] = _L("Remove all painted areas");
|
||||
m_desc["circle"] = _L("Circle");
|
||||
m_desc["sphere"] = _L("Sphere");
|
||||
m_desc["pointer"] = _L("Pointer");
|
||||
|
||||
m_desc["tool_type"] = _L("Tool type");
|
||||
m_desc["tool_brush"] = _L("Brush");
|
||||
m_desc["tool_seed_fill"] = _L("Seed fill");
|
||||
m_desc["tool_bucket_fill"] = _L("Bucket fill");
|
||||
|
||||
m_desc["seed_fill"] = _L("Seed fill");
|
||||
m_desc["seed_fill_angle"] = _L("Seed fill angle");
|
||||
|
||||
init_extruders_data();
|
||||
@ -147,8 +155,8 @@ void GLGizmoMmuSegmentation::set_painter_gizmo_data(const Selection &selection)
|
||||
return;
|
||||
|
||||
ModelObject *model_object = m_c->selection_info()->model_object();
|
||||
int prev_extruders_count = int(m_original_extruders_colors.size());
|
||||
if (prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors) {
|
||||
if (int prev_extruders_count = int(m_original_extruders_colors.size());
|
||||
prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors) {
|
||||
if (wxGetApp().extruders_edited_cnt() > int(GLGizmoMmuSegmentation::EXTRUDERS_LIMIT))
|
||||
show_notification_extruders_limit_exceeded();
|
||||
|
||||
@ -222,7 +230,7 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
if (!m_c->selection_info()->model_object())
|
||||
return;
|
||||
|
||||
const float approx_height = m_imgui->scaled(23.0f);
|
||||
const float approx_height = m_imgui->scaled(25.0f);
|
||||
y = std::min(y, bottom_limit - approx_height);
|
||||
m_imgui->set_next_window_pos(x, y, ImGuiCond_Always);
|
||||
|
||||
@ -232,10 +240,12 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
const float clipping_slider_left = std::max(m_imgui->calc_text_size(m_desc.at("clipping_of_view")).x,
|
||||
m_imgui->calc_text_size(m_desc.at("reset_direction")).x) + m_imgui->scaled(1.5f);
|
||||
const float cursor_slider_left = m_imgui->calc_text_size(m_desc.at("cursor_size")).x + m_imgui->scaled(1.f);
|
||||
const float autoset_slider_left = m_imgui->calc_text_size(m_desc.at("seed_fill_angle")).x + m_imgui->scaled(1.f);
|
||||
const float cursor_type_radio_left = m_imgui->calc_text_size(m_desc.at("cursor_type")).x + m_imgui->scaled(1.f);
|
||||
const float cursor_type_radio_width1 = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
|
||||
const float cursor_type_radio_width2 = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
|
||||
const float seed_fill_slider_left = m_imgui->calc_text_size(m_desc.at("seed_fill_angle")).x + m_imgui->scaled(1.f);
|
||||
|
||||
const float cursor_type_radio_circle = m_imgui->calc_text_size(m_desc["circle"]).x + m_imgui->scaled(2.5f);
|
||||
const float cursor_type_radio_sphere = m_imgui->calc_text_size(m_desc["sphere"]).x + m_imgui->scaled(2.5f);
|
||||
const float cursor_type_radio_pointer = m_imgui->calc_text_size(m_desc["pointer"]).x + m_imgui->scaled(2.5f);
|
||||
|
||||
const float button_width = m_imgui->calc_text_size(m_desc.at("remove_all")).x + m_imgui->scaled(1.f);
|
||||
const float buttons_width = m_imgui->scaled(0.5f);
|
||||
const float minimal_slider_width = m_imgui->scaled(4.f);
|
||||
@ -243,6 +253,10 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
const float combo_label_width = std::max(m_imgui->calc_text_size(m_desc.at("first_color")).x,
|
||||
m_imgui->calc_text_size(m_desc.at("second_color")).x) + m_imgui->scaled(1.f);
|
||||
|
||||
const float tool_type_radio_brush = m_imgui->calc_text_size(m_desc["tool_brush"]).x + m_imgui->scaled(2.5f);
|
||||
const float tool_type_radio_bucket_fill = m_imgui->calc_text_size(m_desc["tool_bucket_fill"]).x + m_imgui->scaled(2.5f);
|
||||
const float tool_type_radio_seed_fill = m_imgui->calc_text_size(m_desc["tool_seed_fill"]).x + m_imgui->scaled(2.5f);
|
||||
|
||||
float caption_max = 0.f;
|
||||
float total_text_max = 0.;
|
||||
for (const auto &t : std::array<std::string, 3>{"first_color", "second_color", "remove"}) {
|
||||
@ -252,10 +266,12 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
caption_max += m_imgui->scaled(1.f);
|
||||
total_text_max += m_imgui->scaled(1.f);
|
||||
|
||||
float window_width = minimal_slider_width + std::max(autoset_slider_left, std::max(cursor_slider_left, clipping_slider_left));
|
||||
float sliders_width = std::max(seed_fill_slider_left, std::max(cursor_slider_left, clipping_slider_left));
|
||||
float window_width = minimal_slider_width + sliders_width;
|
||||
window_width = std::max(window_width, total_text_max);
|
||||
window_width = std::max(window_width, button_width);
|
||||
window_width = std::max(window_width, cursor_type_radio_left + cursor_type_radio_width1 + cursor_type_radio_width2);
|
||||
window_width = std::max(window_width, cursor_type_radio_circle + cursor_type_radio_sphere + cursor_type_radio_pointer);
|
||||
window_width = std::max(window_width, tool_type_radio_brush + tool_type_radio_bucket_fill + tool_type_radio_seed_fill);
|
||||
window_width = std::max(window_width, 2.f * buttons_width + m_imgui->scaled(1.f));
|
||||
|
||||
auto draw_text_with_caption = [this, &caption_max](const wxString &caption, const wxString &text) {
|
||||
@ -292,91 +308,166 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
if(ImGui::ColorEdit4("Second color##color_picker", (float*)&second_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel))
|
||||
m_modified_extruders_colors[m_second_selected_extruder_idx] = {second_color.x, second_color.y, second_color.z, second_color.w};
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (m_imgui->checkbox(_L("Seed fill"), m_seed_fill_enabled))
|
||||
if (!m_seed_fill_enabled)
|
||||
for (auto &triangle_selector : m_triangle_selectors)
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
|
||||
m_imgui->text(m_desc["seed_fill_angle"] + ":");
|
||||
ImGui::AlignTextToFramePadding();
|
||||
std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in FDM supports gizmo,"
|
||||
"placed after the number with no whitespace in between.");
|
||||
ImGui::SameLine(autoset_slider_left);
|
||||
ImGui::PushItemWidth(window_width - autoset_slider_left);
|
||||
m_imgui->disabled_begin(!m_seed_fill_enabled);
|
||||
m_imgui->slider_float("##seed_fill_angle", &m_seed_fill_angle, 0.f, 90.f, format_str.data());
|
||||
m_imgui->disabled_end();
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (m_imgui->button(m_desc.at("remove_all"))) {
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), wxString(_L("Reset selection")));
|
||||
ModelObject *mo = m_c->selection_info()->model_object();
|
||||
int idx = -1;
|
||||
for (ModelVolume *mv : mo->volumes) {
|
||||
if (mv->is_model_part()) {
|
||||
++idx;
|
||||
m_triangle_selectors[idx]->reset();
|
||||
}
|
||||
}
|
||||
|
||||
update_model_object();
|
||||
m_parent.set_as_dirty();
|
||||
}
|
||||
|
||||
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("cursor_size"));
|
||||
ImGui::SameLine(cursor_slider_left);
|
||||
ImGui::PushItemWidth(window_width - cursor_slider_left);
|
||||
ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
ImGui::Separator();
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("cursor_type"));
|
||||
ImGui::SameLine(cursor_type_radio_left + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(cursor_type_radio_width1);
|
||||
m_imgui->text(m_desc.at("tool_type"));
|
||||
|
||||
bool sphere_sel = m_cursor_type == TriangleSelector::CursorType::SPHERE;
|
||||
if (m_imgui->radio_button(m_desc["sphere"], sphere_sel))
|
||||
sphere_sel = true;
|
||||
float tool_type_offset = (window_width - tool_type_radio_brush - tool_type_radio_bucket_fill - tool_type_radio_seed_fill + m_imgui->scaled(2.f)) / 2.f;
|
||||
|
||||
ImGui::NewLine();
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::SameLine(tool_type_offset + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(tool_type_radio_brush);
|
||||
if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BRUSH))
|
||||
m_tool_type = GLGizmoMmuSegmentation::ToolType::BRUSH;
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data());
|
||||
ImGui::TextUnformatted(_L("Paints facets according to the chosen painting brush.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::SameLine(cursor_type_radio_left + cursor_type_radio_width2 + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(cursor_type_radio_width2);
|
||||
|
||||
if (m_imgui->radio_button(m_desc["circle"], !sphere_sel))
|
||||
sphere_sel = false;
|
||||
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(tool_type_radio_bucket_fill);
|
||||
if (m_imgui->radio_button(m_desc["tool_bucket_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BUCKET_FILL)) {
|
||||
m_tool_type = GLGizmoMmuSegmentation::ToolType::BUCKET_FILL;
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data());
|
||||
ImGui::TextUnformatted(_L("Paints neighboring facets that have the same color.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
m_cursor_type = sphere_sel ? TriangleSelector::CursorType::SPHERE : TriangleSelector::CursorType::CIRCLE;
|
||||
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_bucket_fill + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(tool_type_radio_seed_fill);
|
||||
if (m_imgui->radio_button(m_desc["tool_seed_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::SEED_FILL)) {
|
||||
m_tool_type = GLGizmoMmuSegmentation::ToolType::SEED_FILL;
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled);
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints neighboring facets whose relative angle is less or equal to set angle.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
// Manually inserted values aren't clamped by ImGui. Zero cursor size results in a crash.
|
||||
m_cursor_radius = std::clamp(m_cursor_radius, CursorRadiusMin, CursorRadiusMax);
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if(m_tool_type == ToolType::BRUSH) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("cursor_type"));
|
||||
ImGui::NewLine();
|
||||
|
||||
float cursor_type_offset = (window_width - cursor_type_radio_sphere - cursor_type_radio_circle - cursor_type_radio_pointer + m_imgui->scaled(2.f)) / 2.f;
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::SameLine(cursor_type_offset + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(cursor_type_radio_sphere);
|
||||
if (m_imgui->radio_button(m_desc["sphere"], m_cursor_type == TriangleSelector::CursorType::SPHERE))
|
||||
m_cursor_type = TriangleSelector::CursorType::SPHERE;
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints all facets inside, regardless of their orientation.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::SameLine(cursor_type_offset +cursor_type_radio_sphere + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(cursor_type_radio_circle);
|
||||
|
||||
if (m_imgui->radio_button(m_desc["circle"], m_cursor_type == TriangleSelector::CursorType::CIRCLE))
|
||||
m_cursor_type = TriangleSelector::CursorType::CIRCLE;
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Ignores facets facing away from the camera.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::SameLine(cursor_type_offset + cursor_type_radio_sphere + cursor_type_radio_circle + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(cursor_type_radio_pointer);
|
||||
|
||||
if (m_imgui->radio_button(m_desc["pointer"], m_cursor_type == TriangleSelector::CursorType::POINTER))
|
||||
m_cursor_type = TriangleSelector::CursorType::POINTER;
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints only one facet.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
m_imgui->disabled_begin(m_cursor_type != TriangleSelector::CursorType::SPHERE && m_cursor_type != TriangleSelector::CursorType::CIRCLE);
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("cursor_size"));
|
||||
ImGui::SameLine(sliders_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_width);
|
||||
ImGui::SliderFloat(" ", &m_cursor_radius, CursorRadiusMin, CursorRadiusMax, "%.2f");
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
m_imgui->checkbox(_L("Split triangles"), m_triangle_splitting_enabled);
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Split bigger facets into smaller ones while the object is painted.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
m_imgui->disabled_end();
|
||||
|
||||
ImGui::Separator();
|
||||
} else if(m_tool_type == ToolType::SEED_FILL) {
|
||||
m_imgui->text(m_desc["seed_fill_angle"] + ":");
|
||||
ImGui::AlignTextToFramePadding();
|
||||
std::string format_str = std::string("%.f") + I18N::translate_utf8("°", "Degree sign to use in the respective slider in MMU gizmo,"
|
||||
"placed after the number with no whitespace in between.");
|
||||
ImGui::SameLine(sliders_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_width);
|
||||
m_imgui->slider_float("##seed_fill_angle", &m_seed_fill_angle, SeedFillAngleMin, SeedFillAngleMax, format_str.data());
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Alt + Mouse wheel").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
if (m_c->object_clipper()->get_position() == 0.f) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
m_imgui->text(m_desc.at("clipping_of_view"));
|
||||
@ -386,8 +477,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::SameLine(clipping_slider_left);
|
||||
ImGui::PushItemWidth(window_width - clipping_slider_left);
|
||||
ImGui::SameLine(sliders_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_width);
|
||||
auto clp_dist = float(m_c->object_clipper()->get_position());
|
||||
if (ImGui::SliderFloat(" ", &clp_dist, 0.f, 1.f, "%.2f"))
|
||||
m_c->object_clipper()->set_position(clp_dist, true);
|
||||
@ -398,6 +489,23 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
if (m_imgui->button(m_desc.at("remove_all"))) {
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), wxString(_L("Reset selection")));
|
||||
ModelObject * mo = m_c->selection_info()->model_object();
|
||||
int idx = -1;
|
||||
for (ModelVolume *mv : mo->volumes)
|
||||
if (mv->is_model_part()) {
|
||||
++idx;
|
||||
m_triangle_selectors[idx]->reset();
|
||||
m_triangle_selectors[idx]->request_update_render_data();
|
||||
}
|
||||
|
||||
update_model_object();
|
||||
m_parent.set_as_dirty();
|
||||
}
|
||||
|
||||
m_imgui->end();
|
||||
}
|
||||
|
||||
@ -437,8 +545,9 @@ void GLGizmoMmuSegmentation::init_model_triangle_selectors()
|
||||
const TriangleMesh *mesh = &mv->mesh();
|
||||
|
||||
int extruder_idx = (mv->extruder_id() > 0) ? mv->extruder_id() - 1 : 0;
|
||||
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorMmuGui>(*mesh, m_modified_extruders_colors, m_original_extruders_colors[size_t(extruder_idx)]));
|
||||
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorMmGui>(*mesh, m_modified_extruders_colors, m_original_extruders_colors[size_t(extruder_idx)]));
|
||||
m_triangle_selectors.back()->deserialize(mv->mmu_segmentation_facets.get_data());
|
||||
m_triangle_selectors.back()->request_update_render_data();
|
||||
}
|
||||
m_original_volumes_extruder_idxs = get_extruder_id_for_volumes(*mo);
|
||||
}
|
||||
@ -446,6 +555,13 @@ void GLGizmoMmuSegmentation::init_model_triangle_selectors()
|
||||
void GLGizmoMmuSegmentation::update_from_model_object()
|
||||
{
|
||||
wxBusyCursor wait;
|
||||
|
||||
// Extruder colors need to be reloaded before calling init_model_triangle_selectors to render painted triangles
|
||||
// using colors from loaded 3MF and not from printer profile in Slicer.
|
||||
if (int prev_extruders_count = int(m_original_extruders_colors.size());
|
||||
prev_extruders_count != wxGetApp().extruders_edited_cnt() || get_extruders_colors() != m_original_extruders_colors)
|
||||
this->init_extruders_data();
|
||||
|
||||
this->init_model_triangle_selectors();
|
||||
}
|
||||
|
||||
@ -466,56 +582,60 @@ std::array<float, 4> GLGizmoMmuSegmentation::get_cursor_sphere_right_button_colo
|
||||
return {color[0], color[1], color[2], 0.25f};
|
||||
}
|
||||
|
||||
void TriangleSelectorMmuGui::render(ImGuiWrapper *imgui)
|
||||
void TriangleSelectorMmGui::render(ImGuiWrapper *imgui)
|
||||
{
|
||||
static constexpr std::array<float, 4> seed_fill_color{0.f, 1.f, 0.44f, 1.f};
|
||||
|
||||
std::vector<int> color_cnt(m_iva_colors.size());
|
||||
int seed_fill_cnt = 0;
|
||||
for (auto &iva_color : m_iva_colors)
|
||||
iva_color.release_geometry();
|
||||
m_iva_seed_fill.release_geometry();
|
||||
|
||||
auto append_triangle = [this](GLIndexedVertexArray &iva, int &cnt, const Triangle &tr) -> void {
|
||||
for (int i = 0; i < 3; ++i)
|
||||
iva.push_geometry(m_vertices[tr.verts_idxs[i]].v, m_mesh->stl.facet_start[tr.source_triangle].normal);
|
||||
iva.push_triangle(cnt, cnt + 1, cnt + 2);
|
||||
cnt += 3;
|
||||
};
|
||||
|
||||
for (size_t color_idx = 0; color_idx < m_iva_colors.size(); ++color_idx) {
|
||||
for (const Triangle &tr : m_triangles) {
|
||||
if (!tr.valid() || tr.is_split() || tr.is_selected_by_seed_fill() || tr.get_state() != EnforcerBlockerType(color_idx))
|
||||
continue;
|
||||
append_triangle(m_iva_colors[color_idx], color_cnt[color_idx], tr);
|
||||
}
|
||||
}
|
||||
|
||||
for (const Triangle &tr : m_triangles) {
|
||||
if (!tr.valid() || tr.is_split() || !tr.is_selected_by_seed_fill())
|
||||
continue;
|
||||
append_triangle(m_iva_seed_fill, seed_fill_cnt, tr);
|
||||
}
|
||||
|
||||
for (auto &iva_color : m_iva_colors)
|
||||
iva_color.finalize_geometry(true);
|
||||
m_iva_seed_fill.finalize_geometry(true);
|
||||
if (m_update_render_data)
|
||||
update_render_data();
|
||||
|
||||
auto *shader = wxGetApp().get_current_shader();
|
||||
if (!shader)
|
||||
return;
|
||||
assert(shader->get_name() == "gouraud");
|
||||
ScopeGuard guard([shader]() { if (shader) shader->set_uniform("compute_triangle_normals_in_fs", false);});
|
||||
shader->set_uniform("compute_triangle_normals_in_fs", true);
|
||||
|
||||
auto render = [&shader](const GLIndexedVertexArray &iva, const std::array<float, 4> &color) -> void {
|
||||
if (iva.has_VBOs()) {
|
||||
shader->set_uniform("uniform_color", color);
|
||||
iva.render();
|
||||
for (size_t color_idx = 0; color_idx < m_gizmo_scene.triangle_indices.size(); ++color_idx)
|
||||
if (m_gizmo_scene.has_VBOs(color_idx)) {
|
||||
shader->set_uniform("uniform_color", color_idx == 0 ? m_default_volume_color :
|
||||
color_idx == (m_gizmo_scene.triangle_indices.size() - 1) ? seed_fill_color :
|
||||
m_colors[color_idx - 1]);
|
||||
m_gizmo_scene.render(color_idx);
|
||||
}
|
||||
};
|
||||
|
||||
for (size_t color_idx = 0; color_idx < m_iva_colors.size(); ++color_idx)
|
||||
render(m_iva_colors[color_idx], (color_idx == 0) ? m_default_volume_color : m_colors[color_idx - 1]);
|
||||
render(m_iva_seed_fill, seed_fill_color);
|
||||
m_update_render_data = false;
|
||||
}
|
||||
|
||||
void TriangleSelectorMmGui::update_render_data()
|
||||
{
|
||||
m_gizmo_scene.release_geometry();
|
||||
m_vertices.reserve(m_vertices.size() * 3);
|
||||
for (const Vertex &vr : m_vertices) {
|
||||
m_gizmo_scene.vertices.emplace_back(vr.v.x());
|
||||
m_gizmo_scene.vertices.emplace_back(vr.v.y());
|
||||
m_gizmo_scene.vertices.emplace_back(vr.v.z());
|
||||
}
|
||||
m_gizmo_scene.finalize_vertices();
|
||||
|
||||
for (const Triangle &tr : m_triangles)
|
||||
if (tr.valid() && !tr.is_split()) {
|
||||
int color = int(tr.get_state());
|
||||
std::vector<int> &iva = tr.is_selected_by_seed_fill() ? m_gizmo_scene.triangle_indices.back() :
|
||||
color < int(m_gizmo_scene.triangle_indices.size() - 1) ? m_gizmo_scene.triangle_indices[color] :
|
||||
m_gizmo_scene.triangle_indices.front();
|
||||
if (iva.size() + 3 > iva.capacity())
|
||||
iva.reserve(next_highest_power_of_2(iva.size() + 3));
|
||||
|
||||
iva.emplace_back(tr.verts_idxs[0]);
|
||||
iva.emplace_back(tr.verts_idxs[1]);
|
||||
iva.emplace_back(tr.verts_idxs[2]);
|
||||
}
|
||||
|
||||
for (size_t color_idx = 0; color_idx < m_gizmo_scene.triangle_indices.size(); ++color_idx)
|
||||
m_gizmo_scene.triangle_indices_sizes[color_idx] = m_gizmo_scene.triangle_indices[color_idx].size();
|
||||
|
||||
m_gizmo_scene.finalize_triangle_indices();
|
||||
}
|
||||
|
||||
wxString GLGizmoMmuSegmentation::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const
|
||||
@ -530,4 +650,76 @@ wxString GLGizmoMmuSegmentation::handle_snapshot_action_name(bool shift_down, GL
|
||||
return action_name;
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::release_geometry() {
|
||||
if (this->vertices_VBO_id) {
|
||||
glsafe(::glDeleteBuffers(1, &this->vertices_VBO_id));
|
||||
this->vertices_VBO_id = 0;
|
||||
}
|
||||
for(auto &triangle_indices_VBO_id : triangle_indices_VBO_ids) {
|
||||
glsafe(::glDeleteBuffers(1, &triangle_indices_VBO_id));
|
||||
triangle_indices_VBO_id = 0;
|
||||
}
|
||||
this->clear();
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::render(size_t triangle_indices_idx) const
|
||||
{
|
||||
assert(triangle_indices_idx < this->triangle_indices_VBO_ids.size());
|
||||
assert(this->triangle_indices_sizes.size() == this->triangle_indices_VBO_ids.size());
|
||||
assert(this->vertices_VBO_id != 0);
|
||||
assert(this->triangle_indices_VBO_ids[triangle_indices_idx] != 0);
|
||||
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id));
|
||||
glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), (const void*)(0 * sizeof(float))));
|
||||
|
||||
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
||||
|
||||
// Render using the Vertex Buffer Objects.
|
||||
if (this->triangle_indices_sizes[triangle_indices_idx] > 0) {
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_ids[triangle_indices_idx]));
|
||||
glsafe(::glDrawElements(GL_TRIANGLES, GLsizei(this->triangle_indices_sizes[triangle_indices_idx]), GL_UNSIGNED_INT, nullptr));
|
||||
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
}
|
||||
|
||||
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
|
||||
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::finalize_vertices()
|
||||
{
|
||||
assert(this->vertices_VBO_id == 0);
|
||||
if (!this->vertices.empty()) {
|
||||
glsafe(::glGenBuffers(1, &this->vertices_VBO_id));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id));
|
||||
glsafe(::glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * 4, this->vertices.data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
this->vertices.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::finalize_triangle_indices()
|
||||
{
|
||||
assert(std::all_of(triangle_indices_VBO_ids.cbegin(), triangle_indices_VBO_ids.cend(), [](const auto &ti_VBO_id) { return ti_VBO_id == 0; }));
|
||||
|
||||
assert(this->triangle_indices.size() == this->triangle_indices_VBO_ids.size());
|
||||
for (size_t buffer_idx = 0; buffer_idx < this->triangle_indices.size(); ++buffer_idx)
|
||||
if (!this->triangle_indices[buffer_idx].empty()) {
|
||||
glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_ids[buffer_idx]));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_ids[buffer_idx]));
|
||||
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices[buffer_idx].size() * 4, this->triangle_indices[buffer_idx].data(),
|
||||
GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
this->triangle_indices[buffer_idx].clear();
|
||||
}
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::finalize_geometry()
|
||||
{
|
||||
assert(this->vertices_VBO_id == 0);
|
||||
assert(this->triangle_indices.size() == this->triangle_indices_VBO_ids.size());
|
||||
finalize_vertices();
|
||||
finalize_triangle_indices();
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -5,24 +5,80 @@
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
class TriangleSelectorMmuGui : public TriangleSelectorGUI {
|
||||
class GLMmSegmentationGizmo3DScene
|
||||
{
|
||||
public:
|
||||
explicit TriangleSelectorMmuGui(const TriangleMesh& mesh, const std::vector<std::array<float, 4>> &colors, const std::array<float, 4> &default_volume_color)
|
||||
: TriangleSelectorGUI(mesh), m_colors(colors), m_default_volume_color(default_volume_color) {
|
||||
// Plus 1 is because the first position is allocated for non-painted triangles.
|
||||
m_iva_colors = std::vector<GLIndexedVertexArray>(colors.size() + 1);
|
||||
GLMmSegmentationGizmo3DScene() = delete;
|
||||
|
||||
explicit GLMmSegmentationGizmo3DScene(size_t triangle_indices_buffers_count)
|
||||
{
|
||||
this->triangle_indices = std::vector<std::vector<int>>(triangle_indices_buffers_count);
|
||||
this->triangle_indices_sizes = std::vector<size_t>(triangle_indices_buffers_count);
|
||||
this->triangle_indices_VBO_ids = std::vector<unsigned int>(triangle_indices_buffers_count);
|
||||
}
|
||||
~TriangleSelectorMmuGui() override = default;
|
||||
|
||||
virtual ~GLMmSegmentationGizmo3DScene() { release_geometry(); }
|
||||
|
||||
[[nodiscard]] inline bool has_VBOs(size_t triangle_indices_idx) const
|
||||
{
|
||||
assert(triangle_indices_idx < this->triangle_indices.size());
|
||||
return this->triangle_indices_VBO_ids[triangle_indices_idx] != 0;
|
||||
}
|
||||
|
||||
// Finalize the initialization of the geometry and indices, upload the geometry and indices to OpenGL VBO objects
|
||||
// and possibly releasing it if it has been loaded into the VBOs.
|
||||
void finalize_geometry();
|
||||
// Release the geometry data, release OpenGL VBOs.
|
||||
void release_geometry();
|
||||
// Finalize the initialization of the geometry, upload the geometry to OpenGL VBO objects
|
||||
// and possibly releasing it if it has been loaded into the VBOs.
|
||||
void finalize_vertices();
|
||||
// Finalize the initialization of the indices, upload the indices to OpenGL VBO objects
|
||||
// and possibly releasing it if it has been loaded into the VBOs.
|
||||
void finalize_triangle_indices();
|
||||
|
||||
void clear()
|
||||
{
|
||||
this->vertices.clear();
|
||||
for (std::vector<int> &ti : this->triangle_indices)
|
||||
ti.clear();
|
||||
|
||||
for (size_t &triangle_indices_size : this->triangle_indices_sizes)
|
||||
triangle_indices_size = 0;
|
||||
}
|
||||
|
||||
void render(size_t triangle_indices_idx) const;
|
||||
|
||||
std::vector<float> vertices;
|
||||
std::vector<std::vector<int>> triangle_indices;
|
||||
|
||||
// When the triangle indices are loaded into the graphics card as Vertex Buffer Objects,
|
||||
// the above mentioned std::vectors are cleared and the following variables keep their original length.
|
||||
std::vector<size_t> triangle_indices_sizes;
|
||||
|
||||
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
|
||||
// Zero if the VBOs are not sent to GPU yet.
|
||||
unsigned int vertices_VBO_id{0};
|
||||
std::vector<unsigned int> triangle_indices_VBO_ids;
|
||||
};
|
||||
|
||||
class TriangleSelectorMmGui : public TriangleSelectorGUI {
|
||||
public:
|
||||
// Plus 2 in the initialization of m_gizmo_scene is because the first position is allocated for non-painted triangles, and the last position is allocated for seed fill.
|
||||
explicit TriangleSelectorMmGui(const TriangleMesh &mesh, const std::vector<std::array<float, 4>> &colors, const std::array<float, 4> &default_volume_color)
|
||||
: TriangleSelectorGUI(mesh), m_colors(colors), m_default_volume_color(default_volume_color), m_gizmo_scene(colors.size() + 2) {}
|
||||
~TriangleSelectorMmGui() override = default;
|
||||
|
||||
// Render current selection. Transformation matrices are supposed
|
||||
// to be already set.
|
||||
void render(ImGuiWrapper* imgui) override;
|
||||
|
||||
private:
|
||||
void update_render_data();
|
||||
|
||||
const std::vector<std::array<float, 4>> &m_colors;
|
||||
std::vector<GLIndexedVertexArray> m_iva_colors;
|
||||
const std::array<float, 4> m_default_volume_color;
|
||||
GLIndexedVertexArray m_iva_seed_fill;
|
||||
GLMmSegmentationGizmo3DScene m_gizmo_scene;
|
||||
};
|
||||
|
||||
class GLGizmoMmuSegmentation : public GLGizmoPainterBase
|
||||
|
@ -194,10 +194,10 @@ void GLGizmoPainterBase::render_cursor() const
|
||||
if (m_rr.mesh_id == -1)
|
||||
return;
|
||||
|
||||
if (!m_seed_fill_enabled) {
|
||||
if (m_tool_type == ToolType::BRUSH) {
|
||||
if (m_cursor_type == TriangleSelector::SPHERE)
|
||||
render_cursor_sphere(trafo_matrices[m_rr.mesh_id]);
|
||||
else
|
||||
else if (m_cursor_type == TriangleSelector::CIRCLE)
|
||||
render_cursor_circle();
|
||||
}
|
||||
}
|
||||
@ -307,11 +307,19 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
return true;
|
||||
}
|
||||
else if (alt_down) {
|
||||
m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown
|
||||
? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin)
|
||||
: std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax);
|
||||
m_parent.set_as_dirty();
|
||||
return true;
|
||||
if (m_tool_type == ToolType::BRUSH) {
|
||||
m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin)
|
||||
: std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax);
|
||||
m_parent.set_as_dirty();
|
||||
return true;
|
||||
} else if (m_tool_type == ToolType::SEED_FILL) {
|
||||
m_seed_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_seed_fill_angle - SeedFillAngleStep, SeedFillAngleMin)
|
||||
: std::min(m_seed_fill_angle + SeedFillAngleStep, SeedFillAngleMax);
|
||||
m_parent.set_as_dirty();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -396,19 +404,21 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
|
||||
|
||||
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
|
||||
if (m_seed_fill_enabled) {
|
||||
if (m_tool_type == ToolType::SEED_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) {
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_apply_on_triangles(new_state);
|
||||
m_seed_fill_last_mesh_id = -1;
|
||||
} else
|
||||
} else if (m_tool_type == ToolType::BRUSH)
|
||||
m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, int(m_rr.facet), camera_pos, m_cursor_radius, m_cursor_type,
|
||||
new_state, trafo_matrix, m_triangle_splitting_enabled);
|
||||
|
||||
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
|
||||
m_last_mouse_click = mouse_position;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (action == SLAGizmoEventType::Moving && m_seed_fill_enabled) {
|
||||
if (action == SLAGizmoEventType::Moving && (m_tool_type == ToolType::SEED_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER))) {
|
||||
if (m_triangle_selectors.empty())
|
||||
return false;
|
||||
|
||||
@ -428,8 +438,10 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
update_raycast_cache(mouse_position, camera, trafo_matrices);
|
||||
|
||||
auto seed_fill_unselect_all = [this]() {
|
||||
for (auto &triangle_selector : m_triangle_selectors)
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
};
|
||||
|
||||
if (m_rr.mesh_id == -1) {
|
||||
@ -446,7 +458,13 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
seed_fill_unselect_all();
|
||||
|
||||
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_seed_fill_angle);
|
||||
if (m_tool_type == ToolType::SEED_FILL)
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_seed_fill_angle);
|
||||
else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
|
||||
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), false);
|
||||
else if (m_tool_type == ToolType::BUCKET_FILL)
|
||||
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), true);
|
||||
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
|
||||
m_seed_fill_last_mesh_id = m_rr.mesh_id;
|
||||
return true;
|
||||
}
|
||||
@ -589,28 +607,11 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
|
||||
static constexpr std::array<float, 4> enforcers_color{0.47f, 0.47f, 1.f, 1.f};
|
||||
static constexpr std::array<float, 4> blockers_color{1.f, 0.44f, 0.44f, 1.f};
|
||||
|
||||
int enf_cnt = 0;
|
||||
int blc_cnt = 0;
|
||||
|
||||
for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
|
||||
iva->release_geometry();
|
||||
|
||||
for (const Triangle& tr : m_triangles) {
|
||||
if (!tr.valid() || tr.is_split() || tr.get_state() == EnforcerBlockerType::NONE)
|
||||
continue;
|
||||
|
||||
GLIndexedVertexArray &iva = tr.get_state() == EnforcerBlockerType::ENFORCER ? m_iva_enforcers : m_iva_blockers;
|
||||
int & cnt = tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt : blc_cnt;
|
||||
|
||||
for (int i = 0; i < 3; ++i)
|
||||
iva.push_geometry(m_vertices[tr.verts_idxs[i]].v, m_mesh->stl.facet_start[tr.source_triangle].normal);
|
||||
iva.push_triangle(cnt, cnt + 1, cnt + 2);
|
||||
cnt += 3;
|
||||
if (m_update_render_data) {
|
||||
update_render_data();
|
||||
m_update_render_data = false;
|
||||
}
|
||||
|
||||
for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
|
||||
iva->finalize_geometry(true);
|
||||
|
||||
auto* shader = wxGetApp().get_current_shader();
|
||||
if (! shader)
|
||||
return;
|
||||
@ -635,6 +636,33 @@ void TriangleSelectorGUI::render(ImGuiWrapper* imgui)
|
||||
|
||||
|
||||
|
||||
void TriangleSelectorGUI::update_render_data()
|
||||
{
|
||||
int enf_cnt = 0;
|
||||
int blc_cnt = 0;
|
||||
|
||||
for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
|
||||
iva->release_geometry();
|
||||
|
||||
for (const Triangle &tr : m_triangles) {
|
||||
if (!tr.valid() || tr.is_split() || tr.get_state() == EnforcerBlockerType::NONE)
|
||||
continue;
|
||||
|
||||
GLIndexedVertexArray &iva = tr.get_state() == EnforcerBlockerType::ENFORCER ? m_iva_enforcers : m_iva_blockers;
|
||||
int & cnt = tr.get_state() == EnforcerBlockerType::ENFORCER ? enf_cnt : blc_cnt;
|
||||
|
||||
for (int i = 0; i < 3; ++i)
|
||||
iva.push_geometry(m_vertices[tr.verts_idxs[i]].v, m_mesh->stl.facet_start[tr.source_triangle].normal);
|
||||
iva.push_triangle(cnt, cnt + 1, cnt + 2);
|
||||
cnt += 3;
|
||||
}
|
||||
|
||||
for (auto *iva : {&m_iva_enforcers, &m_iva_blockers})
|
||||
iva->finalize_geometry(true);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
|
||||
void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui)
|
||||
{
|
||||
@ -685,7 +713,7 @@ void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui)
|
||||
va = &m_varrays[ORIGINAL];
|
||||
cnt = &cnts[ORIGINAL];
|
||||
}
|
||||
else if (tr.valid) {
|
||||
else if (tr.valid()) {
|
||||
va = &m_varrays[SPLIT];
|
||||
cnt = &cnts[SPLIT];
|
||||
}
|
||||
|
@ -38,13 +38,20 @@ public:
|
||||
virtual void render(ImGuiWrapper *imgui);
|
||||
void render() { this->render(nullptr); }
|
||||
|
||||
void request_update_render_data() { m_update_render_data = true; };
|
||||
|
||||
#ifdef PRUSASLICER_TRIANGLE_SELECTOR_DEBUG
|
||||
void render_debug(ImGuiWrapper* imgui);
|
||||
bool m_show_triangles{false};
|
||||
bool m_show_invalid{false};
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool m_update_render_data = false;
|
||||
|
||||
private:
|
||||
void update_render_data();
|
||||
|
||||
GLIndexedVertexArray m_iva_enforcers;
|
||||
GLIndexedVertexArray m_iva_blockers;
|
||||
std::array<GLIndexedVertexArray, 3> m_varrays;
|
||||
@ -99,13 +106,23 @@ protected:
|
||||
|
||||
TriangleSelector::CursorType m_cursor_type = TriangleSelector::SPHERE;
|
||||
|
||||
bool m_triangle_splitting_enabled = true;
|
||||
bool m_seed_fill_enabled = false;
|
||||
float m_seed_fill_angle = 0.f;
|
||||
enum class ToolType {
|
||||
BRUSH,
|
||||
BUCKET_FILL,
|
||||
SEED_FILL
|
||||
};
|
||||
|
||||
bool m_triangle_splitting_enabled = true;
|
||||
ToolType m_tool_type = ToolType::BRUSH;
|
||||
float m_seed_fill_angle = 0.f;
|
||||
|
||||
static constexpr float SeedFillAngleMin = 0.0f;
|
||||
static constexpr float SeedFillAngleMax = 90.f;
|
||||
static constexpr float SeedFillAngleStep = 1.f;
|
||||
|
||||
// It stores the value of the previous mesh_id to which the seed fill was applied.
|
||||
// It is used to detect when the mouse has moved from one volume to another one.
|
||||
int m_seed_fill_last_mesh_id = -1;
|
||||
int m_seed_fill_last_mesh_id = -1;
|
||||
|
||||
enum class Button {
|
||||
None,
|
||||
|
@ -127,6 +127,7 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
|
||||
if (mv->is_model_part()) {
|
||||
++idx;
|
||||
m_triangle_selectors[idx]->reset();
|
||||
m_triangle_selectors[idx]->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,6 +148,8 @@ void GLGizmoSeam::on_render_input_window(float x, float y, float bottom_limit)
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
// Manually inserted values aren't clamped by ImGui. Zero cursor size results in a crash.
|
||||
m_cursor_radius = std::clamp(m_cursor_radius, CursorRadiusMin, CursorRadiusMax);
|
||||
|
||||
|
||||
ImGui::AlignTextToFramePadding();
|
||||
@ -257,6 +260,7 @@ void GLGizmoSeam::update_from_model_object()
|
||||
|
||||
m_triangle_selectors.emplace_back(std::make_unique<TriangleSelectorGUI>(*mesh));
|
||||
m_triangle_selectors.back()->deserialize(mv->seam_facets.get_data());
|
||||
m_triangle_selectors.back()->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -524,63 +524,87 @@ static bool selectable(const char* label, bool selected, ImGuiSelectableFlags fl
|
||||
ImGuiContext& g = *GImGui;
|
||||
const ImGuiStyle& style = g.Style;
|
||||
|
||||
if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.CurrentColumns) // FIXME-OPT: Avoid if vertically clipped.
|
||||
ImGui::PushColumnsBackground();
|
||||
|
||||
// Submit label or explicit size to ItemSize(), whereas ItemAdd() will submit a larger/spanning rectangle.
|
||||
ImGuiID id = window->GetID(label);
|
||||
ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);
|
||||
ImVec2 size(size_arg.x != 0.0f ? size_arg.x : label_size.x, size_arg.y != 0.0f ? size_arg.y : label_size.y);
|
||||
ImVec2 pos = window->DC.CursorPos;
|
||||
pos.y += window->DC.CurrLineTextBaseOffset;
|
||||
ImRect bb_inner(pos, pos + size);
|
||||
ImGui::ItemSize(size, 0.0f);
|
||||
|
||||
// Fill horizontal space.
|
||||
ImVec2 window_padding = window->WindowPadding;
|
||||
float max_x = (flags & ImGuiSelectableFlags_SpanAllColumns) ? ImGui::GetWindowContentRegionMax().x : ImGui::GetContentRegionMax().x;
|
||||
float w_draw = ImMax(label_size.x, window->Pos.x + max_x - window_padding.x - pos.x);
|
||||
ImVec2 size_draw((size_arg.x != 0 && !(flags & ImGuiSelectableFlags_DrawFillAvailWidth)) ? size_arg.x : w_draw, size_arg.y != 0.0f ? size_arg.y : size.y);
|
||||
ImRect bb(pos, pos + size_draw);
|
||||
if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_DrawFillAvailWidth))
|
||||
bb.Max.x += window_padding.x;
|
||||
// Fill horizontal space
|
||||
// We don't support (size < 0.0f) in Selectable() because the ItemSpacing extension would make explicitly right-aligned sizes not visibly match other widgets.
|
||||
const bool span_all_columns = (flags & ImGuiSelectableFlags_SpanAllColumns) != 0;
|
||||
const float min_x = span_all_columns ? window->ParentWorkRect.Min.x : pos.x;
|
||||
const float max_x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x;
|
||||
if (size_arg.x == 0.0f || (flags & ImGuiSelectableFlags_SpanAvailWidth))
|
||||
size.x = ImMax(label_size.x, max_x - min_x);
|
||||
|
||||
// Selectables are tightly packed together so we extend the box to cover spacing between selectable.
|
||||
const float spacing_x = style.ItemSpacing.x;
|
||||
const float spacing_y = style.ItemSpacing.y;
|
||||
const float spacing_L = IM_FLOOR(spacing_x * 0.50f);
|
||||
const float spacing_U = IM_FLOOR(spacing_y * 0.50f);
|
||||
bb.Min.x -= spacing_L;
|
||||
bb.Min.y -= spacing_U;
|
||||
bb.Max.x += (spacing_x - spacing_L);
|
||||
bb.Max.y += (spacing_y - spacing_U);
|
||||
// Text stays at the submission position, but bounding box may be extended on both sides
|
||||
const ImVec2 text_min = pos;
|
||||
const ImVec2 text_max(min_x + size.x, pos.y + size.y);
|
||||
|
||||
// Selectables are meant to be tightly packed together with no click-gap, so we extend their box to cover spacing between selectable.
|
||||
ImRect bb(min_x, pos.y, text_max.x, text_max.y);
|
||||
if ((flags & ImGuiSelectableFlags_NoPadWithHalfSpacing) == 0)
|
||||
{
|
||||
const float spacing_x = span_all_columns ? 0.0f : style.ItemSpacing.x;
|
||||
const float spacing_y = style.ItemSpacing.y;
|
||||
const float spacing_L = IM_FLOOR(spacing_x * 0.50f);
|
||||
const float spacing_U = IM_FLOOR(spacing_y * 0.50f);
|
||||
bb.Min.x -= spacing_L;
|
||||
bb.Min.y -= spacing_U;
|
||||
bb.Max.x += (spacing_x - spacing_L);
|
||||
bb.Max.y += (spacing_y - spacing_U);
|
||||
}
|
||||
//if (g.IO.KeyCtrl) { GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(0, 255, 0, 255)); }
|
||||
|
||||
// Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackground for every Selectable..
|
||||
const float backup_clip_rect_min_x = window->ClipRect.Min.x;
|
||||
const float backup_clip_rect_max_x = window->ClipRect.Max.x;
|
||||
if (span_all_columns)
|
||||
{
|
||||
window->ClipRect.Min.x = window->ParentWorkRect.Min.x;
|
||||
window->ClipRect.Max.x = window->ParentWorkRect.Max.x;
|
||||
}
|
||||
|
||||
bool item_add;
|
||||
if (flags & ImGuiSelectableFlags_Disabled)
|
||||
{
|
||||
ImGuiItemFlags backup_item_flags = window->DC.ItemFlags;
|
||||
window->DC.ItemFlags |= ImGuiItemFlags_Disabled | ImGuiItemFlags_NoNavDefaultFocus;
|
||||
ImGuiItemFlags backup_item_flags = g.CurrentItemFlags;
|
||||
g.CurrentItemFlags |= ImGuiItemFlags_Disabled | ImGuiItemFlags_NoNavDefaultFocus;
|
||||
item_add = ImGui::ItemAdd(bb, id);
|
||||
window->DC.ItemFlags = backup_item_flags;
|
||||
g.CurrentItemFlags = backup_item_flags;
|
||||
}
|
||||
else
|
||||
{
|
||||
item_add = ImGui::ItemAdd(bb, id);
|
||||
}
|
||||
if (!item_add)
|
||||
|
||||
if (span_all_columns)
|
||||
{
|
||||
if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.CurrentColumns)
|
||||
ImGui::PopColumnsBackground();
|
||||
return false;
|
||||
window->ClipRect.Min.x = backup_clip_rect_min_x;
|
||||
window->ClipRect.Max.x = backup_clip_rect_max_x;
|
||||
}
|
||||
|
||||
if (!item_add)
|
||||
return false;
|
||||
|
||||
// FIXME: We can standardize the behavior of those two, we could also keep the fast path of override ClipRect + full push on render only,
|
||||
// which would be advantageous since most selectable are not selected.
|
||||
if (span_all_columns && window->DC.CurrentColumns)
|
||||
ImGui::PushColumnsBackground();
|
||||
else if (span_all_columns && g.CurrentTable)
|
||||
ImGui::TablePushBackgroundChannel();
|
||||
|
||||
// We use NoHoldingActiveID on menus so user can click and _hold_ on a menu then drag to browse child entries
|
||||
ImGuiButtonFlags button_flags = 0;
|
||||
if (flags & ImGuiSelectableFlags_NoHoldingActiveID) { button_flags |= ImGuiButtonFlags_NoHoldingActiveId; }
|
||||
if (flags & ImGuiSelectableFlags_PressedOnClick) { button_flags |= ImGuiButtonFlags_PressedOnClick; }
|
||||
if (flags & ImGuiSelectableFlags_PressedOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; }
|
||||
if (flags & ImGuiSelectableFlags_Disabled) { button_flags |= ImGuiButtonFlags_Disabled; }
|
||||
if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; }
|
||||
if (flags & ImGuiSelectableFlags_AllowItemOverlap) { button_flags |= ImGuiButtonFlags_AllowItemOverlap; }
|
||||
if (flags & ImGuiSelectableFlags_SelectOnClick) { button_flags |= ImGuiButtonFlags_PressedOnClick; }
|
||||
if (flags & ImGuiSelectableFlags_SelectOnRelease) { button_flags |= ImGuiButtonFlags_PressedOnRelease; }
|
||||
if (flags & ImGuiSelectableFlags_Disabled) { button_flags |= ImGuiButtonFlags_Disabled; }
|
||||
if (flags & ImGuiSelectableFlags_AllowDoubleClick) { button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; }
|
||||
if (flags & ImGuiSelectableFlags_AllowItemOverlap) { button_flags |= ImGuiButtonFlags_AllowItemOverlap; }
|
||||
|
||||
if (flags & ImGuiSelectableFlags_Disabled)
|
||||
selected = false;
|
||||
@ -594,8 +618,8 @@ static bool selectable(const char* label, bool selected, ImGuiSelectableFlags fl
|
||||
{
|
||||
if (!g.NavDisableMouseHover && g.NavWindow == window && g.NavLayer == window->DC.NavLayerCurrent)
|
||||
{
|
||||
ImGui::SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent, ImRect(bb.Min - window->Pos, bb.Max - window->Pos));
|
||||
g.NavDisableHighlight = true;
|
||||
ImGui::SetNavID(id, window->DC.NavLayerCurrent, window->DC.NavFocusScopeIdCurrent);
|
||||
}
|
||||
}
|
||||
if (pressed)
|
||||
@ -618,29 +642,27 @@ static bool selectable(const char* label, bool selected, ImGuiSelectableFlags fl
|
||||
ImGui::RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding);
|
||||
}
|
||||
|
||||
if ((flags & ImGuiSelectableFlags_SpanAllColumns) && window->DC.CurrentColumns)
|
||||
{
|
||||
if (span_all_columns && window->DC.CurrentColumns)
|
||||
ImGui::PopColumnsBackground();
|
||||
bb.Max.x -= (ImGui::GetContentRegionMax().x - max_x);
|
||||
}
|
||||
else if (span_all_columns && g.CurrentTable)
|
||||
ImGui::TablePopBackgroundChannel();
|
||||
|
||||
// mark a label with a ImGui::ColorMarkerHovered, if item is hovered
|
||||
char* marked_label = new char[512]; //255 symbols is not enough for translated string (e.t. to Russian)
|
||||
// mark a label with a ColorMarkerHovered, if item is hovered
|
||||
char marked_label[512]; //255 symbols is not enough for translated string (e.t. to Russian)
|
||||
if (hovered)
|
||||
sprintf(marked_label, "%c%s", ImGui::ColorMarkerHovered, label);
|
||||
else
|
||||
strcpy(marked_label, label);
|
||||
|
||||
if (flags & ImGuiSelectableFlags_Disabled) ImGui::PushStyleColor(ImGuiCol_Text, style.Colors[ImGuiCol_TextDisabled]);
|
||||
ImGui::RenderTextClipped(bb_inner.Min, bb_inner.Max, marked_label, NULL, &label_size, style.SelectableTextAlign, &bb);
|
||||
ImGui::RenderTextClipped(text_min, text_max, marked_label, NULL, &label_size, style.SelectableTextAlign, &bb);
|
||||
if (flags & ImGuiSelectableFlags_Disabled) ImGui::PopStyleColor();
|
||||
|
||||
delete[] marked_label;
|
||||
|
||||
// Automatically close popups
|
||||
if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(window->DC.ItemFlags & ImGuiItemFlags_SelectableDontClosePopup)) ImGui::CloseCurrentPopup();
|
||||
if (pressed && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_DontClosePopups) && !(g.CurrentItemFlags & ImGuiItemFlags_SelectableDontClosePopup))
|
||||
ImGui::CloseCurrentPopup();
|
||||
|
||||
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.ItemFlags);
|
||||
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, window->DC.LastItemStatusFlags);
|
||||
return pressed;
|
||||
}
|
||||
|
||||
|
@ -1293,6 +1293,30 @@ void ObjectDataViewModel::SetExtruder(const wxString& extruder, wxDataViewItem i
|
||||
UpdateVolumesExtruderBitmap(item);
|
||||
}
|
||||
|
||||
bool ObjectDataViewModel::SetName(const wxString& new_name, wxDataViewItem item)
|
||||
{
|
||||
if (!item.IsOk())
|
||||
return false;
|
||||
|
||||
// The icon can't be edited so get its old value and reuse it.
|
||||
wxVariant valueOld;
|
||||
GetValue(valueOld, item, colName);
|
||||
|
||||
DataViewBitmapText bmpText;
|
||||
bmpText << valueOld;
|
||||
|
||||
// But replace the text with the value entered by user.
|
||||
bmpText.SetText(new_name);
|
||||
|
||||
wxVariant value;
|
||||
value << bmpText;
|
||||
if (SetValue(value, item, colName)) {
|
||||
ItemChanged(item);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ObjectDataViewModel::AddAllChildren(const wxDataViewItem& parent)
|
||||
{
|
||||
ObjectDataViewModelNode* node = static_cast<ObjectDataViewModelNode*>(parent.GetID());
|
||||
|
@ -324,6 +324,7 @@ public:
|
||||
unsigned int col);
|
||||
|
||||
void SetExtruder(const wxString& extruder, wxDataViewItem item);
|
||||
bool SetName (const wxString& new_name, wxDataViewItem item);
|
||||
|
||||
// For parent move child from cur_volume_id place to new_volume_id
|
||||
// Remaining items will moved up/down accordingly
|
||||
|
@ -1789,8 +1789,8 @@ struct Plater::priv
|
||||
bool can_replace_with_stl() const;
|
||||
bool can_split(bool to_objects) const;
|
||||
|
||||
void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background);
|
||||
ThumbnailsList generate_thumbnails(const ThumbnailsParams& params);
|
||||
void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type);
|
||||
ThumbnailsList generate_thumbnails(const ThumbnailsParams& params, Camera::EType camera_type);
|
||||
|
||||
void bring_instance_forward() const;
|
||||
|
||||
@ -1866,7 +1866,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
||||
background_process.set_fff_print(&fff_print);
|
||||
background_process.set_sla_print(&sla_print);
|
||||
background_process.set_gcode_result(&gcode_result);
|
||||
background_process.set_thumbnail_cb([this](const ThumbnailsParams& params) { return this->generate_thumbnails(params); });
|
||||
background_process.set_thumbnail_cb([this](const ThumbnailsParams& params) { return this->generate_thumbnails(params, Camera::EType::Ortho); });
|
||||
background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED);
|
||||
background_process.set_finished_event(EVT_PROCESS_COMPLETED);
|
||||
background_process.set_export_began_event(EVT_EXPORT_BEGAN);
|
||||
@ -3285,6 +3285,13 @@ void Plater::priv::replace_with_stl()
|
||||
old_model_object->ensure_on_bed();
|
||||
old_model_object->sort_volumes(wxGetApp().app_config->get("order_volumes") == "1");
|
||||
|
||||
// if object has just one volume, rename object too
|
||||
if (old_model_object->volumes.size() == 1)
|
||||
old_model_object->name = old_model_object->volumes[0]->name;
|
||||
|
||||
// update new name in ObjectList
|
||||
sidebar->obj_list()->update_name_in_list(object_idx, volume_idx);
|
||||
|
||||
sla::reproject_points_and_holes(old_model_object);
|
||||
|
||||
// update 3D scene
|
||||
@ -3344,7 +3351,7 @@ void Plater::priv::reload_from_disk()
|
||||
else
|
||||
missing_input_paths.push_back(volume->source.input_file);
|
||||
}
|
||||
else if (!object->input_file.empty() && volume->is_model_part() && !volume->name.empty())
|
||||
else if (!object->input_file.empty() && volume->is_model_part() && !volume->name.empty() && !volume->source.is_from_builtin_objects)
|
||||
missing_input_paths.push_back(volume->name);
|
||||
}
|
||||
|
||||
@ -4041,18 +4048,18 @@ void Plater::priv::on_3dcanvas_mouse_dragging_finished(SimpleEvent&)
|
||||
}
|
||||
}
|
||||
|
||||
void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool show_bed, bool transparent_background)
|
||||
void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, const ThumbnailsParams& thumbnail_params, Camera::EType camera_type)
|
||||
{
|
||||
view3D->get_canvas3d()->render_thumbnail(data, w, h, printable_only, parts_only, show_bed, transparent_background);
|
||||
view3D->get_canvas3d()->render_thumbnail(data, w, h, thumbnail_params, camera_type);
|
||||
}
|
||||
|
||||
ThumbnailsList Plater::priv::generate_thumbnails(const ThumbnailsParams& params)
|
||||
ThumbnailsList Plater::priv::generate_thumbnails(const ThumbnailsParams& params, Camera::EType camera_type)
|
||||
{
|
||||
ThumbnailsList thumbnails;
|
||||
for (const Vec2d& size : params.sizes) {
|
||||
thumbnails.push_back(ThumbnailData());
|
||||
Point isize(size); // round to ints
|
||||
generate_thumbnail(thumbnails.back(), isize.x(), isize.y(), params.printable_only, params.parts_only, params.show_bed, params.transparent_background);
|
||||
generate_thumbnail(thumbnails.back(), isize.x(), isize.y(), params, camera_type);
|
||||
if (!thumbnails.back().is_valid())
|
||||
thumbnails.pop_back();
|
||||
}
|
||||
@ -4283,14 +4290,12 @@ bool Plater::priv::can_reload_from_disk() const
|
||||
|
||||
// collects selected ModelVolumes
|
||||
const std::set<unsigned int>& selected_volumes_idxs = selection.get_volume_idxs();
|
||||
for (unsigned int idx : selected_volumes_idxs)
|
||||
{
|
||||
for (unsigned int idx : selected_volumes_idxs) {
|
||||
const GLVolume* v = selection.get_volume(idx);
|
||||
int v_idx = v->volume_idx();
|
||||
if (v_idx >= 0)
|
||||
{
|
||||
if (v_idx >= 0) {
|
||||
int o_idx = v->object_idx();
|
||||
if ((0 <= o_idx) && (o_idx < (int)model.objects.size()))
|
||||
if (0 <= o_idx && o_idx < (int)model.objects.size())
|
||||
selected_volumes.push_back({ o_idx, v_idx });
|
||||
}
|
||||
}
|
||||
@ -4299,13 +4304,12 @@ bool Plater::priv::can_reload_from_disk() const
|
||||
|
||||
// collects paths of files to load
|
||||
std::vector<fs::path> paths;
|
||||
for (const SelectedVolume& v : selected_volumes)
|
||||
{
|
||||
for (const SelectedVolume& v : selected_volumes) {
|
||||
const ModelObject* object = model.objects[v.object_idx];
|
||||
const ModelVolume* volume = object->volumes[v.volume_idx];
|
||||
if (!volume->source.input_file.empty())
|
||||
paths.push_back(volume->source.input_file);
|
||||
else if (!object->input_file.empty() && !volume->name.empty())
|
||||
else if (!object->input_file.empty() && !volume->name.empty() && !volume->source.is_from_builtin_objects)
|
||||
paths.push_back(volume->name);
|
||||
}
|
||||
std::sort(paths.begin(), paths.end());
|
||||
@ -4929,11 +4933,11 @@ enum class LoadType : unsigned char
|
||||
|
||||
class ProjectDropDialog : public DPIDialog
|
||||
{
|
||||
wxRadioBox* m_action{ nullptr };
|
||||
int m_action { 0 };
|
||||
public:
|
||||
ProjectDropDialog(const std::string& filename);
|
||||
|
||||
int get_action() const { return m_action->GetSelection() + 1; }
|
||||
int get_action() const { return m_action + 1; }
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect& suggested_rect) override;
|
||||
@ -4954,12 +4958,24 @@ ProjectDropDialog::ProjectDropDialog(const std::string& filename)
|
||||
|
||||
main_sizer->Add(new wxStaticText(this, wxID_ANY,
|
||||
_L("Select an action to apply to the file") + ": " + from_u8(filename)), 0, wxEXPAND | wxALL, 10);
|
||||
m_action = new wxRadioBox(this, wxID_ANY, _L("Action"), wxDefaultPosition, wxDefaultSize,
|
||||
WXSIZEOF(choices), choices, 0, wxRA_SPECIFY_ROWS);
|
||||
|
||||
int action = std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")),
|
||||
static_cast<int>(LoadType::OpenProject), static_cast<int>(LoadType::LoadConfig)) - 1;
|
||||
m_action->SetSelection(action);
|
||||
main_sizer->Add(m_action, 1, wxEXPAND | wxRIGHT | wxLEFT, 10);
|
||||
|
||||
wxStaticBox* action_stb = new wxStaticBox(this, wxID_ANY, _L("Action"));
|
||||
if (!wxOSX) action_stb->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
action_stb->SetFont(wxGetApp().normal_font());
|
||||
|
||||
wxStaticBoxSizer* stb_sizer = new wxStaticBoxSizer(action_stb, wxVERTICAL);
|
||||
int id = 0;
|
||||
for (const wxString& label : choices) {
|
||||
wxRadioButton* btn = new wxRadioButton(this, wxID_ANY, label, wxDefaultPosition, wxDefaultSize, id == 0 ? wxRB_GROUP : 0);
|
||||
btn->SetValue(id == action);
|
||||
btn->Bind(wxEVT_RADIOBUTTON, [this, id](wxCommandEvent&) { m_action = id; });
|
||||
stb_sizer->Add(btn, 0, wxEXPAND | wxTOP, 5);
|
||||
id++;
|
||||
}
|
||||
main_sizer->Add(stb_sizer, 1, wxEXPAND | wxRIGHT | wxLEFT, 10);
|
||||
|
||||
wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
wxCheckBox* check = new wxCheckBox(this, wxID_ANY, _L("Don't show again"));
|
||||
@ -4973,6 +4989,9 @@ ProjectDropDialog::ProjectDropDialog(const std::string& filename)
|
||||
|
||||
SetSizer(main_sizer);
|
||||
main_sizer->SetSizeHints(this);
|
||||
|
||||
// Update DarkUi just for buttons
|
||||
wxGetApp().UpdateDlgDarkUI(this, true);
|
||||
}
|
||||
|
||||
void ProjectDropDialog::on_dpi_changed(const wxRect& suggested_rect)
|
||||
@ -5603,7 +5622,8 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
|
||||
wxBusyCursor wait;
|
||||
bool full_pathnames = wxGetApp().app_config->get("export_sources_full_pathnames") == "1";
|
||||
ThumbnailData thumbnail_data;
|
||||
p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, false, true, true, true);
|
||||
ThumbnailsParams thumbnail_params = { {}, false, true, true, true };
|
||||
p->generate_thumbnail(thumbnail_data, THUMBNAIL_SIZE_3MF.first, THUMBNAIL_SIZE_3MF.second, thumbnail_params, Camera::EType::Ortho);
|
||||
#if ENABLE_PROJECT_DIRTY_STATE
|
||||
bool ret = Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr, full_pathnames, &thumbnail_data);
|
||||
if (ret) {
|
||||
|
@ -393,11 +393,9 @@ void PreferencesDialog::build()
|
||||
sizer->Add(tabs, 1, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, 5);
|
||||
|
||||
auto buttons = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
|
||||
wxButton* btn = static_cast<wxButton*>(FindWindowById(wxID_OK, this));
|
||||
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { accept(); });
|
||||
this->Bind(wxEVT_BUTTON, &PreferencesDialog::accept, this, wxID_OK);
|
||||
|
||||
wxGetApp().UpdateDarkUI(btn);
|
||||
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_CANCEL, this)));
|
||||
wxGetApp().UpdateDlgDarkUI(this, true);
|
||||
|
||||
sizer->Add(buttons, 0, wxALIGN_CENTER_HORIZONTAL | wxBOTTOM | wxTOP, 10);
|
||||
|
||||
@ -406,7 +404,7 @@ void PreferencesDialog::build()
|
||||
this->CenterOnParent();
|
||||
}
|
||||
|
||||
void PreferencesDialog::accept()
|
||||
void PreferencesDialog::accept(wxEvent&)
|
||||
{
|
||||
// if (m_values.find("no_defaults") != m_values.end()
|
||||
// warning_catcher(this, wxString::Format(_L("You need to restart %s to make the changes effective."), SLIC3R_APP_NAME));
|
||||
@ -434,11 +432,6 @@ void PreferencesDialog::accept()
|
||||
}
|
||||
}
|
||||
|
||||
if (m_values.empty()) {
|
||||
EndModal(wxID_CANCEL);
|
||||
return;
|
||||
}
|
||||
|
||||
auto app_config = get_app_config();
|
||||
|
||||
m_seq_top_layer_only_changed = false;
|
||||
|
@ -46,7 +46,7 @@ public:
|
||||
#endif // ENABLE_GCODE_LINES_ID_IN_H_SLIDER
|
||||
bool recreate_GUI() const { return m_recreate_GUI; }
|
||||
void build();
|
||||
void accept();
|
||||
void accept(wxEvent&);
|
||||
|
||||
protected:
|
||||
void on_dpi_changed(const wxRect &suggested_rect) override;
|
||||
|
@ -415,7 +415,7 @@ void Selection::remove_all()
|
||||
// Not taking the snapshot with non-empty Redo stack will likely be more confusing than losing the Redo stack.
|
||||
// Let's wait for user feedback.
|
||||
// if (!wxGetApp().plater()->can_redo())
|
||||
wxGetApp().plater()->take_snapshot(_(L("Selection-Remove All")));
|
||||
wxGetApp().plater()->take_snapshot(_L("Selection-Remove All"));
|
||||
|
||||
m_mode = Instance;
|
||||
clear();
|
||||
@ -432,9 +432,9 @@ void Selection::set_deserialized(EMode mode, const std::vector<std::pair<size_t,
|
||||
m_list.clear();
|
||||
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++ i)
|
||||
if (std::binary_search(volumes_and_instances.begin(), volumes_and_instances.end(), (*m_volumes)[i]->geometry_id))
|
||||
this->do_add_volume(i);
|
||||
do_add_volume(i);
|
||||
update_type();
|
||||
this->set_bounding_boxes_dirty();
|
||||
set_bounding_boxes_dirty();
|
||||
}
|
||||
|
||||
void Selection::clear()
|
||||
@ -447,12 +447,14 @@ void Selection::clear()
|
||||
|
||||
for (unsigned int i : m_list) {
|
||||
(*m_volumes)[i]->selected = false;
|
||||
// ensure the volume gets the proper color before next call to render (expecially needed for transparent volumes)
|
||||
(*m_volumes)[i]->set_render_color();
|
||||
}
|
||||
|
||||
m_list.clear();
|
||||
|
||||
update_type();
|
||||
this->set_bounding_boxes_dirty();
|
||||
set_bounding_boxes_dirty();
|
||||
|
||||
// this happens while the application is closing
|
||||
if (wxGetApp().obj_manipul() == nullptr)
|
||||
|
@ -163,15 +163,14 @@ SysInfoDialog::SysInfoDialog()
|
||||
wxStdDialogButtonSizer* buttons = this->CreateStdDialogButtonSizer(wxOK);
|
||||
m_btn_copy_to_clipboard = new wxButton(this, wxID_ANY, _L("Copy to Clipboard"), wxDefaultPosition, wxDefaultSize);
|
||||
|
||||
buttons->Insert(0, m_btn_copy_to_clipboard, 0, wxLEFT, 5);
|
||||
buttons->Insert(0, m_btn_copy_to_clipboard, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
|
||||
m_btn_copy_to_clipboard->Bind(wxEVT_BUTTON, &SysInfoDialog::onCopyToClipboard, this);
|
||||
|
||||
this->SetEscapeId(wxID_OK);
|
||||
this->Bind(wxEVT_BUTTON, &SysInfoDialog::onCloseDialog, this, wxID_OK);
|
||||
main_sizer->Add(buttons, 0, wxEXPAND | wxRIGHT | wxBOTTOM, 3);
|
||||
|
||||
wxGetApp().UpdateDarkUI(static_cast<wxButton*>(this->FindWindowById(wxID_OK, this)), true);
|
||||
wxGetApp().UpdateDarkUI(m_btn_copy_to_clipboard, true);
|
||||
wxGetApp().UpdateDlgDarkUI(this, true);
|
||||
|
||||
// this->Bind(wxEVT_LEFT_DOWN, &SysInfoDialog::onCloseDialog, this);
|
||||
// logo->Bind(wxEVT_LEFT_DOWN, &SysInfoDialog::onCloseDialog, this);
|
||||
|
@ -3005,6 +3005,7 @@ void TabPrinter::update()
|
||||
m_presets->get_edited_preset().printer_technology() == ptFFF ? update_fff() : update_sla();
|
||||
m_update_cnt--;
|
||||
|
||||
update_description_lines();
|
||||
Layout();
|
||||
|
||||
if (m_update_cnt == 0)
|
||||
@ -4290,6 +4291,9 @@ void TabSLAMaterial::update()
|
||||
if (m_preset_bundle->printers.get_selected_preset().printer_technology() == ptFFF)
|
||||
return;
|
||||
|
||||
update_description_lines();
|
||||
Layout();
|
||||
|
||||
// #ys_FIXME. Just a template for this function
|
||||
// m_update_cnt++;
|
||||
// ! something to update
|
||||
|
@ -3,7 +3,7 @@ add_executable(${_TEST_NAME}_tests
|
||||
${_TEST_NAME}_tests_main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r_gui)
|
||||
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r_gui libslic3r)
|
||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||
|
||||
# catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
||||
|
@ -17,8 +17,7 @@ _constant()
|
||||
STEP_PREPARE_INFILL = posPrepareInfill
|
||||
STEP_INFILL = posInfill
|
||||
STEP_SUPPORTMATERIAL = posSupportMaterial
|
||||
STEP_SKIRT = psSkirt
|
||||
STEP_BRIM = psBrim
|
||||
STEP_SKIRTBRIM = psSkirtBrim
|
||||
STEP_WIPE_TOWER = psWipeTower
|
||||
PROTOTYPE:
|
||||
CODE:
|
||||
|
Loading…
Reference in New Issue
Block a user