mirror of
https://git.mirrors.martin98.com/https://github.com/prusa3d/PrusaSlicer.git
synced 2025-08-15 01:36:13 +08:00
Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_world_coordinates
This commit is contained in:
commit
71826900a3
1
.gitignore
vendored
1
.gitignore
vendored
@ -17,3 +17,4 @@ local-lib
|
||||
/.vscode/
|
||||
build-linux/*
|
||||
deps/build-linux/*
|
||||
**/.DS_Store
|
||||
|
@ -478,13 +478,27 @@ find_package(cereal REQUIRED)
|
||||
# l10n
|
||||
set(L10N_DIR "${SLIC3R_RESOURCES_DIR}/localization")
|
||||
add_custom_target(gettext_make_pot
|
||||
COMMAND xgettext --keyword=L --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --debug
|
||||
COMMAND xgettext --keyword=L --keyword=_L --keyword=_u8L --keyword=L_CONTEXT:1,2c --keyword=_L_PLURAL:1,2 --add-comments=TRN --from-code=UTF-8 --debug --boost
|
||||
-f "${L10N_DIR}/list.txt"
|
||||
-o "${L10N_DIR}/PrusaSlicer.pot"
|
||||
COMMAND hintsToPot ${SLIC3R_RESOURCES_DIR} ${L10N_DIR}
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
COMMENT "Generate pot file from strings in the source tree"
|
||||
)
|
||||
add_custom_target(gettext_merge_po_with_pot
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
COMMENT "Merge localization po with new generted pot file"
|
||||
)
|
||||
file(GLOB L10N_PO_FILES "${L10N_DIR}/*/PrusaSlicer*.po")
|
||||
foreach(po_file ${L10N_PO_FILES})
|
||||
GET_FILENAME_COMPONENT(po_dir "${po_file}" DIRECTORY)
|
||||
SET(po_new_file "${po_dir}/PrusaSlicer_.po")
|
||||
add_custom_command(
|
||||
TARGET gettext_merge_po_with_pot PRE_BUILD
|
||||
COMMAND msgmerge -N -o ${po_file} ${po_file} "${L10N_DIR}/PrusaSlicer.pot"
|
||||
DEPENDS ${po_file}
|
||||
)
|
||||
endforeach()
|
||||
add_custom_target(gettext_po_to_mo
|
||||
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
|
||||
COMMENT "Generate localization po files (binary) from mo files (texts)"
|
||||
@ -495,7 +509,8 @@ foreach(po_file ${L10N_PO_FILES})
|
||||
SET(mo_file "${po_dir}/PrusaSlicer.mo")
|
||||
add_custom_command(
|
||||
TARGET gettext_po_to_mo PRE_BUILD
|
||||
COMMAND msgfmt ARGS -o ${mo_file} ${po_file}
|
||||
COMMAND msgfmt ARGS --check-format -o ${mo_file} ${po_file}
|
||||
#COMMAND msgfmt ARGS --check-compatibility -o ${mo_file} ${po_file}
|
||||
DEPENDS ${po_file}
|
||||
)
|
||||
endforeach()
|
||||
|
@ -6,7 +6,8 @@
|
||||
@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.
|
||||
@ECHO.
|
||||
@ECHO Usage: build_win [-ARCH ^<arch^>] [-CONFIG ^<config^>] [-DESTDIR ^<directory^>]
|
||||
@ECHO Usage: build_win [-ARCH ^<arch^>] [-CONFIG ^<config^>] [-VERSION ^<version^>]
|
||||
@ECHO [-PRODUCT ^<product^>] [-DESTDIR ^<directory^>]
|
||||
@ECHO [-STEPS ^<all^|all-dirty^|app^|app-dirty^|deps^|deps-dirty^>]
|
||||
@ECHO [-RUN ^<console^|custom^|none^|viewer^|window^>]
|
||||
@ECHO.
|
||||
@ -14,6 +15,10 @@
|
||||
@ECHO Default: %PS_ARCH_HOST%
|
||||
@ECHO -c -CONFIG MSVC project config
|
||||
@ECHO Default: %PS_CONFIG_DEFAULT%
|
||||
@ECHO -v -VERSION Major version number of MSVC installation to use for build
|
||||
@ECHO Default: %PS_VERSION_SUPPORTED%
|
||||
@ECHO -p -PRODUCT Product ID of MSVC installation to use for build
|
||||
@ECHO Default: %PS_PRODUCT_DEFAULT%
|
||||
@ECHO -s -STEPS Performs only the specified build steps:
|
||||
@ECHO all - clean and build deps and app
|
||||
@ECHO all-dirty - build deps and app without cleaning
|
||||
@ -55,6 +60,23 @@ SET PS_DEPS_PATH_FILE_NAME=.DEPS_PATH.txt
|
||||
SET PS_DEPS_PATH_FILE=%~dp0deps\build\%PS_DEPS_PATH_FILE_NAME%
|
||||
SET PS_CONFIG_LIST="Debug;MinSizeRel;Release;RelWithDebInfo"
|
||||
|
||||
REM The officially supported toolchain version is 16 (Visual Studio 2019)
|
||||
REM TODO: Update versions after Boost gets rolled to 1.78 or later
|
||||
SET PS_VERSION_SUPPORTED=16
|
||||
SET PS_VERSION_EXCEEDED=17
|
||||
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=4 USEBACKQ delims=." %%I IN (`"%VSWHERE%" -nologo -property productId`) DO SET PS_PRODUCT_DEFAULT=%%I
|
||||
IF "%PS_PRODUCT_DEFAULT%" EQU "" (
|
||||
SET EXIT_STATUS=-1
|
||||
@ECHO ERROR: No Visual Studio installation found. 1>&2
|
||||
GOTO :HELP
|
||||
)
|
||||
REM Default to the latest supported version if multiple are available
|
||||
FOR /F "tokens=1 USEBACKQ delims=." %%I IN (
|
||||
`^""%VSWHERE%" -version "[%PS_VERSION_SUPPORTED%,%PS_VERSION_EXCEEDED%)" -latest -nologo -property catalog_buildVersion^"`
|
||||
) DO SET PS_VERSION_SUPPORTED=%%I
|
||||
|
||||
REM Probe build directories and system state for reasonable default arguments
|
||||
pushd %~dp0
|
||||
SET PS_CONFIG=RelWithDebInfo
|
||||
@ -62,6 +84,8 @@ SET PS_ARCH=%PROCESSOR_ARCHITECTURE:amd64=x64%
|
||||
CALL :TOLOWER PS_ARCH
|
||||
SET PS_RUN=none
|
||||
SET PS_DESTDIR=
|
||||
SET PS_VERSION=
|
||||
SET PS_PRODUCT=%PS_PRODUCT_DEFAULT%
|
||||
CALL :RESOLVE_DESTDIR_CACHE
|
||||
|
||||
REM Set up parameters used by help menu
|
||||
@ -75,7 +99,7 @@ SET EXIT_STATUS=1
|
||||
SET PS_CURRENT_STEP=arguments
|
||||
SET PARSER_STATE=
|
||||
SET PARSER_FAIL=
|
||||
FOR %%I in (%*) DO CALL :PARSE_OPTION "ARCH CONFIG DESTDIR STEPS RUN" PARSER_STATE "%%~I"
|
||||
FOR %%I in (%*) DO CALL :PARSE_OPTION "ARCH CONFIG DESTDIR STEPS RUN VERSION PRODUCT" PARSER_STATE "%%~I"
|
||||
IF "%PARSER_FAIL%" NEQ "" (
|
||||
@ECHO ERROR: Invalid switch: %PARSER_FAIL% 1>&2
|
||||
GOTO :HELP
|
||||
@ -124,6 +148,15 @@ IF "%PS_RUN%" NEQ "none" IF "%PS_STEPS:~0,4%" EQU "deps" (
|
||||
@ECHO ERROR: RUN=none is the only valid option for STEPS "deps" or "deps-dirty"
|
||||
GOTO :HELP
|
||||
)
|
||||
IF DEFINED PS_VERSION (
|
||||
SET /A PS_VERSION_EXCEEDED=%PS_VERSION% + 1
|
||||
) ELSE SET PS_VERSION=%PS_VERSION_SUPPORTED%
|
||||
SET MSVC_FILTER=-products Microsoft.VisualStudio.Product.%PS_PRODUCT% -version "[%PS_VERSION%,%PS_VERSION_EXCEEDED%)"
|
||||
FOR /F "tokens=* USEBACKQ" %%I IN (`^""%VSWHERE%" %MSVC_FILTER% -nologo -property installationPath^"`) DO SET MSVC_DIR=%%I
|
||||
IF NOT EXIST "%MSVC_DIR%" (
|
||||
@ECHO ERROR: Compatible Visual Studio installation not found. 1>&2
|
||||
GOTO :HELP
|
||||
)
|
||||
REM Give the user a chance to cancel if we found something odd.
|
||||
IF "%PS_ASK_TO_CONTINUE%" EQU "" GOTO :BUILD_ENV
|
||||
@ECHO.
|
||||
@ -142,9 +175,6 @@ SET PS_CURRENT_STEP=environment
|
||||
@ECHO ** Run App: %PS_RUN%
|
||||
@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
|
||||
IF %ERRORLEVEL% NEQ 0 GOTO :END
|
||||
@ -276,7 +306,7 @@ REM Functions and stubs start here.
|
||||
SET PS_DEPS_PATH_FILE_FOR_CONFIG=%~dp0build\.vs\%PS_ARCH%\%PS_CONFIG%\%PS_DEPS_PATH_FILE_NAME%
|
||||
mkdir "%~dp0build\.vs\%PS_ARCH%\%PS_CONFIG%" > nul 2> nul
|
||||
REM Copy a legacy file if we don't have one in the proper location.
|
||||
echo f|xcopy /D "%~dp0build\%PS_ARCH%\%PS_CONFIG%\%PS_DEPS_PATH_FILE_NAME%" "%PS_DEPS_PATH_FILE_FOR_CONFIG%"
|
||||
echo f|xcopy /D "%~dp0build\%PS_ARCH%\%PS_CONFIG%\%PS_DEPS_PATH_FILE_NAME%" "%PS_DEPS_PATH_FILE_FOR_CONFIG%" > nul 2> nul
|
||||
CALL :CANONICALIZE_PATH PS_DEPS_PATH_FILE_FOR_CONFIG
|
||||
IF EXIST "%PS_DEPS_PATH_FILE_FOR_CONFIG%" (
|
||||
FOR /F "tokens=* USEBACKQ" %%I IN ("%PS_DEPS_PATH_FILE_FOR_CONFIG%") DO (
|
||||
|
2
deps/wxWidgets/wxWidgets.cmake
vendored
2
deps/wxWidgets/wxWidgets.cmake
vendored
@ -13,7 +13,7 @@ prusaslicer_add_cmake_project(wxWidgets
|
||||
# GIT_REPOSITORY "https://github.com/prusa3d/wxWidgets"
|
||||
# GIT_TAG tm_cross_compile #${_wx_git_tag}
|
||||
URL https://github.com/prusa3d/wxWidgets/archive/refs/heads/v3.1.4-patched.zip
|
||||
URL_HASH SHA256=ed36a2159c781cce07b06378664e683ebd8cb2f51914aba9acd3bfca3d63d7d3
|
||||
URL_HASH SHA256=78adc312e645d738945172d5ddcee16b1a55cca08e82b43379192262377a206a
|
||||
DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} dep_TIFF dep_JPEG
|
||||
CMAKE_ARGS
|
||||
-DwxBUILD_PRECOMP=ON
|
||||
|
19
resources/icons/fdm_supports_.svg
Normal file
19
resources/icons/fdm_supports_.svg
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.0.0, 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 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
|
||||
<g id="paint_x5F_supports">
|
||||
<path fill="#ED6B21" d="M88,38.93c-0.83,0-1.5,0.67-1.5,1.5V70.5h-5V45.14c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5V70.5h-5V49.8
|
||||
c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5v20.7h-5V53.84c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5V70.5h-5V49.8
|
||||
c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5v20.7h-5V45.14c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5V70.5h-5V40.43
|
||||
c0-0.83-0.67-1.5-1.5-1.5s-1.5,0.67-1.5,1.5V72v10.99c0,3.59,2.92,6.51,6.51,6.51h2.98c0.67,0.01,6.51,0.24,6.51,6.5v16
|
||||
c0,3.29,1.99,9.5,9.5,9.5s9.5-6.21,9.5-9.5V96c0-6.26,5.84-6.49,6.5-6.5h3c3.59,0,6.5-2.92,6.5-6.5V72V40.43
|
||||
C89.5,39.6,88.83,38.93,88,38.93z M86.5,83c0,1.93-1.57,3.5-3.5,3.5h-3c-3.29,0-9.5,1.99-9.5,9.5v15.99
|
||||
c-0.01,0.67-0.24,6.51-6.5,6.51s-6.49-5.84-6.5-6.5V96c0-7.51-6.21-9.5-9.5-9.5h-2.99c-1.94,0-3.51-1.57-3.51-3.51V73.5h45V83z"/>
|
||||
<g>
|
||||
<path fill="#808080" d="M64,48.03c-0.26,0-0.52-0.07-0.75-0.2l-48-27.69c-0.46-0.27-0.75-0.76-0.75-1.3V8c0-0.83,0.67-1.5,1.5-1.5
|
||||
s1.5,0.67,1.5,1.5v9.98L64,44.8l46.5-26.83V8c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5v10.84c0,0.54-0.29,1.03-0.75,1.3
|
||||
l-48,27.69C64.52,47.97,64.26,48.03,64,48.03z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
28
resources/icons/mmu_segmentation_.svg
Normal file
28
resources/icons/mmu_segmentation_.svg
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, 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"
|
||||
width="128px" height="128px" viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
|
||||
<path fill="#808080" d="M52.87,108.38c-5.24,0-9.5-4.26-9.5-9.5s4.26-9.5,9.5-9.5s9.5,4.26,9.5,9.5S58.11,108.38,52.87,108.38z
|
||||
M52.87,92.38c-3.58,0-6.5,2.92-6.5,6.5s2.92,6.5,6.5,6.5s6.5-2.92,6.5-6.5S56.46,92.38,52.87,92.38z M29.82,83.59
|
||||
c-5.24,0-9.5-4.26-9.5-9.5s4.26-9.5,9.5-9.5s9.5,4.26,9.5,9.5S35.06,83.59,29.82,83.59z M29.82,67.59c-3.58,0-6.5,2.92-6.5,6.5
|
||||
s2.92,6.5,6.5,6.5s6.5-2.92,6.5-6.5S33.4,67.59,29.82,67.59z M34,49.86c-5.24,0-9.5-4.26-9.5-9.5s4.26-9.5,9.5-9.5s9.5,4.26,9.5,9.5
|
||||
S39.24,49.86,34,49.86z M34,33.86c-3.58,0-6.5,2.92-6.5,6.5s2.92,6.5,6.5,6.5s6.5-2.92,6.5-6.5S37.59,33.86,34,33.86z M64,35.21
|
||||
c-5.24,0-9.5-4.26-9.5-9.5s4.26-9.5,9.5-9.5s9.5,4.26,9.5,9.5S69.24,35.21,64,35.21z M64,19.21c-3.58,0-6.5,2.92-6.5,6.5
|
||||
s2.92,6.5,6.5,6.5s6.5-2.92,6.5-6.5S67.58,19.21,64,19.21z M96.1,52.24c-5.24,0-9.5-4.26-9.5-9.5s4.26-9.5,9.5-9.5s9.5,4.26,9.5,9.5
|
||||
S101.34,52.24,96.1,52.24z M96.1,36.24c-3.58,0-6.5,2.92-6.5,6.5s2.92,6.5,6.5,6.5s6.5-2.92,6.5-6.5S99.69,36.24,96.1,36.24z
|
||||
M72.54,120.87c2.6-0.39,4.78-2.06,5.81-4.46c1.06-2.47,0.77-5.29-0.8-7.52c-3.1-4.43-4.49-9.87-3.92-15.31
|
||||
c0.26-2.47,0.94-4.89,2.03-7.17c0.36-0.75,0.04-1.64-0.71-2c-0.75-0.36-1.64-0.04-2,0.71c-1.23,2.6-2.01,5.34-2.3,8.15
|
||||
c-0.64,6.16,0.94,12.32,4.45,17.34c0.96,1.38,1.15,3.11,0.5,4.62c-0.63,1.47-1.91,2.44-3.5,2.68c-3.29,0.49-6.66,0.68-10.01,0.57
|
||||
c-28.61-0.99-51.7-24.18-52.56-52.79c-0.46-15.2,5.2-29.48,15.94-40.21S50.49,9.07,65.68,9.53c28.62,0.86,51.8,23.94,52.79,52.56
|
||||
c0.11,3.25-0.06,6.51-0.52,9.69c-0.24,1.66-1.31,3.06-2.87,3.73c-1.52,0.66-3.16,0.5-4.49-0.42c-3.29-2.3-7.17-3.8-11.21-4.34
|
||||
c-0.83-0.11-1.58,0.47-1.68,1.29c-0.11,0.82,0.47,1.58,1.29,1.68c3.57,0.47,6.99,1.79,9.89,3.82c2.17,1.52,4.94,1.78,7.4,0.72
|
||||
c2.52-1.09,4.26-3.36,4.65-6.06c0.48-3.36,0.67-6.8,0.55-10.22c-1.04-30.19-25.5-54.55-55.7-55.45
|
||||
c-16.02-0.48-31.1,5.49-42.42,16.81C12.02,34.66,6.05,49.73,6.53,65.77c0.9,30.19,25.26,54.66,55.45,55.7
|
||||
c0.67,0.02,1.34,0.04,2.01,0.04C66.86,121.5,69.73,121.29,72.54,120.87z"/>
|
||||
<path fill="#ED6B21" d="M115.41,105.01l-27.66-38.8c7.76-12.6-22.89-18.09-27.92-23.34c-2.48-2.6-0.44,35.31,15.58,32.26
|
||||
l29.74,37.59c0.54,0.91,3.45,5.54,7.39,6.36c0.39,0.08,0.78,0.12,1.16,0.12c1.26,0,2.48-0.42,3.58-1.25
|
||||
c1.36-1.02,2.14-2.41,2.27-4.01C119.87,109.95,116.14,105.79,115.41,105.01z M78.44,74.13c1.24-0.57,2.54-1.37,3.92-2.42
|
||||
c1.39-1.05,2.53-2.06,3.45-3.04l6.94,9.73l-6.85,5.15L78.44,74.13z M116.56,113.69c-0.06,0.76-0.4,1.35-1.08,1.85
|
||||
c-0.77,0.58-1.51,0.76-2.33,0.6c-2.38-0.49-4.75-3.78-5.46-5.01c-0.04-0.06-0.08-0.12-0.12-0.18L87.76,85.91l6.73-5.06l18.53,25.99
|
||||
c0.04,0.06,0.09,0.12,0.14,0.17C114.11,107.98,116.75,111.3,116.56,113.69z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
35
resources/icons/seam_.svg
Normal file
35
resources/icons/seam_.svg
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.3.0, 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 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
|
||||
<g id="paint_x5F_seams_2_">
|
||||
|
||||
<polyline fill="none" stroke="#808080" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
|
||||
120,32 64,8 8,32 8,96 64,120 "/>
|
||||
<path fill="none" stroke="#808080" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
|
||||
M120,96"/>
|
||||
|
||||
<polyline fill="none" stroke="#808080" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
|
||||
8,32 64,56 64,120 "/>
|
||||
|
||||
<line fill="none" stroke="#808080" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="64" y1="56" x2="120" y2="32"/>
|
||||
|
||||
<line fill="none" stroke="#808080" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="64" y1="120" x2="120" y2="96"/>
|
||||
|
||||
<line fill="none" stroke="#808080" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="120" y1="96" x2="120" y2="32"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="88.05" y1="53.69" x2="95.96" y2="50.3"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="95.96" y1="58.3" x2="103.99" y2="54.86"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="88.05" y1="69.69" x2="95.96" y2="66.3"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="80.05" y1="81.12" x2="88.05" y2="77.69"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="71.94" y1="92.6" x2="80.05" y2="89.12"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="80.05" y1="97.12" x2="88.05" y2="93.69"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="88.05" y1="101.69" x2="96.13" y2="98.23"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.5 KiB |
17
resources/icons/shape_gallery.svg
Normal file
17
resources/icons/shape_gallery.svg
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.0.1, 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"
|
||||
width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<g id="shape_x5F_gallery_x5F_2">
|
||||
<path fill="#808080" d="M11,6.72c0.14,0,0.25,0.11,0.25,0.25V14c0,0.14-0.11,0.25-0.25,0.25H2c-0.14,0-0.25-0.11-0.25-0.25V6.97
|
||||
c0-0.14,0.11-0.25,0.25-0.25H11 M11,5.97H2c-0.55,0-1,0.45-1,1V14c0,0.55,0.45,1,1,1h9c0.55,0,1-0.45,1-1V6.97
|
||||
C12,6.42,11.55,5.97,11,5.97L11,5.97z"/>
|
||||
<path fill="#808080" d="M14,2H5C4.45,2,4,2.45,4,3v1h0.75V3c0-0.14,0.11-0.25,0.25-0.25h9c0.14,0,0.25,0.11,0.25,0.25v8
|
||||
c0,0.14-0.11,0.25-0.25,0.25h-0.5V12H14c0.55,0,1-0.45,1-1V3C15,2.45,14.55,2,14,2z"/>
|
||||
<path fill="#808080" d="M12.5,4h-9c-0.55,0-1,0.45-1,1v0.97h0.75V5c0-0.14,0.11-0.25,0.25-0.25h9c0.14,0,0.25,0.11,0.25,0.25v7.5
|
||||
c0,0.14-0.11,0.25-0.25,0.25H12v0.75h0.5c0.55,0,1-0.45,1-1V5C13.5,4.45,13.05,4,12.5,4z"/>
|
||||
<path fill="#ED6B21" d="M9.07,11.7V9.3c0-0.18-0.1-0.34-0.25-0.43l-2.07-1.2c-0.15-0.09-0.35-0.09-0.5,0l-2.07,1.2
|
||||
C4.02,8.96,3.93,9.13,3.93,9.3v2.39c0,0.18,0.1,0.34,0.25,0.43l2.07,1.2c0.15,0.09,0.35,0.09,0.5,0l2.07-1.2
|
||||
C8.98,12.04,9.07,11.87,9.07,11.7z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
18
resources/icons/sinking.svg
Normal file
18
resources/icons/sinking.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.0.1, 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"
|
||||
width="16px" height="16px" viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<g id="sinking">
|
||||
<g>
|
||||
<path fill="#808080" d="M15,10.5H1c-0.28,0-0.5-0.22-0.5-0.5S0.72,9.5,1,9.5h14c0.28,0,0.5,0.22,0.5,0.5S15.28,10.5,15,10.5z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#808080" d="M14.78,5.47L5.97,1.05C5.88,1.01,5.78,1,5.69,1.03c-0.09,0.03-0.17,0.1-0.22,0.19L2.15,7.83
|
||||
C2.09,7.95,2.1,8.09,2.17,8.2c0.07,0.11,0.19,0.18,0.32,0.18h11.02c0.14,0,0.27-0.08,0.33-0.21l1.1-2.19
|
||||
C15.04,5.79,14.97,5.56,14.78,5.47z"/>
|
||||
<path fill="#ED6B21" d="M11.82,11.8c-0.07-0.11-0.19-0.18-0.32-0.18H4.99c-0.17,0-0.32,0.12-0.36,0.29
|
||||
c-0.04,0.17,0.04,0.34,0.2,0.42l5.21,2.61c0.05,0.03,0.11,0.04,0.17,0.04c0.04,0,0.08-0.01,0.12-0.02
|
||||
c0.09-0.03,0.17-0.1,0.22-0.19l1.31-2.61C11.9,12.05,11.89,11.91,11.82,11.8z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -89,6 +89,7 @@ src/libslic3r/Flow.cpp
|
||||
src/libslic3r/Format/3mf.cpp
|
||||
src/libslic3r/Format/AMF.cpp
|
||||
src/libslic3r/miniz_extension.cpp
|
||||
src/libslic3r/PostProcessor.cpp
|
||||
src/libslic3r/Preset.cpp
|
||||
src/libslic3r/Print.cpp
|
||||
src/libslic3r/SLA/Pad.cpp
|
||||
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
resources/localization/pt_BR/PrusaSlicer.mo
Normal file
BIN
resources/localization/pt_BR/PrusaSlicer.mo
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
resources/localization/zh_CN/PrusaSlicer.mo
Normal file
BIN
resources/localization/zh_CN/PrusaSlicer.mo
Normal file
Binary file not shown.
14950
resources/localization/zh_CN/PrusaSlicer_zh_CN.po
Normal file
14950
resources/localization/zh_CN/PrusaSlicer_zh_CN.po
Normal file
File diff suppressed because it is too large
Load Diff
BIN
resources/localization/zh_TW/PrusaSlicer.mo
Normal file
BIN
resources/localization/zh_TW/PrusaSlicer.mo
Normal file
Binary file not shown.
15906
resources/localization/zh_TW/PrusaSlicer_zh_TW.po
Normal file
15906
resources/localization/zh_TW/PrusaSlicer_zh_TW.po
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
@ -298,7 +298,7 @@ set(CGAL_DO_NOT_WARN_ABOUT_CMAKE_BUILD_TYPE ON CACHE BOOL "" FORCE)
|
||||
|
||||
cmake_policy(PUSH)
|
||||
cmake_policy(SET CMP0011 NEW)
|
||||
find_package(CGAL 4.13 REQUIRED)
|
||||
find_package(CGAL REQUIRED)
|
||||
cmake_policy(POP)
|
||||
|
||||
add_library(libslic3r_cgal STATIC MeshBoolean.cpp MeshBoolean.hpp TryCatchSignal.hpp
|
||||
|
@ -539,7 +539,7 @@ void Layer::make_ironing()
|
||||
fill_params.density = 1.;
|
||||
fill_params.monotonic = true;
|
||||
|
||||
for (size_t i = 0; i < by_extruder.size(); ++ i) {
|
||||
for (size_t i = 0; i < by_extruder.size();) {
|
||||
// Find span of regions equivalent to the ironing operation.
|
||||
IroningParams &ironing_params = by_extruder[i];
|
||||
size_t j = i;
|
||||
@ -589,14 +589,17 @@ void Layer::make_ironing()
|
||||
polygons_append(infills, surface.expolygon);
|
||||
}
|
||||
}
|
||||
|
||||
if (! infills.empty() || j > i + 1) {
|
||||
// Ironing over more than a single region or over solid internal infill.
|
||||
if (! infills.empty())
|
||||
// For IroningType::AllSolid only:
|
||||
// Add solid infill areas for layers, that contain some non-ironable infil (sparse infill, bridge infill).
|
||||
append(polys, std::move(infills));
|
||||
polys = union_safety_offset(polys);
|
||||
}
|
||||
// Trim the top surfaces with half the nozzle diameter.
|
||||
ironing_areas = intersection_ex(polys, offset(this->lslices, - float(scale_(0.5 * nozzle_dmr))));
|
||||
if (! infills.empty()) {
|
||||
// For IroningType::AllSolid only:
|
||||
// Add solid infill areas for layers, that contain some non-ironable infil (sparse infill, bridge infill).
|
||||
append(infills, to_polygons(std::move(ironing_areas)));
|
||||
ironing_areas = union_safety_offset_ex(infills);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the filler object.
|
||||
@ -626,6 +629,9 @@ void Layer::make_ironing()
|
||||
flow_mm3_per_mm, extrusion_width, float(extrusion_height));
|
||||
}
|
||||
}
|
||||
|
||||
// Regions up to j were processed.
|
||||
i = j;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1998,13 +1998,19 @@ GCode::LayerResult GCode::process_layer(
|
||||
// Either printing all copies of all objects, or just a single copy of a single object.
|
||||
assert(single_object_instance_idx == size_t(-1) || layers.size() == 1);
|
||||
|
||||
// First object, support and raft layer, if available.
|
||||
const Layer *object_layer = nullptr;
|
||||
const SupportLayer *support_layer = nullptr;
|
||||
const SupportLayer *raft_layer = nullptr;
|
||||
for (const LayerToPrint &l : layers) {
|
||||
if (l.object_layer != nullptr && object_layer == nullptr)
|
||||
if (l.object_layer && ! object_layer)
|
||||
object_layer = l.object_layer;
|
||||
if (l.support_layer != nullptr && support_layer == nullptr)
|
||||
support_layer = l.support_layer;
|
||||
if (l.support_layer) {
|
||||
if (! support_layer)
|
||||
support_layer = l.support_layer;
|
||||
if (! raft_layer && support_layer->id() < support_layer->object()->slicing_parameters().raft_layers())
|
||||
raft_layer = support_layer;
|
||||
}
|
||||
}
|
||||
const Layer &layer = (object_layer != nullptr) ? *object_layer : *support_layer;
|
||||
GCode::LayerResult result { {}, layer.id(), false, last_layer };
|
||||
@ -2406,7 +2412,7 @@ GCode::LayerResult GCode::process_layer(
|
||||
log_memory_info();
|
||||
|
||||
result.gcode = std::move(gcode);
|
||||
result.cooling_buffer_flush = object_layer || last_layer;
|
||||
result.cooling_buffer_flush = object_layer || raft_layer || last_layer;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ void Layer::make_slices()
|
||||
Polygons slices_p;
|
||||
for (LayerRegion *layerm : m_regions)
|
||||
polygons_append(slices_p, to_polygons(layerm->slices.surfaces));
|
||||
slices = union_ex(slices_p);
|
||||
slices = union_safety_offset_ex(slices_p);
|
||||
}
|
||||
|
||||
this->lslices.clear();
|
||||
|
@ -1210,6 +1210,7 @@ void PrintConfigDef::init_fff_params()
|
||||
def->tooltip = L("When printing with very low layer heights, you might still want to print a thicker "
|
||||
"bottom layer to improve adhesion and tolerance for non perfect build plates.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 0;
|
||||
def->ratio_over = "layer_height";
|
||||
def->set_default_value(new ConfigOptionFloatOrPercent(0.35, false));
|
||||
|
||||
@ -3188,7 +3189,7 @@ void PrintConfigDef::init_sla_params()
|
||||
|
||||
def = this->add("relative_correction_y", coFloat);
|
||||
def->label = L("Printer scaling correction in Y axis");
|
||||
def->full_label = L("Printer scaling X axis correction");
|
||||
def->full_label = L("Printer scaling Y axis correction");
|
||||
def->tooltip = L("Printer scaling correction in Y axis");
|
||||
def->min = 0;
|
||||
def->mode = comExpert;
|
||||
@ -3196,7 +3197,7 @@ void PrintConfigDef::init_sla_params()
|
||||
|
||||
def = this->add("relative_correction_z", coFloat);
|
||||
def->label = L("Printer scaling correction in Z axis");
|
||||
def->full_label = L("Printer scaling X axis correction");
|
||||
def->full_label = L("Printer scaling Z axis correction");
|
||||
def->tooltip = L("Printer scaling correction in Z axis");
|
||||
def->min = 0;
|
||||
def->mode = comExpert;
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "Geometry.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "Layer.hpp"
|
||||
#include "MutablePolygon.hpp"
|
||||
#include "SupportMaterial.hpp"
|
||||
#include "Surface.hpp"
|
||||
#include "Slicing.hpp"
|
||||
@ -1709,9 +1710,6 @@ void PrintObject::clip_fill_surfaces()
|
||||
Layer *layer = m_layers[layer_id];
|
||||
Layer *lower_layer = m_layers[layer_id - 1];
|
||||
// Detect things that we need to support.
|
||||
// Cummulative slices.
|
||||
Polygons slices;
|
||||
polygons_append(slices, layer->lslices);
|
||||
// Cummulative fill surfaces.
|
||||
Polygons fill_surfaces;
|
||||
// Solid surfaces to be supported.
|
||||
@ -1736,7 +1734,7 @@ void PrintObject::clip_fill_surfaces()
|
||||
{
|
||||
// Get perimeters area as the difference between slices and fill_surfaces
|
||||
// Only consider the area that is not supported by lower perimeters
|
||||
Polygons perimeters = intersection(diff(slices, fill_surfaces), lower_layer_fill_surfaces);
|
||||
Polygons perimeters = intersection(diff(layer->lslices, fill_surfaces), lower_layer_fill_surfaces);
|
||||
// Only consider perimeter areas that are at least one extrusion width thick.
|
||||
//FIXME Offset2 eats out from both sides, while the perimeters are create outside in.
|
||||
//Should the pw not be half of the current value?
|
||||
@ -1746,9 +1744,15 @@ void PrintObject::clip_fill_surfaces()
|
||||
// Append such thick perimeters to the areas that need support
|
||||
polygons_append(overhangs, opening(perimeters, pw));
|
||||
}
|
||||
// Find new internal infill.
|
||||
polygons_append(overhangs, std::move(upper_internal));
|
||||
upper_internal = intersection(overhangs, lower_layer_internal_surfaces);
|
||||
// Merge the new overhangs, find new internal infill.
|
||||
polygons_append(upper_internal, std::move(overhangs));
|
||||
static constexpr const auto closing_radius = scaled<float>(2.f);
|
||||
upper_internal = intersection(
|
||||
// Regularize the overhang regions, so that the infill areas will not become excessively jagged.
|
||||
smooth_outward(
|
||||
closing(upper_internal, closing_radius, ClipperLib::jtSquare, 0.),
|
||||
scaled<coord_t>(0.1)),
|
||||
lower_layer_internal_surfaces);
|
||||
// Apply new internal infill to regions.
|
||||
for (LayerRegion *layerm : lower_layer->m_regions) {
|
||||
if (layerm->region().config().fill_density.value == 0)
|
||||
|
@ -1480,7 +1480,7 @@ static inline std::tuple<Polygons, Polygons, Polygons, float> detect_overhangs(
|
||||
overhang_polygons = to_polygons(layer.lslices);
|
||||
#endif
|
||||
// Expand for better stability.
|
||||
contact_polygons = expand(overhang_polygons, scaled<float>(object_config.raft_expansion.value));
|
||||
contact_polygons = object_config.raft_expansion.value > 0 ? expand(overhang_polygons, scaled<float>(object_config.raft_expansion.value)) : overhang_polygons;
|
||||
}
|
||||
else if (! layer.regions().empty())
|
||||
{
|
||||
|
@ -9,6 +9,112 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Check if the line is whole inside the sphere, or it is partially inside (intersecting) the sphere.
|
||||
// Inspired by Christer Ericson's Real-Time Collision Detection, pp. 177-179.
|
||||
static bool test_line_inside_sphere(const Vec3f &line_a, const Vec3f &line_b, const Vec3f &sphere_p, const float sphere_radius)
|
||||
{
|
||||
const float sphere_radius_sqr = Slic3r::sqr(sphere_radius);
|
||||
const Vec3f line_dir = line_b - line_a; // n
|
||||
const Vec3f origins_diff = line_a - sphere_p; // m
|
||||
|
||||
const float m_dot_m = origins_diff.dot(origins_diff);
|
||||
// Check if any of the end-points of the line is inside the sphere.
|
||||
if (m_dot_m <= sphere_radius_sqr || (line_b - sphere_p).squaredNorm() <= sphere_radius_sqr)
|
||||
return true;
|
||||
|
||||
// Check if the infinite line is going through the sphere.
|
||||
const float n_dot_n = line_dir.dot(line_dir);
|
||||
const float m_dot_n = origins_diff.dot(line_dir);
|
||||
|
||||
const float eq_a = n_dot_n;
|
||||
const float eq_b = m_dot_n;
|
||||
const float eq_c = m_dot_m - sphere_radius_sqr;
|
||||
|
||||
const float discr = eq_b * eq_b - eq_a * eq_c;
|
||||
// A negative discriminant corresponds to the infinite line infinite not going through the sphere.
|
||||
if (discr < 0.f)
|
||||
return false;
|
||||
|
||||
// Check if the finite line is going through the sphere.
|
||||
const float discr_sqrt = std::sqrt(discr);
|
||||
const float t1 = (-eq_b - discr_sqrt) / eq_a;
|
||||
if (0.f <= t1 && t1 <= 1.f)
|
||||
return true;
|
||||
|
||||
const float t2 = (-eq_b + discr_sqrt) / eq_a;
|
||||
if (0.f <= t2 && t2 <= 1.f && discr_sqrt > 0.f)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the line is whole inside the finite cylinder, or it is partially inside (intersecting) the finite cylinder.
|
||||
// Inspired by Christer Ericson's Real-Time Collision Detection, pp. 194-198.
|
||||
static bool test_line_inside_cylinder(const Vec3f &line_a, const Vec3f &line_b, const Vec3f &cylinder_P, const Vec3f &cylinder_Q, const float cylinder_radius)
|
||||
{
|
||||
assert(cylinder_P != cylinder_Q);
|
||||
const Vec3f cylinder_dir = cylinder_Q - cylinder_P; // d
|
||||
auto is_point_inside_finite_cylinder = [&cylinder_P, &cylinder_Q, &cylinder_radius, &cylinder_dir](const Vec3f &pt) {
|
||||
const Vec3f first_center_diff = cylinder_P - pt;
|
||||
const Vec3f second_center_diff = cylinder_Q - pt;
|
||||
// First, check if the point pt is laying between planes defined by cylinder_p and cylinder_q.
|
||||
// Then check if it is inside the cylinder between cylinder_p and cylinder_q.
|
||||
return first_center_diff.dot(cylinder_dir) <= 0 && second_center_diff.dot(cylinder_dir) >= 0 &&
|
||||
(first_center_diff.cross(cylinder_dir).norm() / cylinder_dir.norm()) <= cylinder_radius;
|
||||
};
|
||||
|
||||
// Check if any of the end-points of the line is inside the cylinder.
|
||||
if (is_point_inside_finite_cylinder(line_a) || is_point_inside_finite_cylinder(line_b))
|
||||
return true;
|
||||
|
||||
// Check if the line is going through the cylinder.
|
||||
const Vec3f origins_diff = line_a - cylinder_P; // m
|
||||
const Vec3f line_dir = line_b - line_a; // n
|
||||
|
||||
const float m_dot_d = origins_diff.dot(cylinder_dir);
|
||||
const float n_dot_d = line_dir.dot(cylinder_dir);
|
||||
const float d_dot_d = cylinder_dir.dot(cylinder_dir);
|
||||
|
||||
const float n_dot_n = line_dir.dot(line_dir);
|
||||
const float m_dot_n = origins_diff.dot(line_dir);
|
||||
const float m_dot_m = origins_diff.dot(origins_diff);
|
||||
|
||||
const float eq_a = d_dot_d * n_dot_n - n_dot_d * n_dot_d;
|
||||
const float eq_b = d_dot_d * m_dot_n - n_dot_d * m_dot_d;
|
||||
const float eq_c = d_dot_d * (m_dot_m - Slic3r::sqr(cylinder_radius)) - m_dot_d * m_dot_d;
|
||||
|
||||
const float discr = eq_b * eq_b - eq_a * eq_c;
|
||||
// A negative discriminant corresponds to the infinite line not going through the infinite cylinder.
|
||||
if (discr < 0.0f)
|
||||
return false;
|
||||
|
||||
// Check if the finite line is going through the finite cylinder.
|
||||
const float discr_sqrt = std::sqrt(discr);
|
||||
const float t1 = (-eq_b - discr_sqrt) / eq_a;
|
||||
if (0.f <= t1 && t1 <= 1.f)
|
||||
if (const float cylinder_endcap_t1 = m_dot_d + t1 * n_dot_d; 0.f <= cylinder_endcap_t1 && cylinder_endcap_t1 <= d_dot_d)
|
||||
return true;
|
||||
|
||||
const float t2 = (-eq_b + discr_sqrt) / eq_a;
|
||||
if (0.f <= t2 && t2 <= 1.f)
|
||||
if (const float cylinder_endcap_t2 = (m_dot_d + t2 * n_dot_d); 0.f <= cylinder_endcap_t2 && cylinder_endcap_t2 <= d_dot_d)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the line is whole inside the capsule, or it is partially inside (intersecting) the capsule.
|
||||
static bool test_line_inside_capsule(const Vec3f &line_a, const Vec3f &line_b, const Vec3f &capsule_p, const Vec3f &capsule_q, const float capsule_radius) {
|
||||
assert(capsule_p != capsule_q);
|
||||
|
||||
// Check if the line intersect any of the spheres forming the capsule.
|
||||
if (test_line_inside_sphere(line_a, line_b, capsule_p, capsule_radius) || test_line_inside_sphere(line_a, line_b, capsule_q, capsule_radius))
|
||||
return true;
|
||||
|
||||
// Check if the line intersects the cylinder between the centers of the spheres.
|
||||
return test_line_inside_cylinder(line_a, line_b, capsule_p, capsule_q, capsule_radius);
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool TriangleSelector::verify_triangle_midpoints(const Triangle &tr) const
|
||||
{
|
||||
@ -124,24 +230,20 @@ int TriangleSelector::select_unsplit_triangle(const Vec3f &hit, int facet_idx) c
|
||||
return this->select_unsplit_triangle(hit, facet_idx, neighbors);
|
||||
}
|
||||
|
||||
void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
|
||||
const Vec3f& source, float radius,
|
||||
CursorType cursor_type, EnforcerBlockerType new_state,
|
||||
const Transform3d& trafo, const Transform3d& trafo_no_translate,
|
||||
bool triangle_splitting, const ClippingPlane &clp, float highlight_by_angle_deg)
|
||||
void TriangleSelector::select_patch(int facet_start, std::unique_ptr<Cursor> &&cursor, EnforcerBlockerType new_state, const Transform3d& trafo_no_translate, bool triangle_splitting, float highlight_by_angle_deg)
|
||||
{
|
||||
assert(facet_start < m_orig_size_indices);
|
||||
|
||||
// Save current cursor center, squared radius and camera direction, so we don't
|
||||
// have to pass it around.
|
||||
m_cursor = Cursor(hit, source, radius, cursor_type, trafo, clp);
|
||||
m_cursor = std::move(cursor);
|
||||
|
||||
// In case user changed cursor size since last time, update triangle edge limit.
|
||||
// It is necessary to compare the internal radius in m_cursor! radius is in
|
||||
// world coords and does not change after scaling.
|
||||
if (m_old_cursor_radius_sqr != m_cursor.radius_sqr) {
|
||||
set_edge_limit(std::sqrt(m_cursor.radius_sqr) / 5.f);
|
||||
m_old_cursor_radius_sqr = m_cursor.radius_sqr;
|
||||
if (m_old_cursor_radius_sqr != m_cursor->radius_sqr) {
|
||||
set_edge_limit(std::sqrt(m_cursor->radius_sqr) / 5.f);
|
||||
m_old_cursor_radius_sqr = m_cursor->radius_sqr;
|
||||
}
|
||||
|
||||
const float highlight_angle_limit = cos(Geometry::deg2rad(highlight_by_angle_deg));
|
||||
@ -163,7 +265,7 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
|
||||
if (select_triangle(facet, new_state, triangle_splitting)) {
|
||||
// add neighboring facets to list to be processed later
|
||||
for (int neighbor_idx : m_neighbors[facet])
|
||||
if (neighbor_idx >= 0 && (m_cursor.type == SPHERE || faces_camera(neighbor_idx)))
|
||||
if (neighbor_idx >= 0 && m_cursor->is_facet_visible(neighbor_idx, m_face_normals))
|
||||
facets_to_check.push_back(neighbor_idx);
|
||||
}
|
||||
}
|
||||
@ -256,8 +358,9 @@ void TriangleSelector::precompute_all_neighbors_recursive(const int facet_idx, c
|
||||
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_neighbors_recursive(tr->children[i], this->child_neighbors(*tr, neighbors, i),
|
||||
this->child_neighbors_propagated(*tr, neighbors_propagated, i), neighbors_out,
|
||||
const Vec3i child_neighbors = this->child_neighbors(*tr, neighbors, i);
|
||||
this->precompute_all_neighbors_recursive(tr->children[i], child_neighbors,
|
||||
this->child_neighbors_propagated(*tr, neighbors_propagated, i, child_neighbors), neighbors_out,
|
||||
neighbors_propagated_out);
|
||||
}
|
||||
}
|
||||
@ -682,33 +785,29 @@ Vec3i TriangleSelector::child_neighbors(const Triangle &tr, const Vec3i &neighbo
|
||||
|
||||
// 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
|
||||
Vec3i TriangleSelector::child_neighbors_propagated(const Triangle &tr, const Vec3i &neighbors_propagated, int child_idx, const Vec3i &child_neighbors) const
|
||||
{
|
||||
int i = tr.special_side();
|
||||
int j = next_idx_modulo(i, 3);
|
||||
int k = next_idx_modulo(j, 3);
|
||||
|
||||
Vec3i out;
|
||||
auto replace_if_not_exists = [&out](int index_to_replace, int neighbor) {
|
||||
Vec3i out = child_neighbors;
|
||||
auto replace_if_not_exists = [&out, &neighbors_propagated](int index_to_replace, int neighbor_idx) {
|
||||
if (out(index_to_replace) == -1)
|
||||
out(index_to_replace) = neighbor;
|
||||
out(index_to_replace) = neighbors_propagated(neighbor_idx);
|
||||
};
|
||||
|
||||
switch (tr.number_of_split_sides()) {
|
||||
case 1:
|
||||
switch (child_idx) {
|
||||
case 0:
|
||||
out(0) = neighbors(i);
|
||||
out(1) = this->neighbor_child(neighbors(j), tr.verts_idxs[k], tr.verts_idxs[j], Partition::Second);
|
||||
replace_if_not_exists(1, neighbors(j));
|
||||
out(2) = tr.children[1];
|
||||
replace_if_not_exists(0, i);
|
||||
replace_if_not_exists(1, j);
|
||||
break;
|
||||
default:
|
||||
assert(child_idx == 1);
|
||||
out(0) = this->neighbor_child(neighbors(j), tr.verts_idxs[k], tr.verts_idxs[j], Partition::First);
|
||||
replace_if_not_exists(0, neighbors(j));
|
||||
out(1) = neighbors(k);
|
||||
out(2) = tr.children[0];
|
||||
replace_if_not_exists(0, j);
|
||||
replace_if_not_exists(1, k);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@ -716,25 +815,17 @@ Vec3i TriangleSelector::child_neighbors_propagated(const Triangle &tr, const Vec
|
||||
case 2:
|
||||
switch (child_idx) {
|
||||
case 0:
|
||||
out(0) = this->neighbor_child(neighbors(i), tr.verts_idxs[j], tr.verts_idxs[i], Partition::Second);
|
||||
replace_if_not_exists(0, neighbors(i));
|
||||
out(1) = tr.children[1];
|
||||
out(2) = this->neighbor_child(neighbors(k), tr.verts_idxs[i], tr.verts_idxs[k], Partition::First);
|
||||
replace_if_not_exists(2, neighbors(k));
|
||||
replace_if_not_exists(0, i);
|
||||
replace_if_not_exists(2, k);
|
||||
break;
|
||||
case 1:
|
||||
assert(child_idx == 1);
|
||||
out(0) = this->neighbor_child(neighbors(i), tr.verts_idxs[j], tr.verts_idxs[i], Partition::First);
|
||||
replace_if_not_exists(0, neighbors(i));
|
||||
out(1) = tr.children[2];
|
||||
out(2) = tr.children[0];
|
||||
replace_if_not_exists(0, i);
|
||||
break;
|
||||
default:
|
||||
assert(child_idx == 2);
|
||||
out(0) = neighbors(j);
|
||||
out(1) = this->neighbor_child(neighbors(k), tr.verts_idxs[i], tr.verts_idxs[k], Partition::Second);
|
||||
replace_if_not_exists(1, neighbors(k));
|
||||
out(2) = tr.children[1];
|
||||
replace_if_not_exists(0, j);
|
||||
replace_if_not_exists(1, k);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@ -743,31 +834,19 @@ Vec3i TriangleSelector::child_neighbors_propagated(const Triangle &tr, const Vec
|
||||
assert(tr.special_side() == 0);
|
||||
switch (child_idx) {
|
||||
case 0:
|
||||
out(0) = this->neighbor_child(neighbors(0), tr.verts_idxs[1], tr.verts_idxs[0], Partition::Second);
|
||||
replace_if_not_exists(0, neighbors(0));
|
||||
out(1) = tr.children[3];
|
||||
out(2) = this->neighbor_child(neighbors(2), tr.verts_idxs[0], tr.verts_idxs[2], Partition::First);
|
||||
replace_if_not_exists(2, neighbors(2));
|
||||
replace_if_not_exists(0, 0);
|
||||
replace_if_not_exists(2, 2);
|
||||
break;
|
||||
case 1:
|
||||
out(0) = this->neighbor_child(neighbors(0), tr.verts_idxs[1], tr.verts_idxs[0], Partition::First);
|
||||
replace_if_not_exists(0, neighbors(0));
|
||||
out(1) = this->neighbor_child(neighbors(1), tr.verts_idxs[2], tr.verts_idxs[1], Partition::Second);
|
||||
replace_if_not_exists(1, neighbors(1));
|
||||
out(2) = tr.children[3];
|
||||
replace_if_not_exists(0, 0);
|
||||
replace_if_not_exists(1, 1);
|
||||
break;
|
||||
case 2:
|
||||
out(0) = this->neighbor_child(neighbors(1), tr.verts_idxs[2], tr.verts_idxs[1], Partition::First);
|
||||
replace_if_not_exists(0, neighbors(1));
|
||||
out(1) = this->neighbor_child(neighbors(2), tr.verts_idxs[0], tr.verts_idxs[2], Partition::Second);
|
||||
replace_if_not_exists(1, neighbors(2));
|
||||
out(2) = tr.children[3];
|
||||
replace_if_not_exists(0, 1);
|
||||
replace_if_not_exists(1, 2);
|
||||
break;
|
||||
default:
|
||||
assert(child_idx == 3);
|
||||
out(0) = tr.children[1];
|
||||
out(1) = tr.children[2];
|
||||
out(2) = tr.children[0];
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@ -788,11 +867,11 @@ bool TriangleSelector::select_triangle_recursive(int facet_idx, const Vec3i &nei
|
||||
|
||||
assert(this->verify_triangle_neighbors(*tr, neighbors));
|
||||
|
||||
int num_of_inside_vertices = vertices_inside(facet_idx);
|
||||
int num_of_inside_vertices = m_cursor->vertices_inside(*tr, m_vertices);
|
||||
|
||||
if (num_of_inside_vertices == 0
|
||||
&& ! is_pointer_in_triangle(facet_idx)
|
||||
&& ! is_edge_inside_cursor(facet_idx))
|
||||
&& ! m_cursor->is_pointer_in_triangle(*tr, m_vertices)
|
||||
&& ! m_cursor->is_edge_inside_cursor(*tr, m_vertices))
|
||||
return false;
|
||||
|
||||
if (num_of_inside_vertices == 3) {
|
||||
@ -840,7 +919,7 @@ void TriangleSelector::set_facet(int facet_idx, EnforcerBlockerType state)
|
||||
}
|
||||
|
||||
// called by select_patch()->select_triangle()...select_triangle()
|
||||
// to decide which sides of the traingle to split and to actually split it calling set_division() and perform_split().
|
||||
// to decide which sides of the triangle to split and to actually split it calling set_division() and perform_split().
|
||||
void TriangleSelector::split_triangle(int facet_idx, const Vec3i &neighbors)
|
||||
{
|
||||
if (m_triangles[facet_idx].is_split()) {
|
||||
@ -864,9 +943,9 @@ void TriangleSelector::split_triangle(int facet_idx, const Vec3i &neighbors)
|
||||
|
||||
// In case the object is non-uniformly scaled, transform the
|
||||
// points to world coords.
|
||||
if (! m_cursor.uniform_scaling) {
|
||||
if (! m_cursor->uniform_scaling) {
|
||||
for (size_t i=0; i<pts.size(); ++i) {
|
||||
pts_transformed[i] = m_cursor.trafo * (*pts[i]);
|
||||
pts_transformed[i] = m_cursor->trafo * (*pts[i]);
|
||||
pts[i] = &pts_transformed[i];
|
||||
}
|
||||
}
|
||||
@ -897,71 +976,80 @@ void TriangleSelector::split_triangle(int facet_idx, const Vec3i &neighbors)
|
||||
perform_split(facet_idx, neighbors, old_type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Is pointer in a triangle?
|
||||
bool TriangleSelector::is_pointer_in_triangle(int facet_idx) const
|
||||
{
|
||||
const Vec3f& p1 = m_vertices[m_triangles[facet_idx].verts_idxs[0]].v;
|
||||
const Vec3f& p2 = m_vertices[m_triangles[facet_idx].verts_idxs[1]].v;
|
||||
const Vec3f& p3 = m_vertices[m_triangles[facet_idx].verts_idxs[2]].v;
|
||||
return m_cursor.is_pointer_in_triangle(p1, p2, p3);
|
||||
bool TriangleSelector::Cursor::is_pointer_in_triangle(const Triangle &tr, const std::vector<Vertex> &vertices) const {
|
||||
const Vec3f& p1 = vertices[tr.verts_idxs[0]].v;
|
||||
const Vec3f& p2 = vertices[tr.verts_idxs[1]].v;
|
||||
const Vec3f& p3 = vertices[tr.verts_idxs[2]].v;
|
||||
return this->is_pointer_in_triangle(p1, p2, p3);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Determine whether this facet is potentially visible (still can be obscured).
|
||||
bool TriangleSelector::faces_camera(int facet) const
|
||||
bool TriangleSelector::Cursor::is_facet_visible(const Cursor &cursor, int facet_idx, const std::vector<Vec3f> &face_normals)
|
||||
{
|
||||
assert(facet < m_orig_size_indices);
|
||||
Vec3f n = m_face_normals[facet];
|
||||
if (! m_cursor.uniform_scaling)
|
||||
n = m_cursor.trafo_normal * n;
|
||||
return n.dot(m_cursor.dir) < 0.;
|
||||
assert(facet_idx < int(face_normals.size()));
|
||||
Vec3f n = face_normals[facet_idx];
|
||||
if (!cursor.uniform_scaling)
|
||||
n = cursor.trafo_normal * n;
|
||||
return n.dot(cursor.dir) < 0.f;
|
||||
}
|
||||
|
||||
|
||||
// How many vertices of a triangle are inside the circle?
|
||||
int TriangleSelector::vertices_inside(int facet_idx) const
|
||||
int TriangleSelector::Cursor::vertices_inside(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
int inside = 0;
|
||||
for (size_t i=0; i<3; ++i) {
|
||||
if (m_cursor.is_mesh_point_inside(m_vertices[m_triangles[facet_idx].verts_idxs[i]].v))
|
||||
for (size_t i = 0; i < 3; ++i)
|
||||
if (this->is_mesh_point_inside(vertices[tr.verts_idxs[i]].v))
|
||||
++inside;
|
||||
}
|
||||
|
||||
return inside;
|
||||
}
|
||||
|
||||
|
||||
// Is edge inside cursor?
|
||||
bool TriangleSelector::is_edge_inside_cursor(int facet_idx) const
|
||||
// Is any edge inside Sphere cursor?
|
||||
bool TriangleSelector::Sphere::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
std::array<Vec3f, 3> pts;
|
||||
for (int i=0; i<3; ++i) {
|
||||
pts[i] = m_vertices[m_triangles[facet_idx].verts_idxs[i]].v;
|
||||
if (! m_cursor.uniform_scaling)
|
||||
pts[i] = m_cursor.trafo * pts[i];
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
pts[i] = vertices[tr.verts_idxs[i]].v;
|
||||
if (!this->uniform_scaling)
|
||||
pts[i] = this->trafo * pts[i];
|
||||
}
|
||||
|
||||
const Vec3f& p = m_cursor.center;
|
||||
|
||||
for (int side = 0; side < 3; ++side) {
|
||||
const Vec3f& a = pts[side];
|
||||
const Vec3f& b = pts[side<2 ? side+1 : 0];
|
||||
Vec3f s = (b-a).normalized();
|
||||
float t = (p-a).dot(s);
|
||||
Vec3f vector = a+t*s - p;
|
||||
|
||||
// vector is 3D vector from center to the intersection. What we want to
|
||||
// measure is length of its projection onto plane perpendicular to dir.
|
||||
float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(m_cursor.dir), 2.f);
|
||||
if (dist_sqr < m_cursor.radius_sqr && t>=0.f && t<=(b-a).norm())
|
||||
const Vec3f &edge_a = pts[side];
|
||||
const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0];
|
||||
if (test_line_inside_sphere(edge_a, edge_b, this->center, this->radius))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is edge inside cursor?
|
||||
bool TriangleSelector::Circle::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
std::array<Vec3f, 3> pts;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
pts[i] = vertices[tr.verts_idxs[i]].v;
|
||||
if (!this->uniform_scaling)
|
||||
pts[i] = this->trafo * pts[i];
|
||||
}
|
||||
|
||||
const Vec3f &p = this->center;
|
||||
for (int side = 0; side < 3; ++side) {
|
||||
const Vec3f &a = pts[side];
|
||||
const Vec3f &b = pts[side < 2 ? side + 1 : 0];
|
||||
Vec3f s = (b - a).normalized();
|
||||
float t = (p - a).dot(s);
|
||||
Vec3f vector = a + t * s - p;
|
||||
|
||||
// vector is 3D vector from center to the intersection. What we want to
|
||||
// measure is length of its projection onto plane perpendicular to dir.
|
||||
float dist_sqr = vector.squaredNorm() - std::pow(vector.dot(this->dir), 2.f);
|
||||
if (dist_sqr < this->radius_sqr && t >= 0.f && t <= (b - a).norm())
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Recursively remove all subtriangles.
|
||||
void TriangleSelector::undivide_triangle(int facet_idx)
|
||||
@ -1002,7 +1090,6 @@ void TriangleSelector::undivide_triangle(int facet_idx)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TriangleSelector::remove_useless_children(int facet_idx)
|
||||
{
|
||||
// Check that all children are leafs of the same type. If not, try to
|
||||
@ -1041,8 +1128,6 @@ void TriangleSelector::remove_useless_children(int facet_idx)
|
||||
tr.set_state(first_child_type);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void TriangleSelector::garbage_collect()
|
||||
{
|
||||
// First make a map from old to new triangle indices.
|
||||
@ -1103,7 +1188,6 @@ TriangleSelector::TriangleSelector(const TriangleMesh& mesh)
|
||||
reset();
|
||||
}
|
||||
|
||||
|
||||
void TriangleSelector::reset()
|
||||
{
|
||||
m_vertices.clear();
|
||||
@ -1124,17 +1208,11 @@ void TriangleSelector::reset()
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void TriangleSelector::set_edge_limit(float edge_limit)
|
||||
{
|
||||
m_edge_limit_sqr = std::pow(edge_limit, 2.f);
|
||||
}
|
||||
|
||||
|
||||
|
||||
int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, const EnforcerBlockerType state)
|
||||
{
|
||||
for (int i : {a, b, c}) {
|
||||
@ -1426,7 +1504,9 @@ void TriangleSelector::get_seed_fill_contour_recursive(const int facet_idx, cons
|
||||
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->get_seed_fill_contour_recursive(tr->children[i], this->child_neighbors(*tr, neighbors, i), this->child_neighbors_propagated(*tr, neighbors_propagated, i), edges_out);
|
||||
const Vec3i child_neighbors = this->child_neighbors(*tr, neighbors, i);
|
||||
this->get_seed_fill_contour_recursive(tr->children[i], child_neighbors,
|
||||
this->child_neighbors_propagated(*tr, neighbors_propagated, i, child_neighbors), edges_out);
|
||||
}
|
||||
}
|
||||
} else if (tr->is_selected_by_seed_fill()) {
|
||||
@ -1693,54 +1773,132 @@ void TriangleSelector::seed_fill_apply_on_triangles(EnforcerBlockerType new_stat
|
||||
}
|
||||
}
|
||||
|
||||
TriangleSelector::Cursor::Cursor(
|
||||
const Vec3f& center_, const Vec3f& source_, float radius_world,
|
||||
CursorType type_, const Transform3d& trafo_, const ClippingPlane &clipping_plane_)
|
||||
: center{center_},
|
||||
source{source_},
|
||||
type{type_},
|
||||
trafo{trafo_.cast<float>()},
|
||||
clipping_plane(clipping_plane_)
|
||||
TriangleSelector::Cursor::Cursor(const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: source{source_}, trafo{trafo_.cast<float>()}, clipping_plane{clipping_plane_}
|
||||
{
|
||||
Vec3d sf = Geometry::Transformation(trafo_).get_scaling_factor();
|
||||
if (is_approx(sf(0), sf(1)) && is_approx(sf(1), sf(2))) {
|
||||
radius_sqr = float(std::pow(radius_world / sf(0), 2));
|
||||
radius = float(radius_world / sf(0));
|
||||
radius_sqr = float(Slic3r::sqr(radius_world / sf(0)));
|
||||
uniform_scaling = true;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// In case that the transformation is non-uniform, all checks whether
|
||||
// something is inside the cursor should be done in world coords.
|
||||
// First transform center, source and dir in world coords and remember
|
||||
// that we did this.
|
||||
center = trafo * center;
|
||||
source = trafo * source;
|
||||
// First transform source in world coords and remember that we did this.
|
||||
source = trafo * source;
|
||||
uniform_scaling = false;
|
||||
radius_sqr = radius_world * radius_world;
|
||||
trafo_normal = trafo.linear().inverse().transpose();
|
||||
radius = radius_world;
|
||||
radius_sqr = Slic3r::sqr(radius_world);
|
||||
trafo_normal = trafo.linear().inverse().transpose();
|
||||
}
|
||||
}
|
||||
|
||||
TriangleSelector::SinglePointCursor::SinglePointCursor(const Vec3f& center_, const Vec3f& source_, float radius_world, const Transform3d& trafo_, const ClippingPlane &clipping_plane_)
|
||||
: center{center_}, Cursor(source_, radius_world, trafo_, clipping_plane_)
|
||||
{
|
||||
// In case that the transformation is non-uniform, all checks whether
|
||||
// something is inside the cursor should be done in world coords.
|
||||
// Because of the center is transformed.
|
||||
if (!uniform_scaling)
|
||||
center = trafo * center;
|
||||
|
||||
// Calculate dir, in whatever coords is appropriate.
|
||||
dir = (center - source).normalized();
|
||||
}
|
||||
|
||||
// Is a point (in mesh coords) inside a cursor?
|
||||
bool TriangleSelector::Cursor::is_mesh_point_inside(const Vec3f &point) const
|
||||
TriangleSelector::DoublePointCursor::DoublePointCursor(const Vec3f &first_center_, const Vec3f &second_center_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: first_center{first_center_}, second_center{second_center_}, Cursor(source_, radius_world, trafo_, clipping_plane_)
|
||||
{
|
||||
if (!uniform_scaling) {
|
||||
first_center = trafo * first_center_;
|
||||
second_center = trafo * second_center_;
|
||||
}
|
||||
|
||||
// Calculate dir, in whatever coords is appropriate.
|
||||
dir = (first_center - source).normalized();
|
||||
}
|
||||
|
||||
// Returns true if clipping plane is not active or if the point not clipped by clipping plane.
|
||||
inline static bool is_mesh_point_not_clipped(const Vec3f &point, const TriangleSelector::ClippingPlane &clipping_plane)
|
||||
{
|
||||
return !clipping_plane.is_active() || !clipping_plane.is_mesh_point_clipped(point);
|
||||
}
|
||||
|
||||
// Is a point (in mesh coords) inside a Sphere cursor?
|
||||
bool TriangleSelector::Sphere::is_mesh_point_inside(const Vec3f &point) const
|
||||
{
|
||||
const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point);
|
||||
if ((center - transformed_point).squaredNorm() < radius_sqr)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is a point (in mesh coords) inside a Circle cursor?
|
||||
bool TriangleSelector::Circle::is_mesh_point_inside(const Vec3f &point) const
|
||||
{
|
||||
const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point);
|
||||
const Vec3f diff = center - transformed_point;
|
||||
const bool is_point_inside = (type == CIRCLE ? (diff - diff.dot(dir) * dir).squaredNorm() : diff.squaredNorm()) < radius_sqr;
|
||||
|
||||
if (is_point_inside && clipping_plane.is_active())
|
||||
return !clipping_plane.is_mesh_point_clipped(point);
|
||||
if ((diff - diff.dot(dir) * dir).squaredNorm() < radius_sqr)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
|
||||
return is_point_inside;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is a point (in mesh coords) inside a Capsule3D cursor?
|
||||
bool TriangleSelector::Capsule3D::is_mesh_point_inside(const Vec3f &point) const
|
||||
{
|
||||
const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point);
|
||||
const Vec3f first_center_diff = this->first_center - transformed_point;
|
||||
const Vec3f second_center_diff = this->second_center - transformed_point;
|
||||
if (first_center_diff.squaredNorm() < this->radius_sqr || second_center_diff.squaredNorm() < this->radius_sqr)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
|
||||
// First, check if the point pt is laying between planes defined by first_center and second_center.
|
||||
// Then check if it is inside the cylinder between first_center and second_center.
|
||||
const Vec3f centers_diff = this->second_center - this->first_center;
|
||||
if (first_center_diff.dot(centers_diff) <= 0.f && second_center_diff.dot(centers_diff) >= 0.f && (first_center_diff.cross(centers_diff).norm() / centers_diff.norm()) <= this->radius)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is a point (in mesh coords) inside a Capsule2D cursor?
|
||||
bool TriangleSelector::Capsule2D::is_mesh_point_inside(const Vec3f &point) const
|
||||
{
|
||||
const Vec3f transformed_point = uniform_scaling ? point : Vec3f(trafo * point);
|
||||
const Vec3f first_center_diff = this->first_center - transformed_point;
|
||||
const Vec3f first_center_diff_projected = first_center_diff - first_center_diff.dot(this->dir) * this->dir;
|
||||
if (first_center_diff_projected.squaredNorm() < this->radius_sqr)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
|
||||
const Vec3f second_center_diff = this->second_center - transformed_point;
|
||||
const Vec3f second_center_diff_projected = second_center_diff - second_center_diff.dot(this->dir) * this->dir;
|
||||
if (second_center_diff_projected.squaredNorm() < this->radius_sqr)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
|
||||
const Vec3f centers_diff = this->second_center - this->first_center;
|
||||
const Vec3f centers_diff_projected = centers_diff - centers_diff.dot(this->dir) * this->dir;
|
||||
|
||||
// First, check if the point is laying between first_center and second_center.
|
||||
if (first_center_diff_projected.dot(centers_diff_projected) <= 0.f && second_center_diff_projected.dot(centers_diff_projected) >= 0.f) {
|
||||
// Vector in the direction of line |AD| of the rectangle that intersects the circle with the center in first_center.
|
||||
const Vec3f rectangle_da_dir = centers_diff.cross(this->dir);
|
||||
// Vector pointing from first_center to the point 'A' of the rectangle.
|
||||
const Vec3f first_center_rectangle_a_diff = rectangle_da_dir.normalized() * this->radius;
|
||||
const Vec3f rectangle_a = this->first_center - first_center_rectangle_a_diff;
|
||||
const Vec3f rectangle_d = this->first_center + first_center_rectangle_a_diff;
|
||||
// Now check if the point is laying inside the rectangle between circles with centers in first_center and second_center.
|
||||
if ((rectangle_a - transformed_point).dot(rectangle_da_dir) <= 0.f && (rectangle_d - transformed_point).dot(rectangle_da_dir) >= 0.f)
|
||||
return is_mesh_point_not_clipped(point, clipping_plane);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// p1, p2, p3 are in mesh coords!
|
||||
bool TriangleSelector::Cursor::is_pointer_in_triangle(const Vec3f& p1_,
|
||||
const Vec3f& p2_,
|
||||
const Vec3f& p3_) const
|
||||
{
|
||||
static bool is_circle_pointer_inside_triangle(const Vec3f &p1_, const Vec3f &p2_, const Vec3f &p3_, const Vec3f ¢er, const Vec3f &dir, const bool uniform_scaling, const Transform3f &trafo) {
|
||||
const Vec3f& q1 = center + dir;
|
||||
const Vec3f& q2 = center - dir;
|
||||
|
||||
@ -1761,4 +1919,108 @@ bool TriangleSelector::Cursor::is_pointer_in_triangle(const Vec3f& p1_,
|
||||
return signed_volume_sign(q1,q2,p2,p3) == pos && signed_volume_sign(q1,q2,p3,p1) == pos;
|
||||
}
|
||||
|
||||
// p1, p2, p3 are in mesh coords!
|
||||
bool TriangleSelector::SinglePointCursor::is_pointer_in_triangle(const Vec3f &p1_, const Vec3f &p2_, const Vec3f &p3_) const
|
||||
{
|
||||
return is_circle_pointer_inside_triangle(p1_, p2_, p3_, center, dir, uniform_scaling, trafo);
|
||||
}
|
||||
|
||||
// p1, p2, p3 are in mesh coords!
|
||||
bool TriangleSelector::DoublePointCursor::is_pointer_in_triangle(const Vec3f &p1_, const Vec3f &p2_, const Vec3f &p3_) const
|
||||
{
|
||||
return is_circle_pointer_inside_triangle(p1_, p2_, p3_, first_center, dir, uniform_scaling, trafo) ||
|
||||
is_circle_pointer_inside_triangle(p1_, p2_, p3_, second_center, dir, uniform_scaling, trafo);
|
||||
}
|
||||
|
||||
bool line_plane_intersection(const Vec3f &line_a, const Vec3f &line_b, const Vec3f &plane_origin, const Vec3f &plane_normal, Vec3f &out_intersection)
|
||||
{
|
||||
Vec3f line_dir = line_b - line_a;
|
||||
float t_denominator = plane_normal.dot(line_dir);
|
||||
if (t_denominator == 0.f)
|
||||
return false;
|
||||
|
||||
// Compute 'd' in plane equation by using some point (origin) on the plane
|
||||
float plane_d = plane_normal.dot(plane_origin);
|
||||
if (float t = (plane_d - plane_normal.dot(line_a)) / t_denominator; t >= 0.f && t <= 1.f) {
|
||||
out_intersection = line_a + t * line_dir;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TriangleSelector::Capsule3D::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
std::array<Vec3f, 3> pts;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
pts[i] = vertices[tr.verts_idxs[i]].v;
|
||||
if (!this->uniform_scaling)
|
||||
pts[i] = this->trafo * pts[i];
|
||||
}
|
||||
|
||||
for (int side = 0; side < 3; ++side) {
|
||||
const Vec3f &edge_a = pts[side];
|
||||
const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0];
|
||||
if (test_line_inside_capsule(edge_a, edge_b, this->first_center, this->second_center, this->radius))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Is edge inside cursor?
|
||||
bool TriangleSelector::Capsule2D::is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const
|
||||
{
|
||||
std::array<Vec3f, 3> pts;
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
pts[i] = vertices[tr.verts_idxs[i]].v;
|
||||
if (!this->uniform_scaling)
|
||||
pts[i] = this->trafo * pts[i];
|
||||
}
|
||||
|
||||
const Vec3f centers_diff = this->second_center - this->first_center;
|
||||
// Vector in the direction of line |AD| of the rectangle that intersects the circle with the center in first_center.
|
||||
const Vec3f rectangle_da_dir = centers_diff.cross(this->dir);
|
||||
// Vector pointing from first_center to the point 'A' of the rectangle.
|
||||
const Vec3f first_center_rectangle_a_diff = rectangle_da_dir.normalized() * this->radius;
|
||||
const Vec3f rectangle_a = this->first_center - first_center_rectangle_a_diff;
|
||||
const Vec3f rectangle_d = this->first_center + first_center_rectangle_a_diff;
|
||||
|
||||
auto edge_inside_rectangle = [&self = std::as_const(*this), ¢ers_diff](const Vec3f &edge_a, const Vec3f &edge_b, const Vec3f &plane_origin, const Vec3f &plane_normal) -> bool {
|
||||
Vec3f intersection(-1.f, -1.f, -1.f);
|
||||
if (line_plane_intersection(edge_a, edge_b, plane_origin, plane_normal, intersection)) {
|
||||
// Now check if the intersection point is inside the rectangle. That means it is between 'first_center' and 'second_center', resp. between 'A' and 'B'.
|
||||
if (self.first_center.dot(centers_diff) <= intersection.dot(centers_diff) && intersection.dot(centers_diff) <= self.second_center.dot(centers_diff))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
for (int side = 0; side < 3; ++side) {
|
||||
const Vec3f &edge_a = pts[side];
|
||||
const Vec3f &edge_b = pts[side < 2 ? side + 1 : 0];
|
||||
const Vec3f edge_dir = edge_b - edge_a;
|
||||
const Vec3f edge_dir_n = edge_dir.normalized();
|
||||
|
||||
float t1 = (this->first_center - edge_a).dot(edge_dir_n);
|
||||
float t2 = (this->second_center - edge_a).dot(edge_dir_n);
|
||||
Vec3f vector1 = edge_a + t1 * edge_dir_n - this->first_center;
|
||||
Vec3f vector2 = edge_a + t2 * edge_dir_n - this->second_center;
|
||||
|
||||
// Vectors vector1 and vector2 are 3D vector from centers to the intersections. What we want to
|
||||
// measure is length of its projection onto plane perpendicular to dir.
|
||||
if (float dist = vector1.squaredNorm() - std::pow(vector1.dot(this->dir), 2.f); dist < this->radius_sqr && t1 >= 0.f && t1 <= edge_dir.norm())
|
||||
return true;
|
||||
|
||||
if (float dist = vector2.squaredNorm() - std::pow(vector2.dot(this->dir), 2.f); dist < this->radius_sqr && t2 >= 0.f && t2 <= edge_dir.norm())
|
||||
return true;
|
||||
|
||||
// Check if the edge is passing through the rectangle between first_center and second_center.
|
||||
if (edge_inside_rectangle(edge_a, edge_b, rectangle_a, (rectangle_d - rectangle_a)) || edge_inside_rectangle(edge_a, edge_b, rectangle_d, (rectangle_a - rectangle_d)))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -15,7 +15,12 @@ enum class EnforcerBlockerType : int8_t;
|
||||
|
||||
// Following class holds information about selected triangles. It also has power
|
||||
// to recursively subdivide the triangles and make the selection finer.
|
||||
class TriangleSelector {
|
||||
class TriangleSelector
|
||||
{
|
||||
protected:
|
||||
class Triangle;
|
||||
struct Vertex;
|
||||
|
||||
public:
|
||||
enum CursorType {
|
||||
CIRCLE,
|
||||
@ -35,6 +40,146 @@ public:
|
||||
bool is_mesh_point_clipped(const Vec3f &point) const { return normal.dot(point) - offset > 0.f; }
|
||||
};
|
||||
|
||||
class Cursor
|
||||
{
|
||||
public:
|
||||
Cursor() = delete;
|
||||
virtual ~Cursor() = default;
|
||||
|
||||
bool is_pointer_in_triangle(const Triangle &tr, const std::vector<Vertex> &vertices) const;
|
||||
|
||||
virtual bool is_mesh_point_inside(const Vec3f &point) const = 0;
|
||||
virtual bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const = 0;
|
||||
virtual int vertices_inside(const Triangle &tr, const std::vector<Vertex> &vertices) const;
|
||||
virtual bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const = 0;
|
||||
virtual bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const = 0;
|
||||
|
||||
static bool is_facet_visible(const Cursor &cursor, int facet_idx, const std::vector<Vec3f> &face_normals);
|
||||
|
||||
protected:
|
||||
explicit Cursor(const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_);
|
||||
|
||||
Transform3f trafo;
|
||||
Vec3f source;
|
||||
|
||||
bool uniform_scaling;
|
||||
Transform3f trafo_normal;
|
||||
float radius;
|
||||
float radius_sqr;
|
||||
Vec3f dir = Vec3f(0.f, 0.f, 0.f);
|
||||
|
||||
ClippingPlane clipping_plane; // Clipping plane to limit painting to not clipped facets only
|
||||
|
||||
friend TriangleSelector;
|
||||
};
|
||||
|
||||
class SinglePointCursor : public Cursor
|
||||
{
|
||||
public:
|
||||
SinglePointCursor() = delete;
|
||||
~SinglePointCursor() override = default;
|
||||
|
||||
bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const override;
|
||||
|
||||
static std::unique_ptr<Cursor> cursor_factory(const Vec3f ¢er, const Vec3f &camera_pos, const float cursor_radius, const CursorType cursor_type, const Transform3d &trafo_matrix, const ClippingPlane &clipping_plane)
|
||||
{
|
||||
assert(cursor_type == TriangleSelector::CursorType::CIRCLE || cursor_type == TriangleSelector::CursorType::SPHERE);
|
||||
if (cursor_type == TriangleSelector::CursorType::SPHERE)
|
||||
return std::make_unique<TriangleSelector::Sphere>(center, camera_pos, cursor_radius, trafo_matrix, clipping_plane);
|
||||
else
|
||||
return std::make_unique<TriangleSelector::Circle>(center, camera_pos, cursor_radius, trafo_matrix, clipping_plane);
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit SinglePointCursor(const Vec3f ¢er_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_);
|
||||
|
||||
Vec3f center;
|
||||
};
|
||||
|
||||
class DoublePointCursor : public Cursor
|
||||
{
|
||||
public:
|
||||
DoublePointCursor() = delete;
|
||||
~DoublePointCursor() override = default;
|
||||
|
||||
bool is_pointer_in_triangle(const Vec3f &p1, const Vec3f &p2, const Vec3f &p3) const override;
|
||||
|
||||
static std::unique_ptr<Cursor> cursor_factory(const Vec3f &first_center, const Vec3f &second_center, const Vec3f &camera_pos, const float cursor_radius, const CursorType cursor_type, const Transform3d &trafo_matrix, const ClippingPlane &clipping_plane)
|
||||
{
|
||||
assert(cursor_type == TriangleSelector::CursorType::CIRCLE || cursor_type == TriangleSelector::CursorType::SPHERE);
|
||||
if (cursor_type == TriangleSelector::CursorType::SPHERE)
|
||||
return std::make_unique<TriangleSelector::Capsule3D>(first_center, second_center, camera_pos, cursor_radius, trafo_matrix, clipping_plane);
|
||||
else
|
||||
return std::make_unique<TriangleSelector::Capsule2D>(first_center, second_center, camera_pos, cursor_radius, trafo_matrix, clipping_plane);
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit DoublePointCursor(const Vec3f &first_center_, const Vec3f &second_center_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_);
|
||||
|
||||
Vec3f first_center;
|
||||
Vec3f second_center;
|
||||
};
|
||||
|
||||
class Sphere : public SinglePointCursor
|
||||
{
|
||||
public:
|
||||
Sphere() = delete;
|
||||
explicit Sphere(const Vec3f ¢er_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: SinglePointCursor(center_, source_, radius_world, trafo_, clipping_plane_){};
|
||||
~Sphere() override = default;
|
||||
|
||||
bool is_mesh_point_inside(const Vec3f &point) const override;
|
||||
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override { return true; }
|
||||
};
|
||||
|
||||
class Circle : public SinglePointCursor
|
||||
{
|
||||
public:
|
||||
Circle() = delete;
|
||||
explicit Circle(const Vec3f ¢er_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: SinglePointCursor(center_, source_, radius_world, trafo_, clipping_plane_){};
|
||||
~Circle() override = default;
|
||||
|
||||
bool is_mesh_point_inside(const Vec3f &point) const override;
|
||||
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override
|
||||
{
|
||||
return TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals);
|
||||
}
|
||||
};
|
||||
|
||||
class Capsule3D : public DoublePointCursor
|
||||
{
|
||||
public:
|
||||
Capsule3D() = delete;
|
||||
explicit Capsule3D(const Vec3f &first_center_, const Vec3f &second_center_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: TriangleSelector::DoublePointCursor(first_center_, second_center_, source_, radius_world, trafo_, clipping_plane_)
|
||||
{}
|
||||
~Capsule3D() override = default;
|
||||
|
||||
bool is_mesh_point_inside(const Vec3f &point) const override;
|
||||
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override { return true; }
|
||||
};
|
||||
|
||||
class Capsule2D : public DoublePointCursor
|
||||
{
|
||||
public:
|
||||
Capsule2D() = delete;
|
||||
explicit Capsule2D(const Vec3f &first_center_, const Vec3f &second_center_, const Vec3f &source_, float radius_world, const Transform3d &trafo_, const ClippingPlane &clipping_plane_)
|
||||
: TriangleSelector::DoublePointCursor(first_center_, second_center_, source_, radius_world, trafo_, clipping_plane_)
|
||||
{}
|
||||
~Capsule2D() override = default;
|
||||
|
||||
bool is_mesh_point_inside(const Vec3f &point) const override;
|
||||
bool is_edge_inside_cursor(const Triangle &tr, const std::vector<Vertex> &vertices) const override;
|
||||
bool is_facet_visible(int facet_idx, const std::vector<Vec3f> &face_normals) const override
|
||||
{
|
||||
return TriangleSelector::Cursor::is_facet_visible(*this, facet_idx, face_normals);
|
||||
}
|
||||
};
|
||||
|
||||
std::pair<std::vector<Vec3i>, std::vector<Vec3i>> precompute_all_neighbors() const;
|
||||
void precompute_all_neighbors_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec3i> &neighbors_out, std::vector<Vec3i> &neighbors_normal_out) const;
|
||||
|
||||
@ -51,17 +196,12 @@ public:
|
||||
[[nodiscard]] int select_unsplit_triangle(const Vec3f &hit, int facet_idx, const Vec3i &neighbors) 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
|
||||
const Vec3f &source, // camera position (mesh coords)
|
||||
float radius, // radius of the cursor
|
||||
CursorType type, // current type of cursor
|
||||
EnforcerBlockerType new_state, // enforcer or blocker?
|
||||
const Transform3d &trafo, // matrix to get from mesh to world
|
||||
const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation
|
||||
bool triangle_splitting, // If triangles will be split base on the cursor or not
|
||||
const ClippingPlane &clp, // Clipping plane to limit painting to not clipped facets only
|
||||
float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees.
|
||||
void select_patch(int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
|
||||
std::unique_ptr<Cursor> &&cursor, // Cursor containing information about the point where to start, camera position (mesh coords), matrix to get from mesh to world, and its shape and type.
|
||||
EnforcerBlockerType new_state, // enforcer or blocker?
|
||||
const Transform3d &trafo_no_translate, // matrix to get from mesh to world without translation
|
||||
bool triangle_splitting, // If triangles will be split base on the cursor or not
|
||||
float highlight_by_angle_deg = 0.f); // The maximal angle of overhang. If it is set to a non-zero value, it is possible to paint only the triangles of overhang defined by this angle in degrees.
|
||||
|
||||
void seed_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
|
||||
@ -195,44 +335,21 @@ protected:
|
||||
int m_orig_size_vertices = 0;
|
||||
int m_orig_size_indices = 0;
|
||||
|
||||
// Cache for cursor position, radius and direction.
|
||||
struct Cursor {
|
||||
Cursor() = default;
|
||||
Cursor(const Vec3f& center_, const Vec3f& source_, float radius_world,
|
||||
CursorType type_, const Transform3d& trafo_, const ClippingPlane &clipping_plane_);
|
||||
bool is_mesh_point_inside(const Vec3f &pt) const;
|
||||
bool is_pointer_in_triangle(const Vec3f& p1, const Vec3f& p2, const Vec3f& p3) const;
|
||||
|
||||
Vec3f center;
|
||||
Vec3f source;
|
||||
Vec3f dir;
|
||||
float radius_sqr;
|
||||
CursorType type;
|
||||
Transform3f trafo;
|
||||
Transform3f trafo_normal;
|
||||
bool uniform_scaling;
|
||||
ClippingPlane clipping_plane;
|
||||
};
|
||||
|
||||
Cursor m_cursor;
|
||||
std::unique_ptr<Cursor> m_cursor;
|
||||
float m_old_cursor_radius_sqr;
|
||||
|
||||
// Private functions:
|
||||
private:
|
||||
bool select_triangle(int facet_idx, EnforcerBlockerType type, bool triangle_splitting);
|
||||
bool select_triangle_recursive(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType type, bool triangle_splitting);
|
||||
int vertices_inside(int facet_idx) const;
|
||||
bool faces_camera(int facet) const;
|
||||
void undivide_triangle(int facet_idx);
|
||||
void split_triangle(int facet_idx, const Vec3i &neighbors);
|
||||
void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant.
|
||||
bool is_pointer_in_triangle(int facet_idx) const;
|
||||
bool is_edge_inside_cursor(int facet_idx) const;
|
||||
bool is_facet_clipped(int facet_idx, const ClippingPlane &clp) const;
|
||||
int push_triangle(int a, int b, int c, int source_triangle, 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;
|
||||
Vec3i child_neighbors_propagated(const Triangle &tr, const Vec3i &neighbors_propagated, int child_idx, const Vec3i &child_neighbors) 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 {
|
||||
|
@ -72,15 +72,10 @@ namespace Slic3r {
|
||||
#if ENABLE_SMOOTH_NORMALS
|
||||
static void smooth_normals_corner(TriangleMesh& mesh, std::vector<stl_normal>& normals)
|
||||
{
|
||||
mesh.repair();
|
||||
|
||||
using MapMatrixXfUnaligned = Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
|
||||
using MapMatrixXiUnaligned = Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
|
||||
|
||||
std::vector<stl_normal> face_normals(mesh.stl.stats.number_of_facets);
|
||||
for (uint32_t i = 0; i < mesh.stl.stats.number_of_facets; ++i) {
|
||||
face_normals[i] = mesh.stl.facet_start[i].normal;
|
||||
}
|
||||
std::vector<Vec3f> face_normals = its_face_normals(mesh.its);
|
||||
|
||||
Eigen::MatrixXd vertices = MapMatrixXfUnaligned(mesh.its.vertices.front().data(),
|
||||
Eigen::Index(mesh.its.vertices.size()), 3).cast<double>();
|
||||
@ -102,8 +97,6 @@ static void smooth_normals_corner(TriangleMesh& mesh, std::vector<stl_normal>& n
|
||||
|
||||
static void smooth_normals_vertex(TriangleMesh& mesh, std::vector<stl_normal>& normals)
|
||||
{
|
||||
mesh.repair();
|
||||
|
||||
using MapMatrixXfUnaligned = Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
|
||||
using MapMatrixXiUnaligned = Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>>;
|
||||
|
||||
|
@ -47,8 +47,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
||||
if (config->opt_float("layer_height") < EPSILON)
|
||||
{
|
||||
const wxString msg_text = _(L("Layer height is not valid.\n\nThe layer height will be reset to 0.01."));
|
||||
//wxMessageDialog dialog(nullptr, msg_text, _(L("Layer height")), wxICON_WARNING | wxOK);
|
||||
MessageDialog dialog(nullptr, msg_text, _(L("Layer height")), wxICON_WARNING | wxOK);
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, _(L("Layer height")), wxICON_WARNING | wxOK);
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
is_msg_dlg_already_exist = true;
|
||||
dialog.ShowModal();
|
||||
@ -60,8 +59,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
||||
if (config->option<ConfigOptionFloatOrPercent>("first_layer_height")->value < EPSILON)
|
||||
{
|
||||
const wxString msg_text = _(L("First layer height is not valid.\n\nThe first layer height will be reset to 0.01."));
|
||||
//wxMessageDialog dialog(nullptr, msg_text, _(L("First layer height")), wxICON_WARNING | wxOK);
|
||||
MessageDialog dialog(nullptr, msg_text, _(L("First layer height")), wxICON_WARNING | wxOK);
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, _(L("First layer height")), wxICON_WARNING | wxOK);
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
is_msg_dlg_already_exist = true;
|
||||
dialog.ShowModal();
|
||||
@ -90,8 +88,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
||||
"- Detect thin walls disabled"));
|
||||
if (is_global_config)
|
||||
msg_text += "\n\n" + _(L("Shall I adjust those settings in order to enable Spiral Vase?"));
|
||||
//wxMessageDialog dialog(nullptr, msg_text, _(L("Spiral Vase")),
|
||||
MessageDialog dialog(nullptr, msg_text, _(L("Spiral Vase")),
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, _(L("Spiral Vase")),
|
||||
wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK));
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
auto answer = dialog.ShowModal();
|
||||
@ -126,8 +123,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
||||
"(both support_material_extruder and support_material_interface_extruder need to be set to 0)."));
|
||||
if (is_global_config)
|
||||
msg_text += "\n\n" + _(L("Shall I adjust those settings in order to enable the Wipe Tower?"));
|
||||
//wxMessageDialog dialog (nullptr, msg_text, _(L("Wipe Tower")),
|
||||
MessageDialog dialog (nullptr, msg_text, _(L("Wipe Tower")),
|
||||
MessageDialog dialog (m_msg_dlg_parent, msg_text, _(L("Wipe Tower")),
|
||||
wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK));
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
auto answer = dialog.ShowModal();
|
||||
@ -147,8 +143,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
||||
"need to be synchronized with the object layers."));
|
||||
if (is_global_config)
|
||||
msg_text += "\n\n" + _(L("Shall I synchronize support layers in order to enable the Wipe Tower?"));
|
||||
//wxMessageDialog dialog(nullptr, msg_text, _(L("Wipe Tower")),
|
||||
MessageDialog dialog(nullptr, msg_text, _(L("Wipe Tower")),
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, _(L("Wipe Tower")),
|
||||
wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK));
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
auto answer = dialog.ShowModal();
|
||||
@ -169,7 +164,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
||||
"- Detect bridging perimeters"));
|
||||
if (is_global_config)
|
||||
msg_text += "\n\n" + _(L("Shall I adjust those settings for supports?"));
|
||||
MessageDialog dialog(nullptr, msg_text, _L("Support Generator"), wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK));
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, _L("Support Generator"), wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK));
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
auto answer = dialog.ShowModal();
|
||||
if (!is_global_config || answer == wxID_YES) {
|
||||
@ -200,8 +195,7 @@ void ConfigManipulation::update_print_fff_config(DynamicPrintConfig* config, con
|
||||
_(fill_pattern_def->enum_labels[it_pattern - fill_pattern_def->enum_values.begin()]));
|
||||
if (is_global_config)
|
||||
msg_text += "\n\n" + _L("Shall I switch to rectilinear fill pattern?");
|
||||
//wxMessageDialog dialog(nullptr, msg_text, _L("Infill"),
|
||||
MessageDialog dialog(nullptr, msg_text, _L("Infill"),
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, _L("Infill"),
|
||||
wxICON_WARNING | (is_global_config ? wxYES | wxNO : wxOK) );
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
auto answer = dialog.ShowModal();
|
||||
@ -331,8 +325,7 @@ void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, con
|
||||
if (head_penetration > head_width) {
|
||||
wxString msg_text = _(L("Head penetration should not be greater than the head width."));
|
||||
|
||||
//wxMessageDialog dialog(nullptr, msg_text, _(L("Invalid Head penetration")), wxICON_WARNING | wxOK);
|
||||
MessageDialog dialog(nullptr, msg_text, _(L("Invalid Head penetration")), wxICON_WARNING | wxOK);
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, _(L("Invalid Head penetration")), wxICON_WARNING | wxOK);
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
if (dialog.ShowModal() == wxID_OK) {
|
||||
new_conf.set_key_value("support_head_penetration", new ConfigOptionFloat(head_width));
|
||||
@ -345,8 +338,7 @@ void ConfigManipulation::update_print_sla_config(DynamicPrintConfig* config, con
|
||||
if (pinhead_d > pillar_d) {
|
||||
wxString msg_text = _(L("Pinhead diameter should be smaller than the pillar diameter."));
|
||||
|
||||
//wxMessageDialog dialog(nullptr, msg_text, _(L("Invalid pinhead diameter")), wxICON_WARNING | wxOK);
|
||||
MessageDialog dialog(nullptr, msg_text, _(L("Invalid pinhead diameter")), wxICON_WARNING | wxOK);
|
||||
MessageDialog dialog(m_msg_dlg_parent, msg_text, _(L("Invalid pinhead diameter")), wxICON_WARNING | wxOK);
|
||||
|
||||
DynamicPrintConfig new_conf = *config;
|
||||
if (dialog.ShowModal() == wxID_OK) {
|
||||
|
@ -30,15 +30,18 @@ class ConfigManipulation
|
||||
// callback to propagation of changed value, if needed
|
||||
std::function<void(const std::string&, const boost::any&)> cb_value_change = nullptr;
|
||||
ModelConfig* local_config = nullptr;
|
||||
wxWindow* m_msg_dlg_parent {nullptr};
|
||||
|
||||
public:
|
||||
ConfigManipulation(std::function<void()> load_config,
|
||||
std::function<void(const std::string&, bool toggle, int opt_index)> cb_toggle_field,
|
||||
std::function<void(const std::string&, const boost::any&)> cb_value_change,
|
||||
ModelConfig* local_config = nullptr) :
|
||||
ModelConfig* local_config = nullptr,
|
||||
wxWindow* msg_dlg_parent = nullptr) :
|
||||
load_config(load_config),
|
||||
cb_toggle_field(cb_toggle_field),
|
||||
cb_value_change(cb_value_change),
|
||||
m_msg_dlg_parent(msg_dlg_parent),
|
||||
local_config(local_config) {}
|
||||
ConfigManipulation() {}
|
||||
|
||||
|
@ -161,7 +161,7 @@ ExtruderSequenceDialog::ExtruderSequenceDialog(const DoubleSlider::ExtrudersSequ
|
||||
intervals_box_sizer->Add(m_intervals_grid_sizer, 0, wxLEFT, em);
|
||||
option_sizer->Add(intervals_box_sizer, 0, wxEXPAND);
|
||||
|
||||
m_random_sequence = new wxCheckBox(this, wxID_ANY, "Random sequence");
|
||||
m_random_sequence = new wxCheckBox(this, wxID_ANY, _L("Random sequence"));
|
||||
m_random_sequence->SetValue(m_sequence.random_sequence);
|
||||
m_random_sequence->SetToolTip(_L("If enabled, random sequence of the selected extruders will be used."));
|
||||
m_random_sequence->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& e) {
|
||||
@ -169,7 +169,7 @@ ExtruderSequenceDialog::ExtruderSequenceDialog(const DoubleSlider::ExtrudersSequ
|
||||
m_color_repetition->Enable(m_sequence.random_sequence);
|
||||
});
|
||||
|
||||
m_color_repetition = new wxCheckBox(this, wxID_ANY, "Allow next color repetition");
|
||||
m_color_repetition = new wxCheckBox(this, wxID_ANY, _L("Allow next color repetition"));
|
||||
m_color_repetition->SetValue(m_sequence.color_repetition);
|
||||
m_color_repetition->SetToolTip(_L("If enabled, a repetition of the next random color will be allowed."));
|
||||
m_color_repetition->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent& e) {m_sequence.color_repetition = e.IsChecked(); });
|
||||
|
@ -291,6 +291,16 @@ void Field::get_value_by_opt_type(wxString& str, const bool check_value/* = true
|
||||
case coString:
|
||||
case coStrings:
|
||||
case coFloatOrPercent: {
|
||||
if (m_opt.type == coFloatOrPercent && m_opt.opt_key == "first_layer_height" && !str.IsEmpty() && str.Last() == '%') {
|
||||
// Workaroud to avoid of using of the % for first layer height
|
||||
// see https://github.com/prusa3d/PrusaSlicer/issues/7418
|
||||
wxString label = m_opt.full_label.empty() ? _(m_opt.label) : _(m_opt.full_label);
|
||||
show_error(m_parent, from_u8((boost::format(_utf8(L("%s doesn't support percentage"))) % label).str()));
|
||||
const wxString stVal = double_to_string(0.01, 2);
|
||||
set_value(stVal, true);
|
||||
m_value = into_u8(stVal);;
|
||||
break;
|
||||
}
|
||||
if (m_opt.type == coFloatOrPercent && !str.IsEmpty() && str.Last() != '%')
|
||||
{
|
||||
double val = 0.;
|
||||
|
@ -977,8 +977,7 @@ void GCodeViewer::export_toolpaths_to_obj(const char* filename) const
|
||||
for (const RenderPath& path : t_buffer.render_paths) {
|
||||
colors.push_back(path.color);
|
||||
}
|
||||
std::sort(colors.begin(), colors.end());
|
||||
colors.erase(std::unique(colors.begin(), colors.end()), colors.end());
|
||||
sort_remove_duplicates(colors);
|
||||
|
||||
// save materials file
|
||||
boost::filesystem::path mat_filename(filename);
|
||||
@ -1447,7 +1446,7 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
static const unsigned int progress_threshold = 1000;
|
||||
wxProgressDialog* progress_dialog = wxGetApp().is_gcode_viewer() ?
|
||||
new wxProgressDialog(_L("Generating toolpaths"), "...",
|
||||
100, wxGetApp().plater(), wxPD_AUTO_HIDE | wxPD_APP_MODAL) : nullptr;
|
||||
100, wxGetApp().mainframe, wxPD_AUTO_HIDE | wxPD_APP_MODAL) : nullptr;
|
||||
|
||||
wxBusyCursor busy;
|
||||
|
||||
@ -2020,13 +2019,11 @@ void GCodeViewer::load_toolpaths(const GCodeProcessorResult& gcode_result)
|
||||
}
|
||||
|
||||
// roles -> remove duplicates
|
||||
std::sort(m_roles.begin(), m_roles.end());
|
||||
m_roles.erase(std::unique(m_roles.begin(), m_roles.end()), m_roles.end());
|
||||
sort_remove_duplicates(m_roles);
|
||||
m_roles.shrink_to_fit();
|
||||
|
||||
// extruder ids -> remove duplicates
|
||||
std::sort(m_extruder_ids.begin(), m_extruder_ids.end());
|
||||
m_extruder_ids.erase(std::unique(m_extruder_ids.begin(), m_extruder_ids.end()), m_extruder_ids.end());
|
||||
sort_remove_duplicates(m_extruder_ids);
|
||||
m_extruder_ids.shrink_to_fit();
|
||||
|
||||
// set layers z range
|
||||
@ -2374,8 +2371,10 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
||||
}
|
||||
|
||||
RenderPath key{ tbuffer_id, color, static_cast<unsigned int>(ibuffer_id), path_id };
|
||||
if (render_path == nullptr || !RenderPathPropertyEqual()(*render_path, key))
|
||||
render_path = const_cast<RenderPath*>(&(*buffer.render_paths.emplace(key).first));
|
||||
if (render_path == nullptr || !RenderPathPropertyEqual()(*render_path, key)) {
|
||||
buffer.render_paths.emplace_back(key);
|
||||
render_path = const_cast<RenderPath*>(&buffer.render_paths.back());
|
||||
}
|
||||
|
||||
unsigned int delta_1st = 0;
|
||||
if (sub_path.first.s_id < m_sequential_view.current.first && m_sequential_view.current.first <= sub_path.last.s_id)
|
||||
@ -2433,6 +2432,14 @@ void GCodeViewer::refresh_render_paths(bool keep_sequential_current_first, bool
|
||||
#endif
|
||||
}
|
||||
|
||||
// Removes empty render paths and sort.
|
||||
for (size_t b = 0; b < m_buffers.size(); ++b) {
|
||||
TBuffer* buffer = const_cast<TBuffer*>(&m_buffers[b]);
|
||||
buffer->render_paths.erase(std::remove_if(buffer->render_paths.begin(), buffer->render_paths.end(),
|
||||
[](const auto &path){ return path.sizes.empty() || path.offsets.empty(); }),
|
||||
buffer->render_paths.end());
|
||||
}
|
||||
|
||||
// second pass: for buffers using instanced and batched models, update the instances render ranges
|
||||
for (size_t b = 0; b < m_buffers.size(); ++b) {
|
||||
TBuffer& buffer = const_cast<TBuffer&>(m_buffers[b]);
|
||||
@ -2624,12 +2631,7 @@ void GCodeViewer::render_toolpaths()
|
||||
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;
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
auto render_as_points = [this, zoom, point_size, near_plane_height]
|
||||
#else
|
||||
auto render_as_points = [zoom, point_size, near_plane_height]
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
(const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) {
|
||||
auto shader_init_as_points = [zoom, point_size, near_plane_height](GLShaderProgram& shader) {
|
||||
#if ENABLE_FIXED_SCREEN_SIZE_POINT_MARKERS
|
||||
shader.set_uniform("use_fixed_screen_size", 1);
|
||||
#else
|
||||
@ -2640,65 +2642,67 @@ void GCodeViewer::render_toolpaths()
|
||||
shader.set_uniform("percent_center_radius", 0.33f);
|
||||
shader.set_uniform("point_size", point_size);
|
||||
shader.set_uniform("near_plane_height", near_plane_height);
|
||||
};
|
||||
|
||||
auto render_as_points = [
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
this
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
](std::vector<RenderPath>::iterator it_path, std::vector<RenderPath>::iterator it_end, GLShaderProgram& shader, int uniform_color) {
|
||||
glsafe(::glEnable(GL_VERTEX_PROGRAM_POINT_SIZE));
|
||||
glsafe(::glEnable(GL_POINT_SPRITE));
|
||||
|
||||
for (const RenderPath& path : buffer.render_paths) {
|
||||
if (path.ibuffer_id == ibuffer_id) {
|
||||
shader.set_uniform("uniform_color", path.color);
|
||||
glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size()));
|
||||
for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) {
|
||||
const RenderPath& path = *it;
|
||||
glsafe(::glUniform4fv(uniform_color, 1, static_cast<const GLfloat*>(path.color.data())));
|
||||
glsafe(::glMultiDrawElements(GL_POINTS, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size()));
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
++m_statistics.gl_multi_points_calls_count;
|
||||
++m_statistics.gl_multi_points_calls_count;
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
}
|
||||
}
|
||||
|
||||
glsafe(::glDisable(GL_POINT_SPRITE));
|
||||
glsafe(::glDisable(GL_VERTEX_PROGRAM_POINT_SIZE));
|
||||
};
|
||||
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
auto render_as_lines = [this, light_intensity]
|
||||
#else
|
||||
auto render_as_lines = [light_intensity]
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
(const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) {
|
||||
auto shader_init_as_lines = [light_intensity](GLShaderProgram &shader) {
|
||||
shader.set_uniform("light_intensity", light_intensity);
|
||||
for (const RenderPath& path : buffer.render_paths) {
|
||||
if (path.ibuffer_id == ibuffer_id) {
|
||||
shader.set_uniform("uniform_color", path.color);
|
||||
glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size()));
|
||||
};
|
||||
auto render_as_lines = [
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
++m_statistics.gl_multi_lines_calls_count;
|
||||
this
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
](std::vector<RenderPath>::iterator it_path, std::vector<RenderPath>::iterator it_end, GLShaderProgram& shader, int uniform_color) {
|
||||
for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) {
|
||||
const RenderPath& path = *it;
|
||||
glsafe(::glUniform4fv(uniform_color, 1, static_cast<const GLfloat*>(path.color.data())));
|
||||
glsafe(::glMultiDrawElements(GL_LINES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size()));
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
++m_statistics.gl_multi_lines_calls_count;
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto render_as_triangles = [
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
auto render_as_triangles = [this]
|
||||
#else
|
||||
auto render_as_triangles = []
|
||||
this
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
(const TBuffer& buffer, unsigned int ibuffer_id, GLShaderProgram& shader) {
|
||||
for (const RenderPath& path : buffer.render_paths) {
|
||||
if (path.ibuffer_id == ibuffer_id) {
|
||||
shader.set_uniform("uniform_color", path.color);
|
||||
glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size()));
|
||||
](std::vector<RenderPath>::iterator it_path, std::vector<RenderPath>::iterator it_end, GLShaderProgram& shader, int uniform_color) {
|
||||
for (auto it = it_path; it != it_end && it_path->ibuffer_id == it->ibuffer_id; ++it) {
|
||||
const RenderPath& path = *it;
|
||||
glsafe(::glUniform4fv(uniform_color, 1, static_cast<const GLfloat*>(path.color.data())));
|
||||
glsafe(::glMultiDrawElements(GL_TRIANGLES, (const GLsizei*)path.sizes.data(), GL_UNSIGNED_SHORT, (const void* const*)path.offsets.data(), (GLsizei)path.sizes.size()));
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
++m_statistics.gl_multi_triangles_calls_count;
|
||||
++m_statistics.gl_multi_triangles_calls_count;
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto render_as_instanced_model = [
|
||||
#if ENABLE_GCODE_VIEWER_STATISTICS
|
||||
auto render_as_instanced_model = [this]
|
||||
#else
|
||||
auto render_as_instanced_model = []
|
||||
this
|
||||
#endif // ENABLE_GCODE_VIEWER_STATISTICS
|
||||
(TBuffer& buffer, GLShaderProgram & shader) {
|
||||
](TBuffer& buffer, GLShaderProgram & shader) {
|
||||
for (auto& range : buffer.model.instances.render_ranges.ranges) {
|
||||
if (range.vbo == 0 && range.count > 0) {
|
||||
glsafe(::glGenBuffers(1, &range.vbo));
|
||||
@ -2803,8 +2807,20 @@ void GCodeViewer::render_toolpaths()
|
||||
shader->set_uniform("emission_factor", 0.0f);
|
||||
}
|
||||
else {
|
||||
for (size_t j = 0; j < buffer.indices.size(); ++j) {
|
||||
const IBuffer& i_buffer = buffer.indices[j];
|
||||
switch (buffer.render_primitive_type) {
|
||||
case TBuffer::ERenderPrimitiveType::Point: shader_init_as_points(*shader); break;
|
||||
case TBuffer::ERenderPrimitiveType::Line: shader_init_as_lines(*shader); break;
|
||||
default: break;
|
||||
}
|
||||
int uniform_color = shader->get_uniform_location("uniform_color");
|
||||
auto it_path = buffer.render_paths.begin();
|
||||
for (unsigned int ibuffer_id = 0; ibuffer_id < static_cast<unsigned int>(buffer.indices.size()); ++ibuffer_id) {
|
||||
const IBuffer& i_buffer = buffer.indices[ibuffer_id];
|
||||
// Skip all paths with ibuffer_id < ibuffer_id.
|
||||
for (; it_path != buffer.render_paths.end() && it_path->ibuffer_id < ibuffer_id; ++ it_path) ;
|
||||
if (it_path == buffer.render_paths.end() || it_path->ibuffer_id > ibuffer_id)
|
||||
// Not found. This shall not happen.
|
||||
continue;
|
||||
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, i_buffer.vbo));
|
||||
glsafe(::glVertexPointer(buffer.vertices.position_size_floats(), GL_FLOAT, buffer.vertices.vertex_size_bytes(), (const void*)buffer.vertices.position_offset_bytes()));
|
||||
@ -2817,19 +2833,20 @@ void GCodeViewer::render_toolpaths()
|
||||
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, i_buffer.ibo));
|
||||
|
||||
// Render all elements with it_path->ibuffer_id == ibuffer_id, possible with varying colors.
|
||||
switch (buffer.render_primitive_type)
|
||||
{
|
||||
case TBuffer::ERenderPrimitiveType::Point: {
|
||||
render_as_points(buffer, static_cast<unsigned int>(j), *shader);
|
||||
render_as_points(it_path, buffer.render_paths.end(), *shader, uniform_color);
|
||||
break;
|
||||
}
|
||||
case TBuffer::ERenderPrimitiveType::Line: {
|
||||
glsafe(::glLineWidth(static_cast<GLfloat>(line_width(zoom))));
|
||||
render_as_lines(buffer, static_cast<unsigned int>(j), *shader);
|
||||
render_as_lines(it_path, buffer.render_paths.end(), *shader, uniform_color);
|
||||
break;
|
||||
}
|
||||
case TBuffer::ERenderPrimitiveType::Triangle: {
|
||||
render_as_triangles(buffer, static_cast<unsigned int>(j), *shader);
|
||||
render_as_triangles(it_path, buffer.render_paths.end(), *shader, uniform_color);
|
||||
break;
|
||||
}
|
||||
default: { break; }
|
||||
|
@ -259,14 +259,6 @@ class GCodeViewer
|
||||
return false;
|
||||
}
|
||||
};
|
||||
// // for unordered_set implementation of render_paths
|
||||
// struct RenderPathPropertyHash {
|
||||
// size_t operator() (const RenderPath &p) const {
|
||||
// // Convert the RGB value to an integer hash.
|
||||
//// return (size_t(int(p.color[0] * 255) + 255 * int(p.color[1] * 255) + (255 * 255) * int(p.color[2] * 255)) * 7919) ^ size_t(p.ibuffer_id);
|
||||
// return size_t(int(p.color[0] * 255) + 255 * int(p.color[1] * 255) + (255 * 255) * int(p.color[2] * 255)) ^ size_t(p.ibuffer_id);
|
||||
// }
|
||||
// };
|
||||
struct RenderPathPropertyLower {
|
||||
bool operator() (const RenderPath &l, const RenderPath &r) const {
|
||||
if (l.tbuffer_id < r.tbuffer_id)
|
||||
@ -319,9 +311,7 @@ class GCodeViewer
|
||||
|
||||
std::string shader;
|
||||
std::vector<Path> paths;
|
||||
// std::set seems to perform significantly better, at least on Windows.
|
||||
// std::unordered_set<RenderPath, RenderPathPropertyHash, RenderPathPropertyEqual> render_paths;
|
||||
std::set<RenderPath, RenderPathPropertyLower> render_paths;
|
||||
std::vector<RenderPath> render_paths;
|
||||
bool visible{ false };
|
||||
|
||||
void reset();
|
||||
|
@ -2761,7 +2761,8 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
|
||||
// For some reason the Idle event is not being generated after the mouse scroll event in case of scrolling with the two fingers on the touch pad,
|
||||
// if the event is not allowed to be passed further.
|
||||
// https://github.com/prusa3d/PrusaSlicer/issues/2750
|
||||
evt.Skip();
|
||||
// evt.Skip() used to trigger the needed screen refresh, but it does no more. wxWakeUpIdle() seem to work now.
|
||||
wxWakeUpIdle();
|
||||
#endif /* __WXMSW__ */
|
||||
|
||||
// Performs layers editing updates, if enabled
|
||||
|
@ -358,12 +358,38 @@ bool GLShaderProgram::set_uniform(const char* name, const Vec3d& value) const
|
||||
|
||||
int GLShaderProgram::get_attrib_location(const char* name) const
|
||||
{
|
||||
return (m_id > 0) ? ::glGetAttribLocation(m_id, name) : -1;
|
||||
assert(m_id > 0);
|
||||
|
||||
if (m_id <= 0)
|
||||
// Shader program not loaded. This should not happen.
|
||||
return -1;
|
||||
|
||||
auto it = std::find_if(m_attrib_location_cache.begin(), m_attrib_location_cache.end(), [name](const auto& p) { return p.first == name; });
|
||||
if (it != m_attrib_location_cache.end())
|
||||
// Attrib ID cached.
|
||||
return it->second;
|
||||
|
||||
int id = ::glGetAttribLocation(m_id, name);
|
||||
const_cast<GLShaderProgram*>(this)->m_attrib_location_cache.push_back({ name, id });
|
||||
return id;
|
||||
}
|
||||
|
||||
int GLShaderProgram::get_uniform_location(const char* name) const
|
||||
{
|
||||
return (m_id > 0) ? ::glGetUniformLocation(m_id, name) : -1;
|
||||
assert(m_id > 0);
|
||||
|
||||
if (m_id <= 0)
|
||||
// Shader program not loaded. This should not happen.
|
||||
return -1;
|
||||
|
||||
auto it = std::find_if(m_uniform_location_cache.begin(), m_uniform_location_cache.end(), [name](const auto &p) { return p.first == name; });
|
||||
if (it != m_uniform_location_cache.end())
|
||||
// Uniform ID cached.
|
||||
return it->second;
|
||||
|
||||
int id = ::glGetUniformLocation(m_id, name);
|
||||
const_cast<GLShaderProgram*>(this)->m_uniform_location_cache.push_back({ name, id });
|
||||
return id;
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
@ -29,6 +29,8 @@ public:
|
||||
private:
|
||||
std::string m_name;
|
||||
unsigned int m_id{ 0 };
|
||||
std::vector<std::pair<std::string, int>> m_attrib_location_cache;
|
||||
std::vector<std::pair<std::string, int>> m_uniform_location_cache;
|
||||
|
||||
public:
|
||||
~GLShaderProgram();
|
||||
|
@ -779,8 +779,8 @@ void GUI_App::post_init()
|
||||
show_send_system_info_dialog_if_needed();
|
||||
}
|
||||
#ifdef _WIN32
|
||||
// Run external updater on Windows.
|
||||
if (! run_updater_win())
|
||||
// Run external updater on Windows if version check is enabled.
|
||||
if (this->preset_updater->version_check_enabled() && ! run_updater_win())
|
||||
// "prusaslicer-updater.exe" was not started, run our own update check.
|
||||
#endif // _WIN32
|
||||
this->preset_updater->slic3r_update_notify();
|
||||
@ -883,6 +883,8 @@ void GUI_App::init_app_config()
|
||||
dir = wxFileName::GetHomeDir() + wxS("/.config");
|
||||
set_data_dir((dir + "/" + GetAppName()).ToUTF8().data());
|
||||
#endif
|
||||
} else {
|
||||
m_datadir_redefined = true;
|
||||
}
|
||||
|
||||
if (!app_config)
|
||||
@ -915,6 +917,10 @@ void GUI_App::init_app_config()
|
||||
// returns true if found newer version and user agreed to use it
|
||||
bool GUI_App::check_older_app_config(Semver current_version, bool backup)
|
||||
{
|
||||
// If the config folder is redefined - do not check
|
||||
if (m_datadir_redefined)
|
||||
return false;
|
||||
|
||||
// find other version app config (alpha / beta / release)
|
||||
std::string config_path = app_config->config_path();
|
||||
boost::filesystem::path parent_file_path(config_path);
|
||||
@ -1487,6 +1493,7 @@ void GUI_App::update_fonts(const MainFrame *main_frame)
|
||||
m_normal_font = main_frame->normal_font();
|
||||
m_small_font = m_normal_font;
|
||||
m_bold_font = main_frame->normal_font().Bold();
|
||||
m_link_font = m_bold_font.Underlined();
|
||||
m_em_unit = main_frame->em_unit();
|
||||
m_code_font.SetPointSize(m_normal_font.GetPointSize());
|
||||
}
|
||||
|
@ -132,6 +132,7 @@ private:
|
||||
wxFont m_bold_font;
|
||||
wxFont m_normal_font;
|
||||
wxFont m_code_font;
|
||||
wxFont m_link_font;
|
||||
|
||||
int m_em_unit; // width of a "m"-symbol in pixels for current system font
|
||||
// Note: for 100% Scale m_em_unit = 10 -> it's a good enough coefficient for a size setting of controls
|
||||
@ -217,6 +218,7 @@ public:
|
||||
const wxFont& bold_font() { return m_bold_font; }
|
||||
const wxFont& normal_font() { return m_normal_font; }
|
||||
const wxFont& code_font() { return m_code_font; }
|
||||
const wxFont& link_font() { return m_link_font; }
|
||||
int em_unit() const { return m_em_unit; }
|
||||
bool tabs_as_menu() const;
|
||||
wxSize get_min_size() const;
|
||||
@ -350,6 +352,7 @@ private:
|
||||
void check_updates(const bool verbose);
|
||||
|
||||
bool m_init_app_config_from_older { false };
|
||||
bool m_datadir_redefined { false };
|
||||
std::string m_older_data_dir_path;
|
||||
boost::optional<Semver> m_last_config_version;
|
||||
};
|
||||
|
@ -1446,7 +1446,7 @@ void ObjectList::load_part(ModelObject& model_object, std::vector<ModelVolume*>&
|
||||
else
|
||||
wxGetApp().import_model(parent, input_files);
|
||||
|
||||
wxProgressDialog dlg(_L("Loading") + dots, "", 100, wxGetApp().plater(), wxPD_AUTO_HIDE);
|
||||
wxProgressDialog dlg(_L("Loading") + dots, "", 100, wxGetApp().mainframe wxPD_AUTO_HIDE);
|
||||
wxBusyCursor busy;
|
||||
|
||||
for (size_t i = 0; i < input_files.size(); ++i) {
|
||||
@ -1506,7 +1506,7 @@ void ObjectList::load_modifier(ModelObject& model_object, std::vector<ModelVolum
|
||||
else
|
||||
wxGetApp().import_model(parent, input_files);
|
||||
|
||||
wxProgressDialog dlg(_L("Loading") + dots, "", 100, wxGetApp().plater(), wxPD_AUTO_HIDE);
|
||||
wxProgressDialog dlg(_L("Loading") + dots, "", 100, wxGetApp().mainframe, wxPD_AUTO_HIDE);
|
||||
wxBusyCursor busy;
|
||||
|
||||
const int obj_idx = get_selected_obj_idx();
|
||||
@ -4115,7 +4115,7 @@ void ObjectList::fix_through_netfabb()
|
||||
Plater::TakeSnapshot snapshot(plater, _L("Fix through NetFabb"));
|
||||
|
||||
// Open a progress dialog.
|
||||
wxProgressDialog progress_dlg(_L("Fixing through NetFabb"), "", 100, plater,
|
||||
wxProgressDialog progress_dlg(_L("Fixing through NetFabb"), "", 100, find_toplevel_parent(plater),
|
||||
wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
|
||||
int model_idx{ 0 };
|
||||
if (vol_idxs.empty()) {
|
||||
|
@ -110,12 +110,6 @@ bool ObjectSettings::update_settings_list()
|
||||
update_settings_list();
|
||||
m_parent->Layout();
|
||||
});
|
||||
|
||||
/* Check overriden options list after deleting.
|
||||
* Some options couldn't be deleted because of another one.
|
||||
* Like, we couldn't delete fill pattern, if fill density is set to 100%
|
||||
*/
|
||||
update_config_values(config);
|
||||
});
|
||||
return btn;
|
||||
};
|
||||
@ -227,11 +221,12 @@ void ObjectSettings::update_config_values(ModelConfig* config)
|
||||
update_config_values(config);
|
||||
|
||||
if (is_added) {
|
||||
wxTheApp->CallAfter([this]() {
|
||||
// #ysFIXME - Delete after testing! Very likely this CallAfret is no needed
|
||||
// wxTheApp->CallAfter([this]() {
|
||||
wxWindowUpdateLocker noUpdates(m_parent);
|
||||
update_settings_list();
|
||||
m_parent->Layout();
|
||||
});
|
||||
// });
|
||||
}
|
||||
};
|
||||
|
||||
@ -253,9 +248,9 @@ void ObjectSettings::update_config_values(ModelConfig* config)
|
||||
{
|
||||
const int obj_idx = objects_model->GetObjectIdByItem(item);
|
||||
assert(obj_idx >= 0);
|
||||
// for object's part first of all update konfiguration from object
|
||||
main_config.apply(wxGetApp().model().objects[obj_idx]->config.get(), true);
|
||||
printer_technology == ptFFF ? config_manipulation.update_print_fff_config(&main_config) :
|
||||
config_manipulation.update_print_sla_config(&main_config) ;
|
||||
// and then from its own config
|
||||
}
|
||||
|
||||
main_config.apply(config->get(), true);
|
||||
|
@ -13,7 +13,8 @@
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
namespace Slic3r::GUI {
|
||||
|
||||
@ -223,6 +224,126 @@ bool GLGizmoPainterBase::is_mesh_point_clipped(const Vec3d& point, const Transfo
|
||||
return m_c->object_clipper()->get_clipping_plane()->is_point_clipped(transformed_point);
|
||||
}
|
||||
|
||||
// Interpolate points between the previous and current mouse positions, which are then projected onto the object.
|
||||
// Returned projected mouse positions are grouped by mesh_idx. It may contain multiple std::vector<GLGizmoPainterBase::ProjectedMousePosition>
|
||||
// with the same mesh_idx, but all items in std::vector<GLGizmoPainterBase::ProjectedMousePosition> always have the same mesh_idx.
|
||||
std::vector<std::vector<GLGizmoPainterBase::ProjectedMousePosition>> GLGizmoPainterBase::get_projected_mouse_positions(const Vec2d &mouse_position, const double resolution, const std::vector<Transform3d> &trafo_matrices) const
|
||||
{
|
||||
// List of mouse positions that will be used as seeds for painting.
|
||||
std::vector<Vec2d> mouse_positions{mouse_position};
|
||||
if (m_last_mouse_click != Vec2d::Zero()) {
|
||||
// In case current mouse position is far from the last one,
|
||||
// add several positions from between into the list, so there
|
||||
// are no gaps in the painted region.
|
||||
if (size_t patches_in_between = size_t((mouse_position - m_last_mouse_click).norm() / resolution); patches_in_between > 0) {
|
||||
const Vec2d diff = (m_last_mouse_click - mouse_position) / (patches_in_between + 1);
|
||||
for (size_t patch_idx = 1; patch_idx <= patches_in_between; ++patch_idx)
|
||||
mouse_positions.emplace_back(mouse_position + patch_idx * diff);
|
||||
mouse_positions.emplace_back(m_last_mouse_click);
|
||||
}
|
||||
}
|
||||
|
||||
const Camera &camera = wxGetApp().plater()->get_camera();
|
||||
std::vector<ProjectedMousePosition> mesh_hit_points;
|
||||
mesh_hit_points.reserve(mouse_position.size());
|
||||
|
||||
// In mesh_hit_points only the last item could have mesh_id == -1, any other items mustn't.
|
||||
for (const Vec2d &mp : mouse_positions) {
|
||||
update_raycast_cache(mp, camera, trafo_matrices);
|
||||
mesh_hit_points.push_back({m_rr.hit, m_rr.mesh_id, m_rr.facet});
|
||||
if (m_rr.mesh_id == -1)
|
||||
break;
|
||||
}
|
||||
|
||||
// Divide mesh_hit_points into groups with the same mesh_idx. It may contain multiple groups with the same mesh_idx.
|
||||
std::vector<std::vector<ProjectedMousePosition>> mesh_hit_points_by_mesh;
|
||||
for (size_t prev_mesh_hit_point = 0, curr_mesh_hit_point = 0; curr_mesh_hit_point < mesh_hit_points.size(); ++curr_mesh_hit_point) {
|
||||
size_t next_mesh_hit_point = curr_mesh_hit_point + 1;
|
||||
if (next_mesh_hit_point >= mesh_hit_points.size() || mesh_hit_points[curr_mesh_hit_point].mesh_idx != mesh_hit_points[next_mesh_hit_point].mesh_idx) {
|
||||
mesh_hit_points_by_mesh.emplace_back();
|
||||
mesh_hit_points_by_mesh.back().insert(mesh_hit_points_by_mesh.back().end(), mesh_hit_points.begin() + int(prev_mesh_hit_point), mesh_hit_points.begin() + int(next_mesh_hit_point));
|
||||
prev_mesh_hit_point = next_mesh_hit_point;
|
||||
}
|
||||
}
|
||||
|
||||
auto on_same_facet = [](std::vector<ProjectedMousePosition> &hit_points) -> bool {
|
||||
for (const ProjectedMousePosition &mesh_hit_point : hit_points)
|
||||
if (mesh_hit_point.facet_idx != hit_points.front().facet_idx)
|
||||
return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
struct Plane
|
||||
{
|
||||
Vec3d origin;
|
||||
Vec3d first_axis;
|
||||
Vec3d second_axis;
|
||||
};
|
||||
auto find_plane = [](std::vector<ProjectedMousePosition> &hit_points) -> std::optional<Plane> {
|
||||
assert(hit_points.size() >= 3);
|
||||
for (size_t third_idx = 2; third_idx < hit_points.size(); ++third_idx) {
|
||||
const Vec3d &first_point = hit_points[third_idx - 2].mesh_hit.cast<double>();
|
||||
const Vec3d &second_point = hit_points[third_idx - 1].mesh_hit.cast<double>();
|
||||
const Vec3d &third_point = hit_points[third_idx].mesh_hit.cast<double>();
|
||||
|
||||
const Vec3d first_vec = first_point - second_point;
|
||||
const Vec3d second_vec = third_point - second_point;
|
||||
|
||||
// If three points aren't collinear, then there exists only one plane going through all points.
|
||||
if (first_vec.cross(second_vec).squaredNorm() > sqr(EPSILON)) {
|
||||
const Vec3d first_axis_vec_n = first_vec.normalized();
|
||||
// Make second_vec perpendicular to first_axis_vec_n using Gram–Schmidt orthogonalization process
|
||||
const Vec3d second_axis_vec_n = (second_vec - (first_vec.dot(second_vec) / first_vec.dot(first_vec)) * first_vec).normalized();
|
||||
return Plane{second_point, first_axis_vec_n, second_axis_vec_n};
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
};
|
||||
|
||||
for(std::vector<ProjectedMousePosition> &hit_points : mesh_hit_points_by_mesh) {
|
||||
assert(!hit_points.empty());
|
||||
if (hit_points.back().mesh_idx == -1)
|
||||
break;
|
||||
|
||||
if (hit_points.size() <= 2)
|
||||
continue;
|
||||
|
||||
if (on_same_facet(hit_points)) {
|
||||
hit_points = {hit_points.front(), hit_points.back()};
|
||||
} else if (std::optional<Plane> plane = find_plane(hit_points); plane) {
|
||||
Polyline polyline;
|
||||
polyline.points.reserve(hit_points.size());
|
||||
// Project hit_points into its plane to simplified them in the next step.
|
||||
for (auto &hit_point : hit_points) {
|
||||
const Vec3d &point = hit_point.mesh_hit.cast<double>();
|
||||
const double x_cord = plane->first_axis.dot(point - plane->origin);
|
||||
const double y_cord = plane->second_axis.dot(point - plane->origin);
|
||||
polyline.points.emplace_back(scale_(x_cord), scale_(y_cord));
|
||||
}
|
||||
|
||||
polyline.simplify(scale_(m_cursor_radius) / 10.);
|
||||
|
||||
const int mesh_idx = hit_points.front().mesh_idx;
|
||||
std::vector<ProjectedMousePosition> new_hit_points;
|
||||
new_hit_points.reserve(polyline.points.size());
|
||||
// Project 2D simplified hit_points beck to 3D.
|
||||
for (const Point &point : polyline.points) {
|
||||
const double x_cord = unscale<double>(point.x());
|
||||
const double y_cord = unscale<double>(point.y());
|
||||
const Vec3d new_hit_point = plane->origin + x_cord * plane->first_axis + y_cord * plane->second_axis;
|
||||
const int facet_idx = m_c->raycaster()->raycasters()[mesh_idx]->get_closest_facet(new_hit_point.cast<float>());
|
||||
new_hit_points.push_back({new_hit_point.cast<float>(), mesh_idx, size_t(facet_idx)});
|
||||
}
|
||||
|
||||
hit_points = new_hit_points;
|
||||
} else {
|
||||
hit_points = {hit_points.front(), hit_points.back()};
|
||||
}
|
||||
}
|
||||
|
||||
return mesh_hit_points_by_mesh;
|
||||
}
|
||||
|
||||
// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
|
||||
// The gizmo has an opportunity to react - if it does, it should return true so that the Canvas3D is
|
||||
@ -295,28 +416,6 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
const Transform3d instance_trafo = mi->get_transformation().get_matrix();
|
||||
const Transform3d instance_trafo_not_translate = mi->get_transformation().get_matrix(true);
|
||||
|
||||
// List of mouse positions that will be used as seeds for painting.
|
||||
std::vector<Vec2d> mouse_positions{mouse_position};
|
||||
|
||||
// In case current mouse position is far from the last one,
|
||||
// add several positions from between into the list, so there
|
||||
// are no gaps in the painted region.
|
||||
{
|
||||
if (m_last_mouse_click == Vec2d::Zero())
|
||||
m_last_mouse_click = mouse_position;
|
||||
// resolution describes minimal distance limit using circle radius
|
||||
// as a unit (e.g., 2 would mean the patches will be touching).
|
||||
double resolution = 0.7;
|
||||
double diameter_px = resolution * m_cursor_radius * camera.get_zoom();
|
||||
int patches_in_between = int(((mouse_position - m_last_mouse_click).norm() - diameter_px) / diameter_px);
|
||||
if (patches_in_between > 0) {
|
||||
Vec2d diff = (mouse_position - m_last_mouse_click)/(patches_in_between+1);
|
||||
for (int i=1; i<=patches_in_between; ++i)
|
||||
mouse_positions.emplace_back(m_last_mouse_click + i*diff);
|
||||
}
|
||||
}
|
||||
m_last_mouse_click = Vec2d::Zero(); // only actual hits should be saved
|
||||
|
||||
// Precalculate transformations of individual meshes.
|
||||
std::vector<Transform3d> trafo_matrices;
|
||||
std::vector<Transform3d> trafo_matrices_not_translate;
|
||||
@ -326,50 +425,70 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||
trafo_matrices_not_translate.emplace_back(instance_trafo_not_translate * mv->get_matrix(true));
|
||||
}
|
||||
|
||||
// Now "click" into all the prepared points and spill paint around them.
|
||||
for (const Vec2d& mp : mouse_positions) {
|
||||
update_raycast_cache(mp, camera, trafo_matrices);
|
||||
std::vector<std::vector<ProjectedMousePosition>> projected_mouse_positions_by_mesh = get_projected_mouse_positions(mouse_position, 1., trafo_matrices);
|
||||
m_last_mouse_click = Vec2d::Zero(); // only actual hits should be saved
|
||||
|
||||
bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None);
|
||||
for (const std::vector<ProjectedMousePosition> &projected_mouse_positions : projected_mouse_positions_by_mesh) {
|
||||
assert(!projected_mouse_positions.empty());
|
||||
const int mesh_idx = projected_mouse_positions.front().mesh_idx;
|
||||
const bool dragging_while_painting = (action == SLAGizmoEventType::Dragging && m_button_down != Button::None);
|
||||
|
||||
// The mouse button click detection is enabled when there is a valid hit.
|
||||
// Missing the object entirely
|
||||
// shall not capture the mouse.
|
||||
if (m_rr.mesh_id != -1) {
|
||||
if (mesh_idx != -1)
|
||||
if (m_button_down == Button::None)
|
||||
m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right);
|
||||
}
|
||||
|
||||
if (m_rr.mesh_id == -1) {
|
||||
// In case we have no valid hit, we can return. The event will be stopped when
|
||||
// dragging while painting (to prevent scene rotations and moving the object)
|
||||
// In case we have no valid hit, we can return. The event will be stopped when
|
||||
// dragging while painting (to prevent scene rotations and moving the object)
|
||||
if (mesh_idx == -1)
|
||||
return dragging_while_painting;
|
||||
}
|
||||
|
||||
const Transform3d &trafo_matrix = trafo_matrices[m_rr.mesh_id];
|
||||
const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[m_rr.mesh_id];
|
||||
const Transform3d &trafo_matrix = trafo_matrices[mesh_idx];
|
||||
const Transform3d &trafo_matrix_not_translate = trafo_matrices_not_translate[mesh_idx];
|
||||
|
||||
// Calculate direction from camera to the hit (in mesh coords):
|
||||
Vec3f camera_pos = (trafo_matrix.inverse() * camera.get_position()).cast<float>();
|
||||
|
||||
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
|
||||
assert(mesh_idx < int(m_triangle_selectors.size()));
|
||||
const TriangleSelector::ClippingPlane &clp = this->get_clipping_plane_in_volume_coordinates(trafo_matrix);
|
||||
if (m_tool_type == ToolType::SMART_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);
|
||||
if (m_tool_type == ToolType::SMART_FILL)
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), trafo_matrix_not_translate, clp, m_smart_fill_angle,
|
||||
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true);
|
||||
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), clp, false, true);
|
||||
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), clp, true, true);
|
||||
for(const ProjectedMousePosition &projected_mouse_position : projected_mouse_positions) {
|
||||
assert(projected_mouse_position.mesh_idx == mesh_idx);
|
||||
const Vec3f mesh_hit = projected_mouse_position.mesh_hit;
|
||||
const int facet_idx = int(projected_mouse_position.facet_idx);
|
||||
m_triangle_selectors[mesh_idx]->seed_fill_apply_on_triangles(new_state);
|
||||
if (m_tool_type == ToolType::SMART_FILL)
|
||||
m_triangle_selectors[mesh_idx]->seed_fill_select_triangles(mesh_hit, facet_idx, trafo_matrix_not_translate, clp, m_smart_fill_angle,
|
||||
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f, true);
|
||||
else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
|
||||
m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, false, true);
|
||||
else if (m_tool_type == ToolType::BUCKET_FILL)
|
||||
m_triangle_selectors[mesh_idx]->bucket_fill_select_triangles(mesh_hit, facet_idx, clp, true, true);
|
||||
|
||||
m_seed_fill_last_mesh_id = -1;
|
||||
} 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, trafo_matrix_not_translate, m_triangle_splitting_enabled, clp,
|
||||
m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
|
||||
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
|
||||
m_seed_fill_last_mesh_id = -1;
|
||||
}
|
||||
} else if (m_tool_type == ToolType::BRUSH) {
|
||||
assert(m_cursor_type == TriangleSelector::CursorType::CIRCLE || m_cursor_type == TriangleSelector::CursorType::SPHERE);
|
||||
|
||||
if (projected_mouse_positions.size() == 1) {
|
||||
const ProjectedMousePosition &first_position = projected_mouse_positions.front();
|
||||
std::unique_ptr<TriangleSelector::Cursor> cursor = TriangleSelector::SinglePointCursor::cursor_factory(first_position.mesh_hit,
|
||||
camera_pos, m_cursor_radius,
|
||||
m_cursor_type, trafo_matrix, clp);
|
||||
m_triangle_selectors[mesh_idx]->select_patch(int(first_position.facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate,
|
||||
m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
|
||||
} else {
|
||||
for (auto first_position_it = projected_mouse_positions.cbegin(); first_position_it != projected_mouse_positions.cend() - 1; ++first_position_it) {
|
||||
auto second_position_it = first_position_it + 1;
|
||||
std::unique_ptr<TriangleSelector::Cursor> cursor = TriangleSelector::DoublePointCursor::cursor_factory(first_position_it->mesh_hit, second_position_it->mesh_hit, camera_pos, m_cursor_radius, m_cursor_type, trafo_matrix, clp);
|
||||
m_triangle_selectors[mesh_idx]->select_patch(int(first_position_it->facet_idx), std::move(cursor), new_state, trafo_matrix_not_translate, m_triangle_splitting_enabled, m_paint_on_overhangs_only ? m_highlight_by_angle_threshold_deg : 0.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_triangle_selectors[mesh_idx]->request_update_render_data();
|
||||
m_last_mouse_click = mouse_position;
|
||||
}
|
||||
|
||||
|
@ -156,6 +156,13 @@ protected:
|
||||
SMART_FILL
|
||||
};
|
||||
|
||||
struct ProjectedMousePosition
|
||||
{
|
||||
Vec3f mesh_hit;
|
||||
int mesh_idx;
|
||||
size_t facet_idx;
|
||||
};
|
||||
|
||||
bool m_triangle_splitting_enabled = true;
|
||||
ToolType m_tool_type = ToolType::BRUSH;
|
||||
float m_smart_fill_angle = 30.f;
|
||||
@ -188,6 +195,8 @@ protected:
|
||||
TriangleSelector::ClippingPlane get_clipping_plane_in_volume_coordinates(const Transform3d &trafo) const;
|
||||
|
||||
private:
|
||||
std::vector<std::vector<ProjectedMousePosition>> get_projected_mouse_positions(const Vec2d &mouse_position, double resolution, const std::vector<Transform3d> &trafo_matrices) const;
|
||||
|
||||
bool is_mesh_point_clipped(const Vec3d& point, const Transform3d& trafo) const;
|
||||
void update_raycast_cache(const Vec2d& mouse_position,
|
||||
const Camera& camera,
|
||||
|
@ -319,8 +319,13 @@ static void add_tabs_as_menu(wxMenuBar* bar, MainFrame* main_frame, wxWindow* ba
|
||||
|
||||
bar_parent->Bind(wxEVT_MENU_OPEN, [main_frame, bar, is_mainframe_menu](wxMenuEvent& event) {
|
||||
wxMenu* const menu = event.GetMenu();
|
||||
if (!menu || menu->GetMenuItemCount() > 0)
|
||||
if (!menu || menu->GetMenuItemCount() > 0) {
|
||||
// If we are here it means that we open regular menu and not a tab used as a menu
|
||||
event.Skip(); // event.Skip() is verry important to next processing of the wxEVT_UPDATE_UI by this menu items.
|
||||
// If wxEVT_MENU_OPEN will not be pocessed in next event queue then MenuItems of this menu will never caught wxEVT_UPDATE_UI
|
||||
// and, as a result, "check/radio value" will not be updated
|
||||
return;
|
||||
}
|
||||
|
||||
// update tab selection
|
||||
|
||||
@ -1398,7 +1403,7 @@ void MainFrame::init_menubar_as_editor()
|
||||
if (!input_files.IsEmpty())
|
||||
m_plater->sidebar().obj_list()->load_shape_object_from_gallery(input_files);
|
||||
}
|
||||
}, "cog", nullptr, []() {return true; }, this);
|
||||
}, "shape_gallery", nullptr, []() {return true; }, this);
|
||||
|
||||
windowMenu->AppendSeparator();
|
||||
append_menu_item(windowMenu, wxID_ANY, _L("Print &Host Upload Queue") + "\tCtrl+J", _L("Display the Print Host Upload Queue window"),
|
||||
|
@ -202,7 +202,7 @@ public:
|
||||
SettingsDialog m_settings_dialog;
|
||||
DiffPresetDialog diff_dialog;
|
||||
wxWindow* m_plater_page{ nullptr };
|
||||
wxProgressDialog* m_progress_dialog { nullptr };
|
||||
// wxProgressDialog* m_progress_dialog { nullptr };
|
||||
PrintHostQueueDialog* m_printhost_queue_dlg;
|
||||
// std::shared_ptr<ProgressStatusBar> m_statusbar;
|
||||
|
||||
|
@ -304,7 +304,13 @@ Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const
|
||||
return closest_point.cast<float>();
|
||||
}
|
||||
|
||||
|
||||
int MeshRaycaster::get_closest_facet(const Vec3f &point) const
|
||||
{
|
||||
int facet_idx = 0;
|
||||
Vec3d closest_point;
|
||||
m_emesh.squared_distance(point.cast<double>(), facet_idx, closest_point);
|
||||
return facet_idx;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
@ -151,6 +151,9 @@ public:
|
||||
|
||||
Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const;
|
||||
|
||||
// Given a point in mesh coords, the method returns the closest facet from mesh.
|
||||
int get_closest_facet(const Vec3f &point) const;
|
||||
|
||||
Vec3f get_triangle_normal(size_t facet_idx) const;
|
||||
|
||||
private:
|
||||
|
@ -136,7 +136,8 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin
|
||||
int em = wxGetApp().em_unit();
|
||||
|
||||
// if message containes the table
|
||||
if (msg.Contains("<tr>")) {
|
||||
bool is_marked = msg.Contains("<tr>");
|
||||
if (is_marked) {
|
||||
int lines = msg.Freq('\n') + 1;
|
||||
int pos = 0;
|
||||
while (pos < (int)msg.Len() && pos != wxNOT_FOUND) {
|
||||
@ -154,7 +155,7 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin
|
||||
}
|
||||
html->SetMinSize(page_size);
|
||||
|
||||
std::string msg_escaped = xml_escape(msg.ToUTF8().data());
|
||||
std::string msg_escaped = xml_escape(msg.ToUTF8().data(), is_marked);
|
||||
boost::replace_all(msg_escaped, "\r\n", "<br>");
|
||||
boost::replace_all(msg_escaped, "\n", "<br>");
|
||||
if (monospaced_font)
|
||||
|
@ -33,36 +33,6 @@ wxDEFINE_EVENT(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED, EjectDriveNotificationClicke
|
||||
wxDEFINE_EVENT(EVT_EXPORT_GCODE_NOTIFICAION_CLICKED, ExportGcodeNotificationClickedEvent);
|
||||
wxDEFINE_EVENT(EVT_PRESET_UPDATE_AVAILABLE_CLICKED, PresetUpdateAvailableClickedEvent);
|
||||
|
||||
const NotificationManager::NotificationData NotificationManager::basic_notifications[] = {
|
||||
{NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotificationLevel, 10, _u8L("3D Mouse disconnected.") },
|
||||
{NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("Configuration update is available."), _u8L("See more."),
|
||||
[](wxEvtHandler* evnthndlr) {
|
||||
if (evnthndlr != nullptr)
|
||||
wxPostEvent(evnthndlr, PresetUpdateAvailableClickedEvent(EVT_PRESET_UPDATE_AVAILABLE_CLICKED));
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{NotificationType::EmptyColorChangeCode, NotificationLevel::PrintInfoNotificationLevel, 10,
|
||||
_u8L("You have just added a G-code for color change, but its value is empty.\n"
|
||||
"To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") },
|
||||
{NotificationType::EmptyAutoColorChange, NotificationLevel::PrintInfoNotificationLevel, 10,
|
||||
_u8L("No color change event was added to the print. The print does not look like a sign.") },
|
||||
{NotificationType::DesktopIntegrationSuccess, NotificationLevel::RegularNotificationLevel, 10,
|
||||
_u8L("Desktop integration was successful.") },
|
||||
{NotificationType::DesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10,
|
||||
_u8L("Desktop integration failed.") },
|
||||
{NotificationType::UndoDesktopIntegrationSuccess, NotificationLevel::RegularNotificationLevel, 10,
|
||||
_u8L("Undo desktop integration was successful.") },
|
||||
{NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10,
|
||||
_u8L("Undo desktop integration failed.") },
|
||||
{NotificationType::ExportOngoing, NotificationLevel::RegularNotificationLevel, 0, _u8L("Exporting.") },
|
||||
//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) {
|
||||
// wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }},
|
||||
//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") },
|
||||
//{NotificationType::LoadingFailed, NotificationLevel::RegularNotificationLevel, 20, _u8L("Loading of model has Failed") },
|
||||
//{NotificationType::DeviceEjected, NotificationLevel::RegularNotificationLevel, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification
|
||||
};
|
||||
|
||||
namespace {
|
||||
/* // not used?
|
||||
ImFont* add_default_font(float pixel_size)
|
||||
@ -393,8 +363,7 @@ void NotificationManager::PopNotification::render_text(ImGuiWrapper& imgui, cons
|
||||
std::string line;
|
||||
|
||||
for (size_t i = 0; i < (m_multiline ? m_endlines.size() : std::min(m_endlines.size(), (size_t)2)); i++) {
|
||||
if (m_endlines[i] > m_text1.size())
|
||||
break;
|
||||
assert(m_endlines.size() > i && m_text1.size() >= m_endlines[i]);
|
||||
line.clear();
|
||||
ImGui::SetCursorPosX(x_offset);
|
||||
ImGui::SetCursorPosY(starting_y + i * shift_y);
|
||||
@ -681,6 +650,7 @@ void NotificationManager::ExportFinishedNotification::render_text(ImGuiWrapper&
|
||||
float starting_y = m_line_height / 2;//10;
|
||||
float shift_y = m_line_height;// -m_line_height / 20;
|
||||
for (size_t i = 0; i < m_lines_count; i++) {
|
||||
assert(m_text1.size() >= m_endlines[i]);
|
||||
if (m_text1.size() >= m_endlines[i]) {
|
||||
std::string line = m_text1.substr(last_end, m_endlines[i] - last_end);
|
||||
last_end = m_endlines[i];
|
||||
@ -801,6 +771,7 @@ void NotificationManager::ProgressBarNotification::render_text(ImGuiWrapper& img
|
||||
// hypertext is not rendered at all. If it is needed, it needs to be added here.
|
||||
// m_endlines should have endline for each line and then for hypertext thus m_endlines[1] should always be in m_text1
|
||||
if (m_multiline) {
|
||||
assert(m_text1.size() >= m_endlines[0] || m_text1.size() >= m_endlines[1]);
|
||||
if(m_endlines[0] > m_text1.size() || m_endlines[1] > m_text1.size())
|
||||
return;
|
||||
// two lines text (what doesnt fit, wont show), one line bar
|
||||
@ -815,6 +786,7 @@ void NotificationManager::ProgressBarNotification::render_text(ImGuiWrapper& img
|
||||
render_cancel_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
render_bar(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
} else {
|
||||
assert(m_text1.size() >= m_endlines[0]);
|
||||
if (m_endlines[0] > m_text1.size())
|
||||
return;
|
||||
//one line text, one line bar
|
||||
|
@ -747,7 +747,37 @@ private:
|
||||
NotificationType::SimplifySuggestion
|
||||
};
|
||||
//prepared (basic) notifications
|
||||
static const NotificationData basic_notifications[];
|
||||
// non-static so its not loaded too early. If static, the translations wont load correctly.
|
||||
const std::vector<NotificationData> basic_notifications = {
|
||||
{NotificationType::Mouse3dDisconnected, NotificationLevel::RegularNotificationLevel, 10, _u8L("3D Mouse disconnected.") },
|
||||
{NotificationType::PresetUpdateAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("Configuration update is available."), _u8L("See more."),
|
||||
[](wxEvtHandler* evnthndlr) {
|
||||
if (evnthndlr != nullptr)
|
||||
wxPostEvent(evnthndlr, PresetUpdateAvailableClickedEvent(EVT_PRESET_UPDATE_AVAILABLE_CLICKED));
|
||||
return true;
|
||||
}
|
||||
},
|
||||
{NotificationType::EmptyColorChangeCode, NotificationLevel::PrintInfoNotificationLevel, 10,
|
||||
_u8L("You have just added a G-code for color change, but its value is empty.\n"
|
||||
"To export the G-code correctly, check the \"Color Change G-code\" in \"Printer Settings > Custom G-code\"") },
|
||||
{NotificationType::EmptyAutoColorChange, NotificationLevel::PrintInfoNotificationLevel, 10,
|
||||
_u8L("No color change event was added to the print. The print does not look like a sign.") },
|
||||
{NotificationType::DesktopIntegrationSuccess, NotificationLevel::RegularNotificationLevel, 10,
|
||||
_u8L("Desktop integration was successful.") },
|
||||
{NotificationType::DesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10,
|
||||
_u8L("Desktop integration failed.") },
|
||||
{NotificationType::UndoDesktopIntegrationSuccess, NotificationLevel::RegularNotificationLevel, 10,
|
||||
_u8L("Undo desktop integration was successful.") },
|
||||
{NotificationType::UndoDesktopIntegrationFail, NotificationLevel::WarningNotificationLevel, 10,
|
||||
_u8L("Undo desktop integration failed.") },
|
||||
{NotificationType::ExportOngoing, NotificationLevel::RegularNotificationLevel, 0, _u8L("Exporting.") },
|
||||
//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New version is available."), _u8L("See Releases page."), [](wxEvtHandler* evnthndlr) {
|
||||
// wxGetApp().open_browser_with_warning_dialog("https://github.com/prusa3d/PrusaSlicer/releases"); return true; }},
|
||||
//{NotificationType::NewAppAvailable, NotificationLevel::ImportantNotificationLevel, 20, _u8L("New vesion of PrusaSlicer is available.", _u8L("Download page.") },
|
||||
//{NotificationType::LoadingFailed, NotificationLevel::RegularNotificationLevel, 20, _u8L("Loading of model has Failed") },
|
||||
//{NotificationType::DeviceEjected, NotificationLevel::RegularNotificationLevel, 10, _u8L("Removable device has been safely ejected")} // if we want changeble text (like here name of device), we need to do it as CustomNotification
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
}//namespace GUI
|
||||
|
@ -28,17 +28,6 @@ static wxSize get_bitmap_size(const wxBitmap& bmp)
|
||||
#endif
|
||||
}
|
||||
|
||||
static wxString get_url(const wxString& path_end, bool get_default = false)
|
||||
{
|
||||
if (path_end.IsEmpty())
|
||||
return wxEmptyString;
|
||||
|
||||
wxString language = wxGetApp().app_config->get("translation_language");
|
||||
wxString lang_marker = language.IsEmpty() ? "en" : language.BeforeFirst('_');
|
||||
|
||||
return wxString("https://help.prusa3d.com/") + lang_marker + "/article/" + path_end;
|
||||
}
|
||||
|
||||
OG_CustomCtrl::OG_CustomCtrl( wxWindow* parent,
|
||||
OptionsGroup* og,
|
||||
const wxPoint& pos /* = wxDefaultPosition*/,
|
||||
@ -90,7 +79,6 @@ void OG_CustomCtrl::init_ctrl_lines()
|
||||
|
||||
// if we have a single option with no label, no sidetext just add it directly to sizer
|
||||
if (option_set.size() == 1 && opt_group->label_width == 0 && option_set.front().opt.full_width &&
|
||||
option_set.front().opt.label.empty() &&
|
||||
option_set.front().opt.sidetext.size() == 0 && option_set.front().side_widget == nullptr &&
|
||||
line.get_extra_widgets().size() == 0)
|
||||
{
|
||||
@ -168,7 +156,6 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/)
|
||||
// If we have a single option with no sidetext
|
||||
const std::vector<Option>& option_set = line.get_options();
|
||||
if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 &&
|
||||
option_set.front().opt.label.empty() &&
|
||||
option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0)
|
||||
{
|
||||
h_pos += 3 * blinking_button_width;
|
||||
@ -178,13 +165,14 @@ wxPoint OG_CustomCtrl::get_pos(const Line& line, Field* field_in/* = nullptr*/)
|
||||
break;
|
||||
}
|
||||
|
||||
bool is_multioption_line = option_set.size() > 1;
|
||||
for (auto opt : option_set) {
|
||||
Field* field = opt_group->get_field(opt.opt_id);
|
||||
correct_line_height(ctrl_line.height, field->getWindow());
|
||||
|
||||
ConfigOptionDef option = opt.opt;
|
||||
// add label if any
|
||||
if (!option.label.empty()) {
|
||||
if (is_multioption_line && !option.label.empty()) {
|
||||
//! To correct translation by context have to use wxGETTEXT_IN_CONTEXT macro from wxWidget 3.1.1
|
||||
label = (option.label == L_CONTEXT("Top", "Layers") || option.label == L_CONTEXT("Bottom", "Layers")) ?
|
||||
_CTX(option.label, "Layers") : _(option.label);
|
||||
@ -264,7 +252,7 @@ void OG_CustomCtrl::OnMotion(wxMouseEvent& event)
|
||||
line.is_focused = is_point_in_rect(pos, line.rect_label);
|
||||
if (line.is_focused) {
|
||||
if (!suppress_hyperlinks && !line.og_line.label_path.empty())
|
||||
tooltip = get_url(line.og_line.label_path) +"\n\n";
|
||||
tooltip = OptionsGroup::get_url(line.og_line.label_path) +"\n\n";
|
||||
tooltip += line.og_line.label_tooltip;
|
||||
break;
|
||||
}
|
||||
@ -577,7 +565,7 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos)
|
||||
bool is_url_string = false;
|
||||
if (ctrl->opt_group->label_width != 0 && !label.IsEmpty()) {
|
||||
const wxColour* text_clr = (option_set.size() == 1 && field ? field->label_color() : og_line.full_Label_color);
|
||||
is_url_string = !suppress_hyperlinks && !og_line.label_path.IsEmpty();
|
||||
is_url_string = !suppress_hyperlinks && !og_line.label_path.empty();
|
||||
h_pos = draw_text(dc, wxPoint(h_pos, v_pos), label + ":", text_clr, ctrl->opt_group->label_width * ctrl->m_em_unit, is_url_string);
|
||||
}
|
||||
|
||||
@ -592,7 +580,6 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos)
|
||||
|
||||
// If we have a single option with no sidetext just add it directly to the grid sizer
|
||||
if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 &&
|
||||
option_set.front().opt.label.empty() &&
|
||||
option_set.front().side_widget == nullptr && og_line.get_extra_widgets().size() == 0)
|
||||
{
|
||||
if (field && field->undo_to_sys_bitmap())
|
||||
@ -606,11 +593,12 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos)
|
||||
}
|
||||
|
||||
size_t bmp_rect_id = 0;
|
||||
bool is_multioption_line = option_set.size() > 1;
|
||||
for (const Option& opt : option_set) {
|
||||
field = ctrl->opt_group->get_field(opt.opt_id);
|
||||
ConfigOptionDef option = opt.opt;
|
||||
// add label if any
|
||||
if (!option.label.empty()) {
|
||||
if (is_multioption_line && !option.label.empty()) {
|
||||
//! To correct translation by context have to use wxGETTEXT_IN_CONTEXT macro from wxWidget 3.1.1
|
||||
label = (option.label == L_CONTEXT("Top", "Layers") || option.label == L_CONTEXT("Bottom", "Layers")) ?
|
||||
_CTX(option.label, "Layers") : _(option.label);
|
||||
@ -619,7 +607,7 @@ void OG_CustomCtrl::CtrlLine::render(wxDC& dc, wxCoord v_pos)
|
||||
if (is_url_string)
|
||||
is_url_string = false;
|
||||
else if(opt == option_set.front())
|
||||
is_url_string = !suppress_hyperlinks && !og_line.label_path.IsEmpty();
|
||||
is_url_string = !suppress_hyperlinks && !og_line.label_path.empty();
|
||||
h_pos = draw_text(dc, wxPoint(h_pos, v_pos), label, field ? field->label_color() : nullptr, ctrl->opt_group->sublabel_width * ctrl->m_em_unit, is_url_string);
|
||||
}
|
||||
|
||||
@ -766,36 +754,10 @@ wxCoord OG_CustomCtrl::CtrlLine::draw_act_bmps(wxDC& dc, wxPoint pos, const wxBi
|
||||
|
||||
bool OG_CustomCtrl::CtrlLine::launch_browser() const
|
||||
{
|
||||
if (!is_focused || og_line.label_path.IsEmpty())
|
||||
if (!is_focused || og_line.label_path.empty())
|
||||
return false;
|
||||
|
||||
bool launch = true;
|
||||
|
||||
if (get_app_config()->get("suppress_hyperlinks").empty()) {
|
||||
RichMessageDialog dialog(nullptr, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxYES_NO);
|
||||
dialog.ShowCheckBox(_L("Remember my choice"));
|
||||
int answer = dialog.ShowModal();
|
||||
|
||||
if (dialog.IsCheckBoxChecked()) {
|
||||
wxString preferences_item = _L("Suppress to open hyperlink in browser");
|
||||
wxString msg =
|
||||
_L("PrusaSlicer will remember your choice.") + "\n\n" +
|
||||
_L("You will not be asked about it again on label hovering.") + "\n\n" +
|
||||
format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item);
|
||||
|
||||
MessageDialog msg_dlg(nullptr, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
|
||||
if (msg_dlg.ShowModal() == wxID_CANCEL)
|
||||
return false;
|
||||
|
||||
get_app_config()->set("suppress_hyperlinks", dialog.IsCheckBoxChecked() ? (answer == wxID_NO ? "1" : "0") : "");
|
||||
}
|
||||
|
||||
launch = answer == wxID_YES;
|
||||
}
|
||||
if (launch)
|
||||
launch = get_app_config()->get("suppress_hyperlinks") != "1";
|
||||
|
||||
return launch && wxLaunchDefaultBrowser(get_url(og_line.label_path));
|
||||
return OptionsGroup::launch_browser(og_line.label_path);
|
||||
}
|
||||
|
||||
} // GUI
|
||||
|
@ -46,10 +46,10 @@ struct InfoItemAtributes {
|
||||
|
||||
const std::map<InfoItemType, InfoItemAtributes> INFO_ITEMS{
|
||||
// info_item Type info_item Name info_item BitmapName
|
||||
{ InfoItemType::CustomSupports, {L("Paint-on supports"), "fdm_supports" }, },
|
||||
{ InfoItemType::CustomSeam, {L("Paint-on seam"), "seam" }, },
|
||||
{ InfoItemType::MmuSegmentation, {L("Multimaterial painting"), "mmu_segmentation"}, },
|
||||
{ InfoItemType::Sinking, {L("Sinking"), "support_blocker"}, },
|
||||
{ InfoItemType::CustomSupports, {L("Paint-on supports"), "fdm_supports_" }, },
|
||||
{ InfoItemType::CustomSeam, {L("Paint-on seam"), "seam_" }, },
|
||||
{ InfoItemType::MmuSegmentation, {L("Multimaterial painting"), "mmu_segmentation_"}, },
|
||||
{ InfoItemType::Sinking, {L("Sinking"), "sinking"}, },
|
||||
{ InfoItemType::VariableLayerHeight, {L("Variable layer height"), "layers"}, },
|
||||
};
|
||||
|
||||
@ -1682,6 +1682,9 @@ void ObjectDataViewModel::Rescale()
|
||||
m_warning_bmp = create_scaled_bitmap(WarningIcon);
|
||||
m_warning_manifold_bmp = create_scaled_bitmap(WarningManifoldIcon);
|
||||
|
||||
for (auto item : INFO_ITEMS)
|
||||
m_info_bmps[item.first] = create_scaled_bitmap(item.second.bmp_name);
|
||||
|
||||
wxDataViewItemArray all_items;
|
||||
GetAllChildren(wxDataViewItem(0), all_items);
|
||||
|
||||
@ -1705,6 +1708,8 @@ void ObjectDataViewModel::Rescale()
|
||||
node->m_bmp = create_scaled_bitmap(LayerRootIcon);
|
||||
case itLayer:
|
||||
node->m_bmp = create_scaled_bitmap(LayerIcon);
|
||||
case itInfo:
|
||||
node->m_bmp = m_info_bmps.at(node->m_info_item_type);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "Plater.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "OG_CustomCtrl.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
#include <utility>
|
||||
#include <wx/numformatter.h>
|
||||
@ -10,6 +12,7 @@
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include "libslic3r/Exception.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "I18N.hpp"
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
@ -244,7 +247,6 @@ void OptionsGroup::activate_line(Line& line)
|
||||
|
||||
// if we have a single option with no label, no sidetext just add it directly to sizer
|
||||
if (option_set.size() == 1 && label_width == 0 && option_set.front().opt.full_width &&
|
||||
option_set.front().opt.label.empty() &&
|
||||
option_set.front().opt.sidetext.size() == 0 && option_set.front().side_widget == nullptr &&
|
||||
line.get_extra_widgets().size() == 0) {
|
||||
|
||||
@ -323,7 +325,6 @@ void OptionsGroup::activate_line(Line& line)
|
||||
grid_sizer->Add(sizer, 0, wxEXPAND | (staticbox ? wxALL : wxBOTTOM | wxTOP | wxLEFT), staticbox ? 0 : 1);
|
||||
// If we have a single option with no sidetext just add it directly to the grid sizer
|
||||
if (option_set.size() == 1 && option_set.front().opt.sidetext.size() == 0 &&
|
||||
option_set.front().opt.label.empty() &&
|
||||
option_set.front().side_widget == nullptr && line.get_extra_widgets().size() == 0) {
|
||||
const auto& option = option_set.front();
|
||||
const auto& field = build_field(option);
|
||||
@ -338,11 +339,12 @@ void OptionsGroup::activate_line(Line& line)
|
||||
return;
|
||||
}
|
||||
|
||||
bool is_multioption_line = option_set.size() > 1;
|
||||
for (auto opt : option_set) {
|
||||
ConfigOptionDef option = opt.opt;
|
||||
wxSizer* sizer_tmp = sizer;
|
||||
// add label if any
|
||||
if (!option.label.empty() && !custom_ctrl) {
|
||||
if ((is_multioption_line || line.label.IsEmpty()) && !option.label.empty() && !custom_ctrl) {
|
||||
//! To correct translation by context have to use wxGETTEXT_IN_CONTEXT macro from wxWidget 3.1.1
|
||||
wxString str_label = (option.label == L_CONTEXT("Top", "Layers") || option.label == L_CONTEXT("Bottom", "Layers")) ?
|
||||
_CTX(option.label, "Layers") :
|
||||
@ -504,15 +506,13 @@ void OptionsGroup::clear(bool destroy_custom_ctrl)
|
||||
m_fields.clear();
|
||||
}
|
||||
|
||||
Line OptionsGroup::create_single_option_line(const Option& option, const wxString& path/* = wxEmptyString*/) const {
|
||||
// Line retval{ _(option.opt.label), _(option.opt.tooltip) };
|
||||
Line OptionsGroup::create_single_option_line(const Option& option, const std::string& path/* = std::string()*/) const
|
||||
{
|
||||
wxString tooltip = _(option.opt.tooltip);
|
||||
edit_tooltip(tooltip);
|
||||
Line retval{ _(option.opt.label), tooltip };
|
||||
retval.label_path = path;
|
||||
Option tmp(option);
|
||||
tmp.opt.label = std::string("");
|
||||
retval.append_option(tmp);
|
||||
retval.append_option(option);
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -962,6 +962,54 @@ void ConfigOptionsGroup::change_opt_value(const t_config_option_key& opt_key, co
|
||||
m_modelconfig->touch();
|
||||
}
|
||||
|
||||
wxString OptionsGroup::get_url(const std::string& path_end)
|
||||
{
|
||||
if (path_end.empty())
|
||||
return wxEmptyString;
|
||||
|
||||
wxString language = get_app_config()->get("translation_language");
|
||||
wxString lang_marker = language.IsEmpty() ? "en" : language.BeforeFirst('_');
|
||||
|
||||
return wxString("https://help.prusa3d.com/") + lang_marker + wxString("/article/" + path_end);
|
||||
}
|
||||
|
||||
bool OptionsGroup::launch_browser(const std::string& path_end)
|
||||
{
|
||||
bool launch = true;
|
||||
|
||||
if (get_app_config()->get("suppress_hyperlinks").empty()) {
|
||||
RichMessageDialog dialog(nullptr, _L("Open hyperlink in default browser?"), _L("PrusaSlicer: Open hyperlink"), wxYES_NO);
|
||||
dialog.ShowCheckBox(_L("Remember my choice"));
|
||||
int answer = dialog.ShowModal();
|
||||
|
||||
if (dialog.IsCheckBoxChecked()) {
|
||||
wxString preferences_item = _L("Suppress to open hyperlink in browser");
|
||||
wxString msg =
|
||||
_L("PrusaSlicer will remember your choice.") + "\n\n" +
|
||||
_L("You will not be asked about it again on label hovering.") + "\n\n" +
|
||||
format_wxstr(_L("Visit \"Preferences\" and check \"%1%\"\nto changes your choice."), preferences_item);
|
||||
|
||||
MessageDialog msg_dlg(nullptr, msg, _L("PrusaSlicer: Don't ask me again"), wxOK | wxCANCEL | wxICON_INFORMATION);
|
||||
if (msg_dlg.ShowModal() == wxID_CANCEL)
|
||||
return false;
|
||||
|
||||
get_app_config()->set("suppress_hyperlinks", dialog.IsCheckBoxChecked() ? (answer == wxID_NO ? "1" : "0") : "");
|
||||
}
|
||||
|
||||
launch = answer == wxID_YES;
|
||||
}
|
||||
if (launch)
|
||||
launch = get_app_config()->get("suppress_hyperlinks") != "1";
|
||||
|
||||
return launch && wxLaunchDefaultBrowser(OptionsGroup::get_url(path_end));
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-------------------------------------------------------------------------------------------
|
||||
// ogStaticText
|
||||
//-------------------------------------------------------------------------------------------
|
||||
|
||||
ogStaticText::ogStaticText(wxWindow* parent, const wxString& text) :
|
||||
wxStaticText(parent, wxID_ANY, text, wxDefaultPosition, wxDefaultSize)
|
||||
{
|
||||
@ -979,5 +1027,38 @@ void ogStaticText::SetText(const wxString& value, bool wrap/* = true*/)
|
||||
GetParent()->Layout();
|
||||
}
|
||||
|
||||
void ogStaticText::SetPathEnd(const std::string& link)
|
||||
{
|
||||
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& event) {
|
||||
if (HasCapture())
|
||||
return;
|
||||
this->CaptureMouse();
|
||||
event.Skip();
|
||||
} );
|
||||
Bind(wxEVT_LEFT_UP, [link, this](wxMouseEvent& event) {
|
||||
if (!HasCapture())
|
||||
return;
|
||||
ReleaseMouse();
|
||||
OptionsGroup::launch_browser(link);
|
||||
event.Skip();
|
||||
} );
|
||||
Bind(wxEVT_ENTER_WINDOW, [this, link](wxMouseEvent& event) {
|
||||
SetToolTip(OptionsGroup::get_url(get_app_config()->get("suppress_hyperlinks") != "1" ? link : std::string()));
|
||||
FocusText(true);
|
||||
event.Skip();
|
||||
});
|
||||
Bind(wxEVT_LEAVE_WINDOW, [this](wxMouseEvent& event) { FocusText(false); event.Skip(); });
|
||||
}
|
||||
|
||||
void ogStaticText::FocusText(bool focus)
|
||||
{
|
||||
if (get_app_config()->get("suppress_hyperlinks") == "1")
|
||||
return;
|
||||
|
||||
SetFont(focus ? Slic3r::GUI::wxGetApp().link_font() :
|
||||
Slic3r::GUI::wxGetApp().normal_font());
|
||||
Refresh();
|
||||
}
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
@ -53,7 +53,7 @@ class Line {
|
||||
public:
|
||||
wxString label;
|
||||
wxString label_tooltip;
|
||||
wxString label_path;
|
||||
std::string label_path;
|
||||
|
||||
size_t full_width {0};
|
||||
wxColour* full_Label_color {nullptr};
|
||||
@ -133,8 +133,8 @@ public:
|
||||
// delete all controls from the option group
|
||||
void clear(bool destroy_custom_ctrl = false);
|
||||
|
||||
Line create_single_option_line(const Option& option, const wxString& path = wxEmptyString) const;
|
||||
void append_single_option_line(const Option& option, const wxString& path = wxEmptyString) { append_line(create_single_option_line(option, path)); }
|
||||
Line create_single_option_line(const Option& option, const std::string& path = std::string()) const;
|
||||
void append_single_option_line(const Option& option, const std::string& path = std::string()) { append_line(create_single_option_line(option, path)); }
|
||||
void append_separator();
|
||||
|
||||
// return a non-owning pointer reference
|
||||
@ -219,6 +219,10 @@ protected:
|
||||
virtual void on_change_OG(const t_config_option_key& opt_id, const boost::any& value);
|
||||
virtual void back_to_initial_value(const std::string& opt_key) {}
|
||||
virtual void back_to_sys_value(const std::string& opt_key) {}
|
||||
|
||||
public:
|
||||
static wxString get_url(const std::string& path_end);
|
||||
static bool launch_browser(const std::string& path_end);
|
||||
};
|
||||
|
||||
class ConfigOptionsGroup: public OptionsGroup {
|
||||
@ -239,17 +243,17 @@ public:
|
||||
void set_config_category_and_type(const wxString &category, int type) { m_config_category = category; m_config_type = type; }
|
||||
void set_config(DynamicPrintConfig* config) { m_config = config; m_modelconfig = nullptr; }
|
||||
Option get_option(const std::string& opt_key, int opt_index = -1);
|
||||
Line create_single_option_line(const std::string& title, const wxString& path = wxEmptyString, int idx = -1) /*const*/{
|
||||
Line create_single_option_line(const std::string& title, const std::string& path = std::string(), int idx = -1) /*const*/{
|
||||
Option option = get_option(title, idx);
|
||||
return OptionsGroup::create_single_option_line(option, path);
|
||||
}
|
||||
Line create_single_option_line(const Option& option, const wxString& path = wxEmptyString) const {
|
||||
Line create_single_option_line(const Option& option, const std::string& path = std::string()) const {
|
||||
return OptionsGroup::create_single_option_line(option, path);
|
||||
}
|
||||
void append_single_option_line(const Option& option, const wxString& path = wxEmptyString) {
|
||||
void append_single_option_line(const Option& option, const std::string& path = std::string()) {
|
||||
OptionsGroup::append_single_option_line(option, path);
|
||||
}
|
||||
void append_single_option_line(const std::string title, const wxString& path = wxEmptyString, int idx = -1)
|
||||
void append_single_option_line(const std::string title, const std::string& path = std::string(), int idx = -1)
|
||||
{
|
||||
Option option = get_option(title, idx);
|
||||
append_single_option_line(option, path);
|
||||
@ -298,6 +302,9 @@ public:
|
||||
~ogStaticText() {}
|
||||
|
||||
void SetText(const wxString& value, bool wrap = true);
|
||||
// Set special path end. It will be used to generation of the hyperlink on info page
|
||||
void SetPathEnd(const std::string& link);
|
||||
void FocusText(bool focus);
|
||||
};
|
||||
|
||||
}}
|
||||
|
@ -141,11 +141,10 @@ bool Plater::has_illegal_filename_characters(const std::string& name)
|
||||
|
||||
void Plater::show_illegal_characters_warning(wxWindow* parent)
|
||||
{
|
||||
show_error(parent, _L("The supplied name is not valid;") + "\n" +
|
||||
show_error(parent, _L("The provided name is not valid;") + "\n" +
|
||||
_L("the following characters are not allowed:") + " <>:/\\|?*\"");
|
||||
}
|
||||
|
||||
|
||||
// Sidebar widgets
|
||||
|
||||
// struct InfoBox : public wxStaticBox
|
||||
@ -239,6 +238,7 @@ void ObjectInfo::show_sizer(bool show)
|
||||
void ObjectInfo::msw_rescale()
|
||||
{
|
||||
manifold_warning_icon->SetBitmap(create_scaled_bitmap(m_warning_icon_name));
|
||||
info_icon->SetBitmap(create_scaled_bitmap("info"));
|
||||
}
|
||||
|
||||
void ObjectInfo::update_warning_icon(const std::string& warning_icon_name)
|
||||
@ -1137,6 +1137,7 @@ void Sidebar::sys_color_changed()
|
||||
|
||||
for (wxWindow* win : std::vector<wxWindow*>{ this, p->sliced_info->GetStaticBox(), p->object_info->GetStaticBox(), p->btn_reslice, p->btn_export_gcode })
|
||||
wxGetApp().UpdateDarkUI(win);
|
||||
p->object_info->msw_rescale();
|
||||
for (wxWindow* win : std::vector<wxWindow*>{ p->scrolled, p->presets_panel })
|
||||
wxGetApp().UpdateAllStaticTextDarkUI(win);
|
||||
for (wxWindow* btn : std::vector<wxWindow*>{ p->btn_reslice, p->btn_export_gcode })
|
||||
@ -1262,7 +1263,11 @@ void Sidebar::show_info_sizer()
|
||||
if (selection.is_single_volume()) {
|
||||
std::vector<int> obj_idxs, vol_idxs;
|
||||
wxGetApp().obj_list()->get_selection_indexes(obj_idxs, vol_idxs);
|
||||
assert(vol_idxs.size() == 1);
|
||||
if (vol_idxs.size() != 1)
|
||||
// Case when this fuction is called between update selection in ObjectList and on Canvas
|
||||
// Like after try to delete last solid part in object, the object is selected in ObjectLIst when just a part is still selected on Canvas
|
||||
// see https://github.com/prusa3d/PrusaSlicer/issues/7408
|
||||
return;
|
||||
vol = model_object->volumes[vol_idxs[0]];
|
||||
t = model_object->instances[inst_idx]->get_matrix() * vol->get_matrix();
|
||||
}
|
||||
@ -2225,6 +2230,7 @@ Plater::priv::~priv()
|
||||
{
|
||||
if (config != nullptr)
|
||||
delete config;
|
||||
// Saves the database of visited (already shown) hints into hints.ini.
|
||||
notification_manager->deactivate_loaded_hints();
|
||||
}
|
||||
|
||||
@ -2353,7 +2359,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
}
|
||||
|
||||
const auto loading = _L("Loading") + dots;
|
||||
wxProgressDialog dlg(loading, "", 100, q, wxPD_AUTO_HIDE);
|
||||
wxProgressDialog dlg(loading, "", 100, find_toplevel_parent(q), wxPD_AUTO_HIDE);
|
||||
wxBusyCursor busy;
|
||||
|
||||
auto *new_model = (!load_model || one_by_one) ? nullptr : new Slic3r::Model();
|
||||
@ -2744,16 +2750,17 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& mode
|
||||
_L("Object too large?"));
|
||||
}
|
||||
|
||||
// Now ObjectList uses GLCanvas3D::is_object_sinkin() to show/hide "Sinking" InfoItem,
|
||||
// so 3D-scene should be updated before object additing to the ObjectList
|
||||
this->view3D->reload_scene(false, (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH);
|
||||
|
||||
notification_manager->close_notification_of_type(NotificationType::UpdatedItemsInfo);
|
||||
for (const size_t idx : obj_idxs) {
|
||||
wxGetApp().obj_list()->add_object_to_list(idx);
|
||||
}
|
||||
|
||||
update();
|
||||
// Update InfoItems in ObjectList after update() to use of a correct value of the GLCanvas3D::is_sinking(),
|
||||
// which is updated after a view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH) call
|
||||
for (const size_t idx : obj_idxs)
|
||||
wxGetApp().obj_list()->update_info_items(idx);
|
||||
|
||||
object_list_changed();
|
||||
|
||||
this->schedule_background_process();
|
||||
@ -5664,10 +5671,15 @@ void Plater::export_gcode(bool prefer_removable)
|
||||
if (dlg.ShowModal() == wxID_OK) {
|
||||
output_path = into_path(dlg.GetPath());
|
||||
while (has_illegal_filename_characters(output_path.filename().string())) {
|
||||
show_illegal_characters_warning(this);
|
||||
show_error(this, _L("The provided file name is not valid.") + "\n" +
|
||||
_L("The following characters are not allowed by a FAT file system:") + " <>:/\\|?*\"");
|
||||
dlg.SetFilename(from_path(output_path.filename()));
|
||||
if (dlg.ShowModal() == wxID_OK)
|
||||
output_path = into_path(dlg.GetPath());
|
||||
else {
|
||||
output_path.clear();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6306,15 +6318,6 @@ void Plater::force_print_bed_update()
|
||||
|
||||
void Plater::on_activate()
|
||||
{
|
||||
#if defined(__linux__) || defined(_WIN32)
|
||||
// Activating the main frame, and no window has keyboard focus.
|
||||
// Set the keyboard focus to the visible Canvas3D.
|
||||
if (this->p->view3D->IsShown() && wxWindow::FindFocus() != this->p->view3D->get_wxglcanvas())
|
||||
CallAfter([this]() { this->p->view3D->get_wxglcanvas()->SetFocus(); });
|
||||
else if (this->p->preview->IsShown() && wxWindow::FindFocus() != this->p->view3D->get_wxglcanvas())
|
||||
CallAfter([this]() { this->p->preview->get_wxglcanvas()->SetFocus(); });
|
||||
#endif
|
||||
|
||||
this->p->show_delayed_error_message();
|
||||
}
|
||||
|
||||
@ -6879,7 +6882,6 @@ wxMenu* Plater::instance_menu() { return p->menus.instance_menu();
|
||||
wxMenu* Plater::layer_menu() { return p->menus.layer_menu(); }
|
||||
wxMenu* Plater::multi_selection_menu() { return p->menus.multi_selection_menu(); }
|
||||
|
||||
|
||||
SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() :
|
||||
m_was_scheduled(wxGetApp().plater()->is_background_process_update_scheduled())
|
||||
{
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include "Notebook.hpp"
|
||||
#include "ButtonsDescription.hpp"
|
||||
#include "OG_CustomCtrl.hpp"
|
||||
#include <initializer_list>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
@ -466,7 +465,7 @@ void PreferencesDialog::build(size_t selected_tab)
|
||||
|
||||
#ifdef _WIN32
|
||||
// Add "Dark Mode" tab
|
||||
if (is_editor) {
|
||||
{
|
||||
// Add "Dark Mode" tab
|
||||
m_optgroup_dark_mode = create_options_tab(_L("Dark mode (experimental)"), tabs);
|
||||
m_optgroup_dark_mode->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
|
||||
@ -519,21 +518,29 @@ void PreferencesDialog::build(size_t selected_tab)
|
||||
this->CenterOnParent();
|
||||
}
|
||||
|
||||
void PreferencesDialog::update_ctrls_alignment()
|
||||
std::vector<ConfigOptionsGroup*> PreferencesDialog::optgroups()
|
||||
{
|
||||
int max_ctrl_width{ 0 };
|
||||
std::initializer_list<ConfigOptionsGroup*> og_list = { m_optgroup_general.get(), m_optgroup_camera.get(), m_optgroup_gui.get()
|
||||
std::vector<ConfigOptionsGroup*> out;
|
||||
out.reserve(4);
|
||||
for (ConfigOptionsGroup* opt : { m_optgroup_general.get(), m_optgroup_camera.get(), m_optgroup_gui.get()
|
||||
#ifdef _WIN32
|
||||
, m_optgroup_dark_mode.get()
|
||||
#endif // _WIN32
|
||||
};
|
||||
for (auto og : og_list) {
|
||||
})
|
||||
if (opt)
|
||||
out.emplace_back(opt);
|
||||
return out;
|
||||
}
|
||||
|
||||
void PreferencesDialog::update_ctrls_alignment()
|
||||
{
|
||||
int max_ctrl_width{ 0 };
|
||||
for (ConfigOptionsGroup* og : this->optgroups())
|
||||
if (int max = og->custom_ctrl->get_max_win_width();
|
||||
max_ctrl_width < max)
|
||||
max_ctrl_width = max;
|
||||
}
|
||||
if (max_ctrl_width)
|
||||
for (auto og : og_list)
|
||||
for (ConfigOptionsGroup* og : this->optgroups())
|
||||
og->custom_ctrl->set_max_win_width(max_ctrl_width);
|
||||
}
|
||||
|
||||
@ -622,9 +629,8 @@ void PreferencesDialog::accept(wxEvent&)
|
||||
|
||||
void PreferencesDialog::on_dpi_changed(const wxRect &suggested_rect)
|
||||
{
|
||||
m_optgroup_general->msw_rescale();
|
||||
m_optgroup_camera->msw_rescale();
|
||||
m_optgroup_gui->msw_rescale();
|
||||
for (ConfigOptionsGroup* og : this->optgroups())
|
||||
og->msw_rescale();
|
||||
|
||||
msw_buttons_rescale(this, em_unit(), { wxID_OK, wxID_CANCEL });
|
||||
|
||||
@ -788,7 +794,7 @@ void PreferencesDialog::init_highlighter(const t_config_option_key& opt_key)
|
||||
});
|
||||
|
||||
std::pair<OG_CustomCtrl*, bool*> ctrl = { nullptr, nullptr };
|
||||
for (auto opt_group : { m_optgroup_general, m_optgroup_camera, m_optgroup_gui }) {
|
||||
for (ConfigOptionsGroup* opt_group : this->optgroups()) {
|
||||
ctrl = opt_group->get_custom_ctrl_with_blinking_ptr(opt_key, -1);
|
||||
if (ctrl.first && ctrl.second) {
|
||||
m_highlighter.init(ctrl);
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/timer.h>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
class wxColourPickerCtrl;
|
||||
@ -61,6 +62,7 @@ protected:
|
||||
void create_settings_mode_widget();
|
||||
void create_settings_text_color_widget();
|
||||
void init_highlighter(const t_config_option_key& opt_key);
|
||||
std::vector<ConfigOptionsGroup*> optgroups();
|
||||
|
||||
struct PreferencesHighlighter
|
||||
{
|
||||
|
@ -98,8 +98,9 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo
|
||||
}
|
||||
|
||||
if (post_actions.has(PrintHostPostUploadAction::StartSimulation)) {
|
||||
auto* btn_print = add_button(wxID_YES, false, _L("Upload and Simulate"));
|
||||
btn_print->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) {
|
||||
// Using wxID_MORE as a button identifier to be different from the other buttons, wxID_MORE has no other meaning here.
|
||||
auto* btn_simulate = add_button(wxID_MORE, false, _L("Upload and Simulate"));
|
||||
btn_simulate->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) {
|
||||
if (validate_path(txt_filename->GetValue())) {
|
||||
post_upload_action = PrintHostPostUploadAction::StartSimulation;
|
||||
EndDialog(wxID_OK);
|
||||
|
@ -1432,7 +1432,7 @@ void TabPrint::build()
|
||||
load_initial_data();
|
||||
|
||||
auto page = add_options_page(L("Layers and perimeters"), "layers");
|
||||
wxString category_path = "layers-and-perimeters_1748#";
|
||||
std::string category_path = "layers-and-perimeters_1748#";
|
||||
auto optgroup = page->new_optgroup(L("Layer height"));
|
||||
optgroup->append_single_option_line("layer_height", category_path + "layer-height");
|
||||
optgroup->append_single_option_line("first_layer_height", category_path + "first-layer-height");
|
||||
@ -1673,6 +1673,12 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line(option);
|
||||
|
||||
optgroup = page->new_optgroup(L("Post-processing scripts"), 0);
|
||||
line = { "", "" };
|
||||
line.full_width = 1;
|
||||
line.widget = [this](wxWindow* parent) {
|
||||
return description_line_widget(parent, &m_post_process_explanation);
|
||||
};
|
||||
optgroup->append_line(line);
|
||||
option = optgroup->get_option("post_process");
|
||||
option.opt.full_width = true;
|
||||
option.opt.height = 5;//50;
|
||||
@ -1688,7 +1694,7 @@ void TabPrint::build()
|
||||
page = add_options_page(L("Dependencies"), "wrench.png");
|
||||
optgroup = page->new_optgroup(L("Profile dependencies"));
|
||||
|
||||
create_line_with_widget(optgroup.get(), "compatible_printers", wxEmptyString, [this](wxWindow* parent) {
|
||||
create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) {
|
||||
return compatible_widget_create(parent, m_compatible_printers);
|
||||
});
|
||||
|
||||
@ -1721,6 +1727,14 @@ void TabPrint::update_description_lines()
|
||||
m_top_bottom_shell_thickness_explanation->SetText(
|
||||
from_u8(PresetHints::top_bottom_shell_thickness_explanation(*m_preset_bundle)));
|
||||
}
|
||||
|
||||
if (m_active_page && m_active_page->title() == "Output options" && m_post_process_explanation) {
|
||||
m_post_process_explanation->SetText(
|
||||
_u8L("Post processing scripts shall modify G-code file in place."));
|
||||
#ifndef __linux__
|
||||
m_post_process_explanation->SetPathEnd("post-processing-scripts_283913");
|
||||
#endif // __linux__
|
||||
}
|
||||
}
|
||||
|
||||
void TabPrint::toggle_options()
|
||||
@ -1774,6 +1788,7 @@ void TabPrint::clear_pages()
|
||||
|
||||
m_recommended_thin_wall_thickness_description_line = nullptr;
|
||||
m_top_bottom_shell_thickness_explanation = nullptr;
|
||||
m_post_process_explanation = nullptr;
|
||||
}
|
||||
|
||||
bool Tab::validate_custom_gcode(const wxString& title, const std::string& gcode)
|
||||
@ -1938,7 +1953,7 @@ void TabFilament::build()
|
||||
optgroup->append_line(line);
|
||||
|
||||
page = add_options_page(L("Cooling"), "cooling");
|
||||
wxString category_path = "cooling_127569#";
|
||||
std::string category_path = "cooling_127569#";
|
||||
optgroup = page->new_optgroup(L("Enable"));
|
||||
optgroup->append_single_option_line("fan_always_on");
|
||||
optgroup->append_single_option_line("cooling");
|
||||
@ -1999,7 +2014,7 @@ void TabFilament::build()
|
||||
optgroup->append_single_option_line("filament_cooling_initial_speed");
|
||||
optgroup->append_single_option_line("filament_cooling_final_speed");
|
||||
|
||||
create_line_with_widget(optgroup.get(), "filament_ramming_parameters", wxEmptyString, [this](wxWindow* parent) {
|
||||
create_line_with_widget(optgroup.get(), "filament_ramming_parameters", "", [this](wxWindow* parent) {
|
||||
auto ramming_dialog_btn = new wxButton(parent, wxID_ANY, _(L("Ramming settings"))+dots, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
|
||||
wxGetApp().UpdateDarkUI(ramming_dialog_btn);
|
||||
ramming_dialog_btn->SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||
@ -2055,7 +2070,7 @@ void TabFilament::build()
|
||||
|
||||
page = add_options_page(L("Dependencies"), "wrench.png");
|
||||
optgroup = page->new_optgroup(L("Profile dependencies"));
|
||||
create_line_with_widget(optgroup.get(), "compatible_printers", wxEmptyString, [this](wxWindow* parent) {
|
||||
create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) {
|
||||
return compatible_widget_create(parent, m_compatible_printers);
|
||||
});
|
||||
|
||||
@ -2063,7 +2078,7 @@ void TabFilament::build()
|
||||
option.opt.full_width = true;
|
||||
optgroup->append_single_option_line(option);
|
||||
|
||||
create_line_with_widget(optgroup.get(), "compatible_prints", wxEmptyString, [this](wxWindow* parent) {
|
||||
create_line_with_widget(optgroup.get(), "compatible_prints", "", [this](wxWindow* parent) {
|
||||
return compatible_widget_create(parent, m_compatible_prints);
|
||||
});
|
||||
|
||||
@ -2480,8 +2495,7 @@ void TabPrinter::build_sla()
|
||||
|
||||
optgroup = page->new_optgroup(L("Corrections"));
|
||||
line = Line{ m_config->def()->get("relative_correction")->full_label, "" };
|
||||
std::vector<std::string> axes{ "X", "Y", "Z" };
|
||||
for (auto& axis : axes) {
|
||||
for (auto& axis : { "X", "Y", "Z" }) {
|
||||
auto opt = optgroup->get_option(std::string("relative_correction_") + char(std::tolower(axis[0])));
|
||||
opt.opt.label = axis;
|
||||
line.append_option(opt);
|
||||
@ -2590,7 +2604,7 @@ PageShp TabPrinter::build_kinematics_page()
|
||||
optgroup->append_line(line);
|
||||
}
|
||||
|
||||
std::vector<std::string> axes{ "x", "y", "z", "e" };
|
||||
const std::vector<std::string> axes{ "x", "y", "z", "e" };
|
||||
optgroup = page->new_optgroup(L("Maximum feedrates"));
|
||||
for (const std::string &axis : axes) {
|
||||
append_option_line(optgroup, "machine_max_feedrate_" + axis);
|
||||
@ -2695,7 +2709,7 @@ void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/)
|
||||
m_pages.insert(m_pages.begin() + n_before_extruders + extruder_idx, page);
|
||||
|
||||
auto optgroup = page->new_optgroup(L("Size"));
|
||||
optgroup->append_single_option_line("nozzle_diameter", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("nozzle_diameter", "", extruder_idx);
|
||||
|
||||
optgroup->m_on_change = [this, extruder_idx](const t_config_option_key& opt_key, boost::any value)
|
||||
{
|
||||
@ -2734,32 +2748,32 @@ void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/)
|
||||
};
|
||||
|
||||
optgroup = page->new_optgroup(L("Layer height limits"));
|
||||
optgroup->append_single_option_line("min_layer_height", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("max_layer_height", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("min_layer_height", "", extruder_idx);
|
||||
optgroup->append_single_option_line("max_layer_height", "", extruder_idx);
|
||||
|
||||
|
||||
optgroup = page->new_optgroup(L("Position (for multi-extruder printers)"));
|
||||
optgroup->append_single_option_line("extruder_offset", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("extruder_offset", "", extruder_idx);
|
||||
|
||||
optgroup = page->new_optgroup(L("Retraction"));
|
||||
optgroup->append_single_option_line("retract_length", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("retract_lift", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("retract_length", "", extruder_idx);
|
||||
optgroup->append_single_option_line("retract_lift", "", extruder_idx);
|
||||
Line line = { L("Only lift Z"), "" };
|
||||
line.append_option(optgroup->get_option("retract_lift_above", extruder_idx));
|
||||
line.append_option(optgroup->get_option("retract_lift_below", extruder_idx));
|
||||
optgroup->append_line(line);
|
||||
|
||||
optgroup->append_single_option_line("retract_speed", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("deretract_speed", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("retract_restart_extra", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("retract_before_travel", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("retract_layer_change", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("wipe", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("retract_before_wipe", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("retract_speed", "", extruder_idx);
|
||||
optgroup->append_single_option_line("deretract_speed", "", extruder_idx);
|
||||
optgroup->append_single_option_line("retract_restart_extra", "", extruder_idx);
|
||||
optgroup->append_single_option_line("retract_before_travel", "", extruder_idx);
|
||||
optgroup->append_single_option_line("retract_layer_change", "", extruder_idx);
|
||||
optgroup->append_single_option_line("wipe", "", extruder_idx);
|
||||
optgroup->append_single_option_line("retract_before_wipe", "", extruder_idx);
|
||||
|
||||
optgroup = page->new_optgroup(L("Retraction when tool is disabled (advanced settings for multi-extruder setups)"));
|
||||
optgroup->append_single_option_line("retract_length_toolchange", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("retract_restart_extra_toolchange", wxEmptyString, extruder_idx);
|
||||
optgroup->append_single_option_line("retract_length_toolchange", "", extruder_idx);
|
||||
optgroup->append_single_option_line("retract_restart_extra_toolchange", "", extruder_idx);
|
||||
|
||||
optgroup = page->new_optgroup(L("Preview"));
|
||||
|
||||
@ -2787,7 +2801,7 @@ void TabPrinter::build_unregular_pages(bool from_initial_build/* = false*/)
|
||||
|
||||
return sizer;
|
||||
};
|
||||
line = optgroup->create_single_option_line("extruder_colour", wxEmptyString, extruder_idx);
|
||||
line = optgroup->create_single_option_line("extruder_colour", "", extruder_idx);
|
||||
line.append_widget(reset_to_filament_color);
|
||||
optgroup->append_line(line);
|
||||
}
|
||||
@ -3736,7 +3750,7 @@ void Tab::update_ui_from_settings()
|
||||
}
|
||||
}
|
||||
|
||||
void Tab::create_line_with_widget(ConfigOptionsGroup* optgroup, const std::string& opt_key, const wxString& path, widget_t widget)
|
||||
void Tab::create_line_with_widget(ConfigOptionsGroup* optgroup, const std::string& opt_key, const std::string& path, widget_t widget)
|
||||
{
|
||||
Line line = optgroup->create_single_option_line(opt_key);
|
||||
line.widget = widget;
|
||||
@ -4204,8 +4218,7 @@ void TabSLAMaterial::build()
|
||||
|
||||
optgroup = page->new_optgroup(L("Corrections"));
|
||||
auto line = Line{ m_config->def()->get("material_correction")->full_label, "" };
|
||||
std::vector<std::string> axes{ "X", "Y", "Z" };
|
||||
for (auto& axis : axes) {
|
||||
for (auto& axis : { "X", "Y", "Z" }) {
|
||||
auto opt = optgroup->get_option(std::string("material_correction_") + char(std::tolower(axis[0])));
|
||||
opt.opt.label = axis;
|
||||
line.append_option(opt);
|
||||
@ -4224,7 +4237,7 @@ void TabSLAMaterial::build()
|
||||
page = add_options_page(L("Dependencies"), "wrench.png");
|
||||
optgroup = page->new_optgroup(L("Profile dependencies"));
|
||||
|
||||
create_line_with_widget(optgroup.get(), "compatible_printers", wxEmptyString, [this](wxWindow* parent) {
|
||||
create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) {
|
||||
return compatible_widget_create(parent, m_compatible_printers);
|
||||
});
|
||||
|
||||
@ -4232,7 +4245,7 @@ void TabSLAMaterial::build()
|
||||
option.opt.full_width = true;
|
||||
optgroup->append_single_option_line(option);
|
||||
|
||||
create_line_with_widget(optgroup.get(), "compatible_prints", wxEmptyString, [this](wxWindow* parent) {
|
||||
create_line_with_widget(optgroup.get(), "compatible_prints", "", [this](wxWindow* parent) {
|
||||
return compatible_widget_create(parent, m_compatible_prints);
|
||||
});
|
||||
|
||||
@ -4371,7 +4384,7 @@ void TabSLAPrint::build()
|
||||
page = add_options_page(L("Dependencies"), "wrench");
|
||||
optgroup = page->new_optgroup(L("Profile dependencies"));
|
||||
|
||||
create_line_with_widget(optgroup.get(), "compatible_printers", wxEmptyString, [this](wxWindow* parent) {
|
||||
create_line_with_widget(optgroup.get(), "compatible_printers", "", [this](wxWindow* parent) {
|
||||
return compatible_widget_create(parent, m_compatible_printers);
|
||||
});
|
||||
|
||||
@ -4468,7 +4481,7 @@ ConfigManipulation Tab::get_config_manipulation()
|
||||
return on_value_change(opt_key, value);
|
||||
};
|
||||
|
||||
return ConfigManipulation(load_config, cb_toggle_field, cb_value_change);
|
||||
return ConfigManipulation(load_config, cb_toggle_field, cb_value_change, nullptr, this);
|
||||
}
|
||||
|
||||
|
||||
|
@ -351,7 +351,7 @@ public:
|
||||
bool validate_custom_gcodes_was_shown{ false };
|
||||
|
||||
protected:
|
||||
void create_line_with_widget(ConfigOptionsGroup* optgroup, const std::string& opt_key, const wxString& path, widget_t widget);
|
||||
void create_line_with_widget(ConfigOptionsGroup* optgroup, const std::string& opt_key, const std::string& path, widget_t widget);
|
||||
wxSizer* compatible_widget_create(wxWindow* parent, PresetDependencies &deps);
|
||||
void compatible_widget_reload(PresetDependencies &deps);
|
||||
void load_key_value(const std::string& opt_key, const boost::any& value, bool saved_value = false);
|
||||
@ -387,6 +387,7 @@ public:
|
||||
private:
|
||||
ogStaticText* m_recommended_thin_wall_thickness_description_line = nullptr;
|
||||
ogStaticText* m_top_bottom_shell_thickness_explanation = nullptr;
|
||||
ogStaticText* m_post_process_explanation = nullptr;
|
||||
};
|
||||
|
||||
class TabFilament : public Tab
|
||||
|
@ -5,13 +5,15 @@
|
||||
#include <exception>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include <wx/progdlg.h>
|
||||
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "Http.hpp"
|
||||
@ -24,9 +26,16 @@ namespace pt = boost::property_tree;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
#ifdef WIN32
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
namespace {
|
||||
std::string substitute_host(const std::string& orig_addr, const std::string sub_addr)
|
||||
std::string substitute_host(const std::string& orig_addr, std::string sub_addr)
|
||||
{
|
||||
// put ipv6 into [] brackets
|
||||
if (sub_addr.find(':') != std::string::npos && sub_addr.at(0) != '[')
|
||||
sub_addr = "[" + sub_addr + "]";
|
||||
|
||||
#if 0
|
||||
//URI = scheme ":"["//"[userinfo "@"] host [":" port]] path["?" query]["#" fragment]
|
||||
std::string final_addr = orig_addr;
|
||||
// http
|
||||
@ -35,9 +44,16 @@ std::string substitute_host(const std::string& orig_addr, const std::string sub_
|
||||
// userinfo
|
||||
size_t at = orig_addr.find("@");
|
||||
host_start = (at != std::string::npos && at > host_start ? at + 1 : host_start);
|
||||
// end of host, could be port, subpath (could be query or fragment?)
|
||||
size_t host_end = orig_addr.find_first_of(":/?#", host_start);
|
||||
host_end = (host_end == std::string::npos ? orig_addr.length() : host_end);
|
||||
// end of host, could be port(:), subpath(/) (could be query(?) or fragment(#)?)
|
||||
// or it will be ']' if address is ipv6 )
|
||||
size_t potencial_host_end = orig_addr.find_first_of(":/", host_start);
|
||||
// if there are more ':' it must be ipv6
|
||||
if (potencial_host_end != std::string::npos && orig_addr[potencial_host_end] == ':' && orig_addr.rfind(':') != potencial_host_end) {
|
||||
size_t ipv6_end = orig_addr.find(']', host_start);
|
||||
// DK: Uncomment and replace orig_addr.length() if we want to allow subpath after ipv6 without [] parentheses.
|
||||
potencial_host_end = (ipv6_end != std::string::npos ? ipv6_end + 1 : orig_addr.length()); //orig_addr.find('/', host_start));
|
||||
}
|
||||
size_t host_end = (potencial_host_end != std::string::npos ? potencial_host_end : orig_addr.length());
|
||||
// now host_start and host_end should mark where to put resolved addr
|
||||
// check host_start. if its nonsense, lets just use original addr (or resolved addr?)
|
||||
if (host_start >= orig_addr.length()) {
|
||||
@ -45,8 +61,38 @@ std::string substitute_host(const std::string& orig_addr, const std::string sub_
|
||||
}
|
||||
final_addr.replace(host_start, host_end - host_start, sub_addr);
|
||||
return final_addr;
|
||||
#else
|
||||
// Using the new CURL API for handling URL. https://everything.curl.dev/libcurl/url
|
||||
// If anything fails, return the input unchanged.
|
||||
std::string out = orig_addr;
|
||||
CURLU *hurl = curl_url();
|
||||
if (hurl) {
|
||||
// Parse the input URL.
|
||||
CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, orig_addr.c_str(), 0);
|
||||
if (rc == CURLUE_OK) {
|
||||
// Replace the address.
|
||||
rc = curl_url_set(hurl, CURLUPART_HOST, sub_addr.c_str(), 0);
|
||||
if (rc == CURLUE_OK) {
|
||||
// Extract a string fromt the CURL URL handle.
|
||||
char *url;
|
||||
rc = curl_url_get(hurl, CURLUPART_URL, &url, 0);
|
||||
if (rc == CURLUE_OK) {
|
||||
out = url;
|
||||
curl_free(url);
|
||||
} else
|
||||
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to extract the URL after substitution";
|
||||
} else
|
||||
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to substitute host " << sub_addr << " in URL " << orig_addr;
|
||||
} else
|
||||
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to parse URL " << orig_addr;
|
||||
curl_url_cleanup(hurl);
|
||||
} else
|
||||
BOOST_LOG_TRIVIAL(error) << "OctoPrint substitute_host: failed to allocate curl_url";
|
||||
return out;
|
||||
#endif
|
||||
}
|
||||
} //namespace
|
||||
#endif // WIN32
|
||||
|
||||
OctoPrint::OctoPrint(DynamicPrintConfig *config) :
|
||||
m_host(config->opt_string("print_host")),
|
||||
@ -103,9 +149,11 @@ bool OctoPrint::test(wxString &msg) const
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
.on_ip_resolve([&](std::string address) {
|
||||
msg = boost::nowide::widen(address);
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
// Remember resolved address to be reused at successive REST API call.
|
||||
msg = GUI::from_u8(address);
|
||||
})
|
||||
#endif
|
||||
#endif // WIN32
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
@ -131,35 +179,38 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro
|
||||
const auto upload_filename = upload_data.upload_path.filename();
|
||||
const auto upload_parent_path = upload_data.upload_path.parent_path();
|
||||
|
||||
wxString test_msg;
|
||||
if (! test(test_msg)) {
|
||||
error_fn(std::move(test_msg));
|
||||
// If test fails, test_msg_or_host_ip contains the error message.
|
||||
// Otherwise on Windows it contains the resolved IP address of the host.
|
||||
wxString test_msg_or_host_ip;
|
||||
if (! test(test_msg_or_host_ip)) {
|
||||
error_fn(std::move(test_msg_or_host_ip));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string url;
|
||||
bool res = true;
|
||||
|
||||
bool allow_ip_resolve = GUI::get_app_config()->get("allow_ip_resolve") == "1";
|
||||
|
||||
if (m_host.find("https://") == 0 || test_msg.empty() || !allow_ip_resolve) {
|
||||
#ifdef WIN32
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
if (m_host.find("https://") == 0 || test_msg_or_host_ip.empty() || GUI::get_app_config()->get("allow_ip_resolve") != "1")
|
||||
#endif // _WIN32
|
||||
{
|
||||
// If https is entered we assume signed ceritificate is being used
|
||||
// IP resolving will not happen - it could resolve into address not being specified in cert
|
||||
url = make_url("api/files/local");
|
||||
} else {
|
||||
}
|
||||
#ifdef WIN32
|
||||
else {
|
||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||
// Curl uses easy_getinfo to get ip address of last successful transaction.
|
||||
// If it got the address use it instead of the stored in "host" variable.
|
||||
// This new address returns in "test_msg" variable.
|
||||
// This new address returns in "test_msg_or_host_ip" variable.
|
||||
// Solves troubles of uploades failing with name address.
|
||||
std::string resolved_addr = boost::nowide::narrow(test_msg);
|
||||
// put ipv6 into [] brackets
|
||||
if (resolved_addr.find(':') != std::string::npos && resolved_addr.at(0) != '[')
|
||||
resolved_addr = "[" + resolved_addr + "]";
|
||||
// in original address (m_host) replace host for resolved ip
|
||||
std::string final_addr = substitute_host(m_host, resolved_addr);
|
||||
BOOST_LOG_TRIVIAL(debug) << "Upload address after ip resolve: " << final_addr;
|
||||
url = make_url("api/files/local", final_addr);
|
||||
url = substitute_host(make_url("api/files/local"), GUI::into_u8(test_msg_or_host_ip));
|
||||
BOOST_LOG_TRIVIAL(info) << "Upload address after ip resolve: " << url;
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%")
|
||||
% name
|
||||
@ -225,21 +276,6 @@ std::string OctoPrint::make_url(const std::string &path) const
|
||||
}
|
||||
}
|
||||
|
||||
std::string OctoPrint::make_url(const std::string& path, const std::string& addr) const
|
||||
{
|
||||
std::string hst = addr.empty() ? m_host : addr;
|
||||
if (hst.find("http://") == 0 || hst.find("https://") == 0) {
|
||||
if (hst.back() == '/') {
|
||||
return (boost::format("%1%%2%") % hst % path).str();
|
||||
}
|
||||
else {
|
||||
return (boost::format("%1%/%2%") % hst % path).str();
|
||||
}
|
||||
} else {
|
||||
return (boost::format("http://%1%/%2%") % hst % path).str();
|
||||
}
|
||||
}
|
||||
|
||||
SL1Host::SL1Host(DynamicPrintConfig *config) :
|
||||
OctoPrint(config),
|
||||
m_authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
|
||||
|
@ -44,7 +44,6 @@ private:
|
||||
|
||||
virtual void set_auth(Http &http) const;
|
||||
std::string make_url(const std::string &path) const;
|
||||
std::string make_url(const std::string& path, const std::string& addr) const;
|
||||
};
|
||||
|
||||
class SL1Host: public OctoPrint
|
||||
|
@ -954,4 +954,9 @@ void PresetUpdater::on_update_notification_confirm()
|
||||
}
|
||||
}
|
||||
|
||||
bool PresetUpdater::version_check_enabled() const
|
||||
{
|
||||
return p->enabled_version_check;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -57,6 +57,9 @@ public:
|
||||
bool install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
|
||||
|
||||
void on_update_notification_confirm();
|
||||
|
||||
bool version_check_enabled() const;
|
||||
|
||||
private:
|
||||
struct priv;
|
||||
std::unique_ptr<priv> p;
|
||||
|
@ -13,6 +13,8 @@ namespace Utils {
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
// Generic command / response TCP telnet like console class.
|
||||
// Used by the MKS host to send G-code commands to test connection ("M105") and to start printing ("M23 filename", "M24").
|
||||
class TCPConsole
|
||||
{
|
||||
public:
|
||||
|
@ -112,7 +112,17 @@ SCENARIO("2D convex hull of sinking object", "[3mf]") {
|
||||
{ -91501496, 4243 }
|
||||
};
|
||||
|
||||
bool res = hull_2d.points == result;
|
||||
// Allow 1um error due to floating point rounding.
|
||||
bool res = hull_2d.points.size() == result.size();
|
||||
if (res)
|
||||
for (size_t i = 0; i < result.size(); ++ i) {
|
||||
const Point &p1 = result[i];
|
||||
const Point &p2 = hull_2d.points[i];
|
||||
if (std::abs(p1.x() - p2.x()) > 1 || std::abs(p1.y() - p2.y()) > 1) {
|
||||
res = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
THEN("2D convex hull should match with reference") {
|
||||
REQUIRE(res);
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
set(SLIC3R_APP_NAME "PrusaSlicer")
|
||||
set(SLIC3R_APP_KEY "PrusaSlicer")
|
||||
set(SLIC3R_VERSION "2.4.0-beta2")
|
||||
set(SLIC3R_VERSION "2.4.0-beta3")
|
||||
set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN")
|
||||
set(SLIC3R_RC_VERSION "2,4,0,0")
|
||||
set(SLIC3R_RC_VERSION_DOTS "2.4.0.0")
|
||||
|
Loading…
x
Reference in New Issue
Block a user