Merge remote-tracking branch 'origin/master' into ys_aliases
This commit is contained in:
commit
afb8483250
51 changed files with 15585 additions and 487 deletions
3
deps/blosc-mods.patch
vendored
3
deps/blosc-mods.patch
vendored
|
@ -1,8 +1,9 @@
|
|||
From 5669891dfaaa4c814f3ec667ca6bf4e693aea978 Mon Sep 17 00:00:00 2001
|
||||
From 7cf6c014a36f1712efbdbe9bc52d2d4922b54673 Mon Sep 17 00:00:00 2001
|
||||
From: tamasmeszaros <meszaros.q@gmail.com>
|
||||
Date: Wed, 30 Oct 2019 12:54:52 +0100
|
||||
Subject: [PATCH] Blosc 1.17 fixes and cmake config script
|
||||
|
||||
Signed-off-by: tamasmeszaros <meszaros.q@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 105 +++++++++++++++++-----------------
|
||||
blosc/CMakeLists.txt | 118 +++++++++------------------------------
|
||||
|
|
17
deps/deps-unix-common.cmake
vendored
17
deps/deps-unix-common.cmake
vendored
|
@ -55,14 +55,14 @@ find_package(Git REQUIRED)
|
|||
|
||||
ExternalProject_Add(dep_qhull
|
||||
EXCLUDE_FROM_ALL 1
|
||||
URL "https://github.com/qhull/qhull/archive/v7.3.2.tar.gz"
|
||||
URL_HASH SHA256=619c8a954880d545194bc03359404ef36a1abd2dde03678089459757fd790cb0
|
||||
#URL "https://github.com/qhull/qhull/archive/v7.3.2.tar.gz"
|
||||
#URL_HASH SHA256=619c8a954880d545194bc03359404ef36a1abd2dde03678089459757fd790cb0
|
||||
GIT_REPOSITORY https://github.com/qhull/qhull.git
|
||||
GIT_TAG 7afedcc73666e46a9f1d74632412ebecf53b1b30 # v7.3.2 plus the mac build patch
|
||||
CMAKE_ARGS
|
||||
-DBUILD_SHARED_LIBS=OFF
|
||||
-DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local
|
||||
${DEP_CMAKE_OPTS}
|
||||
UPDATE_COMMAND ""
|
||||
PATCH_COMMAND patch -p1 < ${CMAKE_CURRENT_SOURCE_DIR}/qhull-mods.patch
|
||||
)
|
||||
|
||||
ExternalProject_Add(dep_blosc
|
||||
|
@ -80,8 +80,8 @@ ExternalProject_Add(dep_blosc
|
|||
-DBUILD_TESTS=OFF
|
||||
-DBUILD_BENCHMARKS=OFF
|
||||
-DPREFER_EXTERNAL_ZLIB=ON
|
||||
UPDATE_COMMAND ""
|
||||
PATCH_COMMAND ${GIT_EXECUTABLE} apply --whitespace=fix ${CMAKE_CURRENT_SOURCE_DIR}/blosc-mods.patch
|
||||
PATCH_COMMAND ${GIT_EXECUTABLE} reset --hard && git clean -df &&
|
||||
${GIT_EXECUTABLE} apply --whitespace=fix ${CMAKE_CURRENT_SOURCE_DIR}/blosc-mods.patch
|
||||
)
|
||||
|
||||
ExternalProject_Add(dep_openexr
|
||||
|
@ -96,7 +96,6 @@ ExternalProject_Add(dep_openexr
|
|||
-DPYILMBASE_ENABLE:BOOL=OFF
|
||||
-DOPENEXR_VIEWERS_ENABLE:BOOL=OFF
|
||||
-DOPENEXR_BUILD_UTILS:BOOL=OFF
|
||||
UPDATE_COMMAND ""
|
||||
)
|
||||
|
||||
ExternalProject_Add(dep_openvdb
|
||||
|
@ -116,6 +115,6 @@ ExternalProject_Add(dep_openvdb
|
|||
-DOPENVDB_CORE_STATIC=ON
|
||||
-DTBB_STATIC=ON
|
||||
-DOPENVDB_BUILD_VDB_PRINT=ON
|
||||
UPDATE_COMMAND ""
|
||||
PATCH_COMMAND ${GIT_EXECUTABLE} apply --whitespace=fix ${CMAKE_CURRENT_SOURCE_DIR}/openvdb-mods.patch
|
||||
PATCH_COMMAND PATCH_COMMAND ${GIT_EXECUTABLE} checkout -f -- . && git clean -df &&
|
||||
${GIT_EXECUTABLE} apply --whitespace=fix ${CMAKE_CURRENT_SOURCE_DIR}/openvdb-mods.patch
|
||||
)
|
18
deps/deps-windows.cmake
vendored
18
deps/deps-windows.cmake
vendored
|
@ -218,15 +218,16 @@ find_package(Git REQUIRED)
|
|||
|
||||
ExternalProject_Add(dep_qhull
|
||||
EXCLUDE_FROM_ALL 1
|
||||
URL "https://github.com/qhull/qhull/archive/v7.3.2.tar.gz"
|
||||
URL_HASH SHA256=619c8a954880d545194bc03359404ef36a1abd2dde03678089459757fd790cb0
|
||||
#URL "https://github.com/qhull/qhull/archive/v7.3.2.tar.gz"
|
||||
#URL_HASH SHA256=619c8a954880d545194bc03359404ef36a1abd2dde03678089459757fd790cb0
|
||||
GIT_REPOSITORY https://github.com/qhull/qhull.git
|
||||
GIT_TAG 7afedcc73666e46a9f1d74632412ebecf53b1b30 # v7.3.2 plus the mac build patch
|
||||
CMAKE_GENERATOR "${DEP_MSVC_GEN}"
|
||||
CMAKE_ARGS
|
||||
-DCMAKE_INSTALL_PREFIX=${DESTDIR}/usr/local
|
||||
-DBUILD_SHARED_LIBS=OFF
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
|
||||
-DCMAKE_DEBUG_POSTFIX=d
|
||||
UPDATE_COMMAND ""
|
||||
BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj
|
||||
INSTALL_COMMAND ""
|
||||
)
|
||||
|
@ -287,8 +288,8 @@ ExternalProject_Add(dep_blosc
|
|||
-DPREFER_EXTERNAL_ZLIB=ON
|
||||
-DBLOSC_IS_SUBPROJECT:BOOL=ON
|
||||
-DBLOSC_INSTALL:BOOL=ON
|
||||
UPDATE_COMMAND ""
|
||||
PATCH_COMMAND ${GIT_EXECUTABLE} apply --whitespace=fix ${CMAKE_CURRENT_SOURCE_DIR}/blosc-mods.patch
|
||||
PATCH_COMMAND ${GIT_EXECUTABLE} checkout -f -- . && git clean -df &&
|
||||
${GIT_EXECUTABLE} apply --whitespace=fix ${CMAKE_CURRENT_SOURCE_DIR}/blosc-mods.patch
|
||||
BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj
|
||||
INSTALL_COMMAND ""
|
||||
)
|
||||
|
@ -310,7 +311,6 @@ ExternalProject_Add(dep_openexr
|
|||
-DPYILMBASE_ENABLE:BOOL=OFF
|
||||
-DOPENEXR_VIEWERS_ENABLE:BOOL=OFF
|
||||
-DOPENEXR_BUILD_UTILS:BOOL=OFF
|
||||
UPDATE_COMMAND ""
|
||||
BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj
|
||||
INSTALL_COMMAND ""
|
||||
)
|
||||
|
@ -323,7 +323,7 @@ ExternalProject_Add(dep_openvdb
|
|||
#URL_HASH SHA256=dc337399dce8e1c9f21f20e97b1ce7e4933cb0a63bb3b8b734d8fcc464aa0c48
|
||||
GIT_REPOSITORY https://github.com/AcademySoftwareFoundation/openvdb.git
|
||||
GIT_TAG aebaf8d95be5e57fd33949281ec357db4a576c2e #v6.2.1
|
||||
DEPENDS dep_blosc dep_openexr #dep_tbb dep_boost
|
||||
DEPENDS dep_blosc dep_openexr dep_tbb dep_boost
|
||||
CMAKE_GENERATOR "${DEP_MSVC_GEN}"
|
||||
CMAKE_GENERATOR_PLATFORM "${DEP_PLATFORM}"
|
||||
CMAKE_ARGS
|
||||
|
@ -339,8 +339,8 @@ ExternalProject_Add(dep_openvdb
|
|||
-DTBB_STATIC=ON
|
||||
-DOPENVDB_BUILD_VDB_PRINT=ON
|
||||
BUILD_COMMAND msbuild /m /P:Configuration=Release INSTALL.vcxproj
|
||||
UPDATE_COMMAND ""
|
||||
PATCH_COMMAND ${GIT_EXECUTABLE} apply --whitespace=fix ${CMAKE_CURRENT_SOURCE_DIR}/openvdb-mods.patch
|
||||
PATCH_COMMAND ${GIT_EXECUTABLE} checkout -f -- . && git clean -df &&
|
||||
${GIT_EXECUTABLE} apply --whitespace=fix ${CMAKE_CURRENT_SOURCE_DIR}/openvdb-mods.patch
|
||||
INSTALL_COMMAND ""
|
||||
)
|
||||
|
||||
|
|
3
deps/openvdb-mods.patch
vendored
3
deps/openvdb-mods.patch
vendored
|
@ -1,8 +1,9 @@
|
|||
From e48f4a835fe7cb391f9f90945472bd367fb4c4f1 Mon Sep 17 00:00:00 2001
|
||||
From dbe038fce8a15ddc9a5c83ec5156d7bc9e178015 Mon Sep 17 00:00:00 2001
|
||||
From: tamasmeszaros <meszaros.q@gmail.com>
|
||||
Date: Wed, 16 Oct 2019 17:42:50 +0200
|
||||
Subject: [PATCH] Build fixes for PrusaSlicer integration
|
||||
|
||||
Signed-off-by: tamasmeszaros <meszaros.q@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 3 -
|
||||
cmake/FindBlosc.cmake | 218 ---------------
|
||||
|
|
|
@ -1,109 +1,70 @@
|
|||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="180.7mm" height="180.6mm" viewBox="0 0 512.1 512">
|
||||
<title>bed_texture_denser</title>
|
||||
<path d="M510.6,510.9" transform="translate(0.7 0.4)" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<path d="M.4,510.9" transform="translate(0.7 0.4)" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<line x1="511.3" y1="511.3" x2="511.3" y2="0.8" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<path d="M.4.4" transform="translate(0.7 0.4)" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<path d="M510.6.4" transform="translate(0.7 0.4)" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<line x1="1.1" y1="0.8" x2="1.1" y2="511.3" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<line x1="1.1" y1="511.3" x2="1.1" y2="0.8" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<line x1="511.3" y1="0.8" x2="511.3" y2="511.3" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<line x1="1.1" y1="511.3" x2="511.3" y2="511.3" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<line x1="1.1" y1="0.8" x2="511.3" y2="0.8" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" width="180.5mm" height="180.6mm" viewBox="0 0 511.7 512">
|
||||
<title>MINI_bed_texture</title>
|
||||
<path d="M510.6,510.9" transform="translate(0.4 0.4)" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<path d="M.4,510.9" transform="translate(0.4 0.4)" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<line x1="511" y1="511.3" x2="511" y2="0.8" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<path d="M.4.4" transform="translate(0.4 0.4)" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<path d="M510.6.4" transform="translate(0.4 0.4)" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<line x1="0.8" y1="0.8" x2="0.8" y2="511.3" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<line x1="0.8" y1="511.3" x2="0.8" y2="0.8" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<line x1="511" y1="0.8" x2="511" y2="511.3" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<line x1="0.8" y1="511.3" x2="511" y2="511.3" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<line x1="0.8" y1="0.8" x2="511" y2="0.8" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-width: 1.5px"/>
|
||||
<g>
|
||||
<g>
|
||||
<line x1="1.1" y1="383.6" x2="3.2" y2="383.6" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
<line x1="5.4" y1="383.6" x2="7" y2="383.6" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 0.5694814920425415,2.1355555057525635"/>
|
||||
<line x1="8.1" y1="383.6" x2="508.1" y2="383.6" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 4.271111011505127,2.1355555057525635,0.5694814920425415,2.1355555057525635"/>
|
||||
<line x1="509.2" y1="383.6" x2="511.3" y2="383.6" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
<line x1="0.8" y1="383.6" x2="2.9" y2="383.6" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
<line x1="5" y1="383.6" x2="6.6" y2="383.6" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 0.5694814920425415,2.1355555057525635"/>
|
||||
<line x1="7.7" y1="383.6" x2="507.8" y2="383.6" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 4.271111011505127,2.1355555057525635,0.5694814920425415,2.1355555057525635"/>
|
||||
<line x1="508.9" y1="383.6" x2="511" y2="383.6" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="1.1" y1="256" x2="3.2" y2="256" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
<line x1="5.4" y1="256" x2="7" y2="256" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 0.5694814920425415,2.1355555057525635"/>
|
||||
<line x1="8.1" y1="256" x2="508.1" y2="256" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 4.271111011505127,2.1355555057525635,0.5694814920425415,2.1355555057525635"/>
|
||||
<line x1="509.2" y1="256" x2="511.3" y2="256" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
<line x1="0.8" y1="256" x2="2.9" y2="256" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
<line x1="5" y1="256" x2="6.6" y2="256" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 0.5694814920425415,2.1355555057525635"/>
|
||||
<line x1="7.7" y1="256" x2="507.8" y2="256" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 4.271111011505127,2.1355555057525635,0.5694814920425415,2.1355555057525635"/>
|
||||
<line x1="508.9" y1="256" x2="511" y2="256" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="511.3" y1="128.4" x2="509.2" y2="128.4" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
<line x1="507.1" y1="128.4" x2="505.4" y2="128.4" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 0.5694814920425415,2.1355555057525635"/>
|
||||
<line x1="504.4" y1="128.4" x2="4.3" y2="128.4" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 4.271111011505127,2.1355555057525635,0.5694814920425415,2.1355555057525635"/>
|
||||
<line x1="3.2" y1="128.4" x2="1.1" y2="128.4" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
<line x1="511" y1="128.4" x2="508.9" y2="128.4" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
<line x1="506.7" y1="128.4" x2="505.1" y2="128.4" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 0.5694814920425415,2.1355555057525635"/>
|
||||
<line x1="504" y1="128.4" x2="3.9" y2="128.4" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 4.271111011505127,2.1355555057525635,0.5694814920425415,2.1355555057525635"/>
|
||||
<line x1="2.9" y1="128.4" x2="0.8" y2="128.4" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="128.7" y1="511.3" x2="128.7" y2="509.1" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
<line x1="128.7" y1="507" x2="128.7" y2="505.4" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 0.569789707660675,2.1367111206054688"/>
|
||||
<line x1="128.7" y1="504.3" x2="128.7" y2="3.9" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 4.2734222412109375,2.1367111206054688,0.569789707660675,2.1367111206054688"/>
|
||||
<line x1="128.7" y1="2.9" x2="128.7" y2="0.8" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
<line x1="128.3" y1="511.3" x2="128.3" y2="509.1" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
<line x1="128.3" y1="507" x2="128.3" y2="505.4" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 0.569789707660675,2.1367111206054688"/>
|
||||
<line x1="128.3" y1="504.3" x2="128.3" y2="3.9" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 4.2734222412109375,2.1367111206054688,0.569789707660675,2.1367111206054688"/>
|
||||
<line x1="128.3" y1="2.9" x2="128.3" y2="0.8" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="256.2" y1="0.8" x2="256.2" y2="2.9" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
<line x1="256.2" y1="5" x2="256.2" y2="6.7" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 0.569789707660675,2.1367111206054688"/>
|
||||
<line x1="256.2" y1="7.7" x2="256.2" y2="508.1" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 4.2734222412109375,2.1367111206054688,0.569789707660675,2.1367111206054688"/>
|
||||
<line x1="256.2" y1="509.1" x2="256.2" y2="511.3" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
<line x1="255.9" y1="0.8" x2="255.9" y2="2.9" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
<line x1="255.9" y1="5" x2="255.9" y2="6.7" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 0.569789707660675,2.1367111206054688"/>
|
||||
<line x1="255.9" y1="7.7" x2="255.9" y2="508.1" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 4.2734222412109375,2.1367111206054688,0.569789707660675,2.1367111206054688"/>
|
||||
<line x1="255.9" y1="509.1" x2="255.9" y2="511.3" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
</g>
|
||||
<g>
|
||||
<line x1="383.8" y1="482.3" x2="383.8" y2="480.2" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
<line x1="383.8" y1="478" x2="383.8" y2="476.4" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 0.5678567290306091,2.129462480545044"/>
|
||||
<line x1="383.8" y1="475.3" x2="383.8" y2="3.9" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 4.258924961090088,2.129462480545044,0.5678567290306091,2.129462480545044"/>
|
||||
<line x1="383.8" y1="2.9" x2="383.8" y2="0.8" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
<line x1="383.4" y1="484.8" x2="383.4" y2="482.6" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
<line x1="383.4" y1="480.5" x2="383.4" y2="478.9" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 0.5708136558532715,2.1405510902404785"/>
|
||||
<line x1="383.4" y1="477.8" x2="383.4" y2="3.9" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round;stroke-dasharray: 4.281102180480957,2.1405510902404785,0.5708136558532715,2.1405510902404785"/>
|
||||
<line x1="383.4" y1="2.9" x2="383.4" y2="0.8" style="fill: none;stroke: #fff;stroke-linecap: round;stroke-linejoin: round"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M277.3,489.1c4.6,0,7.4,2.8,7.4,8.1s-2.9,8.1-7.4,8.1-7.4-2.9-7.4-8.1S272.9,489.1,277.3,489.1Zm3.7,8.1c0-3.8-1.5-5.7-3.7-5.7s-3.8,1.9-3.8,5.7,1.3,5.6,3.8,5.6S281,500.9,281,497.2Z" transform="translate(0.7 0.4)" style="fill: #fff"/>
|
||||
<path d="M293.1,499h-2.5v6h-3.3V489.3h6.1a7.3,7.3,0,0,1,3.2.6,4.1,4.1,0,0,1,2.6,4,4.4,4.4,0,0,1-3.1,4.3h0l3.5,6.8H296Zm-.1-2.4c1.5,0,2.7-.7,2.7-2.5a2.4,2.4,0,0,0-2.7-2.5h-2.4v5Z" transform="translate(0.7 0.4)" style="fill: #fff"/>
|
||||
<path d="M302,489.3h3.4V505H302Z" transform="translate(0.7 0.4)" style="fill: #fff"/>
|
||||
<path d="M311.6,497.2c0,4,1.4,5.6,3.8,5.6s3.4-1.3,3.6-3.5V499h-3.7v-2.4h6.8V505h-2.6v-2.2h-.1a5,5,0,0,1-4.6,2.5c-4.4,0-6.8-3.1-6.8-7.9s3-8.3,7.4-8.3,6.1,1.7,6.4,4.9h-3.4a2.8,2.8,0,0,0-3-2.5C313,491.5,311.6,493.5,311.6,497.2Z" transform="translate(0.7 0.4)" style="fill: #fff"/>
|
||||
<path d="M325.1,489.3h3.4V505h-3.4Z" transform="translate(0.7 0.4)" style="fill: #fff"/>
|
||||
<path d="M331.7,489.3h3.7l5,8.5a16.8,16.8,0,0,1,1.2,2.3h0V489.3h3.1V505h-3.5l-5.3-8.7a12.8,12.8,0,0,1-1.1-2.4h-.1V505h-3Z" transform="translate(0.7 0.4)" style="fill: #fff"/>
|
||||
<path d="M356.8,501.5H351l-1.1,3.5h-3.2l5.4-15.7h3.8l5.6,15.7h-3.6Zm-3.5-7.1-1.5,4.6H356l-1.5-4.5c-.4-1.4-.6-2.3-.6-2.3h0A15.3,15.3,0,0,1,353.3,494.4Z" transform="translate(0.7 0.4)" style="fill: #fff"/>
|
||||
<path d="M363.4,489.3h3.4v13h6.8V505H363.4Z" transform="translate(0.7 0.4)" style="fill: #fff"/>
|
||||
<path d="M384,499.6V505h-3.4V489.3h5.5c3.4,0,6,1.4,6,5s-2.8,5.3-6.3,5.3Zm2-2.5a2.5,2.5,0,0,0,2.8-2.7c0-1.9-1.1-2.7-2.8-2.7h-2v5.4Z" transform="translate(0.7 0.4)" style="fill: #fff"/>
|
||||
<path d="M400.1,499h-2.4v6h-3.4V489.3h6.1a7.3,7.3,0,0,1,3.2.6,3.9,3.9,0,0,1,2.6,4,4.5,4.5,0,0,1-3,4.3h0l3.5,6.8H403Zm-.1-2.4c1.5,0,2.8-.7,2.8-2.5a2.4,2.4,0,0,0-2.7-2.5h-2.4v5Z" transform="translate(0.7 0.4)" style="fill: #fff"/>
|
||||
<path d="M408.7,489.3H412v9.1a5.6,5.6,0,0,0,.6,3.2,3.6,3.6,0,0,0,5,0c.6-.8.5-2.1.5-3.2v-9.1h3.3v10.2c0,3.9-2.5,5.8-6.4,5.8s-6.3-1.8-6.3-5.8Z" transform="translate(0.7 0.4)" style="fill: #fff"/>
|
||||
<path d="M432.4,493.5a2,2,0,0,0-2.3-2c-1.5,0-2.4.8-2.4,1.9s.6,1.7,2.1,1.9l2.4.5c2.8.5,4.1,2,4.1,4.3s-2.3,5.2-6.3,5.2-6.1-1.9-6.2-4.8h3.5a2.5,2.5,0,0,0,2.8,2.3c1.9,0,2.7-.9,2.7-2.1s-.5-1.7-2.2-2l-2.3-.4c-2.4-.5-4.1-1.9-4.1-4.4s2.2-4.8,5.9-4.8,5.7,2,5.8,4.4Z" transform="translate(0.7 0.4)" style="fill: #fff"/>
|
||||
<path d="M447.4,501.5h-5.7l-1.2,3.5h-3.2l5.5-15.7h3.7l5.6,15.7h-3.5Zm-3.5-7.1-1.4,4.6h4.1l-1.4-4.5a11.3,11.3,0,0,1-.6-2.3h-.1A15.3,15.3,0,0,1,443.9,494.4Z" transform="translate(0.7 0.4)" style="fill: #fff"/>
|
||||
<path d="M458.7,489.3h4.9l2.8,8.4a15.7,15.7,0,0,1,.7,2.3h0l.7-2.3,2.8-8.4h4.9V505h-3.3V492.7h-.1a26.9,26.9,0,0,1-1,3.3l-2.9,9h-2.5l-2.9-9a26.9,26.9,0,0,1-1-3.3h0V505h-3.1Z" transform="translate(0.7 0.4)" style="fill: #fff"/>
|
||||
<path d="M478.6,489.3H482V505h-3.4Z" transform="translate(0.7 0.4)" style="fill: #fff"/>
|
||||
<path d="M485.2,489.3h3.7l5,8.5a16.8,16.8,0,0,1,1.2,2.3h0V489.3h3.1V505h-3.5l-5.3-8.7a12.8,12.8,0,0,1-1.1-2.4h-.1V505h-3Z" transform="translate(0.7 0.4)" style="fill: #fff"/>
|
||||
<path d="M501.3,489.3h3.4V505h-3.4Z" transform="translate(0.7 0.4)" style="fill: #fff"/>
|
||||
<path d="M277.3,489.1c4.6,0,7.4,2.8,7.4,8.1s-2.9,8.1-7.4,8.1-7.4-2.9-7.4-8.1S272.9,489.1,277.3,489.1Zm3.7,8.1c0-3.8-1.5-5.7-3.7-5.7s-3.8,1.9-3.8,5.7,1.3,5.6,3.8,5.6S281,500.9,281,497.2Z" transform="translate(0.4 0.4)" style="fill: #fff"/>
|
||||
<path d="M293.1,499h-2.5v6h-3.3V489.3h6.1a7.3,7.3,0,0,1,3.2.6,4.1,4.1,0,0,1,2.6,4,4.4,4.4,0,0,1-3.1,4.3h0l3.5,6.8H296Zm-.1-2.4c1.5,0,2.7-.7,2.7-2.5a2.4,2.4,0,0,0-2.7-2.5h-2.4v5Z" transform="translate(0.4 0.4)" style="fill: #fff"/>
|
||||
<path d="M302,489.3h3.4V505H302Z" transform="translate(0.4 0.4)" style="fill: #fff"/>
|
||||
<path d="M311.6,497.2c0,4,1.4,5.6,3.8,5.6s3.4-1.3,3.6-3.5V499h-3.7v-2.4h6.8V505h-2.6v-2.2h-.1a5,5,0,0,1-4.6,2.5c-4.4,0-6.8-3.1-6.8-7.9s3-8.3,7.4-8.3,6.1,1.7,6.4,4.9h-3.4a2.8,2.8,0,0,0-3-2.5C313,491.5,311.6,493.5,311.6,497.2Z" transform="translate(0.4 0.4)" style="fill: #fff"/>
|
||||
<path d="M325.1,489.3h3.4V505h-3.4Z" transform="translate(0.4 0.4)" style="fill: #fff"/>
|
||||
<path d="M331.7,489.3h3.7l5,8.5a16.8,16.8,0,0,1,1.2,2.3h0V489.3h3.1V505h-3.5l-5.3-8.7a12.8,12.8,0,0,1-1.1-2.4h-.1V505h-3Z" transform="translate(0.4 0.4)" style="fill: #fff"/>
|
||||
<path d="M356.8,501.5H351l-1.1,3.5h-3.2l5.4-15.7h3.8l5.6,15.7h-3.6Zm-3.5-7.1-1.5,4.6H356l-1.5-4.5c-.4-1.4-.6-2.3-.6-2.3h0A15.3,15.3,0,0,1,353.3,494.4Z" transform="translate(0.4 0.4)" style="fill: #fff"/>
|
||||
<path d="M363.4,489.3h3.4v13h6.8V505H363.4Z" transform="translate(0.4 0.4)" style="fill: #fff"/>
|
||||
<path d="M384,499.6V505h-3.4V489.3h5.5c3.4,0,6,1.4,6,5s-2.8,5.3-6.3,5.3Zm2-2.5a2.5,2.5,0,0,0,2.8-2.7c0-1.9-1.1-2.7-2.8-2.7h-2v5.4Z" transform="translate(0.4 0.4)" style="fill: #fff"/>
|
||||
<path d="M400.1,499h-2.4v6h-3.4V489.3h6.1a7.3,7.3,0,0,1,3.2.6,3.9,3.9,0,0,1,2.6,4,4.5,4.5,0,0,1-3,4.3h0l3.5,6.8H403Zm-.1-2.4c1.5,0,2.8-.7,2.8-2.5a2.4,2.4,0,0,0-2.7-2.5h-2.4v5Z" transform="translate(0.4 0.4)" style="fill: #fff"/>
|
||||
<path d="M408.7,489.3H412v9.1a5.6,5.6,0,0,0,.6,3.2,3.6,3.6,0,0,0,5,0c.6-.8.5-2.1.5-3.2v-9.1h3.3v10.2c0,3.9-2.5,5.8-6.4,5.8s-6.3-1.8-6.3-5.8Z" transform="translate(0.4 0.4)" style="fill: #fff"/>
|
||||
<path d="M432.4,493.5a2,2,0,0,0-2.3-2c-1.5,0-2.4.8-2.4,1.9s.6,1.7,2.1,1.9l2.4.5c2.8.5,4.1,2,4.1,4.3s-2.3,5.2-6.3,5.2-6.1-1.9-6.2-4.8h3.5a2.5,2.5,0,0,0,2.8,2.3c1.9,0,2.7-.9,2.7-2.1s-.5-1.7-2.2-2l-2.3-.4c-2.4-.5-4.1-1.9-4.1-4.4s2.2-4.8,5.9-4.8,5.7,2,5.8,4.4Z" transform="translate(0.4 0.4)" style="fill: #fff"/>
|
||||
<path d="M447.4,501.5h-5.7l-1.2,3.5h-3.2l5.5-15.7h3.7l5.6,15.7h-3.5Zm-3.5-7.1-1.4,4.6h4.1l-1.4-4.5a11.3,11.3,0,0,1-.6-2.3h-.1A15.3,15.3,0,0,1,443.9,494.4Z" transform="translate(0.4 0.4)" style="fill: #fff"/>
|
||||
<path d="M458.7,489.3h4.9l2.8,8.4a15.7,15.7,0,0,1,.7,2.3h0l.7-2.3,2.8-8.4h4.9V505h-3.3V492.7h-.1a26.9,26.9,0,0,1-1,3.3l-2.9,9h-2.5l-2.9-9a26.9,26.9,0,0,1-1-3.3h0V505h-3.1Z" transform="translate(0.4 0.4)" style="fill: #fff"/>
|
||||
<path d="M478.6,489.3H482V505h-3.4Z" transform="translate(0.4 0.4)" style="fill: #fff"/>
|
||||
<path d="M485.2,489.3h3.7l5,8.5a16.8,16.8,0,0,1,1.2,2.3h0V489.3h3.1V505h-3.5l-5.3-8.7a12.8,12.8,0,0,1-1.1-2.4h-.1V505h-3Z" transform="translate(0.4 0.4)" style="fill: #fff"/>
|
||||
<path d="M501.3,489.3h3.4V505h-3.4Z" transform="translate(0.4 0.4)" style="fill: #fff"/>
|
||||
</g>
|
||||
<line x1="0.4" y1="28.7" x2="511" y2="28.7" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="0.4" y1="57.1" x2="511" y2="57.1" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="0.4" y1="28.7" x2="511" y2="28.7" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="0.1" y1="85.4" x2="510.7" y2="85.4" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="0.1" y1="113.8" x2="510.7" y2="113.8" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="0.9" y1="142.1" x2="511.5" y2="142.1" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<path d="M509.9,141.7" transform="translate(0.7 0.4)" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<path d="M-.7,141.7" transform="translate(0.7 0.4)" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="1.5" y1="198.8" x2="512.1" y2="198.8" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line y1="170.5" x2="510.6" y2="170.5" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="0.5" y1="227.2" x2="511.1" y2="227.2" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="0.6" y1="283.9" x2="511.2" y2="283.9" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="0.5" y1="28.7" x2="511.1" y2="28.7" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="0.6" y1="312.2" x2="511.2" y2="312.2" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="0.6" y1="340.6" x2="511.2" y2="340.6" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="0.6" y1="368.9" x2="511.2" y2="368.9" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="0.6" y1="397.2" x2="511.2" y2="397.2" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="0.6" y1="425.6" x2="511.2" y2="425.6" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="0.6" y1="453.9" x2="511.2" y2="453.9" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="0.6" y1="482.3" x2="511.2" y2="482.3" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<path d="M481.9,511.4" transform="translate(0.7 0.4)" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<path d="M481.9.8" transform="translate(0.7 0.4)" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="454.3" y1="1.2" x2="454.3" y2="482.3" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="482.6" y1="1.2" x2="482.6" y2="482.3" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="425.9" y1="0.8" x2="425.9" y2="482.3" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="397.6" y1="0.8" x2="397.6" y2="482.3" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="369.2" y1="1.7" x2="369.2" y2="482.3" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="312.6" y1="2.2" x2="312.6" y2="482.3" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="340.9" y1="0.8" x2="340.9" y2="482.3" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="284.2" y1="1.3" x2="284.2" y2="482.3" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="227.5" y1="1.3" x2="227.5" y2="511.9" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="482.6" y1="1.3" x2="482.6" y2="482.3" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="199.2" y1="1.3" x2="199.2" y2="511.9" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="170.8" y1="1.3" x2="170.8" y2="511.9" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="142.5" y1="1.3" x2="142.5" y2="511.9" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="114.1" y1="1.3" x2="114.1" y2="511.9" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="85.8" y1="1.3" x2="85.8" y2="511.9" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="57.4" y1="1.3" x2="57.4" y2="511.9" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
<line x1="29.1" y1="1.3" x2="29.1" y2="511.9" style="fill: none;stroke: #fff;stroke-miterlimit: 10;stroke-width: 0.25px"/>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 8.8 KiB |
BIN
resources/localization/pt_br/PrusaSlicer.mo
Normal file
BIN
resources/localization/pt_br/PrusaSlicer.mo
Normal file
Binary file not shown.
9386
resources/localization/pt_br/PrusaSlicer_pt_br.po
Normal file
9386
resources/localization/pt_br/PrusaSlicer_pt_br.po
Normal file
File diff suppressed because it is too large
Load diff
45
resources/udev/90-3dconnexion.rules
Normal file
45
resources/udev/90-3dconnexion.rules
Normal file
|
@ -0,0 +1,45 @@
|
|||
# See src/slic3r/GUI/Mouse3DController.cpp for the list of devices
|
||||
|
||||
# Logitech vendor devices
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c603", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c605", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c606", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c621", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c623", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c625", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c626", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c627", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c628", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c629", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c62b", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c62e", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c62f", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c631", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c632", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c633", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c635", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c636", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c640", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="046d", ATTRS{idProduct}=="c652", MODE="0666"
|
||||
|
||||
# 3D Connexion vendor devices
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c603", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c605", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c606", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c621", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c623", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c625", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c626", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c627", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c628", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c629", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c62b", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c62e", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c62f", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c631", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c632", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c633", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c635", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c636", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c640", MODE="0666"
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="256f", ATTRS{idProduct}=="c652", MODE="0666"
|
|
@ -22,6 +22,8 @@ add_subdirectory(libslic3r)
|
|||
|
||||
if (SLIC3R_GUI)
|
||||
add_subdirectory(imgui)
|
||||
add_subdirectory(hidapi)
|
||||
include_directories(hidapi/include)
|
||||
|
||||
if(WIN32)
|
||||
message(STATUS "WXWIN environment set to: $ENV{WXWIN}")
|
||||
|
|
|
@ -184,9 +184,20 @@ extern void stl_mirror_xz(stl_file *stl);
|
|||
|
||||
extern void stl_get_size(stl_file *stl);
|
||||
|
||||
// the following function is not used
|
||||
/*
|
||||
template<typename T>
|
||||
extern void stl_transform(stl_file *stl, T *trafo3x4)
|
||||
{
|
||||
Eigen::Matrix<T, 3, 3, Eigen::DontAlign> trafo3x3;
|
||||
for (int i = 0; i < 3; ++i)
|
||||
{
|
||||
for (int j = 0; j < 3; ++j)
|
||||
{
|
||||
trafo3x3(i, j) = (i * 4) + j;
|
||||
}
|
||||
}
|
||||
Eigen::Matrix<T, 3, 3, Eigen::DontAlign> r = trafo3x3.inverse().transpose();
|
||||
for (uint32_t i_face = 0; i_face < stl->stats.number_of_facets; ++ i_face) {
|
||||
stl_facet &face = stl->facet_start[i_face];
|
||||
for (int i_vertex = 0; i_vertex < 3; ++ i_vertex) {
|
||||
|
@ -196,20 +207,17 @@ extern void stl_transform(stl_file *stl, T *trafo3x4)
|
|||
v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2) + trafo3x4[7]);
|
||||
v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2) + trafo3x4[11]);
|
||||
}
|
||||
stl_vertex &v_dst = face.normal;
|
||||
stl_vertex v_src = v_dst;
|
||||
v_dst(0) = T(trafo3x4[0] * v_src(0) + trafo3x4[1] * v_src(1) + trafo3x4[2] * v_src(2));
|
||||
v_dst(1) = T(trafo3x4[4] * v_src(0) + trafo3x4[5] * v_src(1) + trafo3x4[6] * v_src(2));
|
||||
v_dst(2) = T(trafo3x4[8] * v_src(0) + trafo3x4[9] * v_src(1) + trafo3x4[10] * v_src(2));
|
||||
face.normal = (r * face.normal.template cast<T>()).template cast<float>().eval();
|
||||
}
|
||||
|
||||
stl_get_size(stl);
|
||||
}
|
||||
*/
|
||||
|
||||
template<typename T>
|
||||
inline void stl_transform(stl_file *stl, const Eigen::Transform<T, 3, Eigen::Affine, Eigen::DontAlign>& t)
|
||||
{
|
||||
const Eigen::Matrix<double, 3, 3, Eigen::DontAlign> r = t.matrix().template block<3, 3>(0, 0);
|
||||
const Eigen::Matrix<T, 3, 3, Eigen::DontAlign> r = t.matrix().template block<3, 3>(0, 0).inverse().transpose();
|
||||
for (size_t i = 0; i < stl->stats.number_of_facets; ++ i) {
|
||||
stl_facet &f = stl->facet_start[i];
|
||||
for (size_t j = 0; j < 3; ++j)
|
||||
|
@ -223,11 +231,12 @@ inline void stl_transform(stl_file *stl, const Eigen::Transform<T, 3, Eigen::Aff
|
|||
template<typename T>
|
||||
inline void stl_transform(stl_file *stl, const Eigen::Matrix<T, 3, 3, Eigen::DontAlign>& m)
|
||||
{
|
||||
const Eigen::Matrix<T, 3, 3, Eigen::DontAlign> r = m.inverse().transpose();
|
||||
for (size_t i = 0; i < stl->stats.number_of_facets; ++ i) {
|
||||
stl_facet &f = stl->facet_start[i];
|
||||
for (size_t j = 0; j < 3; ++j)
|
||||
f.vertex[j] = (m * f.vertex[j].template cast<T>()).template cast<float>().eval();
|
||||
f.normal = (m * f.normal.template cast<T>()).template cast<float>().eval();
|
||||
f.normal = (r * f.normal.template cast<T>()).template cast<float>().eval();
|
||||
}
|
||||
|
||||
stl_get_size(stl);
|
||||
|
|
17
src/hidapi/CMakeLists.txt
Normal file
17
src/hidapi/CMakeLists.txt
Normal file
|
@ -0,0 +1,17 @@
|
|||
|
||||
if (WIN32)
|
||||
set(HIDAPI_IMPL win/hid.c)
|
||||
elseif (APPLE)
|
||||
set(HIDAPI_IMPL mac/hid.c)
|
||||
else ()
|
||||
# Assume Linux or Unix other than Mac OS
|
||||
set(HIDAPI_IMPL linux/hid.c)
|
||||
endif()
|
||||
|
||||
include_directories(include)
|
||||
|
||||
add_library(hidapi STATIC ${HIDAPI_IMPL})
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
target_link_libraries(hidapi udev)
|
||||
endif()
|
395
src/hidapi/include/hidapi.h
Normal file
395
src/hidapi/include/hidapi.h
Normal file
|
@ -0,0 +1,395 @@
|
|||
/*******************************************************
|
||||
HIDAPI - Multi-Platform library for
|
||||
communication with HID devices.
|
||||
|
||||
Alan Ott
|
||||
Signal 11 Software
|
||||
|
||||
8/22/2009
|
||||
|
||||
Copyright 2009, All Rights Reserved.
|
||||
|
||||
At the discretion of the user of this library,
|
||||
this software may be licensed under the terms of the
|
||||
GNU General Public License v3, a BSD-Style license, or the
|
||||
original HIDAPI license as outlined in the LICENSE.txt,
|
||||
LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
|
||||
files located at the root of the source distribution.
|
||||
These files may also be found in the public source
|
||||
code repository located at:
|
||||
http://github.com/signal11/hidapi .
|
||||
********************************************************/
|
||||
|
||||
/** @file
|
||||
* @defgroup API hidapi API
|
||||
*/
|
||||
|
||||
#ifndef HIDAPI_H__
|
||||
#define HIDAPI_H__
|
||||
|
||||
#include <wchar.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define HID_API_EXPORT __declspec(dllexport)
|
||||
#define HID_API_CALL
|
||||
#else
|
||||
#define HID_API_EXPORT /**< API export macro */
|
||||
#define HID_API_CALL /**< API call macro */
|
||||
#endif
|
||||
|
||||
#define HID_API_EXPORT_CALL HID_API_EXPORT HID_API_CALL /**< API export and call macro*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
struct hid_device_;
|
||||
typedef struct hid_device_ hid_device; /**< opaque hidapi structure */
|
||||
|
||||
/** hidapi info structure */
|
||||
struct hid_device_info {
|
||||
/** Platform-specific device path */
|
||||
char *path;
|
||||
/** Device Vendor ID */
|
||||
unsigned short vendor_id;
|
||||
/** Device Product ID */
|
||||
unsigned short product_id;
|
||||
/** Serial Number */
|
||||
wchar_t *serial_number;
|
||||
/** Device Release Number in binary-coded decimal,
|
||||
also known as Device Version Number */
|
||||
unsigned short release_number;
|
||||
/** Manufacturer String */
|
||||
wchar_t *manufacturer_string;
|
||||
/** Product string */
|
||||
wchar_t *product_string;
|
||||
/** Usage Page for this Device/Interface
|
||||
(Windows/Mac only). */
|
||||
unsigned short usage_page;
|
||||
/** Usage for this Device/Interface
|
||||
(Windows/Mac only).*/
|
||||
unsigned short usage;
|
||||
/** The USB interface which this logical device
|
||||
represents.
|
||||
|
||||
* Valid on both Linux implementations in all cases.
|
||||
* Valid on the Windows implementation only if the device
|
||||
contains more than one interface.
|
||||
* Valid on the Mac implementation if and only if the device
|
||||
is a USB HID device. */
|
||||
int interface_number;
|
||||
|
||||
/** Pointer to the next device */
|
||||
struct hid_device_info *next;
|
||||
};
|
||||
|
||||
|
||||
/** @brief Initialize the HIDAPI library.
|
||||
|
||||
This function initializes the HIDAPI library. Calling it is not
|
||||
strictly necessary, as it will be called automatically by
|
||||
hid_enumerate() and any of the hid_open_*() functions if it is
|
||||
needed. This function should be called at the beginning of
|
||||
execution however, if there is a chance of HIDAPI handles
|
||||
being opened by different threads simultaneously.
|
||||
|
||||
@ingroup API
|
||||
|
||||
@returns
|
||||
This function returns 0 on success and -1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT HID_API_CALL hid_init(void);
|
||||
|
||||
/** @brief Finalize the HIDAPI library.
|
||||
|
||||
This function frees all of the static data associated with
|
||||
HIDAPI. It should be called at the end of execution to avoid
|
||||
memory leaks.
|
||||
|
||||
@ingroup API
|
||||
|
||||
@returns
|
||||
This function returns 0 on success and -1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT HID_API_CALL hid_exit(void);
|
||||
|
||||
/** @brief Enumerate the HID Devices.
|
||||
|
||||
This function returns a linked list of all the HID devices
|
||||
attached to the system which match vendor_id and product_id.
|
||||
If @p vendor_id is set to 0 then any vendor matches.
|
||||
If @p product_id is set to 0 then any product matches.
|
||||
If @p vendor_id and @p product_id are both set to 0, then
|
||||
all HID devices will be returned.
|
||||
|
||||
@ingroup API
|
||||
@param vendor_id The Vendor ID (VID) of the types of device
|
||||
to open.
|
||||
@param product_id The Product ID (PID) of the types of
|
||||
device to open.
|
||||
|
||||
@returns
|
||||
This function returns a pointer to a linked list of type
|
||||
struct #hid_device_info, containing information about the HID devices
|
||||
attached to the system, or NULL in the case of failure. Free
|
||||
this linked list by calling hid_free_enumeration().
|
||||
*/
|
||||
struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id);
|
||||
|
||||
/** @brief Free an enumeration Linked List
|
||||
|
||||
This function frees a linked list created by hid_enumerate().
|
||||
|
||||
@ingroup API
|
||||
@param devs Pointer to a list of struct_device returned from
|
||||
hid_enumerate().
|
||||
*/
|
||||
void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs);
|
||||
|
||||
/** @brief Open a HID device using a Vendor ID (VID), Product ID
|
||||
(PID) and optionally a serial number.
|
||||
|
||||
If @p serial_number is NULL, the first device with the
|
||||
specified VID and PID is opened.
|
||||
|
||||
@ingroup API
|
||||
@param vendor_id The Vendor ID (VID) of the device to open.
|
||||
@param product_id The Product ID (PID) of the device to open.
|
||||
@param serial_number The Serial Number of the device to open
|
||||
(Optionally NULL).
|
||||
|
||||
@returns
|
||||
This function returns a pointer to a #hid_device object on
|
||||
success or NULL on failure.
|
||||
*/
|
||||
HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number);
|
||||
|
||||
/** @brief Open a HID device by its path name.
|
||||
|
||||
The path name be determined by calling hid_enumerate(), or a
|
||||
platform-specific path name can be used (eg: /dev/hidraw0 on
|
||||
Linux).
|
||||
|
||||
@ingroup API
|
||||
@param path The path name of the device to open
|
||||
|
||||
@returns
|
||||
This function returns a pointer to a #hid_device object on
|
||||
success or NULL on failure.
|
||||
*/
|
||||
HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path);
|
||||
|
||||
/** @brief Write an Output report to a HID device.
|
||||
|
||||
The first byte of @p data[] must contain the Report ID. For
|
||||
devices which only support a single report, this must be set
|
||||
to 0x0. The remaining bytes contain the report data. Since
|
||||
the Report ID is mandatory, calls to hid_write() will always
|
||||
contain one more byte than the report contains. For example,
|
||||
if a hid report is 16 bytes long, 17 bytes must be passed to
|
||||
hid_write(), the Report ID (or 0x0, for devices with a
|
||||
single report), followed by the report data (16 bytes). In
|
||||
this example, the length passed in would be 17.
|
||||
|
||||
hid_write() will send the data on the first OUT endpoint, if
|
||||
one exists. If it does not, it will send the data through
|
||||
the Control Endpoint (Endpoint 0).
|
||||
|
||||
@ingroup API
|
||||
@param dev A device handle returned from hid_open().
|
||||
@param data The data to send, including the report number as
|
||||
the first byte.
|
||||
@param length The length in bytes of the data to send.
|
||||
|
||||
@returns
|
||||
This function returns the actual number of bytes written and
|
||||
-1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length);
|
||||
|
||||
/** @brief Read an Input report from a HID device with timeout.
|
||||
|
||||
Input reports are returned
|
||||
to the host through the INTERRUPT IN endpoint. The first byte will
|
||||
contain the Report number if the device uses numbered reports.
|
||||
|
||||
@ingroup API
|
||||
@param dev A device handle returned from hid_open().
|
||||
@param data A buffer to put the read data into.
|
||||
@param length The number of bytes to read. For devices with
|
||||
multiple reports, make sure to read an extra byte for
|
||||
the report number.
|
||||
@param milliseconds timeout in milliseconds or -1 for blocking wait.
|
||||
|
||||
@returns
|
||||
This function returns the actual number of bytes read and
|
||||
-1 on error. If no packet was available to be read within
|
||||
the timeout period, this function returns 0.
|
||||
*/
|
||||
int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds);
|
||||
|
||||
/** @brief Read an Input report from a HID device.
|
||||
|
||||
Input reports are returned
|
||||
to the host through the INTERRUPT IN endpoint. The first byte will
|
||||
contain the Report number if the device uses numbered reports.
|
||||
|
||||
@ingroup API
|
||||
@param dev A device handle returned from hid_open().
|
||||
@param data A buffer to put the read data into.
|
||||
@param length The number of bytes to read. For devices with
|
||||
multiple reports, make sure to read an extra byte for
|
||||
the report number.
|
||||
|
||||
@returns
|
||||
This function returns the actual number of bytes read and
|
||||
-1 on error. If no packet was available to be read and
|
||||
the handle is in non-blocking mode, this function returns 0.
|
||||
*/
|
||||
int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length);
|
||||
|
||||
/** @brief Set the device handle to be non-blocking.
|
||||
|
||||
In non-blocking mode calls to hid_read() will return
|
||||
immediately with a value of 0 if there is no data to be
|
||||
read. In blocking mode, hid_read() will wait (block) until
|
||||
there is data to read before returning.
|
||||
|
||||
Nonblocking can be turned on and off at any time.
|
||||
|
||||
@ingroup API
|
||||
@param dev A device handle returned from hid_open().
|
||||
@param nonblock enable or not the nonblocking reads
|
||||
- 1 to enable nonblocking
|
||||
- 0 to disable nonblocking.
|
||||
|
||||
@returns
|
||||
This function returns 0 on success and -1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock);
|
||||
|
||||
/** @brief Send a Feature report to the device.
|
||||
|
||||
Feature reports are sent over the Control endpoint as a
|
||||
Set_Report transfer. The first byte of @p data[] must
|
||||
contain the Report ID. For devices which only support a
|
||||
single report, this must be set to 0x0. The remaining bytes
|
||||
contain the report data. Since the Report ID is mandatory,
|
||||
calls to hid_send_feature_report() will always contain one
|
||||
more byte than the report contains. For example, if a hid
|
||||
report is 16 bytes long, 17 bytes must be passed to
|
||||
hid_send_feature_report(): the Report ID (or 0x0, for
|
||||
devices which do not use numbered reports), followed by the
|
||||
report data (16 bytes). In this example, the length passed
|
||||
in would be 17.
|
||||
|
||||
@ingroup API
|
||||
@param dev A device handle returned from hid_open().
|
||||
@param data The data to send, including the report number as
|
||||
the first byte.
|
||||
@param length The length in bytes of the data to send, including
|
||||
the report number.
|
||||
|
||||
@returns
|
||||
This function returns the actual number of bytes written and
|
||||
-1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length);
|
||||
|
||||
/** @brief Get a feature report from a HID device.
|
||||
|
||||
Set the first byte of @p data[] to the Report ID of the
|
||||
report to be read. Make sure to allow space for this
|
||||
extra byte in @p data[]. Upon return, the first byte will
|
||||
still contain the Report ID, and the report data will
|
||||
start in data[1].
|
||||
|
||||
@ingroup API
|
||||
@param dev A device handle returned from hid_open().
|
||||
@param data A buffer to put the read data into, including
|
||||
the Report ID. Set the first byte of @p data[] to the
|
||||
Report ID of the report to be read, or set it to zero
|
||||
if your device does not use numbered reports.
|
||||
@param length The number of bytes to read, including an
|
||||
extra byte for the report ID. The buffer can be longer
|
||||
than the actual report.
|
||||
|
||||
@returns
|
||||
This function returns the number of bytes read plus
|
||||
one for the report ID (which is still in the first
|
||||
byte), or -1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length);
|
||||
|
||||
/** @brief Close a HID device.
|
||||
|
||||
@ingroup API
|
||||
@param dev A device handle returned from hid_open().
|
||||
*/
|
||||
void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev);
|
||||
|
||||
/** @brief Get The Manufacturer String from a HID device.
|
||||
|
||||
@ingroup API
|
||||
@param dev A device handle returned from hid_open().
|
||||
@param string A wide string buffer to put the data into.
|
||||
@param maxlen The length of the buffer in multiples of wchar_t.
|
||||
|
||||
@returns
|
||||
This function returns 0 on success and -1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen);
|
||||
|
||||
/** @brief Get The Product String from a HID device.
|
||||
|
||||
@ingroup API
|
||||
@param dev A device handle returned from hid_open().
|
||||
@param string A wide string buffer to put the data into.
|
||||
@param maxlen The length of the buffer in multiples of wchar_t.
|
||||
|
||||
@returns
|
||||
This function returns 0 on success and -1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen);
|
||||
|
||||
/** @brief Get The Serial Number String from a HID device.
|
||||
|
||||
@ingroup API
|
||||
@param dev A device handle returned from hid_open().
|
||||
@param string A wide string buffer to put the data into.
|
||||
@param maxlen The length of the buffer in multiples of wchar_t.
|
||||
|
||||
@returns
|
||||
This function returns 0 on success and -1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen);
|
||||
|
||||
/** @brief Get a string from a HID device, based on its string index.
|
||||
|
||||
@ingroup API
|
||||
@param dev A device handle returned from hid_open().
|
||||
@param string_index The index of the string to get.
|
||||
@param string A wide string buffer to put the data into.
|
||||
@param maxlen The length of the buffer in multiples of wchar_t.
|
||||
|
||||
@returns
|
||||
This function returns 0 on success and -1 on error.
|
||||
*/
|
||||
int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen);
|
||||
|
||||
/** @brief Get a string describing the last error which occurred.
|
||||
|
||||
@ingroup API
|
||||
@param dev A device handle returned from hid_open().
|
||||
|
||||
@returns
|
||||
This function returns a string containing the last error
|
||||
which occurred or NULL if none has occurred.
|
||||
*/
|
||||
HID_API_EXPORT const wchar_t* HID_API_CALL hid_error(hid_device *dev);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
797
src/hidapi/linux/hid.c
Normal file
797
src/hidapi/linux/hid.c
Normal file
|
@ -0,0 +1,797 @@
|
|||
/*******************************************************
|
||||
HIDAPI - Multi-Platform library for
|
||||
communication with HID devices.
|
||||
|
||||
Alan Ott
|
||||
Signal 11 Software
|
||||
|
||||
8/22/2009
|
||||
Linux Version - 6/2/2009
|
||||
|
||||
Copyright 2009, All Rights Reserved.
|
||||
|
||||
At the discretion of the user of this library,
|
||||
this software may be licensed under the terms of the
|
||||
GNU General Public License v3, a BSD-Style license, or the
|
||||
original HIDAPI license as outlined in the LICENSE.txt,
|
||||
LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
|
||||
files located at the root of the source distribution.
|
||||
These files may also be found in the public source
|
||||
code repository located at:
|
||||
http://github.com/signal11/hidapi .
|
||||
********************************************************/
|
||||
|
||||
/* C */
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <locale.h>
|
||||
#include <errno.h>
|
||||
|
||||
/* Unix */
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
|
||||
/* Linux */
|
||||
#include <linux/hidraw.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/input.h>
|
||||
#include <libudev.h>
|
||||
|
||||
#include "hidapi.h"
|
||||
|
||||
/* Definitions from linux/hidraw.h. Since these are new, some distros
|
||||
may not have header files which contain them. */
|
||||
#ifndef HIDIOCSFEATURE
|
||||
#define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x06, len)
|
||||
#endif
|
||||
#ifndef HIDIOCGFEATURE
|
||||
#define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x07, len)
|
||||
#endif
|
||||
|
||||
|
||||
/* USB HID device property names */
|
||||
const char *device_string_names[] = {
|
||||
"manufacturer",
|
||||
"product",
|
||||
"serial",
|
||||
};
|
||||
|
||||
/* Symbolic names for the properties above */
|
||||
enum device_string_id {
|
||||
DEVICE_STRING_MANUFACTURER,
|
||||
DEVICE_STRING_PRODUCT,
|
||||
DEVICE_STRING_SERIAL,
|
||||
|
||||
DEVICE_STRING_COUNT,
|
||||
};
|
||||
|
||||
struct hid_device_ {
|
||||
int device_handle;
|
||||
int blocking;
|
||||
int uses_numbered_reports;
|
||||
};
|
||||
|
||||
|
||||
static __u32 kernel_version = 0;
|
||||
|
||||
static __u32 detect_kernel_version(void)
|
||||
{
|
||||
struct utsname name;
|
||||
int major, minor, release;
|
||||
int ret;
|
||||
|
||||
uname(&name);
|
||||
ret = sscanf(name.release, "%d.%d.%d", &major, &minor, &release);
|
||||
if (ret == 3) {
|
||||
return KERNEL_VERSION(major, minor, release);
|
||||
}
|
||||
|
||||
ret = sscanf(name.release, "%d.%d", &major, &minor);
|
||||
if (ret == 2) {
|
||||
return KERNEL_VERSION(major, minor, 0);
|
||||
}
|
||||
|
||||
printf("Couldn't determine kernel version from version string \"%s\"\n", name.release);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static hid_device *new_hid_device(void)
|
||||
{
|
||||
hid_device *dev = calloc(1, sizeof(hid_device));
|
||||
dev->device_handle = -1;
|
||||
dev->blocking = 1;
|
||||
dev->uses_numbered_reports = 0;
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
|
||||
/* The caller must free the returned string with free(). */
|
||||
static wchar_t *utf8_to_wchar_t(const char *utf8)
|
||||
{
|
||||
wchar_t *ret = NULL;
|
||||
|
||||
if (utf8) {
|
||||
size_t wlen = mbstowcs(NULL, utf8, 0);
|
||||
if ((size_t) -1 == wlen) {
|
||||
return wcsdup(L"");
|
||||
}
|
||||
ret = calloc(wlen+1, sizeof(wchar_t));
|
||||
mbstowcs(ret, utf8, wlen+1);
|
||||
ret[wlen] = 0x0000;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Get an attribute value from a udev_device and return it as a whar_t
|
||||
string. The returned string must be freed with free() when done.*/
|
||||
static wchar_t *copy_udev_string(struct udev_device *dev, const char *udev_name)
|
||||
{
|
||||
return utf8_to_wchar_t(udev_device_get_sysattr_value(dev, udev_name));
|
||||
}
|
||||
|
||||
/* uses_numbered_reports() returns 1 if report_descriptor describes a device
|
||||
which contains numbered reports. */
|
||||
static int uses_numbered_reports(__u8 *report_descriptor, __u32 size) {
|
||||
unsigned int i = 0;
|
||||
int size_code;
|
||||
int data_len, key_size;
|
||||
|
||||
while (i < size) {
|
||||
int key = report_descriptor[i];
|
||||
|
||||
/* Check for the Report ID key */
|
||||
if (key == 0x85/*Report ID*/) {
|
||||
/* This device has a Report ID, which means it uses
|
||||
numbered reports. */
|
||||
return 1;
|
||||
}
|
||||
|
||||
//printf("key: %02hhx\n", key);
|
||||
|
||||
if ((key & 0xf0) == 0xf0) {
|
||||
/* This is a Long Item. The next byte contains the
|
||||
length of the data section (value) for this key.
|
||||
See the HID specification, version 1.11, section
|
||||
6.2.2.3, titled "Long Items." */
|
||||
if (i+1 < size)
|
||||
data_len = report_descriptor[i+1];
|
||||
else
|
||||
data_len = 0; /* malformed report */
|
||||
key_size = 3;
|
||||
}
|
||||
else {
|
||||
/* This is a Short Item. The bottom two bits of the
|
||||
key contain the size code for the data section
|
||||
(value) for this key. Refer to the HID
|
||||
specification, version 1.11, section 6.2.2.2,
|
||||
titled "Short Items." */
|
||||
size_code = key & 0x3;
|
||||
switch (size_code) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
data_len = size_code;
|
||||
break;
|
||||
case 3:
|
||||
data_len = 4;
|
||||
break;
|
||||
default:
|
||||
/* Can't ever happen since size_code is & 0x3 */
|
||||
data_len = 0;
|
||||
break;
|
||||
};
|
||||
key_size = 1;
|
||||
}
|
||||
|
||||
/* Skip over this key and it's associated data */
|
||||
i += data_len + key_size;
|
||||
}
|
||||
|
||||
/* Didn't find a Report ID key. Device doesn't use numbered reports. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The caller is responsible for free()ing the (newly-allocated) character
|
||||
* strings pointed to by serial_number_utf8 and product_name_utf8 after use.
|
||||
*/
|
||||
static int
|
||||
parse_uevent_info(const char *uevent, int *bus_type,
|
||||
unsigned short *vendor_id, unsigned short *product_id,
|
||||
char **serial_number_utf8, char **product_name_utf8)
|
||||
{
|
||||
char *tmp = strdup(uevent);
|
||||
char *saveptr = NULL;
|
||||
char *line;
|
||||
char *key;
|
||||
char *value;
|
||||
|
||||
int found_id = 0;
|
||||
int found_serial = 0;
|
||||
int found_name = 0;
|
||||
|
||||
line = strtok_r(tmp, "\n", &saveptr);
|
||||
while (line != NULL) {
|
||||
/* line: "KEY=value" */
|
||||
key = line;
|
||||
value = strchr(line, '=');
|
||||
if (!value) {
|
||||
goto next_line;
|
||||
}
|
||||
*value = '\0';
|
||||
value++;
|
||||
|
||||
if (strcmp(key, "HID_ID") == 0) {
|
||||
/**
|
||||
* type vendor product
|
||||
* HID_ID=0003:000005AC:00008242
|
||||
**/
|
||||
int ret = sscanf(value, "%x:%hx:%hx", bus_type, vendor_id, product_id);
|
||||
if (ret == 3) {
|
||||
found_id = 1;
|
||||
}
|
||||
} else if (strcmp(key, "HID_NAME") == 0) {
|
||||
/* The caller has to free the product name */
|
||||
*product_name_utf8 = strdup(value);
|
||||
found_name = 1;
|
||||
} else if (strcmp(key, "HID_UNIQ") == 0) {
|
||||
/* The caller has to free the serial number */
|
||||
*serial_number_utf8 = strdup(value);
|
||||
found_serial = 1;
|
||||
}
|
||||
|
||||
next_line:
|
||||
line = strtok_r(NULL, "\n", &saveptr);
|
||||
}
|
||||
|
||||
free(tmp);
|
||||
return (found_id && found_name && found_serial);
|
||||
}
|
||||
|
||||
|
||||
static int get_device_string(hid_device *dev, enum device_string_id key, wchar_t *string, size_t maxlen)
|
||||
{
|
||||
struct udev *udev;
|
||||
struct udev_device *udev_dev, *parent, *hid_dev;
|
||||
struct stat s;
|
||||
int ret = -1;
|
||||
char *serial_number_utf8 = NULL;
|
||||
char *product_name_utf8 = NULL;
|
||||
|
||||
/* Create the udev object */
|
||||
udev = udev_new();
|
||||
if (!udev) {
|
||||
printf("Can't create udev\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Get the dev_t (major/minor numbers) from the file handle. */
|
||||
ret = fstat(dev->device_handle, &s);
|
||||
if (-1 == ret)
|
||||
return ret;
|
||||
/* Open a udev device from the dev_t. 'c' means character device. */
|
||||
udev_dev = udev_device_new_from_devnum(udev, 'c', s.st_rdev);
|
||||
if (udev_dev) {
|
||||
hid_dev = udev_device_get_parent_with_subsystem_devtype(
|
||||
udev_dev,
|
||||
"hid",
|
||||
NULL);
|
||||
if (hid_dev) {
|
||||
unsigned short dev_vid;
|
||||
unsigned short dev_pid;
|
||||
int bus_type;
|
||||
size_t retm;
|
||||
|
||||
ret = parse_uevent_info(
|
||||
udev_device_get_sysattr_value(hid_dev, "uevent"),
|
||||
&bus_type,
|
||||
&dev_vid,
|
||||
&dev_pid,
|
||||
&serial_number_utf8,
|
||||
&product_name_utf8);
|
||||
|
||||
if (bus_type == BUS_BLUETOOTH) {
|
||||
switch (key) {
|
||||
case DEVICE_STRING_MANUFACTURER:
|
||||
wcsncpy(string, L"", maxlen);
|
||||
ret = 0;
|
||||
break;
|
||||
case DEVICE_STRING_PRODUCT:
|
||||
retm = mbstowcs(string, product_name_utf8, maxlen);
|
||||
ret = (retm == (size_t)-1)? -1: 0;
|
||||
break;
|
||||
case DEVICE_STRING_SERIAL:
|
||||
retm = mbstowcs(string, serial_number_utf8, maxlen);
|
||||
ret = (retm == (size_t)-1)? -1: 0;
|
||||
break;
|
||||
case DEVICE_STRING_COUNT:
|
||||
default:
|
||||
ret = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
/* This is a USB device. Find its parent USB Device node. */
|
||||
parent = udev_device_get_parent_with_subsystem_devtype(
|
||||
udev_dev,
|
||||
"usb",
|
||||
"usb_device");
|
||||
if (parent) {
|
||||
const char *str;
|
||||
const char *key_str = NULL;
|
||||
|
||||
if (key >= 0 && key < DEVICE_STRING_COUNT) {
|
||||
key_str = device_string_names[key];
|
||||
} else {
|
||||
ret = -1;
|
||||
goto end;
|
||||
}
|
||||
|
||||
str = udev_device_get_sysattr_value(parent, key_str);
|
||||
if (str) {
|
||||
/* Convert the string from UTF-8 to wchar_t */
|
||||
retm = mbstowcs(string, str, maxlen);
|
||||
ret = (retm == (size_t)-1)? -1: 0;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
free(serial_number_utf8);
|
||||
free(product_name_utf8);
|
||||
|
||||
udev_device_unref(udev_dev);
|
||||
/* parent and hid_dev don't need to be (and can't be) unref'd.
|
||||
I'm not sure why, but they'll throw double-free() errors. */
|
||||
udev_unref(udev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT hid_init(void)
|
||||
{
|
||||
const char *locale;
|
||||
|
||||
/* Set the locale if it's not set. */
|
||||
locale = setlocale(LC_CTYPE, NULL);
|
||||
if (!locale)
|
||||
setlocale(LC_CTYPE, "");
|
||||
|
||||
kernel_version = detect_kernel_version();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT hid_exit(void)
|
||||
{
|
||||
/* Nothing to do for this in the Linux/hidraw implementation. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
struct hid_device_info HID_API_EXPORT *hid_enumerate(unsigned short vendor_id, unsigned short product_id)
|
||||
{
|
||||
struct udev *udev;
|
||||
struct udev_enumerate *enumerate;
|
||||
struct udev_list_entry *devices, *dev_list_entry;
|
||||
|
||||
struct hid_device_info *root = NULL; /* return object */
|
||||
struct hid_device_info *cur_dev = NULL;
|
||||
struct hid_device_info *prev_dev = NULL; /* previous device */
|
||||
|
||||
hid_init();
|
||||
|
||||
/* Create the udev object */
|
||||
udev = udev_new();
|
||||
if (!udev) {
|
||||
printf("Can't create udev\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Create a list of the devices in the 'hidraw' subsystem. */
|
||||
enumerate = udev_enumerate_new(udev);
|
||||
udev_enumerate_add_match_subsystem(enumerate, "hidraw");
|
||||
udev_enumerate_scan_devices(enumerate);
|
||||
devices = udev_enumerate_get_list_entry(enumerate);
|
||||
/* For each item, see if it matches the vid/pid, and if so
|
||||
create a udev_device record for it */
|
||||
udev_list_entry_foreach(dev_list_entry, devices) {
|
||||
const char *sysfs_path;
|
||||
const char *dev_path;
|
||||
const char *str;
|
||||
struct udev_device *raw_dev; /* The device's hidraw udev node. */
|
||||
struct udev_device *hid_dev; /* The device's HID udev node. */
|
||||
struct udev_device *usb_dev; /* The device's USB udev node. */
|
||||
struct udev_device *intf_dev; /* The device's interface (in the USB sense). */
|
||||
unsigned short dev_vid;
|
||||
unsigned short dev_pid;
|
||||
char *serial_number_utf8 = NULL;
|
||||
char *product_name_utf8 = NULL;
|
||||
int bus_type;
|
||||
int result;
|
||||
|
||||
/* Get the filename of the /sys entry for the device
|
||||
and create a udev_device object (dev) representing it */
|
||||
sysfs_path = udev_list_entry_get_name(dev_list_entry);
|
||||
raw_dev = udev_device_new_from_syspath(udev, sysfs_path);
|
||||
dev_path = udev_device_get_devnode(raw_dev);
|
||||
|
||||
hid_dev = udev_device_get_parent_with_subsystem_devtype(
|
||||
raw_dev,
|
||||
"hid",
|
||||
NULL);
|
||||
|
||||
if (!hid_dev) {
|
||||
/* Unable to find parent hid device. */
|
||||
goto next;
|
||||
}
|
||||
|
||||
result = parse_uevent_info(
|
||||
udev_device_get_sysattr_value(hid_dev, "uevent"),
|
||||
&bus_type,
|
||||
&dev_vid,
|
||||
&dev_pid,
|
||||
&serial_number_utf8,
|
||||
&product_name_utf8);
|
||||
|
||||
if (!result) {
|
||||
/* parse_uevent_info() failed for at least one field. */
|
||||
goto next;
|
||||
}
|
||||
|
||||
if (bus_type != BUS_USB && bus_type != BUS_BLUETOOTH) {
|
||||
/* We only know how to handle USB and BT devices. */
|
||||
goto next;
|
||||
}
|
||||
|
||||
/* Check the VID/PID against the arguments */
|
||||
if ((vendor_id == 0x0 || vendor_id == dev_vid) &&
|
||||
(product_id == 0x0 || product_id == dev_pid)) {
|
||||
struct hid_device_info *tmp;
|
||||
|
||||
/* VID/PID match. Create the record. */
|
||||
tmp = malloc(sizeof(struct hid_device_info));
|
||||
if (cur_dev) {
|
||||
cur_dev->next = tmp;
|
||||
}
|
||||
else {
|
||||
root = tmp;
|
||||
}
|
||||
prev_dev = cur_dev;
|
||||
cur_dev = tmp;
|
||||
|
||||
/* Fill out the record */
|
||||
cur_dev->next = NULL;
|
||||
cur_dev->path = dev_path? strdup(dev_path): NULL;
|
||||
|
||||
/* VID/PID */
|
||||
cur_dev->vendor_id = dev_vid;
|
||||
cur_dev->product_id = dev_pid;
|
||||
|
||||
/* Serial Number */
|
||||
cur_dev->serial_number = utf8_to_wchar_t(serial_number_utf8);
|
||||
|
||||
/* Release Number */
|
||||
cur_dev->release_number = 0x0;
|
||||
|
||||
/* Interface Number */
|
||||
cur_dev->interface_number = -1;
|
||||
|
||||
switch (bus_type) {
|
||||
case BUS_USB:
|
||||
/* The device pointed to by raw_dev contains information about
|
||||
the hidraw device. In order to get information about the
|
||||
USB device, get the parent device with the
|
||||
subsystem/devtype pair of "usb"/"usb_device". This will
|
||||
be several levels up the tree, but the function will find
|
||||
it. */
|
||||
usb_dev = udev_device_get_parent_with_subsystem_devtype(
|
||||
raw_dev,
|
||||
"usb",
|
||||
"usb_device");
|
||||
|
||||
if (!usb_dev) {
|
||||
/* Free this device */
|
||||
free(cur_dev->serial_number);
|
||||
free(cur_dev->path);
|
||||
free(cur_dev);
|
||||
|
||||
/* Take it off the device list. */
|
||||
if (prev_dev) {
|
||||
prev_dev->next = NULL;
|
||||
cur_dev = prev_dev;
|
||||
}
|
||||
else {
|
||||
cur_dev = root = NULL;
|
||||
}
|
||||
|
||||
goto next;
|
||||
}
|
||||
|
||||
/* Manufacturer and Product strings */
|
||||
cur_dev->manufacturer_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_MANUFACTURER]);
|
||||
cur_dev->product_string = copy_udev_string(usb_dev, device_string_names[DEVICE_STRING_PRODUCT]);
|
||||
|
||||
/* Release Number */
|
||||
str = udev_device_get_sysattr_value(usb_dev, "bcdDevice");
|
||||
cur_dev->release_number = (str)? strtol(str, NULL, 16): 0x0;
|
||||
|
||||
/* Get a handle to the interface's udev node. */
|
||||
intf_dev = udev_device_get_parent_with_subsystem_devtype(
|
||||
raw_dev,
|
||||
"usb",
|
||||
"usb_interface");
|
||||
if (intf_dev) {
|
||||
str = udev_device_get_sysattr_value(intf_dev, "bInterfaceNumber");
|
||||
cur_dev->interface_number = (str)? strtol(str, NULL, 16): -1;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case BUS_BLUETOOTH:
|
||||
/* Manufacturer and Product strings */
|
||||
cur_dev->manufacturer_string = wcsdup(L"");
|
||||
cur_dev->product_string = utf8_to_wchar_t(product_name_utf8);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
/* Unknown device type - this should never happen, as we
|
||||
* check for USB and Bluetooth devices above */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
next:
|
||||
free(serial_number_utf8);
|
||||
free(product_name_utf8);
|
||||
udev_device_unref(raw_dev);
|
||||
/* hid_dev, usb_dev and intf_dev don't need to be (and can't be)
|
||||
unref()d. It will cause a double-free() error. I'm not
|
||||
sure why. */
|
||||
}
|
||||
/* Free the enumerator and udev objects. */
|
||||
udev_enumerate_unref(enumerate);
|
||||
udev_unref(udev);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
void HID_API_EXPORT hid_free_enumeration(struct hid_device_info *devs)
|
||||
{
|
||||
struct hid_device_info *d = devs;
|
||||
while (d) {
|
||||
struct hid_device_info *next = d->next;
|
||||
free(d->path);
|
||||
free(d->serial_number);
|
||||
free(d->manufacturer_string);
|
||||
free(d->product_string);
|
||||
free(d);
|
||||
d = next;
|
||||
}
|
||||
}
|
||||
|
||||
hid_device * hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
|
||||
{
|
||||
struct hid_device_info *devs, *cur_dev;
|
||||
const char *path_to_open = NULL;
|
||||
hid_device *handle = NULL;
|
||||
|
||||
devs = hid_enumerate(vendor_id, product_id);
|
||||
cur_dev = devs;
|
||||
while (cur_dev) {
|
||||
if (cur_dev->vendor_id == vendor_id &&
|
||||
cur_dev->product_id == product_id) {
|
||||
if (serial_number) {
|
||||
if (wcscmp(serial_number, cur_dev->serial_number) == 0) {
|
||||
path_to_open = cur_dev->path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
path_to_open = cur_dev->path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cur_dev = cur_dev->next;
|
||||
}
|
||||
|
||||
if (path_to_open) {
|
||||
/* Open the device */
|
||||
handle = hid_open_path(path_to_open);
|
||||
}
|
||||
|
||||
hid_free_enumeration(devs);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
hid_device * HID_API_EXPORT hid_open_path(const char *path)
|
||||
{
|
||||
hid_device *dev = NULL;
|
||||
|
||||
hid_init();
|
||||
|
||||
dev = new_hid_device();
|
||||
|
||||
/* OPEN HERE */
|
||||
dev->device_handle = open(path, O_RDWR);
|
||||
|
||||
/* If we have a good handle, return it. */
|
||||
if (dev->device_handle > 0) {
|
||||
|
||||
/* Get the report descriptor */
|
||||
int res, desc_size = 0;
|
||||
struct hidraw_report_descriptor rpt_desc;
|
||||
|
||||
memset(&rpt_desc, 0x0, sizeof(rpt_desc));
|
||||
|
||||
/* Get Report Descriptor Size */
|
||||
res = ioctl(dev->device_handle, HIDIOCGRDESCSIZE, &desc_size);
|
||||
if (res < 0)
|
||||
perror("HIDIOCGRDESCSIZE");
|
||||
|
||||
|
||||
/* Get Report Descriptor */
|
||||
rpt_desc.size = desc_size;
|
||||
res = ioctl(dev->device_handle, HIDIOCGRDESC, &rpt_desc);
|
||||
if (res < 0) {
|
||||
perror("HIDIOCGRDESC");
|
||||
} else {
|
||||
/* Determine if this device uses numbered reports. */
|
||||
dev->uses_numbered_reports =
|
||||
uses_numbered_reports(rpt_desc.value,
|
||||
rpt_desc.size);
|
||||
}
|
||||
|
||||
return dev;
|
||||
}
|
||||
else {
|
||||
/* Unable to open any devices. */
|
||||
free(dev);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int HID_API_EXPORT hid_write(hid_device *dev, const unsigned char *data, size_t length)
|
||||
{
|
||||
int bytes_written;
|
||||
|
||||
bytes_written = write(dev->device_handle, data, length);
|
||||
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
|
||||
int HID_API_EXPORT hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
|
||||
{
|
||||
int bytes_read;
|
||||
|
||||
if (milliseconds >= 0) {
|
||||
/* Milliseconds is either 0 (non-blocking) or > 0 (contains
|
||||
a valid timeout). In both cases we want to call poll()
|
||||
and wait for data to arrive. Don't rely on non-blocking
|
||||
operation (O_NONBLOCK) since some kernels don't seem to
|
||||
properly report device disconnection through read() when
|
||||
in non-blocking mode. */
|
||||
int ret;
|
||||
struct pollfd fds;
|
||||
|
||||
fds.fd = dev->device_handle;
|
||||
fds.events = POLLIN;
|
||||
fds.revents = 0;
|
||||
ret = poll(&fds, 1, milliseconds);
|
||||
if (ret == -1 || ret == 0) {
|
||||
/* Error or timeout */
|
||||
return ret;
|
||||
}
|
||||
else {
|
||||
/* Check for errors on the file descriptor. This will
|
||||
indicate a device disconnection. */
|
||||
if (fds.revents & (POLLERR | POLLHUP | POLLNVAL))
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
bytes_read = read(dev->device_handle, data, length);
|
||||
if (bytes_read < 0 && (errno == EAGAIN || errno == EINPROGRESS))
|
||||
bytes_read = 0;
|
||||
|
||||
if (bytes_read >= 0 &&
|
||||
kernel_version != 0 &&
|
||||
kernel_version < KERNEL_VERSION(2,6,34) &&
|
||||
dev->uses_numbered_reports) {
|
||||
/* Work around a kernel bug. Chop off the first byte. */
|
||||
memmove(data, data+1, bytes_read);
|
||||
bytes_read--;
|
||||
}
|
||||
|
||||
return bytes_read;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT hid_read(hid_device *dev, unsigned char *data, size_t length)
|
||||
{
|
||||
return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0);
|
||||
}
|
||||
|
||||
int HID_API_EXPORT hid_set_nonblocking(hid_device *dev, int nonblock)
|
||||
{
|
||||
/* Do all non-blocking in userspace using poll(), since it looks
|
||||
like there's a bug in the kernel in some versions where
|
||||
read() will not return -1 on disconnection of the USB device */
|
||||
|
||||
dev->blocking = !nonblock;
|
||||
return 0; /* Success */
|
||||
}
|
||||
|
||||
|
||||
int HID_API_EXPORT hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ioctl(dev->device_handle, HIDIOCSFEATURE(length), data);
|
||||
if (res < 0)
|
||||
perror("ioctl (SFEATURE)");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = ioctl(dev->device_handle, HIDIOCGFEATURE(length), data);
|
||||
if (res < 0)
|
||||
perror("ioctl (GFEATURE)");
|
||||
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
void HID_API_EXPORT hid_close(hid_device *dev)
|
||||
{
|
||||
if (!dev)
|
||||
return;
|
||||
close(dev->device_handle);
|
||||
free(dev);
|
||||
}
|
||||
|
||||
|
||||
int HID_API_EXPORT_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
||||
{
|
||||
return get_device_string(dev, DEVICE_STRING_MANUFACTURER, string, maxlen);
|
||||
}
|
||||
|
||||
int HID_API_EXPORT_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
||||
{
|
||||
return get_device_string(dev, DEVICE_STRING_PRODUCT, string, maxlen);
|
||||
}
|
||||
|
||||
int HID_API_EXPORT_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
||||
{
|
||||
return get_device_string(dev, DEVICE_STRING_SERIAL, string, maxlen);
|
||||
}
|
||||
|
||||
int HID_API_EXPORT_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev)
|
||||
{
|
||||
return NULL;
|
||||
}
|
1121
src/hidapi/mac/hid.c
Normal file
1121
src/hidapi/mac/hid.c
Normal file
File diff suppressed because it is too large
Load diff
956
src/hidapi/win/hid.c
Normal file
956
src/hidapi/win/hid.c
Normal file
|
@ -0,0 +1,956 @@
|
|||
/*******************************************************
|
||||
HIDAPI - Multi-Platform library for
|
||||
communication with HID devices.
|
||||
|
||||
Alan Ott
|
||||
Signal 11 Software
|
||||
|
||||
8/22/2009
|
||||
|
||||
Copyright 2009, All Rights Reserved.
|
||||
|
||||
At the discretion of the user of this library,
|
||||
this software may be licensed under the terms of the
|
||||
GNU General Public License v3, a BSD-Style license, or the
|
||||
original HIDAPI license as outlined in the LICENSE.txt,
|
||||
LICENSE-gpl3.txt, LICENSE-bsd.txt, and LICENSE-orig.txt
|
||||
files located at the root of the source distribution.
|
||||
These files may also be found in the public source
|
||||
code repository located at:
|
||||
http://github.com/signal11/hidapi .
|
||||
********************************************************/
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#ifndef _NTDEF_
|
||||
typedef LONG NTSTATUS;
|
||||
#endif
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#include <ntdef.h>
|
||||
#include <winbase.h>
|
||||
#endif
|
||||
|
||||
#ifdef __CYGWIN__
|
||||
#include <ntdef.h>
|
||||
#define _wcsdup wcsdup
|
||||
#endif
|
||||
|
||||
/* The maximum number of characters that can be passed into the
|
||||
HidD_Get*String() functions without it failing.*/
|
||||
#define MAX_STRING_WCHARS 0xFFF
|
||||
|
||||
/*#define HIDAPI_USE_DDK*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include <setupapi.h>
|
||||
#include <winioctl.h>
|
||||
#ifdef HIDAPI_USE_DDK
|
||||
#include <hidsdi.h>
|
||||
#endif
|
||||
|
||||
/* Copied from inc/ddk/hidclass.h, part of the Windows DDK. */
|
||||
#define HID_OUT_CTL_CODE(id) \
|
||||
CTL_CODE(FILE_DEVICE_KEYBOARD, (id), METHOD_OUT_DIRECT, FILE_ANY_ACCESS)
|
||||
#define IOCTL_HID_GET_FEATURE HID_OUT_CTL_CODE(100)
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
|
||||
#include "hidapi.h"
|
||||
|
||||
#undef MIN
|
||||
#define MIN(x,y) ((x) < (y)? (x): (y))
|
||||
|
||||
#ifdef _MSC_VER
|
||||
/* Thanks Microsoft, but I know how to use strncpy(). */
|
||||
#pragma warning(disable:4996)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef HIDAPI_USE_DDK
|
||||
/* Since we're not building with the DDK, and the HID header
|
||||
files aren't part of the SDK, we have to define all this
|
||||
stuff here. In lookup_functions(), the function pointers
|
||||
defined below are set. */
|
||||
typedef struct _HIDD_ATTRIBUTES{
|
||||
ULONG Size;
|
||||
USHORT VendorID;
|
||||
USHORT ProductID;
|
||||
USHORT VersionNumber;
|
||||
} HIDD_ATTRIBUTES, *PHIDD_ATTRIBUTES;
|
||||
|
||||
typedef USHORT USAGE;
|
||||
typedef struct _HIDP_CAPS {
|
||||
USAGE Usage;
|
||||
USAGE UsagePage;
|
||||
USHORT InputReportByteLength;
|
||||
USHORT OutputReportByteLength;
|
||||
USHORT FeatureReportByteLength;
|
||||
USHORT Reserved[17];
|
||||
USHORT fields_not_used_by_hidapi[10];
|
||||
} HIDP_CAPS, *PHIDP_CAPS;
|
||||
typedef void* PHIDP_PREPARSED_DATA;
|
||||
#define HIDP_STATUS_SUCCESS 0x110000
|
||||
|
||||
typedef BOOLEAN (__stdcall *HidD_GetAttributes_)(HANDLE device, PHIDD_ATTRIBUTES attrib);
|
||||
typedef BOOLEAN (__stdcall *HidD_GetSerialNumberString_)(HANDLE device, PVOID buffer, ULONG buffer_len);
|
||||
typedef BOOLEAN (__stdcall *HidD_GetManufacturerString_)(HANDLE handle, PVOID buffer, ULONG buffer_len);
|
||||
typedef BOOLEAN (__stdcall *HidD_GetProductString_)(HANDLE handle, PVOID buffer, ULONG buffer_len);
|
||||
typedef BOOLEAN (__stdcall *HidD_SetFeature_)(HANDLE handle, PVOID data, ULONG length);
|
||||
typedef BOOLEAN (__stdcall *HidD_GetFeature_)(HANDLE handle, PVOID data, ULONG length);
|
||||
typedef BOOLEAN (__stdcall *HidD_GetIndexedString_)(HANDLE handle, ULONG string_index, PVOID buffer, ULONG buffer_len);
|
||||
typedef BOOLEAN (__stdcall *HidD_GetPreparsedData_)(HANDLE handle, PHIDP_PREPARSED_DATA *preparsed_data);
|
||||
typedef BOOLEAN (__stdcall *HidD_FreePreparsedData_)(PHIDP_PREPARSED_DATA preparsed_data);
|
||||
typedef NTSTATUS (__stdcall *HidP_GetCaps_)(PHIDP_PREPARSED_DATA preparsed_data, HIDP_CAPS *caps);
|
||||
typedef BOOLEAN (__stdcall *HidD_SetNumInputBuffers_)(HANDLE handle, ULONG number_buffers);
|
||||
|
||||
static HidD_GetAttributes_ HidD_GetAttributes;
|
||||
static HidD_GetSerialNumberString_ HidD_GetSerialNumberString;
|
||||
static HidD_GetManufacturerString_ HidD_GetManufacturerString;
|
||||
static HidD_GetProductString_ HidD_GetProductString;
|
||||
static HidD_SetFeature_ HidD_SetFeature;
|
||||
static HidD_GetFeature_ HidD_GetFeature;
|
||||
static HidD_GetIndexedString_ HidD_GetIndexedString;
|
||||
static HidD_GetPreparsedData_ HidD_GetPreparsedData;
|
||||
static HidD_FreePreparsedData_ HidD_FreePreparsedData;
|
||||
static HidP_GetCaps_ HidP_GetCaps;
|
||||
static HidD_SetNumInputBuffers_ HidD_SetNumInputBuffers;
|
||||
|
||||
static HMODULE lib_handle = NULL;
|
||||
static BOOLEAN initialized = FALSE;
|
||||
#endif /* HIDAPI_USE_DDK */
|
||||
|
||||
struct hid_device_ {
|
||||
HANDLE device_handle;
|
||||
BOOL blocking;
|
||||
USHORT output_report_length;
|
||||
size_t input_report_length;
|
||||
void *last_error_str;
|
||||
DWORD last_error_num;
|
||||
BOOL read_pending;
|
||||
char *read_buf;
|
||||
OVERLAPPED ol;
|
||||
};
|
||||
|
||||
static hid_device *new_hid_device()
|
||||
{
|
||||
hid_device *dev = (hid_device*) calloc(1, sizeof(hid_device));
|
||||
dev->device_handle = INVALID_HANDLE_VALUE;
|
||||
dev->blocking = TRUE;
|
||||
dev->output_report_length = 0;
|
||||
dev->input_report_length = 0;
|
||||
dev->last_error_str = NULL;
|
||||
dev->last_error_num = 0;
|
||||
dev->read_pending = FALSE;
|
||||
dev->read_buf = NULL;
|
||||
memset(&dev->ol, 0, sizeof(dev->ol));
|
||||
dev->ol.hEvent = CreateEvent(NULL, FALSE, FALSE /*initial state f=nonsignaled*/, NULL);
|
||||
|
||||
return dev;
|
||||
}
|
||||
|
||||
static void free_hid_device(hid_device *dev)
|
||||
{
|
||||
CloseHandle(dev->ol.hEvent);
|
||||
CloseHandle(dev->device_handle);
|
||||
LocalFree(dev->last_error_str);
|
||||
free(dev->read_buf);
|
||||
free(dev);
|
||||
}
|
||||
|
||||
static void register_error(hid_device *dev, const char *op)
|
||||
{
|
||||
WCHAR *ptr, *msg;
|
||||
|
||||
FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL,
|
||||
GetLastError(),
|
||||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
(LPVOID)&msg, 0/*sz*/,
|
||||
NULL);
|
||||
|
||||
/* Get rid of the CR and LF that FormatMessage() sticks at the
|
||||
end of the message. Thanks Microsoft! */
|
||||
ptr = msg;
|
||||
while (*ptr) {
|
||||
if (*ptr == '\r') {
|
||||
*ptr = 0x0000;
|
||||
break;
|
||||
}
|
||||
ptr++;
|
||||
}
|
||||
|
||||
/* Store the message off in the Device entry so that
|
||||
the hid_error() function can pick it up. */
|
||||
LocalFree(dev->last_error_str);
|
||||
dev->last_error_str = msg;
|
||||
}
|
||||
|
||||
#ifndef HIDAPI_USE_DDK
|
||||
static int lookup_functions()
|
||||
{
|
||||
lib_handle = LoadLibraryA("hid.dll");
|
||||
if (lib_handle) {
|
||||
#define RESOLVE(x) x = (x##_)GetProcAddress(lib_handle, #x); if (!x) return -1;
|
||||
RESOLVE(HidD_GetAttributes);
|
||||
RESOLVE(HidD_GetSerialNumberString);
|
||||
RESOLVE(HidD_GetManufacturerString);
|
||||
RESOLVE(HidD_GetProductString);
|
||||
RESOLVE(HidD_SetFeature);
|
||||
RESOLVE(HidD_GetFeature);
|
||||
RESOLVE(HidD_GetIndexedString);
|
||||
RESOLVE(HidD_GetPreparsedData);
|
||||
RESOLVE(HidD_FreePreparsedData);
|
||||
RESOLVE(HidP_GetCaps);
|
||||
RESOLVE(HidD_SetNumInputBuffers);
|
||||
#undef RESOLVE
|
||||
}
|
||||
else
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static HANDLE open_device(const char *path, BOOL open_rw)
|
||||
{
|
||||
HANDLE handle;
|
||||
DWORD desired_access = (open_rw)? (GENERIC_WRITE | GENERIC_READ): 0;
|
||||
DWORD share_mode = FILE_SHARE_READ|FILE_SHARE_WRITE;
|
||||
|
||||
handle = CreateFileA(path,
|
||||
desired_access,
|
||||
share_mode,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_OVERLAPPED,/*FILE_ATTRIBUTE_NORMAL,*/
|
||||
0);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT hid_init(void)
|
||||
{
|
||||
#ifndef HIDAPI_USE_DDK
|
||||
if (!initialized) {
|
||||
if (lookup_functions() < 0) {
|
||||
hid_exit();
|
||||
return -1;
|
||||
}
|
||||
initialized = TRUE;
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT hid_exit(void)
|
||||
{
|
||||
#ifndef HIDAPI_USE_DDK
|
||||
if (lib_handle)
|
||||
FreeLibrary(lib_handle);
|
||||
lib_handle = NULL;
|
||||
initialized = FALSE;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct hid_device_info HID_API_EXPORT * HID_API_CALL hid_enumerate(unsigned short vendor_id, unsigned short product_id)
|
||||
{
|
||||
BOOL res;
|
||||
struct hid_device_info *root = NULL; /* return object */
|
||||
struct hid_device_info *cur_dev = NULL;
|
||||
|
||||
/* Windows objects for interacting with the driver. */
|
||||
GUID InterfaceClassGuid = {0x4d1e55b2, 0xf16f, 0x11cf, {0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30} };
|
||||
SP_DEVINFO_DATA devinfo_data;
|
||||
SP_DEVICE_INTERFACE_DATA device_interface_data;
|
||||
SP_DEVICE_INTERFACE_DETAIL_DATA_A *device_interface_detail_data = NULL;
|
||||
HDEVINFO device_info_set = INVALID_HANDLE_VALUE;
|
||||
int device_index = 0;
|
||||
int i;
|
||||
|
||||
if (hid_init() < 0)
|
||||
return NULL;
|
||||
|
||||
/* Initialize the Windows objects. */
|
||||
memset(&devinfo_data, 0x0, sizeof(devinfo_data));
|
||||
devinfo_data.cbSize = sizeof(SP_DEVINFO_DATA);
|
||||
device_interface_data.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
|
||||
|
||||
/* Get information for all the devices belonging to the HID class. */
|
||||
device_info_set = SetupDiGetClassDevsA(&InterfaceClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
||||
|
||||
/* Iterate over each device in the HID class, looking for the right one. */
|
||||
|
||||
for (;;) {
|
||||
HANDLE write_handle = INVALID_HANDLE_VALUE;
|
||||
DWORD required_size = 0;
|
||||
HIDD_ATTRIBUTES attrib;
|
||||
|
||||
res = SetupDiEnumDeviceInterfaces(device_info_set,
|
||||
NULL,
|
||||
&InterfaceClassGuid,
|
||||
device_index,
|
||||
&device_interface_data);
|
||||
|
||||
if (!res) {
|
||||
/* A return of FALSE from this function means that
|
||||
there are no more devices. */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Call with 0-sized detail size, and let the function
|
||||
tell us how long the detail struct needs to be. The
|
||||
size is put in &required_size. */
|
||||
res = SetupDiGetDeviceInterfaceDetailA(device_info_set,
|
||||
&device_interface_data,
|
||||
NULL,
|
||||
0,
|
||||
&required_size,
|
||||
NULL);
|
||||
|
||||
/* Allocate a long enough structure for device_interface_detail_data. */
|
||||
device_interface_detail_data = (SP_DEVICE_INTERFACE_DETAIL_DATA_A*) malloc(required_size);
|
||||
device_interface_detail_data->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A);
|
||||
|
||||
/* Get the detailed data for this device. The detail data gives us
|
||||
the device path for this device, which is then passed into
|
||||
CreateFile() to get a handle to the device. */
|
||||
res = SetupDiGetDeviceInterfaceDetailA(device_info_set,
|
||||
&device_interface_data,
|
||||
device_interface_detail_data,
|
||||
required_size,
|
||||
NULL,
|
||||
NULL);
|
||||
|
||||
if (!res) {
|
||||
/* register_error(dev, "Unable to call SetupDiGetDeviceInterfaceDetail");
|
||||
Continue to the next device. */
|
||||
goto cont;
|
||||
}
|
||||
|
||||
/* Make sure this device is of Setup Class "HIDClass" and has a
|
||||
driver bound to it. */
|
||||
for (i = 0; ; i++) {
|
||||
char driver_name[256];
|
||||
|
||||
/* Populate devinfo_data. This function will return failure
|
||||
when there are no more interfaces left. */
|
||||
res = SetupDiEnumDeviceInfo(device_info_set, i, &devinfo_data);
|
||||
if (!res)
|
||||
goto cont;
|
||||
|
||||
res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data,
|
||||
SPDRP_CLASS, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL);
|
||||
if (!res)
|
||||
goto cont;
|
||||
|
||||
if ((strcmp(driver_name, "HIDClass") == 0) ||
|
||||
(strcmp(driver_name, "Mouse") == 0) ||
|
||||
(strcmp(driver_name, "Keyboard") == 0)) {
|
||||
/* See if there's a driver bound. */
|
||||
res = SetupDiGetDeviceRegistryPropertyA(device_info_set, &devinfo_data,
|
||||
SPDRP_DRIVER, NULL, (PBYTE)driver_name, sizeof(driver_name), NULL);
|
||||
if (res)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//wprintf(L"HandleName: %s\n", device_interface_detail_data->DevicePath);
|
||||
|
||||
/* Open a handle to the device */
|
||||
write_handle = open_device(device_interface_detail_data->DevicePath, FALSE);
|
||||
|
||||
/* Check validity of write_handle. */
|
||||
if (write_handle == INVALID_HANDLE_VALUE) {
|
||||
/* Unable to open the device. */
|
||||
//register_error(dev, "CreateFile");
|
||||
goto cont_close;
|
||||
}
|
||||
|
||||
|
||||
/* Get the Vendor ID and Product ID for this device. */
|
||||
attrib.Size = sizeof(HIDD_ATTRIBUTES);
|
||||
HidD_GetAttributes(write_handle, &attrib);
|
||||
//wprintf(L"Product/Vendor: %x %x\n", attrib.ProductID, attrib.VendorID);
|
||||
|
||||
/* Check the VID/PID to see if we should add this
|
||||
device to the enumeration list. */
|
||||
if ((vendor_id == 0x0 || attrib.VendorID == vendor_id) &&
|
||||
(product_id == 0x0 || attrib.ProductID == product_id)) {
|
||||
|
||||
#define WSTR_LEN 512
|
||||
const char *str;
|
||||
struct hid_device_info *tmp;
|
||||
PHIDP_PREPARSED_DATA pp_data = NULL;
|
||||
HIDP_CAPS caps;
|
||||
BOOLEAN res;
|
||||
NTSTATUS nt_res;
|
||||
wchar_t wstr[WSTR_LEN]; /* TODO: Determine Size */
|
||||
size_t len;
|
||||
|
||||
/* VID/PID match. Create the record. */
|
||||
tmp = (struct hid_device_info*) calloc(1, sizeof(struct hid_device_info));
|
||||
if (cur_dev) {
|
||||
cur_dev->next = tmp;
|
||||
}
|
||||
else {
|
||||
root = tmp;
|
||||
}
|
||||
cur_dev = tmp;
|
||||
|
||||
/* Get the Usage Page and Usage for this device. */
|
||||
res = HidD_GetPreparsedData(write_handle, &pp_data);
|
||||
if (res) {
|
||||
nt_res = HidP_GetCaps(pp_data, &caps);
|
||||
if (nt_res == HIDP_STATUS_SUCCESS) {
|
||||
cur_dev->usage_page = caps.UsagePage;
|
||||
cur_dev->usage = caps.Usage;
|
||||
}
|
||||
|
||||
HidD_FreePreparsedData(pp_data);
|
||||
}
|
||||
|
||||
/* Fill out the record */
|
||||
cur_dev->next = NULL;
|
||||
str = device_interface_detail_data->DevicePath;
|
||||
if (str) {
|
||||
len = strlen(str);
|
||||
cur_dev->path = (char*) calloc(len+1, sizeof(char));
|
||||
strncpy(cur_dev->path, str, len+1);
|
||||
cur_dev->path[len] = '\0';
|
||||
}
|
||||
else
|
||||
cur_dev->path = NULL;
|
||||
|
||||
/* Serial Number */
|
||||
res = HidD_GetSerialNumberString(write_handle, wstr, sizeof(wstr));
|
||||
wstr[WSTR_LEN-1] = 0x0000;
|
||||
if (res) {
|
||||
cur_dev->serial_number = _wcsdup(wstr);
|
||||
}
|
||||
|
||||
/* Manufacturer String */
|
||||
res = HidD_GetManufacturerString(write_handle, wstr, sizeof(wstr));
|
||||
wstr[WSTR_LEN-1] = 0x0000;
|
||||
if (res) {
|
||||
cur_dev->manufacturer_string = _wcsdup(wstr);
|
||||
}
|
||||
|
||||
/* Product String */
|
||||
res = HidD_GetProductString(write_handle, wstr, sizeof(wstr));
|
||||
wstr[WSTR_LEN-1] = 0x0000;
|
||||
if (res) {
|
||||
cur_dev->product_string = _wcsdup(wstr);
|
||||
}
|
||||
|
||||
/* VID/PID */
|
||||
cur_dev->vendor_id = attrib.VendorID;
|
||||
cur_dev->product_id = attrib.ProductID;
|
||||
|
||||
/* Release Number */
|
||||
cur_dev->release_number = attrib.VersionNumber;
|
||||
|
||||
/* Interface Number. It can sometimes be parsed out of the path
|
||||
on Windows if a device has multiple interfaces. See
|
||||
http://msdn.microsoft.com/en-us/windows/hardware/gg487473 or
|
||||
search for "Hardware IDs for HID Devices" at MSDN. If it's not
|
||||
in the path, it's set to -1. */
|
||||
cur_dev->interface_number = -1;
|
||||
if (cur_dev->path) {
|
||||
char *interface_component = strstr(cur_dev->path, "&mi_");
|
||||
if (interface_component) {
|
||||
char *hex_str = interface_component + 4;
|
||||
char *endptr = NULL;
|
||||
cur_dev->interface_number = strtol(hex_str, &endptr, 16);
|
||||
if (endptr == hex_str) {
|
||||
/* The parsing failed. Set interface_number to -1. */
|
||||
cur_dev->interface_number = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cont_close:
|
||||
CloseHandle(write_handle);
|
||||
cont:
|
||||
/* We no longer need the detail data. It can be freed */
|
||||
free(device_interface_detail_data);
|
||||
|
||||
device_index++;
|
||||
|
||||
}
|
||||
|
||||
/* Close the device information handle. */
|
||||
SetupDiDestroyDeviceInfoList(device_info_set);
|
||||
|
||||
return root;
|
||||
|
||||
}
|
||||
|
||||
void HID_API_EXPORT HID_API_CALL hid_free_enumeration(struct hid_device_info *devs)
|
||||
{
|
||||
/* TODO: Merge this with the Linux version. This function is platform-independent. */
|
||||
struct hid_device_info *d = devs;
|
||||
while (d) {
|
||||
struct hid_device_info *next = d->next;
|
||||
free(d->path);
|
||||
free(d->serial_number);
|
||||
free(d->manufacturer_string);
|
||||
free(d->product_string);
|
||||
free(d);
|
||||
d = next;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HID_API_EXPORT hid_device * HID_API_CALL hid_open(unsigned short vendor_id, unsigned short product_id, const wchar_t *serial_number)
|
||||
{
|
||||
/* TODO: Merge this functions with the Linux version. This function should be platform independent. */
|
||||
struct hid_device_info *devs, *cur_dev;
|
||||
const char *path_to_open = NULL;
|
||||
hid_device *handle = NULL;
|
||||
|
||||
devs = hid_enumerate(vendor_id, product_id);
|
||||
cur_dev = devs;
|
||||
while (cur_dev) {
|
||||
if (cur_dev->vendor_id == vendor_id &&
|
||||
cur_dev->product_id == product_id) {
|
||||
if (serial_number) {
|
||||
if (wcscmp(serial_number, cur_dev->serial_number) == 0) {
|
||||
path_to_open = cur_dev->path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
path_to_open = cur_dev->path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
cur_dev = cur_dev->next;
|
||||
}
|
||||
|
||||
if (path_to_open) {
|
||||
/* Open the device */
|
||||
handle = hid_open_path(path_to_open);
|
||||
}
|
||||
|
||||
hid_free_enumeration(devs);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
HID_API_EXPORT hid_device * HID_API_CALL hid_open_path(const char *path)
|
||||
{
|
||||
hid_device *dev;
|
||||
HIDP_CAPS caps;
|
||||
PHIDP_PREPARSED_DATA pp_data = NULL;
|
||||
BOOLEAN res;
|
||||
NTSTATUS nt_res;
|
||||
|
||||
if (hid_init() < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dev = new_hid_device();
|
||||
|
||||
/* Open a handle to the device */
|
||||
dev->device_handle = open_device(path, TRUE);
|
||||
|
||||
/* Check validity of write_handle. */
|
||||
if (dev->device_handle == INVALID_HANDLE_VALUE) {
|
||||
/* System devices, such as keyboards and mice, cannot be opened in
|
||||
read-write mode, because the system takes exclusive control over
|
||||
them. This is to prevent keyloggers. However, feature reports
|
||||
can still be sent and received. Retry opening the device, but
|
||||
without read/write access. */
|
||||
dev->device_handle = open_device(path, FALSE);
|
||||
|
||||
/* Check the validity of the limited device_handle. */
|
||||
if (dev->device_handle == INVALID_HANDLE_VALUE) {
|
||||
/* Unable to open the device, even without read-write mode. */
|
||||
register_error(dev, "CreateFile");
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the Input Report buffer size to 64 reports. */
|
||||
res = HidD_SetNumInputBuffers(dev->device_handle, 64);
|
||||
if (!res) {
|
||||
register_error(dev, "HidD_SetNumInputBuffers");
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Get the Input Report length for the device. */
|
||||
res = HidD_GetPreparsedData(dev->device_handle, &pp_data);
|
||||
if (!res) {
|
||||
register_error(dev, "HidD_GetPreparsedData");
|
||||
goto err;
|
||||
}
|
||||
nt_res = HidP_GetCaps(pp_data, &caps);
|
||||
if (nt_res != HIDP_STATUS_SUCCESS) {
|
||||
register_error(dev, "HidP_GetCaps");
|
||||
goto err_pp_data;
|
||||
}
|
||||
dev->output_report_length = caps.OutputReportByteLength;
|
||||
dev->input_report_length = caps.InputReportByteLength;
|
||||
HidD_FreePreparsedData(pp_data);
|
||||
|
||||
dev->read_buf = (char*) malloc(dev->input_report_length);
|
||||
|
||||
return dev;
|
||||
|
||||
err_pp_data:
|
||||
HidD_FreePreparsedData(pp_data);
|
||||
err:
|
||||
free_hid_device(dev);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length)
|
||||
{
|
||||
DWORD bytes_written;
|
||||
BOOL res;
|
||||
|
||||
OVERLAPPED ol;
|
||||
unsigned char *buf;
|
||||
memset(&ol, 0, sizeof(ol));
|
||||
|
||||
/* Make sure the right number of bytes are passed to WriteFile. Windows
|
||||
expects the number of bytes which are in the _longest_ report (plus
|
||||
one for the report number) bytes even if the data is a report
|
||||
which is shorter than that. Windows gives us this value in
|
||||
caps.OutputReportByteLength. If a user passes in fewer bytes than this,
|
||||
create a temporary buffer which is the proper size. */
|
||||
if (length >= dev->output_report_length) {
|
||||
/* The user passed the right number of bytes. Use the buffer as-is. */
|
||||
buf = (unsigned char *) data;
|
||||
} else {
|
||||
/* Create a temporary buffer and copy the user's data
|
||||
into it, padding the rest with zeros. */
|
||||
buf = (unsigned char *) malloc(dev->output_report_length);
|
||||
memcpy(buf, data, length);
|
||||
memset(buf + length, 0, dev->output_report_length - length);
|
||||
length = dev->output_report_length;
|
||||
}
|
||||
|
||||
res = WriteFile(dev->device_handle, buf, length, NULL, &ol);
|
||||
|
||||
if (!res) {
|
||||
if (GetLastError() != ERROR_IO_PENDING) {
|
||||
/* WriteFile() failed. Return error. */
|
||||
register_error(dev, "WriteFile");
|
||||
bytes_written = -1;
|
||||
goto end_of_function;
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait here until the write is done. This makes
|
||||
hid_write() synchronous. */
|
||||
res = GetOverlappedResult(dev->device_handle, &ol, &bytes_written, TRUE/*wait*/);
|
||||
if (!res) {
|
||||
/* The Write operation failed. */
|
||||
register_error(dev, "WriteFile");
|
||||
bytes_written = -1;
|
||||
goto end_of_function;
|
||||
}
|
||||
|
||||
end_of_function:
|
||||
if (buf != data)
|
||||
free(buf);
|
||||
|
||||
return bytes_written;
|
||||
}
|
||||
|
||||
|
||||
int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds)
|
||||
{
|
||||
DWORD bytes_read = 0;
|
||||
size_t copy_len = 0;
|
||||
BOOL res;
|
||||
|
||||
/* Copy the handle for convenience. */
|
||||
HANDLE ev = dev->ol.hEvent;
|
||||
|
||||
if (!dev->read_pending) {
|
||||
/* Start an Overlapped I/O read. */
|
||||
dev->read_pending = TRUE;
|
||||
memset(dev->read_buf, 0, dev->input_report_length);
|
||||
ResetEvent(ev);
|
||||
res = ReadFile(dev->device_handle, dev->read_buf, dev->input_report_length, &bytes_read, &dev->ol);
|
||||
|
||||
if (!res) {
|
||||
if (GetLastError() != ERROR_IO_PENDING) {
|
||||
/* ReadFile() has failed.
|
||||
Clean up and return error. */
|
||||
CancelIo(dev->device_handle);
|
||||
dev->read_pending = FALSE;
|
||||
goto end_of_function;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (milliseconds >= 0) {
|
||||
/* See if there is any data yet. */
|
||||
res = WaitForSingleObject(ev, milliseconds);
|
||||
if (res != WAIT_OBJECT_0) {
|
||||
/* There was no data this time. Return zero bytes available,
|
||||
but leave the Overlapped I/O running. */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Either WaitForSingleObject() told us that ReadFile has completed, or
|
||||
we are in non-blocking mode. Get the number of bytes read. The actual
|
||||
data has been copied to the data[] array which was passed to ReadFile(). */
|
||||
res = GetOverlappedResult(dev->device_handle, &dev->ol, &bytes_read, TRUE/*wait*/);
|
||||
|
||||
/* Set pending back to false, even if GetOverlappedResult() returned error. */
|
||||
dev->read_pending = FALSE;
|
||||
|
||||
if (res && bytes_read > 0) {
|
||||
if (dev->read_buf[0] == 0x0) {
|
||||
/* If report numbers aren't being used, but Windows sticks a report
|
||||
number (0x0) on the beginning of the report anyway. To make this
|
||||
work like the other platforms, and to make it work more like the
|
||||
HID spec, we'll skip over this byte. */
|
||||
bytes_read--;
|
||||
copy_len = length > bytes_read ? bytes_read : length;
|
||||
memcpy(data, dev->read_buf+1, copy_len);
|
||||
}
|
||||
else {
|
||||
/* Copy the whole buffer, report number and all. */
|
||||
copy_len = length > bytes_read ? bytes_read : length;
|
||||
memcpy(data, dev->read_buf, copy_len);
|
||||
}
|
||||
}
|
||||
|
||||
end_of_function:
|
||||
if (!res) {
|
||||
register_error(dev, "GetOverlappedResult");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return copy_len;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT HID_API_CALL hid_read(hid_device *dev, unsigned char *data, size_t length)
|
||||
{
|
||||
return hid_read_timeout(dev, data, length, (dev->blocking)? -1: 0);
|
||||
}
|
||||
|
||||
int HID_API_EXPORT HID_API_CALL hid_set_nonblocking(hid_device *dev, int nonblock)
|
||||
{
|
||||
dev->blocking = !nonblock;
|
||||
return 0; /* Success */
|
||||
}
|
||||
|
||||
int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length)
|
||||
{
|
||||
BOOL res = HidD_SetFeature(dev->device_handle, (PVOID)data, length);
|
||||
if (!res) {
|
||||
register_error(dev, "HidD_SetFeature");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
|
||||
int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length)
|
||||
{
|
||||
BOOL res;
|
||||
#if 0
|
||||
res = HidD_GetFeature(dev->device_handle, data, length);
|
||||
if (!res) {
|
||||
register_error(dev, "HidD_GetFeature");
|
||||
return -1;
|
||||
}
|
||||
return 0; /* HidD_GetFeature() doesn't give us an actual length, unfortunately */
|
||||
#else
|
||||
DWORD bytes_returned;
|
||||
|
||||
OVERLAPPED ol;
|
||||
memset(&ol, 0, sizeof(ol));
|
||||
|
||||
res = DeviceIoControl(dev->device_handle,
|
||||
IOCTL_HID_GET_FEATURE,
|
||||
data, length,
|
||||
data, length,
|
||||
&bytes_returned, &ol);
|
||||
|
||||
if (!res) {
|
||||
if (GetLastError() != ERROR_IO_PENDING) {
|
||||
/* DeviceIoControl() failed. Return error. */
|
||||
register_error(dev, "Send Feature Report DeviceIoControl");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait here until the write is done. This makes
|
||||
hid_get_feature_report() synchronous. */
|
||||
res = GetOverlappedResult(dev->device_handle, &ol, &bytes_returned, TRUE/*wait*/);
|
||||
if (!res) {
|
||||
/* The operation failed. */
|
||||
register_error(dev, "Send Feature Report GetOverLappedResult");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* bytes_returned does not include the first byte which contains the
|
||||
report ID. The data buffer actually contains one more byte than
|
||||
bytes_returned. */
|
||||
bytes_returned++;
|
||||
|
||||
return bytes_returned;
|
||||
#endif
|
||||
}
|
||||
|
||||
void HID_API_EXPORT HID_API_CALL hid_close(hid_device *dev)
|
||||
{
|
||||
if (!dev)
|
||||
return;
|
||||
CancelIo(dev->device_handle);
|
||||
free_hid_device(dev);
|
||||
}
|
||||
|
||||
int HID_API_EXPORT_CALL HID_API_CALL hid_get_manufacturer_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
||||
{
|
||||
BOOL res;
|
||||
|
||||
res = HidD_GetManufacturerString(dev->device_handle, string, sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS));
|
||||
if (!res) {
|
||||
register_error(dev, "HidD_GetManufacturerString");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT_CALL HID_API_CALL hid_get_product_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
||||
{
|
||||
BOOL res;
|
||||
|
||||
res = HidD_GetProductString(dev->device_handle, string, sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS));
|
||||
if (!res) {
|
||||
register_error(dev, "HidD_GetProductString");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT_CALL HID_API_CALL hid_get_serial_number_string(hid_device *dev, wchar_t *string, size_t maxlen)
|
||||
{
|
||||
BOOL res;
|
||||
|
||||
res = HidD_GetSerialNumberString(dev->device_handle, string, sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS));
|
||||
if (!res) {
|
||||
register_error(dev, "HidD_GetSerialNumberString");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int HID_API_EXPORT_CALL HID_API_CALL hid_get_indexed_string(hid_device *dev, int string_index, wchar_t *string, size_t maxlen)
|
||||
{
|
||||
BOOL res;
|
||||
|
||||
res = HidD_GetIndexedString(dev->device_handle, string_index, string, sizeof(wchar_t) * MIN(maxlen, MAX_STRING_WCHARS));
|
||||
if (!res) {
|
||||
register_error(dev, "HidD_GetIndexedString");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
HID_API_EXPORT const wchar_t * HID_API_CALL hid_error(hid_device *dev)
|
||||
{
|
||||
return (wchar_t*)dev->last_error_str;
|
||||
}
|
||||
|
||||
|
||||
/*#define PICPGM*/
|
||||
/*#define S11*/
|
||||
#define P32
|
||||
#ifdef S11
|
||||
unsigned short VendorID = 0xa0a0;
|
||||
unsigned short ProductID = 0x0001;
|
||||
#endif
|
||||
|
||||
#ifdef P32
|
||||
unsigned short VendorID = 0x04d8;
|
||||
unsigned short ProductID = 0x3f;
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef PICPGM
|
||||
unsigned short VendorID = 0x04d8;
|
||||
unsigned short ProductID = 0x0033;
|
||||
#endif
|
||||
|
||||
|
||||
#if 0
|
||||
int __cdecl main(int argc, char* argv[])
|
||||
{
|
||||
int res;
|
||||
unsigned char buf[65];
|
||||
|
||||
UNREFERENCED_PARAMETER(argc);
|
||||
UNREFERENCED_PARAMETER(argv);
|
||||
|
||||
/* Set up the command buffer. */
|
||||
memset(buf,0x00,sizeof(buf));
|
||||
buf[0] = 0;
|
||||
buf[1] = 0x81;
|
||||
|
||||
|
||||
/* Open the device. */
|
||||
int handle = open(VendorID, ProductID, L"12345");
|
||||
if (handle < 0)
|
||||
printf("unable to open device\n");
|
||||
|
||||
|
||||
/* Toggle LED (cmd 0x80) */
|
||||
buf[1] = 0x80;
|
||||
res = write(handle, buf, 65);
|
||||
if (res < 0)
|
||||
printf("Unable to write()\n");
|
||||
|
||||
/* Request state (cmd 0x81) */
|
||||
buf[1] = 0x81;
|
||||
write(handle, buf, 65);
|
||||
if (res < 0)
|
||||
printf("Unable to write() (2)\n");
|
||||
|
||||
/* Read requested state */
|
||||
read(handle, buf, 65);
|
||||
if (res < 0)
|
||||
printf("Unable to read()\n");
|
||||
|
||||
/* Print out the returned buffer. */
|
||||
for (int i = 0; i < 4; i++)
|
||||
printf("buf[%d]: %d\n", i, buf[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
#endif
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "../ClipperUtils.hpp"
|
||||
#include "../EdgeGrid.hpp"
|
||||
#include "../Geometry.hpp"
|
||||
#include "../Surface.hpp"
|
||||
#include "../PrintConfig.hpp"
|
||||
#include "../libslic3r.h"
|
||||
|
@ -777,6 +778,8 @@ void mark_boundary_segments_touching_infill(
|
|||
const Vec2d *pt2;
|
||||
} visitor(grid, boundary, boundary_data, distance_colliding * distance_colliding);
|
||||
|
||||
BoundingBoxf bboxf(boundary_bbox.min.cast<double>(), boundary_bbox.max.cast<double>());
|
||||
bboxf.offset(- SCALED_EPSILON);
|
||||
for (const Polyline &polyline : infill) {
|
||||
// Clip the infill polyline by the Eucledian distance along the polyline.
|
||||
SegmentPoint start_point = clip_start_segment_and_point(polyline.points, clip_distance);
|
||||
|
@ -814,9 +817,11 @@ void mark_boundary_segments_touching_infill(
|
|||
Vec2d vperp(-v.y(), v.x());
|
||||
Vec2d a = pt1 - v - vperp;
|
||||
Vec2d b = pt1 + v - vperp;
|
||||
if (Geometry::liang_barsky_line_clipping(a, b, bboxf))
|
||||
grid.visit_cells_intersecting_line(a.cast<coord_t>(), b.cast<coord_t>(), visitor);
|
||||
a = pt1 - v + vperp;
|
||||
b = pt1 + v + vperp;
|
||||
if (Geometry::liang_barsky_line_clipping(a, b, bboxf))
|
||||
grid.visit_cells_intersecting_line(a.cast<coord_t>(), b.cast<coord_t>(), visitor);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -6,9 +6,6 @@
|
|||
#include "Geometry.hpp"
|
||||
#include "GCode/PrintExtents.hpp"
|
||||
#include "GCode/WipeTower.hpp"
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "GCode/ThumbnailData.hpp"
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "ShortestPath.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
|
@ -35,9 +32,7 @@
|
|||
|
||||
#include <Shiny/Shiny.h>
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
#include "miniz_extension.hpp"
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
|
||||
#if 0
|
||||
// Enable debugging and asserts, even in the release build.
|
||||
|
@ -695,7 +690,7 @@ std::vector<std::pair<coordf_t, std::vector<GCode::LayerToPrint>>> GCode::collec
|
|||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_data, const std::vector<ThumbnailData>* thumbnail_data)
|
||||
void GCode::do_export(Print* print, const char* path, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb)
|
||||
#else
|
||||
void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_data)
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
@ -725,7 +720,7 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
|
|||
try {
|
||||
m_placeholder_parser_failed_templates.clear();
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
this->_do_export(*print, file, thumbnail_data);
|
||||
this->_do_export(*print, file, thumbnail_cb);
|
||||
#else
|
||||
this->_do_export(*print, file);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
@ -793,9 +788,9 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
|
|||
}
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void GCode::_do_export(Print& print, FILE* file, const std::vector<ThumbnailData>* thumbnail_data)
|
||||
void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb)
|
||||
#else
|
||||
void GCode::_do_export(Print &print, FILE *file)
|
||||
void GCode::_do_export(Print& print, FILE* file)
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
{
|
||||
PROFILE_FUNC();
|
||||
|
@ -909,7 +904,8 @@ void GCode::_do_export(Print &print, FILE *file)
|
|||
std::sort(zs.begin(), zs.end());
|
||||
m_layer_count += (unsigned int)(object->copies().size() * (std::unique(zs.begin(), zs.end()) - zs.begin()));
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
// Print all objects with the same print_z together.
|
||||
std::vector<coordf_t> zs;
|
||||
for (auto object : print.objects()) {
|
||||
|
@ -936,31 +932,31 @@ void GCode::_do_export(Print &print, FILE *file)
|
|||
// get the minimum cross-section used in the print
|
||||
std::vector<double> mm3_per_mm;
|
||||
for (auto object : print.objects()) {
|
||||
for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id) {
|
||||
for (size_t region_id = 0; region_id < object->region_volumes.size(); ++region_id) {
|
||||
const PrintRegion* region = print.regions()[region_id];
|
||||
for (auto layer : object->layers()) {
|
||||
const LayerRegion* layerm = layer->regions()[region_id];
|
||||
if (region->config().get_abs_value("perimeter_speed" ) == 0 ||
|
||||
region->config().get_abs_value("small_perimeter_speed" ) == 0 ||
|
||||
region->config().get_abs_value("external_perimeter_speed" ) == 0 ||
|
||||
region->config().get_abs_value("bridge_speed" ) == 0)
|
||||
if (region->config().get_abs_value("perimeter_speed") == 0 ||
|
||||
region->config().get_abs_value("small_perimeter_speed") == 0 ||
|
||||
region->config().get_abs_value("external_perimeter_speed") == 0 ||
|
||||
region->config().get_abs_value("bridge_speed") == 0)
|
||||
mm3_per_mm.push_back(layerm->perimeters.min_mm3_per_mm());
|
||||
if (region->config().get_abs_value("infill_speed" ) == 0 ||
|
||||
region->config().get_abs_value("solid_infill_speed" ) == 0 ||
|
||||
region->config().get_abs_value("top_solid_infill_speed" ) == 0 ||
|
||||
region->config().get_abs_value("bridge_speed" ) == 0)
|
||||
if (region->config().get_abs_value("infill_speed") == 0 ||
|
||||
region->config().get_abs_value("solid_infill_speed") == 0 ||
|
||||
region->config().get_abs_value("top_solid_infill_speed") == 0 ||
|
||||
region->config().get_abs_value("bridge_speed") == 0)
|
||||
mm3_per_mm.push_back(layerm->fills.min_mm3_per_mm());
|
||||
}
|
||||
}
|
||||
if (object->config().get_abs_value("support_material_speed" ) == 0 ||
|
||||
object->config().get_abs_value("support_material_interface_speed" ) == 0)
|
||||
if (object->config().get_abs_value("support_material_speed") == 0 ||
|
||||
object->config().get_abs_value("support_material_interface_speed") == 0)
|
||||
for (auto layer : object->support_layers())
|
||||
mm3_per_mm.push_back(layer->support_fills.min_mm3_per_mm());
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
// filter out 0-width segments
|
||||
mm3_per_mm.erase(std::remove_if(mm3_per_mm.begin(), mm3_per_mm.end(), [](double v) { return v < 0.000001; }), mm3_per_mm.end());
|
||||
if (! mm3_per_mm.empty()) {
|
||||
if (!mm3_per_mm.empty()) {
|
||||
// In order to honor max_print_speed we need to find a target volumetric
|
||||
// speed that we can use throughout the print. So we define this target
|
||||
// volumetric speed as the volumetric speed produced by printing the
|
||||
|
@ -991,15 +987,15 @@ void GCode::_do_export(Print &print, FILE *file)
|
|||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
// Write thumbnails using base64 encoding
|
||||
if (thumbnail_data != nullptr)
|
||||
if (thumbnail_cb != nullptr)
|
||||
{
|
||||
const size_t max_row_length = 78;
|
||||
|
||||
for (const ThumbnailData& data : *thumbnail_data)
|
||||
ThumbnailsList thumbnails;
|
||||
thumbnail_cb(thumbnails, print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, false);
|
||||
for (const ThumbnailData& data : thumbnails)
|
||||
{
|
||||
if (data.is_valid())
|
||||
{
|
||||
#if ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
size_t png_size = 0;
|
||||
void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1);
|
||||
if (png_data != nullptr)
|
||||
|
@ -1025,39 +1021,6 @@ void GCode::_do_export(Print &print, FILE *file)
|
|||
|
||||
mz_free(png_data);
|
||||
}
|
||||
#else
|
||||
_write_format(file, "\n;\n; thumbnail begin %dx%d\n", data.width, data.height);
|
||||
|
||||
size_t row_size = 4 * data.width;
|
||||
for (int r = (int)data.height - 1; r >= 0; --r)
|
||||
{
|
||||
std::string encoded;
|
||||
encoded.resize(boost::beast::detail::base64::encoded_size(row_size));
|
||||
encoded.resize(boost::beast::detail::base64::encode((void*)&encoded[0], (const void*)(data.pixels.data() + r * row_size), row_size));
|
||||
|
||||
unsigned int row_count = 0;
|
||||
while (encoded.size() > max_row_length)
|
||||
{
|
||||
if (row_count == 0)
|
||||
_write_format(file, "; %s\n", encoded.substr(0, max_row_length).c_str());
|
||||
else
|
||||
_write_format(file, ";>%s\n", encoded.substr(0, max_row_length).c_str());
|
||||
|
||||
encoded = encoded.substr(max_row_length);
|
||||
++row_count;
|
||||
}
|
||||
|
||||
if (encoded.size() > 0)
|
||||
{
|
||||
if (row_count == 0)
|
||||
_write_format(file, "; %s\n", encoded.c_str());
|
||||
else
|
||||
_write_format(file, ";>%s\n", encoded.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
_write(file, "; thumbnail end\n;\n");
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
}
|
||||
print.throw_if_canceled();
|
||||
}
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
#include "GCodeTimeEstimator.hpp"
|
||||
#include "EdgeGrid.hpp"
|
||||
#include "GCode/Analyzer.hpp"
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "GCode/ThumbnailData.hpp"
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
@ -30,9 +33,6 @@ namespace Slic3r {
|
|||
// Forward declarations.
|
||||
class GCode;
|
||||
class GCodePreviewData;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
struct ThumbnailData;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
class AvoidCrossingPerimeters {
|
||||
public:
|
||||
|
@ -167,7 +167,7 @@ public:
|
|||
// throws std::runtime_exception on error,
|
||||
// throws CanceledException through print->throw_if_canceled().
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void do_export(Print* print, const char* path, GCodePreviewData* preview_data = nullptr, const std::vector<ThumbnailData>* thumbnail_data = nullptr);
|
||||
void do_export(Print* print, const char* path, GCodePreviewData* preview_data = nullptr, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
|
||||
#else
|
||||
void do_export(Print *print, const char *path, GCodePreviewData *preview_data = nullptr);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
@ -199,7 +199,7 @@ public:
|
|||
|
||||
protected:
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void _do_export(Print& print, FILE* file, const std::vector<ThumbnailData>* thumbnail_data);
|
||||
void _do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thumbnail_cb);
|
||||
#else
|
||||
void _do_export(Print &print, FILE *file);
|
||||
#endif //ENABLE_THUMBNAIL_GENERATOR
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
#include <vector>
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -20,6 +21,9 @@ struct ThumbnailData
|
|||
bool is_valid() const;
|
||||
};
|
||||
|
||||
typedef std::vector<ThumbnailData> ThumbnailsList;
|
||||
typedef std::function<void(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool transparent_background)> ThumbnailsGeneratorCallback;
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
|
|
@ -137,6 +137,79 @@ inline bool segments_intersect(
|
|||
segments_could_intersect(jp1, jp2, ip1, ip2) <= 0;
|
||||
}
|
||||
|
||||
// Based on Liang-Barsky function by Daniel White @ http://www.skytopia.com/project/articles/compsci/clipping.html
|
||||
template<typename T>
|
||||
bool liang_barsky_line_clipping(
|
||||
// Start and end points of the source line, result will be stored there as well.
|
||||
Eigen::Matrix<T, 2, 1, Eigen::DontAlign> &x0,
|
||||
Eigen::Matrix<T, 2, 1, Eigen::DontAlign> &x1,
|
||||
// Bounding box to clip with.
|
||||
const BoundingBoxBase<Eigen::Matrix<T, 2, 1, Eigen::DontAlign>> &bbox)
|
||||
{
|
||||
Eigen::Matrix<T, 2, 1, Eigen::DontAlign> v = x1 - x0;
|
||||
double t0 = 0.0;
|
||||
double t1 = 1.0;
|
||||
|
||||
// Traverse through left, right, bottom, top edges.
|
||||
for (int edge = 0; edge < 4; ++ edge)
|
||||
{
|
||||
double p, q;
|
||||
switch (edge) {
|
||||
case 0: p = - v.x(); q = - bbox.min.x() + x0.x(); break;
|
||||
case 1: p = v.x(); q = bbox.max.x() - x0.x(); break;
|
||||
case 2: p = - v.y(); q = - bbox.min.y() + x0.y(); break;
|
||||
default: p = v.y(); q = bbox.max.y() - x0.y(); break;
|
||||
}
|
||||
|
||||
if (p == 0) {
|
||||
if (q < 0)
|
||||
// Line parallel to the bounding box edge is fully outside of the bounding box.
|
||||
return false;
|
||||
// else don't clip
|
||||
} else {
|
||||
double r = q / p;
|
||||
if (p < 0) {
|
||||
if (r > t1)
|
||||
// Fully clipped.
|
||||
return false;
|
||||
if (r > t0)
|
||||
// Partially clipped.
|
||||
t0 = r;
|
||||
} else {
|
||||
assert(p > 0);
|
||||
if (r < t0)
|
||||
// Fully clipped.
|
||||
return false;
|
||||
if (r < t1)
|
||||
// Partially clipped.
|
||||
t1 = r;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clipped successfully.
|
||||
x1 = x0 + t1 * v;
|
||||
x0 += t0 * v;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Based on Liang-Barsky function by Daniel White @ http://www.skytopia.com/project/articles/compsci/clipping.html
|
||||
template<typename T>
|
||||
bool liang_barsky_line_clipping(
|
||||
// Start and end points of the source line.
|
||||
const Eigen::Matrix<T, 2, 1, Eigen::DontAlign> &x0src,
|
||||
const Eigen::Matrix<T, 2, 1, Eigen::DontAlign> &x1src,
|
||||
// Bounding box to clip with.
|
||||
const BoundingBoxBase<Eigen::Matrix<T, 2, 1, Eigen::DontAlign>> &bbox,
|
||||
// Start and end points of the clipped line.
|
||||
Eigen::Matrix<T, 2, 1, Eigen::DontAlign> &x0clip,
|
||||
Eigen::Matrix<T, 2, 1, Eigen::DontAlign> &x1clip)
|
||||
{
|
||||
x0clip = x0src;
|
||||
x1clip = x1src;
|
||||
return liang_barsky_line_clipping(x0clip, x1clip, bbox);
|
||||
}
|
||||
|
||||
Pointf3s convex_hull(Pointf3s points);
|
||||
Polygon convex_hull(Points points);
|
||||
Polygon convex_hull(const Polygons &polygons);
|
||||
|
|
|
@ -107,6 +107,17 @@ bool Line::intersection(const Line &l2, Point *intersection) const
|
|||
return false; // not intersecting
|
||||
}
|
||||
|
||||
bool Line::clip_with_bbox(const BoundingBox &bbox)
|
||||
{
|
||||
Vec2d x0clip, x1clip;
|
||||
bool result = Geometry::liang_barsky_line_clipping<double>(this->a.cast<double>(), this->b.cast<double>(), BoundingBoxf(bbox.min.cast<double>(), bbox.max.cast<double>()), x0clip, x1clip);
|
||||
if (result) {
|
||||
this->a = x0clip.cast<coord_t>();
|
||||
this->b = x1clip.cast<coord_t>();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Vec3d Linef3::intersect_plane(double z) const
|
||||
{
|
||||
auto v = (this->b - this->a).cast<double>();
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
namespace Slic3r {
|
||||
|
||||
class BoundingBox;
|
||||
class Line;
|
||||
class Line3;
|
||||
class Linef3;
|
||||
|
@ -43,6 +44,8 @@ public:
|
|||
Vector normal() const { return Vector((this->b(1) - this->a(1)), -(this->b(0) - this->a(0))); }
|
||||
bool intersection(const Line& line, Point* intersection) const;
|
||||
double ccw(const Point& point) const { return point.ccw(*this); }
|
||||
// Clip a line with a bounding box. Returns false if the line is completely outside of the bounding box.
|
||||
bool clip_with_bbox(const BoundingBox &bbox);
|
||||
|
||||
static double distance_to_squared(const Point &point, const Point &a, const Point &b);
|
||||
static double distance_to(const Point &point, const Point &a, const Point &b) { return sqrt(distance_to_squared(point, a, b)); }
|
||||
|
|
|
@ -319,7 +319,7 @@ Polyline MotionPlannerGraph::shortest_path(size_t node_start, size_t node_end) c
|
|||
std::vector<size_t> map_node_to_queue_id(m_adjacency_list.size(), size_t(-1));
|
||||
distance[node_start] = 0.;
|
||||
|
||||
auto queue = make_mutable_priority_queue<node_t>(
|
||||
auto queue = make_mutable_priority_queue<node_t, false>(
|
||||
[&map_node_to_queue_id](const node_t node, size_t idx) { map_node_to_queue_id[node] = idx; },
|
||||
[&distance](const node_t node1, const node_t node2) { return distance[node1] < distance[node2]; });
|
||||
queue.reserve(m_adjacency_list.size());
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#include <assert.h>
|
||||
|
||||
template<typename T, typename IndexSetter, typename LessPredicate>
|
||||
template<typename T, typename IndexSetter, typename LessPredicate, const bool ResetIndexWhenRemoved = false>
|
||||
class MutablePriorityQueue
|
||||
{
|
||||
public:
|
||||
|
@ -42,26 +42,30 @@ private:
|
|||
LessPredicate m_less_predicate;
|
||||
};
|
||||
|
||||
template<typename T, typename IndexSetter, typename LessPredicate>
|
||||
MutablePriorityQueue<T, IndexSetter, LessPredicate> make_mutable_priority_queue(IndexSetter &&index_setter, LessPredicate &&less_predicate)
|
||||
template<typename T, const bool ResetIndexWhenRemoved, typename IndexSetter, typename LessPredicate>
|
||||
MutablePriorityQueue<T, IndexSetter, LessPredicate, ResetIndexWhenRemoved> make_mutable_priority_queue(IndexSetter &&index_setter, LessPredicate &&less_predicate)
|
||||
{
|
||||
return MutablePriorityQueue<T, IndexSetter, LessPredicate>(
|
||||
return MutablePriorityQueue<T, IndexSetter, LessPredicate, ResetIndexWhenRemoved>(
|
||||
std::forward<IndexSetter>(index_setter), std::forward<LessPredicate>(less_predicate));
|
||||
}
|
||||
|
||||
template<class T, class LessPredicate, class IndexSetter>
|
||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::clear()
|
||||
template<class T, class LessPredicate, class IndexSetter, const bool ResetIndexWhenRemoved>
|
||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter, ResetIndexWhenRemoved>::clear()
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
#ifdef NDEBUG
|
||||
// Only mark as removed from the queue in release mode, if configured so.
|
||||
if (ResetIndexWhenRemoved)
|
||||
#endif /* NDEBUG */
|
||||
{
|
||||
for (size_t idx = 0; idx < m_heap.size(); ++ idx)
|
||||
// Mark as removed from the queue.
|
||||
m_index_setter(m_heap[idx], std::numeric_limits<size_t>::max());
|
||||
#endif /* NDEBUG */
|
||||
}
|
||||
m_heap.clear();
|
||||
}
|
||||
|
||||
template<class T, class LessPredicate, class IndexSetter>
|
||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::push(const T &item)
|
||||
template<class T, class LessPredicate, class IndexSetter, const bool ResetIndexWhenRemoved>
|
||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter, ResetIndexWhenRemoved>::push(const T &item)
|
||||
{
|
||||
size_t idx = m_heap.size();
|
||||
m_heap.emplace_back(item);
|
||||
|
@ -69,8 +73,8 @@ inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::push(const T &i
|
|||
update_heap_up(0, idx);
|
||||
}
|
||||
|
||||
template<class T, class LessPredicate, class IndexSetter>
|
||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::push(T &&item)
|
||||
template<class T, class LessPredicate, class IndexSetter, const bool ResetIndexWhenRemoved>
|
||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter, ResetIndexWhenRemoved>::push(T &&item)
|
||||
{
|
||||
size_t idx = m_heap.size();
|
||||
m_heap.emplace_back(std::move(item));
|
||||
|
@ -78,14 +82,18 @@ inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::push(T &&item)
|
|||
update_heap_up(0, idx);
|
||||
}
|
||||
|
||||
template<class T, class LessPredicate, class IndexSetter>
|
||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::pop()
|
||||
template<class T, class LessPredicate, class IndexSetter, const bool ResetIndexWhenRemoved>
|
||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter, ResetIndexWhenRemoved>::pop()
|
||||
{
|
||||
assert(! m_heap.empty());
|
||||
#ifndef NDEBUG
|
||||
#ifdef NDEBUG
|
||||
// Only mark as removed from the queue in release mode, if configured so.
|
||||
if (ResetIndexWhenRemoved)
|
||||
#endif /* NDEBUG */
|
||||
{
|
||||
// Mark as removed from the queue.
|
||||
m_index_setter(m_heap.front(), std::numeric_limits<size_t>::max());
|
||||
#endif /* NDEBUG */
|
||||
}
|
||||
if (m_heap.size() > 1) {
|
||||
m_heap.front() = m_heap.back();
|
||||
m_heap.pop_back();
|
||||
|
@ -95,14 +103,18 @@ inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::pop()
|
|||
m_heap.clear();
|
||||
}
|
||||
|
||||
template<class T, class LessPredicate, class IndexSetter>
|
||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::remove(size_t idx)
|
||||
template<class T, class LessPredicate, class IndexSetter, const bool ResetIndexWhenRemoved>
|
||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter, ResetIndexWhenRemoved>::remove(size_t idx)
|
||||
{
|
||||
assert(idx < m_heap.size());
|
||||
#ifndef NDEBUG
|
||||
#ifdef NDEBUG
|
||||
// Only mark as removed from the queue in release mode, if configured so.
|
||||
if (ResetIndexWhenRemoved)
|
||||
#endif /* NDEBUG */
|
||||
{
|
||||
// Mark as removed from the queue.
|
||||
m_index_setter(m_heap[idx], std::numeric_limits<size_t>::max());
|
||||
#endif /* NDEBUG */
|
||||
}
|
||||
if (idx + 1 == m_heap.size()) {
|
||||
m_heap.pop_back();
|
||||
return;
|
||||
|
@ -114,8 +126,8 @@ inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::remove(size_t i
|
|||
update_heap_up(0, idx);
|
||||
}
|
||||
|
||||
template<class T, class LessPredicate, class IndexSetter>
|
||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::update_heap_up(size_t top, size_t bottom)
|
||||
template<class T, class LessPredicate, class IndexSetter, const bool ResetIndexWhenRemoved>
|
||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter, ResetIndexWhenRemoved>::update_heap_up(size_t top, size_t bottom)
|
||||
{
|
||||
size_t childIdx = bottom;
|
||||
T *child = &m_heap[childIdx];
|
||||
|
@ -138,8 +150,8 @@ inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::update_heap_up(
|
|||
}
|
||||
}
|
||||
|
||||
template<class T, class LessPredicate, class IndexSetter>
|
||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter>::update_heap_down(size_t top, size_t bottom)
|
||||
template<class T, class LessPredicate, class IndexSetter, const bool ResetIndexWhenRemoved>
|
||||
inline void MutablePriorityQueue<T, LessPredicate, IndexSetter, ResetIndexWhenRemoved>::update_heap_down(size_t top, size_t bottom)
|
||||
{
|
||||
size_t parentIdx = top;
|
||||
T *parent = &m_heap[parentIdx];
|
||||
|
|
|
@ -1538,7 +1538,7 @@ void Print::process()
|
|||
// write error into the G-code, cannot execute post-processing scripts).
|
||||
// It is up to the caller to show an error message.
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
std::string Print::export_gcode(const std::string& path_template, GCodePreviewData* preview_data, const std::vector<ThumbnailData>* thumbnail_data)
|
||||
std::string Print::export_gcode(const std::string& path_template, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb)
|
||||
#else
|
||||
std::string Print::export_gcode(const std::string &path_template, GCodePreviewData *preview_data)
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
@ -1559,7 +1559,7 @@ std::string Print::export_gcode(const std::string &path_template, GCodePreviewDa
|
|||
// The following line may die for multiple reasons.
|
||||
GCode gcode;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
gcode.do_export(this, path.c_str(), preview_data, thumbnail_data);
|
||||
gcode.do_export(this, path.c_str(), preview_data, thumbnail_cb);
|
||||
#else
|
||||
gcode.do_export(this, path.c_str(), preview_data);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
|
|
@ -11,6 +11,9 @@
|
|||
#include "Slicing.hpp"
|
||||
#include "GCode/ToolOrdering.hpp"
|
||||
#include "GCode/WipeTower.hpp"
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "GCode/ThumbnailData.hpp"
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -19,9 +22,6 @@ class PrintObject;
|
|||
class ModelObject;
|
||||
class GCode;
|
||||
class GCodePreviewData;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
struct ThumbnailData;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
// Print step IDs for keeping track of the print state.
|
||||
enum PrintStep {
|
||||
|
@ -311,7 +311,7 @@ public:
|
|||
// Exports G-code into a file name based on the path_template, returns the file path of the generated G-code file.
|
||||
// If preview_data is not null, the preview_data is filled in for the G-code visualization (not used by the command line Slic3r).
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
std::string export_gcode(const std::string& path_template, GCodePreviewData* preview_data, const std::vector<ThumbnailData>* thumbnail_data = nullptr);
|
||||
std::string export_gcode(const std::string& path_template, GCodePreviewData* preview_data, ThumbnailsGeneratorCallback thumbnail_cb = nullptr);
|
||||
#else
|
||||
std::string export_gcode(const std::string &path_template, GCodePreviewData *preview_data);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
|
|
@ -191,7 +191,7 @@ std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals
|
|||
}
|
||||
|
||||
// Initialize a heap of end points sorted by the lowest distance to the next valid point of a path.
|
||||
auto queue = make_mutable_priority_queue<EndPoint*>(
|
||||
auto queue = make_mutable_priority_queue<EndPoint*, false>(
|
||||
[](EndPoint *ep, size_t idx){ ep->heap_idx = idx; },
|
||||
[](EndPoint *l, EndPoint *r){ return l->distance_out < r->distance_out; });
|
||||
queue.reserve(end_points.size() * 2 - 1);
|
||||
|
@ -389,6 +389,585 @@ std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals
|
|||
return out;
|
||||
}
|
||||
|
||||
template<typename QueueType, typename KDTreeType, typename ChainsType, typename EndPointType>
|
||||
void update_end_point_in_queue(QueueType &queue, const KDTreeType &kdtree, ChainsType &chains, std::vector<EndPointType> &end_points, EndPointType &end_point, size_t first_point_idx, const EndPointType *first_point)
|
||||
{
|
||||
// Updating an end point or a 2nd from an end point.
|
||||
size_t this_idx = end_point.index(end_points);
|
||||
// If this segment is not the starting segment, then this end point or the opposite is unconnected.
|
||||
assert(first_point_idx == this_idx || first_point_idx == (this_idx ^ 1) || end_point.chain_id == 0 || end_point.opposite(end_points).chain_id == 0);
|
||||
end_point.edge_candidate = nullptr;
|
||||
if (first_point_idx == this_idx || (end_point.chain_id > 0 && first_point_idx == (this_idx ^ 1)))
|
||||
{
|
||||
// One may never flip the 1st edge, don't try it again.
|
||||
if (! end_point.heap_idx_invalid())
|
||||
queue.remove(end_point.heap_idx);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update edge_candidate and distance.
|
||||
size_t chain1a = end_point.chain_id;
|
||||
size_t chain1b = end_points[this_idx ^ 1].chain_id;
|
||||
size_t this_chain = chains.equivalent(std::max(chain1a, chain1b));
|
||||
// Find the closest point to this end_point, which lies on a different extrusion path (filtered by the filter lambda).
|
||||
size_t next_idx = find_closest_point(kdtree, end_point.pos, [&end_points, &chains, this_idx, first_point_idx, first_point, this_chain](size_t idx) {
|
||||
assert(end_points[this_idx].edge_candidate == nullptr);
|
||||
// Either this end of the edge or the other end of the edge is not yet connected.
|
||||
assert((end_points[this_idx ].chain_id == 0 && end_points[this_idx ].edge_out == nullptr) ||
|
||||
(end_points[this_idx ^ 1].chain_id == 0 && end_points[this_idx ^ 1].edge_out == nullptr));
|
||||
if ((idx ^ this_idx) <= 1 || idx == first_point_idx)
|
||||
// Points of the same segment shall not be connected.
|
||||
// Don't connect to the first point, we must not flip the 1st edge.
|
||||
return false;
|
||||
size_t chain2a = end_points[idx].chain_id;
|
||||
size_t chain2b = end_points[idx ^ 1].chain_id;
|
||||
if (chain2a > 0 && chain2b > 0)
|
||||
// Only unconnected end point or a point next to an unconnected end point may be connected to.
|
||||
// Ideally those would be removed from the KD tree, but the update is difficult.
|
||||
return false;
|
||||
assert(chain2a == 0 || chain2b == 0);
|
||||
size_t chain2 = chains.equivalent(std::max(chain2a, chain2b));
|
||||
if (this_chain == chain2)
|
||||
// Don't connect back to the same chain, don't create a loop.
|
||||
return this_chain == 0;
|
||||
// Don't connect to a segment requiring flipping if the segment starts or ends with the first point.
|
||||
if (chain2a > 0) {
|
||||
// Chain requires flipping.
|
||||
assert(chain2b == 0);
|
||||
auto &chain = chains.chain(chain2);
|
||||
if (chain.begin == first_point || chain.end == first_point)
|
||||
return false;
|
||||
}
|
||||
// Everything is all right, try to connect.
|
||||
return true;
|
||||
});
|
||||
assert(next_idx < end_points.size());
|
||||
assert(chains.equivalent(end_points[next_idx].chain_id) != chains.equivalent(end_points[next_idx ^ 1].chain_id) || end_points[next_idx].chain_id == 0);
|
||||
end_point.edge_candidate = &end_points[next_idx];
|
||||
end_point.distance_out = (end_points[next_idx].pos - end_point.pos).norm();
|
||||
if (end_point.chain_id > 0)
|
||||
end_point.distance_out += chains.chain_flip_penalty(this_chain);
|
||||
if (end_points[next_idx].chain_id > 0)
|
||||
// The candidate chain is flipped.
|
||||
end_point.distance_out += chains.chain_flip_penalty(end_points[next_idx].chain_id);
|
||||
// Update position of this end point in the queue based on the distance calculated at the line above.
|
||||
if (end_point.heap_idx_invalid())
|
||||
queue.push(&end_point);
|
||||
else
|
||||
queue.update(end_point.heap_idx);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename PointType, typename SegmentEndPointFunc, bool REVERSE_COULD_FAIL, typename CouldReverseFunc>
|
||||
std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals2_(SegmentEndPointFunc end_point_func, CouldReverseFunc could_reverse_func, size_t num_segments, const PointType *start_near)
|
||||
{
|
||||
std::vector<std::pair<size_t, bool>> out;
|
||||
|
||||
if (num_segments == 0) {
|
||||
// Nothing to do.
|
||||
}
|
||||
else if (num_segments == 1)
|
||||
{
|
||||
// Just sort the end points so that the first point visited is closest to start_near.
|
||||
out.emplace_back(0, start_near != nullptr &&
|
||||
(end_point_func(0, true) - *start_near).template cast<double>().squaredNorm() < (end_point_func(0, false) - *start_near).template cast<double>().squaredNorm());
|
||||
}
|
||||
else
|
||||
{
|
||||
// End points of segments for the KD tree closest point search.
|
||||
// A single end point is inserted into the search structure for loops, two end points are entered for open paths.
|
||||
struct EndPoint {
|
||||
EndPoint(const Vec2d &pos) : pos(pos) {}
|
||||
Vec2d pos;
|
||||
|
||||
// Candidate for a new connection link.
|
||||
EndPoint *edge_candidate = nullptr;
|
||||
// Distance to the next end point following the link.
|
||||
// Zero value -> start of the final path.
|
||||
double distance_out = std::numeric_limits<double>::max();
|
||||
|
||||
size_t heap_idx = std::numeric_limits<size_t>::max();
|
||||
bool heap_idx_invalid() const { return this->heap_idx == std::numeric_limits<size_t>::max(); }
|
||||
|
||||
// Identifier of the chain, to which this end point belongs. Zero means unassigned.
|
||||
size_t chain_id = 0;
|
||||
// Double linked chain of segment end points in current path.
|
||||
EndPoint *edge_out = nullptr;
|
||||
|
||||
size_t index(std::vector<EndPoint> &endpoints) const { return this - endpoints.data(); }
|
||||
// Opposite end point of the same segment.
|
||||
EndPoint& opposite(std::vector<EndPoint> &endpoints) { return endpoints[(this - endpoints.data()) ^ 1]; }
|
||||
const EndPoint& opposite(const std::vector<EndPoint> &endpoints) const { return endpoints[(this - endpoints.data()) ^ 1]; }
|
||||
};
|
||||
|
||||
std::vector<EndPoint> end_points;
|
||||
end_points.reserve(num_segments * 2);
|
||||
for (size_t i = 0; i < num_segments; ++ i) {
|
||||
end_points.emplace_back(end_point_func(i, true ).template cast<double>());
|
||||
end_points.emplace_back(end_point_func(i, false).template cast<double>());
|
||||
}
|
||||
|
||||
// Construct the closest point KD tree over end points of segments.
|
||||
auto coordinate_fn = [&end_points](size_t idx, size_t dimension) -> double { return end_points[idx].pos[dimension]; };
|
||||
KDTreeIndirect<2, double, decltype(coordinate_fn)> kdtree(coordinate_fn, end_points.size());
|
||||
|
||||
// Chained segments with their sum of connection lengths.
|
||||
// The chain supports flipping all the segments, connecting the segments at the opposite ends.
|
||||
// (this is a very useful path optimization for infill lines).
|
||||
struct Chain {
|
||||
size_t num_segments = 0;
|
||||
double cost = 0.;
|
||||
double cost_flipped = 0.;
|
||||
EndPoint *begin = nullptr;
|
||||
EndPoint *end = nullptr;
|
||||
size_t equivalent_with = 0;
|
||||
|
||||
// Flipping the chain has a time complexity of O(n).
|
||||
void flip(std::vector<EndPoint> &endpoints)
|
||||
{
|
||||
assert(this->num_segments > 1);
|
||||
assert(this->begin->edge_out == nullptr);
|
||||
assert(this->end ->edge_out == nullptr);
|
||||
assert(this->begin->opposite(endpoints).edge_out != nullptr);
|
||||
assert(this->end ->opposite(endpoints).edge_out != nullptr);
|
||||
// Start of the current segment processed.
|
||||
EndPoint *ept = this->begin;
|
||||
// Previous end point to connect the other side of ept to.
|
||||
EndPoint *ept_prev = nullptr;
|
||||
do {
|
||||
EndPoint *ept_end = &ept->opposite(endpoints);
|
||||
EndPoint *ept_next = ept_end->edge_out;
|
||||
assert(ept_next == nullptr || ept_next->edge_out == ept_end);
|
||||
// Connect to the preceding segment.
|
||||
ept_end->edge_out = ept_prev;
|
||||
if (ept_prev != nullptr)
|
||||
ept_prev->edge_out = ept_end;
|
||||
ept_prev = ept;
|
||||
ept = ept_next;
|
||||
} while (ept != nullptr);
|
||||
ept_prev->edge_out = nullptr;
|
||||
// Swap the costs.
|
||||
std::swap(this->cost, this->cost_flipped);
|
||||
// Swap the ends.
|
||||
EndPoint *new_begin = &this->begin->opposite(endpoints);
|
||||
EndPoint *new_end = &this->end->opposite(endpoints);
|
||||
std::swap(this->begin->chain_id, new_begin->chain_id);
|
||||
std::swap(this->end ->chain_id, new_end ->chain_id);
|
||||
this->begin = new_begin;
|
||||
this->end = new_end;
|
||||
assert(this->begin->edge_out == nullptr);
|
||||
assert(this->end ->edge_out == nullptr);
|
||||
assert(this->begin->opposite(endpoints).edge_out != nullptr);
|
||||
assert(this->end ->opposite(endpoints).edge_out != nullptr);
|
||||
}
|
||||
|
||||
double flip_penalty() const { return this->cost_flipped - this->cost; }
|
||||
};
|
||||
|
||||
// Helper to detect loops in already connected paths and to accomodate flipping of chains.
|
||||
//
|
||||
// Unique chain IDs are assigned to paths. If paths are connected, end points will not have their chain IDs updated, but the chain IDs
|
||||
// will remember an "equivalent" chain ID, which is the lowest ID of all the IDs in the path, and the lowest ID is equivalent to itself.
|
||||
// Chain IDs are indexed starting with 1.
|
||||
//
|
||||
// Chains remember their lengths and their lengths when each segment of the chain is flipped.
|
||||
class Chains {
|
||||
public:
|
||||
// Zero'th chain ID is invalid.
|
||||
Chains(size_t reserve) {
|
||||
m_chains.reserve(reserve / 2);
|
||||
// Indexing starts with 1.
|
||||
m_chains.emplace_back();
|
||||
}
|
||||
|
||||
// Generate next equivalence class.
|
||||
size_t next_id() {
|
||||
m_chains.emplace_back();
|
||||
m_chains.back().equivalent_with = ++ m_last_chain_id;
|
||||
return m_last_chain_id;
|
||||
}
|
||||
|
||||
// Get equivalence class for chain ID, update the "equivalent_with" along the equivalence path.
|
||||
size_t equivalent(size_t chain_id) {
|
||||
if (chain_id != 0) {
|
||||
for (size_t last = chain_id;;) {
|
||||
size_t lower = m_chains[last].equivalent_with;
|
||||
if (lower == last) {
|
||||
m_chains[chain_id].equivalent_with = lower;
|
||||
chain_id = lower;
|
||||
break;
|
||||
}
|
||||
last = lower;
|
||||
}
|
||||
}
|
||||
return chain_id;
|
||||
}
|
||||
|
||||
// Return a lowest chain ID of the two input chains.
|
||||
// Produce a new chain ID of both chain IDs are zero.
|
||||
size_t merge(size_t chain_id1, size_t chain_id2) {
|
||||
if (chain_id1 == 0)
|
||||
return (chain_id2 == 0) ? this->next_id() : chain_id2;
|
||||
if (chain_id2 == 0)
|
||||
return chain_id1;
|
||||
assert(m_chains[chain_id1].equivalent_with == chain_id1);
|
||||
assert(m_chains[chain_id2].equivalent_with == chain_id2);
|
||||
size_t chain_id = std::min(chain_id1, chain_id2);
|
||||
m_chains[chain_id1].equivalent_with = chain_id;
|
||||
m_chains[chain_id2].equivalent_with = chain_id;
|
||||
return chain_id;
|
||||
}
|
||||
|
||||
Chain& chain(size_t chain_id) { return m_chains[chain_id]; }
|
||||
const Chain& chain(size_t chain_id) const { return m_chains[chain_id]; }
|
||||
|
||||
double chain_flip_penalty(size_t chain_id) {
|
||||
chain_id = this->equivalent(chain_id);
|
||||
return m_chains[chain_id].flip_penalty();
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
bool validate()
|
||||
{
|
||||
// Validate that the segments merged chain IDs make up a directed acyclic graph
|
||||
// with edges oriented towards the lower chain ID, therefore all ending up
|
||||
// in the lowest chain ID of all of them.
|
||||
assert(m_last_chain_id >= 0);
|
||||
assert(m_last_chain_id + 1 == m_chains.size());
|
||||
for (size_t i = 0; i < m_chains.size(); ++ i) {
|
||||
for (size_t last = i;;) {
|
||||
size_t lower = m_chains[last].equivalent_with;
|
||||
assert(lower <= last);
|
||||
if (lower == last)
|
||||
break;
|
||||
last = lower;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
|
||||
private:
|
||||
std::vector<Chain> m_chains;
|
||||
// Unique chain ID assigned to chains of end points of segments.
|
||||
size_t m_last_chain_id = 0;
|
||||
} chains(num_segments);
|
||||
|
||||
// Find the first end point closest to start_near.
|
||||
EndPoint *first_point = nullptr;
|
||||
size_t first_point_idx = std::numeric_limits<size_t>::max();
|
||||
if (start_near != nullptr) {
|
||||
size_t idx = find_closest_point(kdtree, start_near->template cast<double>());
|
||||
assert(idx < end_points.size());
|
||||
first_point = &end_points[idx];
|
||||
first_point->distance_out = 0.;
|
||||
first_point->chain_id = chains.next_id();
|
||||
Chain &chain = chains.chain(first_point->chain_id);
|
||||
chain.begin = first_point;
|
||||
chain.end = &first_point->opposite(end_points);
|
||||
first_point_idx = idx;
|
||||
}
|
||||
EndPoint *initial_point = first_point;
|
||||
EndPoint *last_point = nullptr;
|
||||
|
||||
// Assign the closest point and distance to the end points.
|
||||
for (EndPoint &end_point : end_points) {
|
||||
assert(end_point.edge_candidate == nullptr);
|
||||
if (&end_point != first_point) {
|
||||
size_t this_idx = end_point.index(end_points);
|
||||
// Find the closest point to this end_point, which lies on a different extrusion path (filtered by the lambda).
|
||||
// Ignore the starting point as the starting point is considered to be occupied, no end point coud connect to it.
|
||||
size_t next_idx = find_closest_point(kdtree, end_point.pos,
|
||||
[this_idx, first_point_idx](size_t idx){ return idx != first_point_idx && (idx ^ this_idx) > 1; });
|
||||
assert(next_idx < end_points.size());
|
||||
EndPoint &end_point2 = end_points[next_idx];
|
||||
end_point.edge_candidate = &end_point2;
|
||||
end_point.distance_out = (end_point2.pos - end_point.pos).norm();
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize a heap of end points sorted by the lowest distance to the next valid point of a path.
|
||||
auto queue = make_mutable_priority_queue<EndPoint*, true>(
|
||||
[](EndPoint *ep, size_t idx){ ep->heap_idx = idx; },
|
||||
[](EndPoint *l, EndPoint *r){ return l->distance_out < r->distance_out; });
|
||||
queue.reserve(end_points.size() * 2);
|
||||
for (EndPoint &ep : end_points)
|
||||
if (first_point != &ep)
|
||||
queue.push(&ep);
|
||||
|
||||
#ifndef NDEBUG
|
||||
auto validate_graph_and_queue = [&chains, &end_points, &queue, first_point]() -> bool {
|
||||
assert(chains.validate());
|
||||
for (EndPoint &ep : end_points) {
|
||||
if (ep.heap_idx < queue.size()) {
|
||||
// End point is on the heap.
|
||||
assert(*(queue.cbegin() + ep.heap_idx) == &ep);
|
||||
// One side or the other of the segment is not yet connected.
|
||||
assert(ep.chain_id == 0 || ep.opposite(end_points).chain_id == 0);
|
||||
} else {
|
||||
// End point is NOT on the heap, therefore it must part of the output path.
|
||||
assert(ep.heap_idx_invalid());
|
||||
assert(ep.chain_id != 0);
|
||||
if (&ep == first_point) {
|
||||
assert(ep.edge_out == nullptr);
|
||||
} else {
|
||||
assert(ep.edge_out != nullptr);
|
||||
// Detect loops.
|
||||
for (EndPoint *pt = &ep; pt != nullptr;) {
|
||||
// Out of queue. It is a final point.
|
||||
EndPoint *pt_other = &pt->opposite(end_points);
|
||||
if (pt_other->heap_idx < queue.size()) {
|
||||
// The other side of this segment is undecided yet.
|
||||
// assert(pt_other->edge_out == nullptr);
|
||||
break;
|
||||
}
|
||||
pt = pt_other->edge_out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (EndPoint *ep : queue)
|
||||
// Points in the queue or the opposites of the same segment are not connected yet.
|
||||
assert(ep->chain_id == 0 || ep->opposite(end_points).chain_id == 0);
|
||||
return true;
|
||||
};
|
||||
#endif /* NDEBUG */
|
||||
|
||||
// Chain the end points: find (num_segments - 1) shortest links not forming bifurcations or loops.
|
||||
assert(num_segments >= 2);
|
||||
#ifndef NDEBUG
|
||||
double distance_taken_last = 0.;
|
||||
#endif /* NDEBUG */
|
||||
// Some links stored onto the priority queue are being invalidated during the calculation and they are not
|
||||
// updated immediately. If such a situation is detected for an end point pulled from the priority queue,
|
||||
// the end point is being updated and re-inserted into the priority queue. Therefore the number of iterations
|
||||
// required is higher than expected (it would be the number of links, num_segments - 1).
|
||||
// The limit here may not be necessary, but it guards us against an endless loop if something goes wrong.
|
||||
size_t num_iter = num_segments * 16;
|
||||
for (size_t num_connections_to_end = num_segments - 1; num_iter > 0; -- num_iter) {
|
||||
assert(validate_graph_and_queue());
|
||||
// Take the first end point, for which the link points to the currently closest valid neighbor.
|
||||
EndPoint *end_point1 = queue.top();
|
||||
assert(end_point1 != first_point);
|
||||
EndPoint *end_point1_other = &end_point1->opposite(end_points);
|
||||
// true if end_point1 is not the end of its chain, but the 2nd point. When connecting to the 2nd point, this chain needs
|
||||
// to be flipped first.
|
||||
bool chain1_flip = end_point1->chain_id > 0;
|
||||
// Either this point at the queue is not connected, or it is the 2nd point of a chain.
|
||||
// If connecting to a 2nd point of a chain, the 1st point shall not yet be connected and this chain will need
|
||||
// to be flipped.
|
||||
assert( chain1_flip || (end_point1->chain_id == 0 && end_point1->edge_out == nullptr));
|
||||
assert(! chain1_flip || (end_point1_other->chain_id == 0 && end_point1_other->edge_out == nullptr));
|
||||
assert(end_point1->edge_candidate != nullptr);
|
||||
#ifndef NDEBUG
|
||||
// Each edge added shall be longer than the previous one taken.
|
||||
//assert(end_point1->distance_out > distance_taken_last - SCALED_EPSILON);
|
||||
if (end_point1->distance_out < distance_taken_last - SCALED_EPSILON) {
|
||||
// printf("Warning: taking shorter length than previously is suspicious\n");
|
||||
}
|
||||
distance_taken_last = end_point1->distance_out;
|
||||
#endif /* NDEBUG */
|
||||
// Take the closest end point to the first end point,
|
||||
EndPoint *end_point2 = end_point1->edge_candidate;
|
||||
EndPoint *end_point2_other = &end_point2->opposite(end_points);
|
||||
bool chain2_flip = end_point2->chain_id > 0;
|
||||
// Is the link from end_point1 to end_point2 still valid? If yes, the link may be taken. Otherwise the link needs to be refreshed.
|
||||
bool valid = true;
|
||||
size_t end_point1_chain_id = 0;
|
||||
size_t end_point2_chain_id = 0;
|
||||
if (end_point2->chain_id > 0 && end_point2_other->chain_id > 0) {
|
||||
// The other side is part of the output path. Don't connect to end_point2, update end_point1 and try another one.
|
||||
valid = false;
|
||||
} else {
|
||||
// End points of the opposite ends of the segments.
|
||||
end_point1_chain_id = chains.equivalent((chain1_flip ? end_point1 : end_point1_other)->chain_id);
|
||||
end_point2_chain_id = chains.equivalent((chain2_flip ? end_point2 : end_point2_other)->chain_id);
|
||||
if (end_point1_chain_id == end_point2_chain_id && end_point1_chain_id != 0)
|
||||
// This edge forms a loop. Update end_point1 and try another one.
|
||||
valid = false;
|
||||
else {
|
||||
// Verify whether end_point1.distance_out still matches the current state of the two end points to be connected and their chains.
|
||||
// Namely, the other chain may have been flipped in the meantime.
|
||||
double dist = (end_point2->pos - end_point1->pos).norm();
|
||||
if (chain1_flip)
|
||||
dist += chains.chain_flip_penalty(end_point1_chain_id);
|
||||
if (chain2_flip)
|
||||
dist += chains.chain_flip_penalty(end_point2_chain_id);
|
||||
if (std::abs(dist - end_point1->distance_out) > SCALED_EPSILON)
|
||||
// The distance changed due to flipping of one of the chains. Refresh this end point in the queue.
|
||||
valid = false;
|
||||
}
|
||||
if (valid && first_point != nullptr) {
|
||||
// Verify that a chain starting or ending with the first_point does not get flipped.
|
||||
if (chain1_flip) {
|
||||
Chain &chain = chains.chain(end_point1_chain_id);
|
||||
if (chain.begin == first_point || chain.end == first_point)
|
||||
valid = false;
|
||||
}
|
||||
if (valid && chain2_flip) {
|
||||
Chain &chain = chains.chain(end_point2_chain_id);
|
||||
if (chain.begin == first_point || chain.end == first_point)
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (valid) {
|
||||
// Remove the first and second point from the queue.
|
||||
queue.pop();
|
||||
queue.remove(end_point2->heap_idx);
|
||||
assert(end_point1->edge_candidate == end_point2);
|
||||
end_point1->edge_candidate = nullptr;
|
||||
Chain *chain1 = (end_point1_chain_id == 0) ? nullptr : &chains.chain(end_point1_chain_id);
|
||||
Chain *chain2 = (end_point2_chain_id == 0) ? nullptr : &chains.chain(end_point2_chain_id);
|
||||
assert(chain1 == nullptr || (chain1_flip ? (chain1->begin == end_point1_other || chain1->end == end_point1_other) : (chain1->begin == end_point1 || chain1->end == end_point1)));
|
||||
assert(chain2 == nullptr || (chain2_flip ? (chain2->begin == end_point2_other || chain2->end == end_point2_other) : (chain2->begin == end_point2 || chain2->end == end_point2)));
|
||||
if (chain1_flip)
|
||||
chain1->flip(end_points);
|
||||
if (chain2_flip)
|
||||
chain2->flip(end_points);
|
||||
assert(chain1 == nullptr || chain1->begin == end_point1 || chain1->end == end_point1);
|
||||
assert(chain2 == nullptr || chain2->begin == end_point2 || chain2->end == end_point2);
|
||||
size_t chain_id = chains.merge(end_point1_chain_id, end_point2_chain_id);
|
||||
Chain &chain = chains.chain(chain_id);
|
||||
{
|
||||
Chain chain_dst;
|
||||
chain_dst.begin = (chain1 == nullptr) ? end_point1_other : (chain1->begin == end_point1) ? chain1->end : chain1->begin;
|
||||
chain_dst.end = (chain2 == nullptr) ? end_point2_other : (chain2->begin == end_point2) ? chain2->end : chain2->begin;
|
||||
chain_dst.cost = (chain1 == 0 ? 0. : chain1->cost) + (chain2 == 0 ? 0. : chain2->cost) + (end_point2->pos - end_point1->pos).norm();
|
||||
chain_dst.cost_flipped = (chain1 == 0 ? 0. : chain1->cost_flipped) + (chain2 == 0 ? 0. : chain2->cost_flipped) + (end_point2_other->pos - end_point1_other->pos).norm();
|
||||
chain_dst.num_segments = (chain1 == 0 ? 1 : chain1->num_segments) + (chain2 == 0 ? 1 : chain2->num_segments);
|
||||
chain_dst.equivalent_with = chain_id;
|
||||
chain = chain_dst;
|
||||
}
|
||||
if (chain.begin != end_point1_other && ! end_point1_other->heap_idx_invalid())
|
||||
queue.remove(end_point1_other->heap_idx);
|
||||
if (chain.end != end_point2_other && ! end_point2_other->heap_idx_invalid())
|
||||
queue.remove(end_point2_other->heap_idx);
|
||||
end_point1->edge_out = end_point2;
|
||||
end_point2->edge_out = end_point1;
|
||||
end_point1->chain_id = chain_id;
|
||||
end_point2->chain_id = chain_id;
|
||||
end_point1_other->chain_id = chain_id;
|
||||
end_point2_other->chain_id = chain_id;
|
||||
if (chain.begin != first_point)
|
||||
chain.begin->chain_id = 0;
|
||||
if (chain.end != first_point)
|
||||
chain.end->chain_id = 0;
|
||||
if (-- num_connections_to_end == 0) {
|
||||
assert(validate_graph_and_queue());
|
||||
// Last iteration. There shall be exactly one or two end points waiting to be connected.
|
||||
assert(queue.size() <= ((first_point == nullptr) ? 4 : 2));
|
||||
if (first_point == nullptr) {
|
||||
// Find the first remaining end point.
|
||||
do {
|
||||
first_point = queue.top();
|
||||
queue.pop();
|
||||
} while (first_point->edge_out != nullptr);
|
||||
assert(first_point->edge_out == nullptr);
|
||||
}
|
||||
// Find the first remaining end point.
|
||||
do {
|
||||
last_point = queue.top();
|
||||
queue.pop();
|
||||
} while (last_point->edge_out != nullptr);
|
||||
assert(last_point->edge_out == nullptr);
|
||||
#ifndef NDEBUG
|
||||
while (! queue.empty()) {
|
||||
assert(queue.top()->edge_out != nullptr && queue.top()->chain_id > 0);
|
||||
queue.pop();
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
break;
|
||||
} else {
|
||||
//FIXME update the 2nd end points on the queue.
|
||||
// Update end points of the flipped segments.
|
||||
update_end_point_in_queue(queue, kdtree, chains, end_points, chain.begin->opposite(end_points), first_point_idx, first_point);
|
||||
update_end_point_in_queue(queue, kdtree, chains, end_points, chain.end->opposite(end_points), first_point_idx, first_point);
|
||||
if (chain1_flip)
|
||||
update_end_point_in_queue(queue, kdtree, chains, end_points, *chain.begin, first_point_idx, first_point);
|
||||
if (chain2_flip)
|
||||
update_end_point_in_queue(queue, kdtree, chains, end_points, *chain.end, first_point_idx, first_point);
|
||||
// End points of chains shall certainly stay in the queue.
|
||||
assert(chain.begin == first_point || chain.begin->heap_idx < queue.size());
|
||||
assert(chain.end == first_point || chain.end ->heap_idx < queue.size());
|
||||
assert(&chain.begin->opposite(end_points) != first_point &&
|
||||
(chain.begin == first_point ? chain.begin->opposite(end_points).heap_idx_invalid() : chain.begin->opposite(end_points).heap_idx < queue.size()));
|
||||
assert(&chain.end ->opposite(end_points) != first_point &&
|
||||
(chain.end == first_point ? chain.end ->opposite(end_points).heap_idx_invalid() : chain.end ->opposite(end_points).heap_idx < queue.size()));
|
||||
|
||||
}
|
||||
} else {
|
||||
// This edge forms a loop. Update end_point1 and try another one.
|
||||
update_end_point_in_queue(queue, kdtree, chains, end_points, *end_point1, first_point_idx, first_point);
|
||||
#ifndef NDEBUG
|
||||
// Each edge shall be longer than the last one removed from the queue.
|
||||
//assert(end_point1->distance_out > distance_taken_last - SCALED_EPSILON);
|
||||
if (end_point1->distance_out < distance_taken_last - SCALED_EPSILON) {
|
||||
// printf("Warning: taking shorter length than previously is suspicious\n");
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
//FIXME Remove the other end point from the KD tree.
|
||||
// As the KD tree update is expensive, do it only after some larger number of points is removed from the queue.
|
||||
}
|
||||
assert(validate_graph_and_queue());
|
||||
}
|
||||
assert(queue.empty());
|
||||
|
||||
// Now interconnect pairs of segments into a chain.
|
||||
assert(first_point != nullptr);
|
||||
out.reserve(num_segments);
|
||||
bool failed = false;
|
||||
do {
|
||||
assert(out.size() < num_segments);
|
||||
size_t first_point_id = first_point - &end_points.front();
|
||||
size_t segment_id = first_point_id >> 1;
|
||||
bool reverse = (first_point_id & 1) != 0;
|
||||
EndPoint *second_point = &end_points[first_point_id ^ 1];
|
||||
if (REVERSE_COULD_FAIL) {
|
||||
if (reverse && ! could_reverse_func(segment_id)) {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
assert(! reverse || could_reverse_func(segment_id));
|
||||
}
|
||||
out.emplace_back(segment_id, reverse);
|
||||
first_point = second_point->edge_out;
|
||||
} while (first_point != nullptr);
|
||||
if (REVERSE_COULD_FAIL) {
|
||||
if (failed) {
|
||||
if (start_near == nullptr) {
|
||||
// We may try the reverse order.
|
||||
out.clear();
|
||||
first_point = last_point;
|
||||
failed = false;
|
||||
do {
|
||||
assert(out.size() < num_segments);
|
||||
size_t first_point_id = first_point - &end_points.front();
|
||||
size_t segment_id = first_point_id >> 1;
|
||||
bool reverse = (first_point_id & 1) != 0;
|
||||
EndPoint *second_point = &end_points[first_point_id ^ 1];
|
||||
if (reverse && ! could_reverse_func(segment_id)) {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
out.emplace_back(segment_id, reverse);
|
||||
first_point = second_point->edge_out;
|
||||
} while (first_point != nullptr);
|
||||
}
|
||||
}
|
||||
if (failed)
|
||||
// As a last resort, try a dumb algorithm, which is not sensitive to edge reversal constraints.
|
||||
out = chain_segments_closest_point<EndPoint, decltype(kdtree), CouldReverseFunc>(end_points, kdtree, could_reverse_func, (initial_point != nullptr) ? *initial_point : end_points.front());
|
||||
} else {
|
||||
assert(! failed);
|
||||
}
|
||||
}
|
||||
|
||||
assert(out.size() == num_segments);
|
||||
return out;
|
||||
}
|
||||
|
||||
template<typename PointType, typename SegmentEndPointFunc, typename CouldReverseFunc>
|
||||
std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals(SegmentEndPointFunc end_point_func, CouldReverseFunc could_reverse_func, size_t num_segments, const PointType *start_near)
|
||||
{
|
||||
|
@ -402,6 +981,19 @@ std::vector<std::pair<size_t, bool>> chain_segments_greedy(SegmentEndPointFunc e
|
|||
return chain_segments_greedy_constrained_reversals_<PointType, SegmentEndPointFunc, false, decltype(could_reverse_func)>(end_point_func, could_reverse_func, num_segments, start_near);
|
||||
}
|
||||
|
||||
template<typename PointType, typename SegmentEndPointFunc, typename CouldReverseFunc>
|
||||
std::vector<std::pair<size_t, bool>> chain_segments_greedy_constrained_reversals2(SegmentEndPointFunc end_point_func, CouldReverseFunc could_reverse_func, size_t num_segments, const PointType *start_near)
|
||||
{
|
||||
return chain_segments_greedy_constrained_reversals2_<PointType, SegmentEndPointFunc, true, CouldReverseFunc>(end_point_func, could_reverse_func, num_segments, start_near);
|
||||
}
|
||||
|
||||
template<typename PointType, typename SegmentEndPointFunc>
|
||||
std::vector<std::pair<size_t, bool>> chain_segments_greedy2(SegmentEndPointFunc end_point_func, size_t num_segments, const PointType *start_near)
|
||||
{
|
||||
auto could_reverse_func = [](size_t /* idx */) -> bool { return true; };
|
||||
return chain_segments_greedy_constrained_reversals2_<PointType, SegmentEndPointFunc, false, decltype(could_reverse_func)>(end_point_func, could_reverse_func, num_segments, start_near);
|
||||
}
|
||||
|
||||
std::vector<std::pair<size_t, bool>> chain_extrusion_entities(std::vector<ExtrusionEntity*> &entities, const Point *start_near)
|
||||
{
|
||||
auto segment_end_point = [&entities](size_t idx, bool first_point) -> const Point& { return first_point ? entities[idx]->first_point() : entities[idx]->last_point(); };
|
||||
|
@ -472,8 +1064,22 @@ std::vector<size_t> chain_points(const Points &points, Point *start_near)
|
|||
return out;
|
||||
}
|
||||
|
||||
// Flip the sequences of polylines to lower the total length of connecting lines.
|
||||
#ifndef NDEBUG
|
||||
// #define DEBUG_SVG_OUTPUT
|
||||
#endif /* NDEBUG */
|
||||
|
||||
#ifdef DEBUG_SVG_OUTPUT
|
||||
void svg_draw_polyline_chain(const char *name, size_t idx, const Polylines &polylines)
|
||||
{
|
||||
BoundingBox bbox = get_extents(polylines);
|
||||
SVG svg(debug_out_path("%s-%d.svg", name, idx).c_str(), bbox);
|
||||
svg.draw(polylines);
|
||||
for (size_t i = 1; i < polylines.size(); ++i)
|
||||
svg.draw(Line(polylines[i - 1].last_point(), polylines[i].first_point()), "red");
|
||||
}
|
||||
#endif /* DEBUG_SVG_OUTPUT */
|
||||
|
||||
// Flip the sequences of polylines to lower the total length of connecting lines.
|
||||
static inline void improve_ordering_by_segment_flipping(Polylines &polylines, bool fixed_start)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
|
@ -487,14 +1093,8 @@ static inline void improve_ordering_by_segment_flipping(Polylines &polylines, bo
|
|||
|
||||
static int iRun = 0;
|
||||
++ iRun;
|
||||
BoundingBox bbox = get_extents(polylines);
|
||||
#ifdef DEBUG_SVG_OUTPUT
|
||||
{
|
||||
SVG svg(debug_out_path("improve_ordering_by_segment_flipping-initial-%d.svg", iRun).c_str(), bbox);
|
||||
svg.draw(polylines);
|
||||
for (size_t i = 1; i < polylines.size(); ++ i)
|
||||
svg.draw(Line(polylines[i - 1].last_point(), polylines[i].first_point()), "red");
|
||||
}
|
||||
svg_draw_polyline_chain("improve_ordering_by_segment_flipping-initial", iRun, polylines);
|
||||
#endif /* DEBUG_SVG_OUTPUT */
|
||||
#endif /* NDEBUG */
|
||||
|
||||
|
@ -550,7 +1150,7 @@ static inline void improve_ordering_by_segment_flipping(Polylines &polylines, bo
|
|||
#endif /* NDEBUG */
|
||||
|
||||
// Initialize a MutablePriorityHeap of connections between polylines.
|
||||
auto queue = make_mutable_priority_queue<Connection*>(
|
||||
auto queue = make_mutable_priority_queue<Connection*, false>(
|
||||
[](Connection *connection, size_t idx){ connection->heap_idx = idx; },
|
||||
// Sort by decreasing connection distance.
|
||||
[&polylines, &connections](Connection *l, Connection *r){ return l->squaredNorm(polylines, connections) > r->squaredNorm(polylines, connections); });
|
||||
|
@ -643,34 +1243,285 @@ static inline void improve_ordering_by_segment_flipping(Polylines &polylines, bo
|
|||
#ifndef NDEBUG
|
||||
double cost_final = cost();
|
||||
#ifdef DEBUG_SVG_OUTPUT
|
||||
svg_draw_polyline_chain("improve_ordering_by_segment_flipping-final", iRun, polylines);
|
||||
#endif /* DEBUG_SVG_OUTPUT */
|
||||
assert(cost_final <= cost_prev);
|
||||
assert(cost_final <= cost_initial);
|
||||
#endif /* NDEBUG */
|
||||
}
|
||||
|
||||
struct FlipEdge {
|
||||
FlipEdge(const Vec2d &p1, const Vec2d &p2, size_t source_index) : p1(p1), p2(p2), source_index(source_index) {}
|
||||
void flip() { std::swap(this->p1, this->p2); }
|
||||
Vec2d p1;
|
||||
Vec2d p2;
|
||||
size_t source_index;
|
||||
};
|
||||
|
||||
struct ConnectionCost {
|
||||
ConnectionCost(double cost, double cost_flipped) : cost(cost), cost_flipped(cost_flipped) {}
|
||||
ConnectionCost() : cost(0.), cost_flipped(0.) {}
|
||||
void flip() { std::swap(this->cost, this->cost_flipped); }
|
||||
double cost = 0;
|
||||
double cost_flipped = 0;
|
||||
};
|
||||
static inline ConnectionCost operator-(const ConnectionCost &lhs, const ConnectionCost& rhs) { return ConnectionCost(lhs.cost - rhs.cost, lhs.cost_flipped - rhs.cost_flipped); }
|
||||
|
||||
static inline std::pair<double, size_t> minimum_crossover_cost(
|
||||
const std::vector<FlipEdge> &edges,
|
||||
const std::pair<size_t, size_t> &span1, const ConnectionCost &cost1,
|
||||
const std::pair<size_t, size_t> &span2, const ConnectionCost &cost2,
|
||||
const std::pair<size_t, size_t> &span3, const ConnectionCost &cost3,
|
||||
const double cost_current)
|
||||
{
|
||||
auto connection_cost = [&edges](
|
||||
const std::pair<size_t, size_t> &span1, const ConnectionCost &cost1, bool reversed1, bool flipped1,
|
||||
const std::pair<size_t, size_t> &span2, const ConnectionCost &cost2, bool reversed2, bool flipped2,
|
||||
const std::pair<size_t, size_t> &span3, const ConnectionCost &cost3, bool reversed3, bool flipped3) {
|
||||
auto first_point = [&edges](const std::pair<size_t, size_t> &span, bool flipped) { return flipped ? edges[span.first].p2 : edges[span.first].p1; };
|
||||
auto last_point = [&edges](const std::pair<size_t, size_t> &span, bool flipped) { return flipped ? edges[span.second - 1].p1 : edges[span.second - 1].p2; };
|
||||
auto point = [first_point, last_point](const std::pair<size_t, size_t> &span, bool start, bool flipped) { return start ? first_point(span, flipped) : last_point(span, flipped); };
|
||||
auto cost = [](const ConnectionCost &acost, bool flipped) {
|
||||
assert(acost.cost >= 0. && acost.cost_flipped >= 0.);
|
||||
return flipped ? acost.cost_flipped : acost.cost;
|
||||
};
|
||||
// Ignore reversed single segment spans.
|
||||
auto simple_span_ignore = [](const std::pair<size_t, size_t>& span, bool reversed) {
|
||||
return span.first + 1 == span.second && reversed;
|
||||
};
|
||||
assert(span1.first < span1.second);
|
||||
assert(span2.first < span2.second);
|
||||
assert(span3.first < span3.second);
|
||||
return
|
||||
simple_span_ignore(span1, reversed1) || simple_span_ignore(span2, reversed2) || simple_span_ignore(span3, reversed3) ?
|
||||
// Don't perform unnecessary calculations simulating reversion of single segment spans.
|
||||
std::numeric_limits<double>::max() :
|
||||
// Calculate the cost of reverting chains and / or flipping segment orientations.
|
||||
cost(cost1, flipped1) + cost(cost2, flipped2) + cost(cost3, flipped3) +
|
||||
(point(span2, ! reversed2, flipped2) - point(span1, reversed1, flipped1)).norm() +
|
||||
(point(span3, ! reversed3, flipped3) - point(span2, reversed2, flipped2)).norm();
|
||||
};
|
||||
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
SVG svg(debug_out_path("improve_ordering_by_segment_flipping-final-%d.svg", iRun).c_str(), bbox);
|
||||
svg.draw(polylines);
|
||||
for (size_t i = 1; i < polylines.size(); ++ i)
|
||||
svg.draw(Line(polylines[i - 1].last_point(), polylines[i].first_point()), "red");
|
||||
double c = connection_cost(span1, cost1, false, false, span2, cost2, false, false, span3, cost3, false, false);
|
||||
assert(std::abs(c - cost_current) < SCALED_EPSILON);
|
||||
}
|
||||
#endif /* NDEBUG */
|
||||
|
||||
double cost_min = cost_current;
|
||||
size_t flip_min = 0; // no flip, no improvement
|
||||
for (size_t i = 0; i < (1 << 6); ++ i) {
|
||||
// From the three combinations of 1,2,3 ordering, the other three are reversals of the first three.
|
||||
double c1 = (i == 0) ? cost_current :
|
||||
connection_cost(span1, cost1, (i & 1) != 0, (i & (1 << 1)) != 0, span2, cost2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, cost3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0);
|
||||
double c2 = connection_cost(span1, cost1, (i & 1) != 0, (i & (1 << 1)) != 0, span3, cost3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span2, cost2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0);
|
||||
double c3 = connection_cost(span2, cost2, (i & 1) != 0, (i & (1 << 1)) != 0, span1, cost1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, cost3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0);
|
||||
if (c1 < cost_min) {
|
||||
cost_min = c1;
|
||||
flip_min = i;
|
||||
}
|
||||
if (c2 < cost_min) {
|
||||
cost_min = c2;
|
||||
flip_min = i + (1 << 6);
|
||||
}
|
||||
if (c3 < cost_min) {
|
||||
cost_min = c3;
|
||||
flip_min = i + (2 << 6);
|
||||
}
|
||||
}
|
||||
return std::make_pair(cost_min, flip_min);
|
||||
}
|
||||
|
||||
static inline void do_crossover(const std::vector<FlipEdge> &edges_in, std::vector<FlipEdge> &edges_out,
|
||||
const std::pair<size_t, size_t> &span1, const std::pair<size_t, size_t> &span2, const std::pair<size_t, size_t> &span3,
|
||||
size_t i)
|
||||
{
|
||||
assert(edges_in.size() == edges_out.size());
|
||||
auto do_it = [&edges_in, &edges_out](
|
||||
const std::pair<size_t, size_t> &span1, bool reversed1, bool flipped1,
|
||||
const std::pair<size_t, size_t> &span2, bool reversed2, bool flipped2,
|
||||
const std::pair<size_t, size_t> &span3, bool reversed3, bool flipped3) {
|
||||
auto it_edges_out = edges_out.begin();
|
||||
auto copy_span = [&edges_in, &edges_out, &it_edges_out](std::pair<size_t, size_t> span, bool reversed, bool flipped) {
|
||||
assert(span.first < span.second);
|
||||
auto it = it_edges_out;
|
||||
if (reversed)
|
||||
std::reverse_copy(edges_in.begin() + span.first, edges_in.begin() + span.second, it_edges_out);
|
||||
else
|
||||
std::copy (edges_in.begin() + span.first, edges_in.begin() + span.second, it_edges_out);
|
||||
it_edges_out += span.second - span.first;
|
||||
if (reversed != flipped) {
|
||||
for (; it != it_edges_out; ++ it)
|
||||
it->flip();
|
||||
}
|
||||
};
|
||||
copy_span(span1, reversed1, flipped1);
|
||||
copy_span(span2, reversed2, flipped2);
|
||||
copy_span(span3, reversed3, flipped3);
|
||||
};
|
||||
switch (i >> 6) {
|
||||
case 0:
|
||||
do_it(span1, (i & 1) != 0, (i & (1 << 1)) != 0, span2, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0);
|
||||
break;
|
||||
case 1:
|
||||
do_it(span1, (i & 1) != 0, (i & (1 << 1)) != 0, span3, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span2, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0);
|
||||
break;
|
||||
default:
|
||||
assert((i >> 6) == 2);
|
||||
do_it(span2, (i & 1) != 0, (i & (1 << 1)) != 0, span1, (i & (1 << 2)) != 0, (i & (1 << 3)) != 0, span3, (i & (1 << 4)) != 0, (i & (1 << 5)) != 0);
|
||||
}
|
||||
assert(edges_in.size() == edges_out.size());
|
||||
}
|
||||
|
||||
static inline void reorder_by_two_exchanges_with_segment_flipping(std::vector<FlipEdge> &edges)
|
||||
{
|
||||
if (edges.size() < 2)
|
||||
return;
|
||||
|
||||
std::vector<ConnectionCost> connections(edges.size());
|
||||
std::vector<FlipEdge> edges_tmp(edges);
|
||||
std::vector<std::pair<double, size_t>> connection_lengths(edges.size() - 1, std::pair<double, size_t>(0., 0));
|
||||
std::vector<char> connection_tried(edges.size(), false);
|
||||
for (size_t iter = 0; iter < edges.size(); ++ iter) {
|
||||
// Initialize connection costs and connection lengths.
|
||||
for (size_t i = 1; i < edges.size(); ++ i) {
|
||||
const FlipEdge &e1 = edges[i - 1];
|
||||
const FlipEdge &e2 = edges[i];
|
||||
ConnectionCost &c = connections[i];
|
||||
c = connections[i - 1];
|
||||
double l = (e2.p1 - e1.p2).norm();
|
||||
c.cost += l;
|
||||
c.cost_flipped += (e2.p2 - e1.p1).norm();
|
||||
connection_lengths[i - 1] = std::make_pair(l, i);
|
||||
}
|
||||
std::sort(connection_lengths.begin(), connection_lengths.end(), [](const std::pair<double, size_t> &l, const std::pair<double, size_t> &r) { return l.first > r.first; });
|
||||
std::fill(connection_tried.begin(), connection_tried.end(), false);
|
||||
size_t crossover1_pos_final = std::numeric_limits<size_t>::max();
|
||||
size_t crossover2_pos_final = std::numeric_limits<size_t>::max();
|
||||
size_t crossover_flip_final = 0;
|
||||
for (const std::pair<double, size_t> &first_crossover_candidate : connection_lengths) {
|
||||
double longest_connection_length = first_crossover_candidate.first;
|
||||
size_t longest_connection_idx = first_crossover_candidate.second;
|
||||
connection_tried[longest_connection_idx] = true;
|
||||
// Find the second crossover connection with the lowest total chain cost.
|
||||
size_t crossover_pos_min = std::numeric_limits<size_t>::max();
|
||||
double crossover_cost_min = connections.back().cost;
|
||||
size_t crossover_flip_min = 0;
|
||||
for (size_t j = 1; j < connections.size(); ++ j)
|
||||
if (! connection_tried[j]) {
|
||||
size_t a = j;
|
||||
size_t b = longest_connection_idx;
|
||||
if (a > b)
|
||||
std::swap(a, b);
|
||||
std::pair<double, size_t> cost_and_flip = minimum_crossover_cost(edges,
|
||||
std::make_pair(size_t(0), a), connections[a - 1], std::make_pair(a, b), connections[b - 1] - connections[a], std::make_pair(b, edges.size()), connections.back() - connections[b],
|
||||
connections.back().cost);
|
||||
if (cost_and_flip.second > 0 && cost_and_flip.first < crossover_cost_min) {
|
||||
crossover_pos_min = j;
|
||||
crossover_cost_min = cost_and_flip.first;
|
||||
crossover_flip_min = cost_and_flip.second;
|
||||
assert(crossover_cost_min < connections.back().cost + EPSILON);
|
||||
}
|
||||
}
|
||||
if (crossover_cost_min < connections.back().cost) {
|
||||
// The cost of the chain with the proposed two crossovers has a lower total cost than the current chain. Apply the crossover.
|
||||
crossover1_pos_final = longest_connection_idx;
|
||||
crossover2_pos_final = crossover_pos_min;
|
||||
crossover_flip_final = crossover_flip_min;
|
||||
break;
|
||||
} else {
|
||||
// Continue with another long candidate edge.
|
||||
}
|
||||
}
|
||||
if (crossover_flip_final > 0) {
|
||||
// Pair of cross over positions and flip / reverse constellation has been found, which improves the total cost of the connection.
|
||||
// Perform a crossover.
|
||||
if (crossover1_pos_final > crossover2_pos_final)
|
||||
std::swap(crossover1_pos_final, crossover2_pos_final);
|
||||
do_crossover(edges, edges_tmp, std::make_pair(size_t(0), crossover1_pos_final), std::make_pair(crossover1_pos_final, crossover2_pos_final), std::make_pair(crossover2_pos_final, edges.size()), crossover_flip_final);
|
||||
edges.swap(edges_tmp);
|
||||
} else {
|
||||
// No valid pair of cross over positions was found improving the total cost. Giving up.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Flip the sequences of polylines to lower the total length of connecting lines.
|
||||
static inline void improve_ordering_by_two_exchanges_with_segment_flipping(Polylines &polylines, bool fixed_start)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
auto cost = [&polylines]() {
|
||||
double sum = 0.;
|
||||
for (size_t i = 1; i < polylines.size(); ++i)
|
||||
sum += (polylines[i].first_point() - polylines[i - 1].last_point()).cast<double>().norm();
|
||||
return sum;
|
||||
};
|
||||
double cost_initial = cost();
|
||||
|
||||
static int iRun = 0;
|
||||
++ iRun;
|
||||
#ifdef DEBUG_SVG_OUTPUT
|
||||
svg_draw_polyline_chain("improve_ordering_by_two_exchanges_with_segment_flipping-initial", iRun, polylines);
|
||||
#endif /* DEBUG_SVG_OUTPUT */
|
||||
#endif /* NDEBUG */
|
||||
|
||||
assert(cost_final <= cost_prev);
|
||||
std::vector<FlipEdge> edges;
|
||||
edges.reserve(polylines.size());
|
||||
std::transform(polylines.begin(), polylines.end(), std::back_inserter(edges),
|
||||
[&polylines](const Polyline &pl){ return FlipEdge(pl.first_point().cast<double>(), pl.last_point().cast<double>(), &pl - polylines.data()); });
|
||||
reorder_by_two_exchanges_with_segment_flipping(edges);
|
||||
Polylines out;
|
||||
out.reserve(polylines.size());
|
||||
for (const FlipEdge &edge : edges) {
|
||||
Polyline &pl = polylines[edge.source_index];
|
||||
out.emplace_back(std::move(pl));
|
||||
if (edge.p2 == pl.first_point().cast<double>()) {
|
||||
// Polyline is flipped.
|
||||
out.back().reverse();
|
||||
} else {
|
||||
// Polyline is not flipped.
|
||||
assert(edge.p1 == pl.first_point().cast<double>());
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
double cost_final = cost();
|
||||
#ifdef DEBUG_SVG_OUTPUT
|
||||
svg_draw_polyline_chain("improve_ordering_by_two_exchanges_with_segment_flipping-final", iRun, out);
|
||||
#endif /* DEBUG_SVG_OUTPUT */
|
||||
assert(cost_final <= cost_initial);
|
||||
#endif /* NDEBUG */
|
||||
}
|
||||
|
||||
Polylines chain_polylines(Polylines &&polylines, const Point *start_near)
|
||||
{
|
||||
#ifdef DEBUG_SVG_OUTPUT
|
||||
static int iRun = 0;
|
||||
++ iRun;
|
||||
svg_draw_polyline_chain("chain_polylines-initial", iRun, polylines);
|
||||
#endif /* DEBUG_SVG_OUTPUT */
|
||||
|
||||
Polylines out;
|
||||
if (! polylines.empty()) {
|
||||
auto segment_end_point = [&polylines](size_t idx, bool first_point) -> const Point& { return first_point ? polylines[idx].first_point() : polylines[idx].last_point(); };
|
||||
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy<Point, decltype(segment_end_point)>(segment_end_point, polylines.size(), start_near);
|
||||
std::vector<std::pair<size_t, bool>> ordered = chain_segments_greedy2<Point, decltype(segment_end_point)>(segment_end_point, polylines.size(), start_near);
|
||||
out.reserve(polylines.size());
|
||||
for (auto &segment_and_reversal : ordered) {
|
||||
out.emplace_back(std::move(polylines[segment_and_reversal.first]));
|
||||
if (segment_and_reversal.second)
|
||||
out.back().reverse();
|
||||
}
|
||||
if (out.size() > 1)
|
||||
improve_ordering_by_segment_flipping(out, start_near != nullptr);
|
||||
if (out.size() > 1 && start_near == nullptr) {
|
||||
improve_ordering_by_two_exchanges_with_segment_flipping(out, start_near != nullptr);
|
||||
//improve_ordering_by_segment_flipping(out, start_near != nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_SVG_OUTPUT
|
||||
svg_draw_polyline_chain("chain_polylines-final", iRun, out);
|
||||
#endif /* DEBUG_SVG_OUTPUT */
|
||||
return out;
|
||||
}
|
||||
|
||||
|
|
|
@ -224,40 +224,59 @@ std::vector<coordf_t> layer_height_profile_from_ranges(
|
|||
|
||||
// Based on the work of @platsch
|
||||
// Fill layer_height_profile by heights ensuring a prescribed maximum cusp height.
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
std::vector<double> layer_height_profile_adaptive(const SlicingParameters& slicing_params,
|
||||
const ModelObject& object, float cusp_value)
|
||||
#else
|
||||
std::vector<coordf_t> layer_height_profile_adaptive(
|
||||
const SlicingParameters &slicing_params,
|
||||
const t_layer_config_ranges & /* layer_config_ranges */,
|
||||
const ModelVolumePtrs &volumes)
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
{
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
// 1) Initialize the SlicingAdaptive class with the object meshes.
|
||||
SlicingAdaptive as;
|
||||
as.set_slicing_parameters(slicing_params);
|
||||
for (const ModelVolume *volume : volumes)
|
||||
as.set_object(object);
|
||||
#else
|
||||
// 1) Initialize the SlicingAdaptive class with the object meshes.
|
||||
SlicingAdaptive as;
|
||||
as.set_slicing_parameters(slicing_params);
|
||||
for (const ModelVolume* volume : volumes)
|
||||
if (volume->is_model_part())
|
||||
as.add_mesh(&volume->mesh());
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
as.prepare();
|
||||
|
||||
// 2) Generate layers using the algorithm of @platsch
|
||||
// loop until we have at least one layer and the max slice_z reaches the object height
|
||||
//FIXME make it configurable
|
||||
// Cusp value: A maximum allowed distance from a corner of a rectangular extrusion to a chrodal line, in mm.
|
||||
const coordf_t cusp_value = 0.2; // $self->config->get_value('cusp_value');
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
double cusp_value = 0.2;
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
std::vector<coordf_t> layer_height_profile;
|
||||
layer_height_profile.push_back(0.);
|
||||
std::vector<double> layer_height_profile;
|
||||
layer_height_profile.push_back(0.0);
|
||||
layer_height_profile.push_back(slicing_params.first_object_layer_height);
|
||||
if (slicing_params.first_object_layer_height_fixed()) {
|
||||
layer_height_profile.push_back(slicing_params.first_object_layer_height);
|
||||
layer_height_profile.push_back(slicing_params.first_object_layer_height);
|
||||
}
|
||||
coordf_t slice_z = slicing_params.first_object_layer_height;
|
||||
coordf_t height = slicing_params.first_object_layer_height;
|
||||
double slice_z = slicing_params.first_object_layer_height;
|
||||
int current_facet = 0;
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
while (slice_z <= slicing_params.object_print_z_height()) {
|
||||
double height = 999.0;
|
||||
#else
|
||||
double height = slicing_params.first_object_layer_height;
|
||||
while ((slice_z - height) <= slicing_params.object_print_z_height()) {
|
||||
height = 999;
|
||||
height = 999.0;
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
// Slic3r::debugf "\n Slice layer: %d\n", $id;
|
||||
// determine next layer height
|
||||
coordf_t cusp_height = as.cusp_height(slice_z, cusp_value, current_facet);
|
||||
double cusp_height = as.cusp_height((float)slice_z, cusp_value, current_facet);
|
||||
|
||||
// check for horizontal features and object size
|
||||
/*
|
||||
if($self->config->get_value('match_horizontal_surfaces')) {
|
||||
|
@ -303,19 +322,113 @@ std::vector<coordf_t> layer_height_profile_adaptive(
|
|||
layer_height_profile.push_back(slice_z);
|
||||
layer_height_profile.push_back(height);
|
||||
slice_z += height;
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
layer_height_profile.push_back(slice_z);
|
||||
layer_height_profile.push_back(height);
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
}
|
||||
|
||||
coordf_t last = std::max(slicing_params.first_object_layer_height, layer_height_profile[layer_height_profile.size() - 2]);
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
double z_gap = slicing_params.object_print_z_height() - layer_height_profile[layer_height_profile.size() - 2];
|
||||
if (z_gap > 0.0)
|
||||
{
|
||||
layer_height_profile.push_back(slicing_params.object_print_z_height());
|
||||
layer_height_profile.push_back(clamp(slicing_params.min_layer_height, slicing_params.max_layer_height, z_gap));
|
||||
}
|
||||
#else
|
||||
double last = std::max(slicing_params.first_object_layer_height, layer_height_profile[layer_height_profile.size() - 2]);
|
||||
layer_height_profile.push_back(last);
|
||||
layer_height_profile.push_back(slicing_params.first_object_layer_height);
|
||||
layer_height_profile.push_back(slicing_params.object_print_z_height());
|
||||
layer_height_profile.push_back(slicing_params.first_object_layer_height);
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
return layer_height_profile;
|
||||
}
|
||||
|
||||
std::vector<double> smooth_height_profile(const std::vector<double>& profile, const SlicingParameters& slicing_params, const HeightProfileSmoothingParams& smoothing_params)
|
||||
{
|
||||
auto gauss_blur = [&slicing_params](const std::vector<double>& profile, const HeightProfileSmoothingParams& smoothing_params) -> std::vector<double> {
|
||||
auto gauss_kernel = [] (unsigned int radius) -> std::vector<double> {
|
||||
unsigned int size = 2 * radius + 1;
|
||||
std::vector<double> ret;
|
||||
ret.reserve(size);
|
||||
|
||||
// Reworked from static inline int getGaussianKernelSize(float sigma) taken from opencv-4.1.2\modules\features2d\src\kaze\AKAZEFeatures.cpp
|
||||
double sigma = 0.3 * (double)(radius - 1) + 0.8;
|
||||
double two_sq_sigma = 2.0 * sigma * sigma;
|
||||
double inv_root_two_pi_sq_sigma = 1.0 / ::sqrt(M_PI * two_sq_sigma);
|
||||
|
||||
for (unsigned int i = 0; i < size; ++i)
|
||||
{
|
||||
double x = (double)i - (double)radius;
|
||||
ret.push_back(inv_root_two_pi_sq_sigma * ::exp(-x * x / two_sq_sigma));
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
// skip first layer ?
|
||||
size_t skip_count = slicing_params.first_object_layer_height_fixed() ? 4 : 0;
|
||||
|
||||
// not enough data to smmoth
|
||||
if ((int)profile.size() - (int)skip_count < 6)
|
||||
return profile;
|
||||
|
||||
unsigned int radius = std::max(smoothing_params.radius, (unsigned int)1);
|
||||
std::vector<double> kernel = gauss_kernel(radius);
|
||||
int two_radius = 2 * (int)radius;
|
||||
|
||||
std::vector<double> ret;
|
||||
size_t size = profile.size();
|
||||
ret.reserve(size);
|
||||
|
||||
// leave first layer untouched
|
||||
for (size_t i = 0; i < skip_count; ++i)
|
||||
{
|
||||
ret.push_back(profile[i]);
|
||||
}
|
||||
|
||||
// smooth the rest of the profile by biasing a gaussian blur
|
||||
// the bias moves the smoothed profile closer to the min_layer_height
|
||||
double delta_h = slicing_params.max_layer_height - slicing_params.min_layer_height;
|
||||
double inv_delta_h = (delta_h != 0.0) ? 1.0 / delta_h : 1.0;
|
||||
|
||||
double max_dz_band = (double)radius * slicing_params.layer_height;
|
||||
for (size_t i = skip_count; i < size; i += 2)
|
||||
{
|
||||
double zi = profile[i];
|
||||
double hi = profile[i + 1];
|
||||
ret.push_back(zi);
|
||||
ret.push_back(0.0);
|
||||
double& height = ret.back();
|
||||
int begin = std::max((int)i - two_radius, (int)skip_count);
|
||||
int end = std::min((int)i + two_radius, (int)size - 2);
|
||||
double weight_total = 0.0;
|
||||
for (int j = begin; j <= end; j += 2)
|
||||
{
|
||||
int kernel_id = radius + (j - (int)i) / 2;
|
||||
double dz = std::abs(zi - profile[j]);
|
||||
if (dz * slicing_params.layer_height <= max_dz_band)
|
||||
{
|
||||
double dh = std::abs(slicing_params.max_layer_height - profile[j + 1]);
|
||||
double weight = kernel[kernel_id] * sqrt(dh * inv_delta_h);
|
||||
height += weight * profile[j + 1];
|
||||
weight_total += weight;
|
||||
}
|
||||
}
|
||||
|
||||
height = clamp(slicing_params.min_layer_height, slicing_params.max_layer_height, (weight_total != 0.0) ? height /= weight_total : hi);
|
||||
if (smoothing_params.keep_min)
|
||||
height = std::min(height, hi);
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
return gauss_blur(profile, smoothing_params);
|
||||
}
|
||||
|
||||
void adjust_layer_height_profile(
|
||||
const SlicingParameters &slicing_params,
|
||||
std::vector<coordf_t> &layer_height_profile,
|
||||
|
@ -609,7 +722,11 @@ int generate_layer_height_texture(
|
|||
const Vec3crd &color1 = palette_raw[idx1];
|
||||
const Vec3crd &color2 = palette_raw[idx2];
|
||||
coordf_t z = cell_to_z * coordf_t(cell);
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
assert((lo - EPSILON <= z) && (z <= hi + EPSILON));
|
||||
#else
|
||||
assert(z >= lo && z <= hi);
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
// Intensity profile to visualize the layers.
|
||||
coordf_t intensity = cos(M_PI * 0.7 * (mid - z) / h);
|
||||
// Color mapping from layer height to RGB.
|
||||
|
|
|
@ -18,8 +18,12 @@ namespace Slic3r
|
|||
|
||||
class PrintConfig;
|
||||
class PrintObjectConfig;
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
class ModelObject;
|
||||
#else
|
||||
class ModelVolume;
|
||||
typedef std::vector<ModelVolume*> ModelVolumePtrs;
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
// Parameters to guide object slicing and support generation.
|
||||
// The slicing parameters account for a raft and whether the 1st object layer is printed with a normal or a bridging flow
|
||||
|
@ -138,11 +142,29 @@ extern std::vector<coordf_t> layer_height_profile_from_ranges(
|
|||
const SlicingParameters &slicing_params,
|
||||
const t_layer_config_ranges &layer_config_ranges);
|
||||
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
extern std::vector<double> layer_height_profile_adaptive(
|
||||
const SlicingParameters& slicing_params,
|
||||
const ModelObject& object, float cusp_value);
|
||||
|
||||
struct HeightProfileSmoothingParams
|
||||
{
|
||||
unsigned int radius;
|
||||
bool keep_min;
|
||||
|
||||
HeightProfileSmoothingParams() : radius(5), keep_min(false) {}
|
||||
HeightProfileSmoothingParams(unsigned int radius, bool keep_min) : radius(radius), keep_min(keep_min) {}
|
||||
};
|
||||
|
||||
extern std::vector<double> smooth_height_profile(
|
||||
const std::vector<double>& profile, const SlicingParameters& slicing_params,
|
||||
const HeightProfileSmoothingParams& smoothing_params);
|
||||
#else
|
||||
extern std::vector<coordf_t> layer_height_profile_adaptive(
|
||||
const SlicingParameters &slicing_params,
|
||||
const t_layer_config_ranges &layer_config_ranges,
|
||||
const ModelVolumePtrs &volumes);
|
||||
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
enum LayerHeightEditActionType : unsigned int {
|
||||
LAYER_HEIGHT_EDIT_ACTION_INCREASE = 0,
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
#include "libslic3r.h"
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
#include "Model.hpp"
|
||||
#else
|
||||
#include "TriangleMesh.hpp"
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
#include "SlicingAdaptive.hpp"
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
void SlicingAdaptive::clear()
|
||||
{
|
||||
m_meshes.clear();
|
||||
m_faces.clear();
|
||||
m_face_normal_z.clear();
|
||||
}
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
std::pair<float, float> face_z_span(const stl_facet *f)
|
||||
{
|
||||
|
@ -21,14 +27,35 @@ std::pair<float, float> face_z_span(const stl_facet *f)
|
|||
|
||||
void SlicingAdaptive::prepare()
|
||||
{
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
if (m_object == nullptr)
|
||||
return;
|
||||
|
||||
m_faces.clear();
|
||||
m_face_normal_z.clear();
|
||||
|
||||
m_mesh = m_object->raw_mesh();
|
||||
const ModelInstance* first_instance = m_object->instances.front();
|
||||
m_mesh.transform(first_instance->get_matrix(), first_instance->is_left_handed());
|
||||
for (stl_facet& facet : m_mesh.stl.facet_start)
|
||||
{
|
||||
facet.normal.normalize();
|
||||
}
|
||||
|
||||
// 1) Collect faces from mesh.
|
||||
m_faces.reserve(m_mesh.stl.stats.number_of_facets);
|
||||
for (const stl_facet& face : m_mesh.stl.facet_start)
|
||||
m_faces.emplace_back(&face);
|
||||
#else
|
||||
// 1) Collect faces of all meshes.
|
||||
int nfaces_total = 0;
|
||||
for (std::vector<const TriangleMesh*>::const_iterator it_mesh = m_meshes.begin(); it_mesh != m_meshes.end(); ++ it_mesh)
|
||||
nfaces_total += (*it_mesh)->stl.stats.number_of_facets;
|
||||
m_faces.reserve(nfaces_total);
|
||||
for (std::vector<const TriangleMesh*>::const_iterator it_mesh = m_meshes.begin(); it_mesh != m_meshes.end(); ++ it_mesh)
|
||||
for (const stl_facet &face : (*it_mesh)->stl.facet_start)
|
||||
for (const stl_facet& face : (*it_mesh)->stl.facet_start)
|
||||
m_faces.emplace_back(&face);
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
// 2) Sort faces lexicographically by their Z span.
|
||||
std::sort(m_faces.begin(), m_faces.end(), [](const stl_facet *f1, const stl_facet *f2) {
|
||||
|
@ -45,7 +72,7 @@ void SlicingAdaptive::prepare()
|
|||
|
||||
float SlicingAdaptive::cusp_height(float z, float cusp_value, int ¤t_facet)
|
||||
{
|
||||
float height = m_slicing_params.max_layer_height;
|
||||
float height = (float)m_slicing_params.max_layer_height;
|
||||
bool first_hit = false;
|
||||
|
||||
// find all facets intersecting the slice-layer
|
||||
|
@ -117,6 +144,7 @@ float SlicingAdaptive::cusp_height(float z, float cusp_value, int ¤t_facet
|
|||
return height;
|
||||
}
|
||||
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
// Returns the distance to the next horizontal facet in Z-dir
|
||||
// to consider horizontal object features in slice thickness
|
||||
float SlicingAdaptive::horizontal_facet_distance(float z)
|
||||
|
@ -127,14 +155,14 @@ float SlicingAdaptive::horizontal_facet_distance(float z)
|
|||
if (zspan.first > z + m_slicing_params.max_layer_height)
|
||||
break;
|
||||
// min_z == max_z -> horizontal facet
|
||||
if (zspan.first > z && zspan.first == zspan.second)
|
||||
if ((zspan.first > z) && (zspan.first == zspan.second))
|
||||
return zspan.first - z;
|
||||
}
|
||||
|
||||
// objects maximum?
|
||||
return (z + m_slicing_params.max_layer_height > m_slicing_params.object_print_z_height()) ?
|
||||
std::max<float>(m_slicing_params.object_print_z_height() - z, 0.f) :
|
||||
m_slicing_params.max_layer_height;
|
||||
return (z + (float)m_slicing_params.max_layer_height > (float)m_slicing_params.object_print_z_height()) ?
|
||||
std::max((float)m_slicing_params.object_print_z_height() - z, 0.f) : (float)m_slicing_params.max_layer_height;
|
||||
}
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
|
|
@ -5,26 +5,46 @@
|
|||
|
||||
#include "Slicing.hpp"
|
||||
#include "admesh/stl.h"
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
#include "TriangleMesh.hpp"
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
namespace Slic3r
|
||||
{
|
||||
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
class ModelVolume;
|
||||
#else
|
||||
class TriangleMesh;
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
class SlicingAdaptive
|
||||
{
|
||||
public:
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
void clear();
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
void set_slicing_parameters(SlicingParameters params) { m_slicing_params = params; }
|
||||
void add_mesh(const TriangleMesh *mesh) { m_meshes.push_back(mesh); }
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
void set_object(const ModelObject& object) { m_object = &object; }
|
||||
#else
|
||||
void add_mesh(const TriangleMesh* mesh) { m_meshes.push_back(mesh); }
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
void prepare();
|
||||
float cusp_height(float z, float cusp_value, int ¤t_facet);
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
float horizontal_facet_distance(float z);
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
protected:
|
||||
SlicingParameters m_slicing_params;
|
||||
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
const ModelObject* m_object;
|
||||
TriangleMesh m_mesh;
|
||||
#else
|
||||
std::vector<const TriangleMesh*> m_meshes;
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
// Collected faces of all meshes, sorted by raising Z of the bottom most face.
|
||||
std::vector<const stl_facet*> m_faces;
|
||||
// Z component of face normals, normalized.
|
||||
|
|
|
@ -40,6 +40,8 @@
|
|||
// Enable thumbnail generator
|
||||
#define ENABLE_THUMBNAIL_GENERATOR (1 && ENABLE_2_2_0_ALPHA1)
|
||||
#define ENABLE_THUMBNAIL_GENERATOR_DEBUG (0 && ENABLE_THUMBNAIL_GENERATOR)
|
||||
#define ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE (1 && ENABLE_THUMBNAIL_GENERATOR)
|
||||
|
||||
// Enable adaptive layer height profile
|
||||
#define ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE (1 && ENABLE_2_2_0_ALPHA1)
|
||||
|
||||
#endif // _technologies_h_
|
||||
|
|
|
@ -136,6 +136,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/ProgressStatusBar.cpp
|
||||
GUI/PrintHostDialogs.cpp
|
||||
GUI/PrintHostDialogs.hpp
|
||||
GUI/Mouse3DController.cpp
|
||||
GUI/Mouse3DController.hpp
|
||||
Utils/Http.cpp
|
||||
Utils/Http.hpp
|
||||
Utils/FixModelByWin10.cpp
|
||||
|
@ -170,7 +172,7 @@ add_library(libslic3r_gui STATIC ${SLIC3R_GUI_SOURCES})
|
|||
|
||||
encoding_check(libslic3r_gui)
|
||||
|
||||
target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui ${GLEW_LIBRARIES})
|
||||
target_link_libraries(libslic3r_gui libslic3r avrdude cereal imgui ${GLEW_LIBRARIES} hidapi)
|
||||
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
|
||||
add_precompiled_header(libslic3r_gui pchheader.hpp FORCEINCLUDE)
|
||||
endif ()
|
||||
|
|
|
@ -271,6 +271,80 @@ void AppConfig::set_recent_projects(const std::vector<std::string>& recent_proje
|
|||
}
|
||||
}
|
||||
|
||||
void AppConfig::set_mouse_device(const std::string& name, double translation_speed, double translation_deadzone, float rotation_speed, float rotation_deadzone)
|
||||
{
|
||||
std::string key = std::string("mouse_device:") + name;
|
||||
auto it = m_storage.find(key);
|
||||
if (it == m_storage.end())
|
||||
it = m_storage.insert(std::map<std::string, std::map<std::string, std::string>>::value_type(key, std::map<std::string, std::string>())).first;
|
||||
|
||||
it->second.clear();
|
||||
it->second["translation_speed"] = std::to_string(translation_speed);
|
||||
it->second["translation_deadzone"] = std::to_string(translation_deadzone);
|
||||
it->second["rotation_speed"] = std::to_string(rotation_speed);
|
||||
it->second["rotation_deadzone"] = std::to_string(rotation_deadzone);
|
||||
}
|
||||
|
||||
bool AppConfig::get_mouse_device_translation_speed(const std::string& name, double& speed)
|
||||
{
|
||||
std::string key = std::string("mouse_device:") + name;
|
||||
auto it = m_storage.find(key);
|
||||
if (it == m_storage.end())
|
||||
return false;
|
||||
|
||||
auto it_val = it->second.find("translation_speed");
|
||||
if (it_val == it->second.end())
|
||||
return false;
|
||||
|
||||
speed = ::atof(it_val->second.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AppConfig::get_mouse_device_translation_deadzone(const std::string& name, double& deadzone)
|
||||
{
|
||||
std::string key = std::string("mouse_device:") + name;
|
||||
auto it = m_storage.find(key);
|
||||
if (it == m_storage.end())
|
||||
return false;
|
||||
|
||||
auto it_val = it->second.find("translation_deadzone");
|
||||
if (it_val == it->second.end())
|
||||
return false;
|
||||
|
||||
deadzone = ::atof(it_val->second.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AppConfig::get_mouse_device_rotation_speed(const std::string& name, float& speed)
|
||||
{
|
||||
std::string key = std::string("mouse_device:") + name;
|
||||
auto it = m_storage.find(key);
|
||||
if (it == m_storage.end())
|
||||
return false;
|
||||
|
||||
auto it_val = it->second.find("rotation_speed");
|
||||
if (it_val == it->second.end())
|
||||
return false;
|
||||
|
||||
speed = (float)::atof(it_val->second.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AppConfig::get_mouse_device_rotation_deadzone(const std::string& name, float& deadzone)
|
||||
{
|
||||
std::string key = std::string("mouse_device:") + name;
|
||||
auto it = m_storage.find(key);
|
||||
if (it == m_storage.end())
|
||||
return false;
|
||||
|
||||
auto it_val = it->second.find("rotation_deadzone");
|
||||
if (it_val == it->second.end())
|
||||
return false;
|
||||
|
||||
deadzone = (float)::atof(it_val->second.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
void AppConfig::update_config_dir(const std::string &dir)
|
||||
{
|
||||
this->set("recent", "config_directory", dir);
|
||||
|
|
|
@ -131,8 +131,15 @@ public:
|
|||
std::vector<std::string> get_recent_projects() const;
|
||||
void set_recent_projects(const std::vector<std::string>& recent_projects);
|
||||
|
||||
void set_mouse_device(const std::string& name, double translation_speed, double translation_deadzone, float rotation_speed, float rotation_deadzone);
|
||||
bool get_mouse_device_translation_speed(const std::string& name, double& speed);
|
||||
bool get_mouse_device_translation_deadzone(const std::string& name, double& deadzone);
|
||||
bool get_mouse_device_rotation_speed(const std::string& name, float& speed);
|
||||
bool get_mouse_device_rotation_deadzone(const std::string& name, float& deadzone);
|
||||
|
||||
static const std::string SECTION_FILAMENTS;
|
||||
static const std::string SECTION_MATERIALS;
|
||||
|
||||
private:
|
||||
// Map of section, name -> value
|
||||
std::map<std::string, std::map<std::string, std::string>> m_storage;
|
||||
|
|
|
@ -20,9 +20,6 @@
|
|||
#include "libslic3r/Utils.hpp"
|
||||
#include "libslic3r/GCode/PostProcessor.hpp"
|
||||
#include "libslic3r/GCode/PreviewData.hpp"
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "libslic3r/libslic3r.h"
|
||||
|
||||
#include <cassert>
|
||||
|
@ -91,7 +88,7 @@ void BackgroundSlicingProcess::process_fff()
|
|||
m_print->process();
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_slicing_completed_id));
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_data);
|
||||
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data, m_thumbnail_cb);
|
||||
#else
|
||||
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
@ -139,9 +136,12 @@ void BackgroundSlicingProcess::process_sla()
|
|||
m_sla_print->export_raster(zipper);
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
if (m_thumbnail_data != nullptr)
|
||||
if (m_thumbnail_cb != nullptr)
|
||||
{
|
||||
for (const ThumbnailData& data : *m_thumbnail_data)
|
||||
ThumbnailsList thumbnails;
|
||||
m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, false);
|
||||
// m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, false, false); // renders also supports and pad
|
||||
for (const ThumbnailData& data : thumbnails)
|
||||
{
|
||||
if (data.is_valid())
|
||||
write_thumbnail(zipper, data);
|
||||
|
@ -461,9 +461,12 @@ void BackgroundSlicingProcess::prepare_upload()
|
|||
Zipper zipper{source_path.string()};
|
||||
m_sla_print->export_raster(zipper, m_upload_job.upload_data.upload_path.string());
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
if (m_thumbnail_data != nullptr)
|
||||
if (m_thumbnail_cb != nullptr)
|
||||
{
|
||||
for (const ThumbnailData& data : *m_thumbnail_data)
|
||||
ThumbnailsList thumbnails;
|
||||
m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, true, false);
|
||||
// m_thumbnail_cb(thumbnails, current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values, true, false, false); // renders also supports and pad
|
||||
for (const ThumbnailData& data : thumbnails)
|
||||
{
|
||||
if (data.is_valid())
|
||||
write_thumbnail(zipper, data);
|
||||
|
|
|
@ -17,9 +17,6 @@ namespace Slic3r {
|
|||
|
||||
class DynamicPrintConfig;
|
||||
class GCodePreviewData;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
struct ThumbnailData;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
class Model;
|
||||
class SLAPrint;
|
||||
|
||||
|
@ -53,7 +50,7 @@ public:
|
|||
void set_sla_print(SLAPrint *print) { m_sla_print = print; }
|
||||
void set_gcode_preview_data(GCodePreviewData *gpd) { m_gcode_preview_data = gpd; }
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void set_thumbnail_data(const std::vector<ThumbnailData>* data) { m_thumbnail_data = data; }
|
||||
void set_thumbnail_cb(ThumbnailsGeneratorCallback cb) { m_thumbnail_cb = cb; }
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
// The following wxCommandEvent will be sent to the UI thread / Platter window, when the slicing is finished
|
||||
|
@ -159,8 +156,8 @@ private:
|
|||
// Data structure, to which the G-code export writes its annotations.
|
||||
GCodePreviewData *m_gcode_preview_data = nullptr;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
// Data structures, used to write thumbnails into gcode.
|
||||
const std::vector<ThumbnailData>* m_thumbnail_data = nullptr;
|
||||
// Callback function, used to write thumbnails into gcode.
|
||||
ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
// Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID.
|
||||
std::string m_temp_output_path;
|
||||
|
|
|
@ -91,10 +91,16 @@ void Camera::select_next_type()
|
|||
|
||||
void Camera::set_target(const Vec3d& target)
|
||||
{
|
||||
m_target = target;
|
||||
m_target(0) = clamp(m_scene_box.min(0), m_scene_box.max(0), m_target(0));
|
||||
m_target(1) = clamp(m_scene_box.min(1), m_scene_box.max(1), m_target(1));
|
||||
m_target(2) = clamp(m_scene_box.min(2), m_scene_box.max(2), m_target(2));
|
||||
BoundingBoxf3 test_box = m_scene_box;
|
||||
test_box.translate(-m_scene_box.center());
|
||||
// We may let this factor be customizable
|
||||
static const double ScaleFactor = 1.5;
|
||||
test_box.scale(ScaleFactor);
|
||||
test_box.translate(m_scene_box.center());
|
||||
|
||||
m_target(0) = clamp(test_box.min(0), test_box.max(0), target(0));
|
||||
m_target(1) = clamp(test_box.min(1), test_box.max(1), target(1));
|
||||
m_target(2) = clamp(test_box.min(2), test_box.max(2), target(2));
|
||||
}
|
||||
|
||||
void Camera::set_theta(float theta, bool apply_limit)
|
||||
|
@ -109,20 +115,20 @@ void Camera::set_theta(float theta, bool apply_limit)
|
|||
}
|
||||
}
|
||||
|
||||
void Camera::set_zoom(double zoom, const BoundingBoxf3& max_box, int canvas_w, int canvas_h)
|
||||
void Camera::update_zoom(double delta_zoom)
|
||||
{
|
||||
zoom = std::max(std::min(zoom, 4.0), -4.0) / 10.0;
|
||||
zoom = m_zoom / (1.0 - zoom);
|
||||
set_zoom(m_zoom / (1.0 - std::max(std::min(delta_zoom, 4.0), -4.0) * 0.1));
|
||||
}
|
||||
|
||||
void Camera::set_zoom(double zoom)
|
||||
{
|
||||
// Don't allow to zoom too far outside the scene.
|
||||
double zoom_min = calc_zoom_to_bounding_box_factor(max_box, canvas_w, canvas_h);
|
||||
double zoom_min = calc_zoom_to_bounding_box_factor(m_scene_box, (int)m_viewport[2], (int)m_viewport[3]);
|
||||
if (zoom_min > 0.0)
|
||||
zoom = std::max(zoom, zoom_min * 0.7);
|
||||
|
||||
// Don't allow to zoom too close to the scene.
|
||||
zoom = std::min(zoom, 100.0);
|
||||
|
||||
m_zoom = zoom;
|
||||
m_zoom = std::min(zoom, 100.0);
|
||||
}
|
||||
|
||||
bool Camera::select_view(const std::string& direction)
|
||||
|
|
|
@ -70,8 +70,8 @@ public:
|
|||
void set_theta(float theta, bool apply_limit);
|
||||
|
||||
double get_zoom() const { return m_zoom; }
|
||||
void set_zoom(double zoom, const BoundingBoxf3& max_box, int canvas_w, int canvas_h);
|
||||
void set_zoom(double zoom) { m_zoom = zoom; }
|
||||
void update_zoom(double delta_zoom);
|
||||
void set_zoom(double zoom);
|
||||
|
||||
const BoundingBoxf3& get_scene_box() const { return m_scene_box; }
|
||||
void set_scene_box(const BoundingBoxf3& box) { m_scene_box = box; }
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "GUI_App.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
#include "GUI_ObjectManipulation.hpp"
|
||||
#include "Mouse3DController.hpp"
|
||||
#include "I18N.hpp"
|
||||
|
||||
#if ENABLE_RETINA_GL
|
||||
|
@ -130,6 +131,9 @@ GLCanvas3D::LayersEditing::LayersEditing()
|
|||
, m_object_max_z(0.f)
|
||||
, m_slicing_parameters(nullptr)
|
||||
, m_layer_height_profile_modified(false)
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
, m_adaptive_cusp(0.2f)
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
, state(Unknown)
|
||||
, band_width(2.0f)
|
||||
, strength(0.005f)
|
||||
|
@ -150,7 +154,9 @@ GLCanvas3D::LayersEditing::~LayersEditing()
|
|||
}
|
||||
|
||||
const float GLCanvas3D::LayersEditing::THICKNESS_BAR_WIDTH = 70.0f;
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
const float GLCanvas3D::LayersEditing::THICKNESS_RESET_BUTTON_HEIGHT = 22.0f;
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
bool GLCanvas3D::LayersEditing::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename)
|
||||
{
|
||||
|
@ -217,13 +223,103 @@ void GLCanvas3D::LayersEditing::render_overlay(const GLCanvas3D& canvas) const
|
|||
if (!m_enabled)
|
||||
return;
|
||||
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
static const ImVec4 orange(0.757f, 0.404f, 0.216f, 1.0f);
|
||||
|
||||
const Size& cnv_size = canvas.get_canvas_size();
|
||||
float canvas_w = (float)cnv_size.get_width();
|
||||
float canvas_h = (float)cnv_size.get_height();
|
||||
|
||||
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
||||
imgui.set_next_window_pos(canvas_w - imgui.get_style_scaling() * THICKNESS_BAR_WIDTH, canvas_h, ImGuiCond_Always, 1.0f, 1.0f);
|
||||
imgui.set_next_window_bg_alpha(0.5f);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
|
||||
imgui.begin(_(L("Layer height profile")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, orange);
|
||||
imgui.text(_(L("Left mouse button:")));
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SameLine();
|
||||
imgui.text(_(L("Add detail")));
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, orange);
|
||||
imgui.text(_(L("Right mouse button:")));
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SameLine();
|
||||
imgui.text(_(L("Remove detail")));
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, orange);
|
||||
imgui.text(_(L("Shift + Left mouse button:")));
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SameLine();
|
||||
imgui.text(_(L("Reset to base")));
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, orange);
|
||||
imgui.text(_(L("Shift + Right mouse button:")));
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SameLine();
|
||||
imgui.text(_(L("Smoothing")));
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, orange);
|
||||
imgui.text(_(L("Mouse wheel:")));
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SameLine();
|
||||
imgui.text(_(L("Increase/decrease edit area")));
|
||||
|
||||
ImGui::Separator();
|
||||
if (imgui.button(_(L("Adaptive"))))
|
||||
wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), Event<float>(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, m_adaptive_cusp));
|
||||
|
||||
ImGui::SameLine();
|
||||
float text_align = ImGui::GetCursorPosX();
|
||||
imgui.text(_(L("Cusp (mm)")));
|
||||
ImGui::SameLine();
|
||||
float widget_align = ImGui::GetCursorPosX();
|
||||
ImGui::PushItemWidth(120.0f);
|
||||
m_adaptive_cusp = std::min(m_adaptive_cusp, (float)m_slicing_parameters->max_layer_height);
|
||||
ImGui::SliderFloat("", &m_adaptive_cusp, 0.0f, (float)m_slicing_parameters->max_layer_height, "%.2f");
|
||||
|
||||
ImGui::Separator();
|
||||
if (imgui.button(_(L("Smooth"))))
|
||||
wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), HeightProfileSmoothEvent(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, m_smooth_params ));
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::SetCursorPosX(text_align);
|
||||
imgui.text(_(L("Radius")));
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(120.0f);
|
||||
ImGui::SetCursorPosX(widget_align);
|
||||
int radius = (int)m_smooth_params.radius;
|
||||
if (ImGui::SliderInt("##1", &radius, 1, 10))
|
||||
m_smooth_params.radius = (unsigned int)radius;
|
||||
|
||||
ImGui::SetCursorPosX(text_align);
|
||||
imgui.text(_(L("Keep min")));
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(120.0f);
|
||||
ImGui::SetCursorPosX(widget_align);
|
||||
imgui.checkbox("##2", m_smooth_params.keep_min);
|
||||
|
||||
ImGui::Separator();
|
||||
if (imgui.button(_(L("Reset"))))
|
||||
wxPostEvent((wxEvtHandler*)canvas.get_wxglcanvas(), SimpleEvent(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE));
|
||||
|
||||
imgui.end();
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
const Rect& bar_rect = get_bar_rect_viewport(canvas);
|
||||
#else
|
||||
const Rect& bar_rect = get_bar_rect_viewport(canvas);
|
||||
const Rect& reset_rect = get_reset_rect_viewport(canvas);
|
||||
|
||||
_render_tooltip_texture(canvas, bar_rect, reset_rect);
|
||||
_render_reset_texture(reset_rect);
|
||||
_render_active_object_annotations(canvas, bar_rect);
|
||||
_render_profile(bar_rect);
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
render_active_object_annotations(canvas, bar_rect);
|
||||
render_profile(bar_rect);
|
||||
}
|
||||
|
||||
float GLCanvas3D::LayersEditing::get_cursor_z_relative(const GLCanvas3D& canvas)
|
||||
|
@ -248,11 +344,13 @@ bool GLCanvas3D::LayersEditing::bar_rect_contains(const GLCanvas3D& canvas, floa
|
|||
return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom());
|
||||
}
|
||||
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
bool GLCanvas3D::LayersEditing::reset_rect_contains(const GLCanvas3D& canvas, float x, float y)
|
||||
{
|
||||
const Rect& rect = get_reset_rect_screen(canvas);
|
||||
return (rect.get_left() <= x) && (x <= rect.get_right()) && (rect.get_top() <= y) && (y <= rect.get_bottom());
|
||||
}
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas)
|
||||
{
|
||||
|
@ -260,9 +358,14 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_screen(const GLCanvas3D& canvas)
|
|||
float w = (float)cnv_size.get_width();
|
||||
float h = (float)cnv_size.get_height();
|
||||
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
return Rect(w - thickness_bar_width(canvas), 0.0f, w, h);
|
||||
#else
|
||||
return Rect(w - thickness_bar_width(canvas), 0.0f, w, h - reset_button_height(canvas));
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
}
|
||||
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
Rect GLCanvas3D::LayersEditing::get_reset_rect_screen(const GLCanvas3D& canvas)
|
||||
{
|
||||
const Size& cnv_size = canvas.get_canvas_size();
|
||||
|
@ -271,6 +374,7 @@ Rect GLCanvas3D::LayersEditing::get_reset_rect_screen(const GLCanvas3D& canvas)
|
|||
|
||||
return Rect(w - thickness_bar_width(canvas), h - reset_button_height(canvas), w, h);
|
||||
}
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas)
|
||||
{
|
||||
|
@ -281,9 +385,14 @@ Rect GLCanvas3D::LayersEditing::get_bar_rect_viewport(const GLCanvas3D& canvas)
|
|||
float zoom = (float)canvas.get_camera().get_zoom();
|
||||
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
|
||||
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom);
|
||||
#else
|
||||
return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, half_h * inv_zoom, half_w * inv_zoom, (-half_h + reset_button_height(canvas)) * inv_zoom);
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
}
|
||||
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
Rect GLCanvas3D::LayersEditing::get_reset_rect_viewport(const GLCanvas3D& canvas)
|
||||
{
|
||||
const Size& cnv_size = canvas.get_canvas_size();
|
||||
|
@ -295,13 +404,14 @@ Rect GLCanvas3D::LayersEditing::get_reset_rect_viewport(const GLCanvas3D& canvas
|
|||
|
||||
return Rect((half_w - thickness_bar_width(canvas)) * inv_zoom, (-half_h + reset_button_height(canvas)) * inv_zoom, half_w * inv_zoom, -half_h * inv_zoom);
|
||||
}
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
|
||||
bool GLCanvas3D::LayersEditing::_is_initialized() const
|
||||
bool GLCanvas3D::LayersEditing::is_initialized() const
|
||||
{
|
||||
return m_shader.is_initialized();
|
||||
}
|
||||
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas, const Rect& bar_rect, const Rect& reset_rect) const
|
||||
{
|
||||
// TODO: do this with ImGui
|
||||
|
@ -347,8 +457,9 @@ void GLCanvas3D::LayersEditing::_render_reset_texture(const Rect& reset_rect) co
|
|||
|
||||
GLTexture::render_texture(m_reset_texture.get_id(), reset_rect.get_left(), reset_rect.get_right(), reset_rect.get_bottom(), reset_rect.get_top());
|
||||
}
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
void GLCanvas3D::LayersEditing::_render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const
|
||||
void GLCanvas3D::LayersEditing::render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const
|
||||
{
|
||||
m_shader.start_using();
|
||||
|
||||
|
@ -379,7 +490,7 @@ void GLCanvas3D::LayersEditing::_render_active_object_annotations(const GLCanvas
|
|||
m_shader.stop_using();
|
||||
}
|
||||
|
||||
void GLCanvas3D::LayersEditing::_render_profile(const Rect& bar_rect) const
|
||||
void GLCanvas3D::LayersEditing::render_profile(const Rect& bar_rect) const
|
||||
{
|
||||
//FIXME show some kind of legend.
|
||||
|
||||
|
@ -496,6 +607,24 @@ void GLCanvas3D::LayersEditing::reset_layer_height_profile(GLCanvas3D& canvas)
|
|||
canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
||||
}
|
||||
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
void GLCanvas3D::LayersEditing::adaptive_layer_height_profile(GLCanvas3D& canvas, float cusp)
|
||||
{
|
||||
m_layer_height_profile = layer_height_profile_adaptive(*m_slicing_parameters, *m_model_object, cusp);
|
||||
const_cast<ModelObject*>(m_model_object)->layer_height_profile = m_layer_height_profile;
|
||||
m_layers_texture.valid = false;
|
||||
canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
||||
}
|
||||
|
||||
void GLCanvas3D::LayersEditing::smooth_layer_height_profile(GLCanvas3D& canvas, const HeightProfileSmoothingParams& smoothing_params)
|
||||
{
|
||||
m_layer_height_profile = smooth_height_profile(m_layer_height_profile, *m_slicing_parameters, smoothing_params);
|
||||
const_cast<ModelObject*>(m_model_object)->layer_height_profile = m_layer_height_profile;
|
||||
m_layers_texture.valid = false;
|
||||
canvas.post_event(SimpleEvent(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS));
|
||||
}
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
void GLCanvas3D::LayersEditing::generate_layer_height_texture()
|
||||
{
|
||||
this->update_slicing_parameters();
|
||||
|
@ -557,6 +686,7 @@ float GLCanvas3D::LayersEditing::thickness_bar_width(const GLCanvas3D &canvas)
|
|||
* THICKNESS_BAR_WIDTH;
|
||||
}
|
||||
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
float GLCanvas3D::LayersEditing::reset_button_height(const GLCanvas3D &canvas)
|
||||
{
|
||||
return
|
||||
|
@ -567,6 +697,7 @@ float GLCanvas3D::LayersEditing::reset_button_height(const GLCanvas3D &canvas)
|
|||
#endif
|
||||
* THICKNESS_RESET_BUTTON_HEIGHT;
|
||||
}
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
|
||||
const Point GLCanvas3D::Mouse::Drag::Invalid_2D_Point(INT_MAX, INT_MAX);
|
||||
|
@ -1118,6 +1249,11 @@ wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent);
|
|||
wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event<float>);
|
||||
wxDEFINE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent);
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
const double GLCanvas3D::DefaultCameraZoomToBoxMarginFactor = 1.25;
|
||||
|
@ -1372,7 +1508,7 @@ void GLCanvas3D::set_model(Model* model)
|
|||
|
||||
void GLCanvas3D::bed_shape_changed()
|
||||
{
|
||||
m_camera.set_scene_box(scene_bounding_box());
|
||||
refresh_camera_scene_box();
|
||||
m_camera.requires_zoom_to_bed = true;
|
||||
m_dirty = true;
|
||||
if (m_bed.is_prusa())
|
||||
|
@ -1398,7 +1534,7 @@ BoundingBoxf3 GLCanvas3D::volumes_bounding_box() const
|
|||
BoundingBoxf3 GLCanvas3D::scene_bounding_box() const
|
||||
{
|
||||
BoundingBoxf3 bb = volumes_bounding_box();
|
||||
bb.merge(m_bed.get_bounding_box(false));
|
||||
bb.merge(m_bed.get_bounding_box(true));
|
||||
|
||||
if (m_config != nullptr)
|
||||
{
|
||||
|
@ -1420,6 +1556,29 @@ bool GLCanvas3D::is_layers_editing_allowed() const
|
|||
return m_layers_editing.is_allowed();
|
||||
}
|
||||
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
void GLCanvas3D::reset_layer_height_profile()
|
||||
{
|
||||
m_layers_editing.reset_layer_height_profile(*this);
|
||||
m_layers_editing.state = LayersEditing::Completed;
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
void GLCanvas3D::adaptive_layer_height_profile(float cusp)
|
||||
{
|
||||
m_layers_editing.adaptive_layer_height_profile(*this, cusp);
|
||||
m_layers_editing.state = LayersEditing::Completed;
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
void GLCanvas3D::smooth_layer_height_profile(const HeightProfileSmoothingParams& smoothing_params)
|
||||
{
|
||||
m_layers_editing.smooth_layer_height_profile(*this, smoothing_params);
|
||||
m_layers_editing.state = LayersEditing::Completed;
|
||||
m_dirty = true;
|
||||
}
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
bool GLCanvas3D::is_reload_delayed() const
|
||||
{
|
||||
return m_reload_delayed;
|
||||
|
@ -1546,10 +1705,11 @@ void GLCanvas3D::render()
|
|||
return;
|
||||
}
|
||||
|
||||
const Size& cnv_size = get_canvas_size();
|
||||
|
||||
if (m_camera.requires_zoom_to_bed)
|
||||
{
|
||||
zoom_to_bed();
|
||||
const Size& cnv_size = get_canvas_size();
|
||||
_resize((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height());
|
||||
m_camera.requires_zoom_to_bed = false;
|
||||
}
|
||||
|
@ -1640,6 +1800,8 @@ void GLCanvas3D::render()
|
|||
m_camera.debug_render();
|
||||
#endif // ENABLE_CAMERA_STATISTICS
|
||||
|
||||
wxGetApp().plater()->get_mouse3d_controller().render_settings_dialog((unsigned int)cnv_size.get_width(), (unsigned int)cnv_size.get_height());
|
||||
|
||||
wxGetApp().imgui()->render();
|
||||
|
||||
m_canvas->SwapBuffers();
|
||||
|
@ -2098,7 +2260,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
|||
post_event(Event<bool>(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, false));
|
||||
}
|
||||
|
||||
m_camera.set_scene_box(scene_bounding_box());
|
||||
refresh_camera_scene_box();
|
||||
|
||||
if (m_selection.is_empty())
|
||||
{
|
||||
|
@ -2134,12 +2296,12 @@ static void reserve_new_volume_finalize_old_volume(GLVolume& vol_new, GLVolume&
|
|||
|
||||
static void load_gcode_retractions(const GCodePreviewData::Retraction& retractions, GLCanvas3D::GCodePreviewVolumeIndex::EType extrusion_type, GLVolumeCollection &volumes, GLCanvas3D::GCodePreviewVolumeIndex &volume_index, bool gl_initialized)
|
||||
{
|
||||
volume_index.first_volumes.emplace_back(extrusion_type, 0, (unsigned int)volumes.volumes.size());
|
||||
|
||||
// nothing to render, return
|
||||
if (retractions.positions.empty())
|
||||
return;
|
||||
|
||||
volume_index.first_volumes.emplace_back(extrusion_type, 0, (unsigned int)volumes.volumes.size());
|
||||
|
||||
GLVolume *volume = volumes.new_nontoolpath_volume(retractions.color.rgba, VERTEX_BUFFER_RESERVE_SIZE);
|
||||
|
||||
GCodePreviewData::Retraction::PositionsList copy(retractions.positions);
|
||||
|
@ -2205,6 +2367,9 @@ void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const
|
|||
++ idx_volume_index_src;
|
||||
idx_volume_of_this_type_last = (idx_volume_index_src + 1 == m_gcode_preview_volume_index.first_volumes.size()) ? m_volumes.volumes.size() : m_gcode_preview_volume_index.first_volumes[idx_volume_index_src + 1].id;
|
||||
idx_volume_of_this_type_first_new = idx_volume_dst;
|
||||
if (idx_volume_src == idx_volume_of_this_type_last)
|
||||
// Empty sequence of volumes for the current index item.
|
||||
continue;
|
||||
}
|
||||
if (! m_volumes.volumes[idx_volume_src]->print_zs.empty())
|
||||
m_volumes.volumes[idx_volume_dst ++] = m_volumes.volumes[idx_volume_src];
|
||||
|
@ -2338,14 +2503,21 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
|
|||
m_dirty |= m_main_toolbar.update_items_state();
|
||||
m_dirty |= m_undoredo_toolbar.update_items_state();
|
||||
m_dirty |= m_view_toolbar.update_items_state();
|
||||
bool mouse3d_controller_applied = wxGetApp().plater()->get_mouse3d_controller().apply(m_camera);
|
||||
m_dirty |= mouse3d_controller_applied;
|
||||
|
||||
if (!m_dirty)
|
||||
return;
|
||||
|
||||
_refresh_if_shown_on_screen();
|
||||
|
||||
if (m_keep_dirty)
|
||||
if (m_keep_dirty || mouse3d_controller_applied)
|
||||
{
|
||||
m_dirty = true;
|
||||
evt.RequestMore();
|
||||
}
|
||||
else
|
||||
m_dirty = false;
|
||||
}
|
||||
|
||||
void GLCanvas3D::on_char(wxKeyEvent& evt)
|
||||
|
@ -2390,6 +2562,20 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
|
|||
#endif /* __APPLE__ */
|
||||
post_event(SimpleEvent(EVT_GLTOOLBAR_COPY));
|
||||
break;
|
||||
|
||||
#ifdef __APPLE__
|
||||
case 'm':
|
||||
case 'M':
|
||||
#else /* __APPLE__ */
|
||||
case WXK_CONTROL_M:
|
||||
#endif /* __APPLE__ */
|
||||
{
|
||||
Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller();
|
||||
controller.show_settings_dialog(!controller.is_settings_dialog_shown());
|
||||
m_dirty = true;
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
case 'v':
|
||||
case 'V':
|
||||
|
@ -2457,11 +2643,11 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
|
|||
case 'B':
|
||||
case 'b': { zoom_to_bed(); break; }
|
||||
case 'I':
|
||||
case 'i': { set_camera_zoom(1.0); break; }
|
||||
case 'i': { _update_camera_zoom(1.0); break; }
|
||||
case 'K':
|
||||
case 'k': { m_camera.select_next_type(); m_dirty = true; break; }
|
||||
case 'O':
|
||||
case 'o': { set_camera_zoom(-1.0); break; }
|
||||
case 'o': { _update_camera_zoom(-1.0); break; }
|
||||
#if ENABLE_RENDER_PICKING_PASS
|
||||
case 'T':
|
||||
case 't': {
|
||||
|
@ -2564,6 +2750,11 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
|
|||
|
||||
void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
|
||||
{
|
||||
// try to filter out events coming from mouse 3d
|
||||
Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller();
|
||||
if (controller.process_mouse_wheel())
|
||||
return;
|
||||
|
||||
if (!m_initialized)
|
||||
return;
|
||||
|
||||
|
@ -2608,7 +2799,7 @@ void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
|
|||
return;
|
||||
|
||||
// Calculate the zoom delta and apply it to the current zoom factor
|
||||
set_camera_zoom((double)evt.GetWheelRotation() / (double)evt.GetWheelDelta());
|
||||
_update_camera_zoom((double)evt.GetWheelRotation() / (double)evt.GetWheelDelta());
|
||||
}
|
||||
|
||||
void GLCanvas3D::on_timer(wxTimerEvent& evt)
|
||||
|
@ -2800,6 +2991,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||
m_layers_editing.state = LayersEditing::Editing;
|
||||
_perform_layer_editing_action(&evt);
|
||||
}
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
else if ((layer_editing_object_idx != -1) && m_layers_editing.reset_rect_contains(*this, pos(0), pos(1)))
|
||||
{
|
||||
if (evt.LeftDown())
|
||||
|
@ -2812,6 +3004,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||
m_dirty = true;
|
||||
}
|
||||
}
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
else if (evt.LeftDown() && (evt.ShiftDown() || evt.AltDown()) && m_picking_enabled)
|
||||
{
|
||||
if (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)
|
||||
|
@ -3391,13 +3584,6 @@ void GLCanvas3D::do_mirror(const std::string& snapshot_type)
|
|||
m_dirty = true;
|
||||
}
|
||||
|
||||
void GLCanvas3D::set_camera_zoom(double zoom)
|
||||
{
|
||||
const Size& cnv_size = get_canvas_size();
|
||||
m_camera.set_zoom(zoom, _max_bounding_box(false, true), cnv_size.get_width(), cnv_size.get_height());
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
void GLCanvas3D::update_gizmos_on_off_state()
|
||||
{
|
||||
set_as_dirty();
|
||||
|
@ -4183,8 +4369,6 @@ void GLCanvas3D::_resize(unsigned int w, unsigned int h)
|
|||
|
||||
// updates camera
|
||||
m_camera.apply_viewport(0, 0, w, h);
|
||||
|
||||
m_dirty = false;
|
||||
}
|
||||
|
||||
BoundingBoxf3 GLCanvas3D::_max_bounding_box(bool include_gizmos, bool include_bed_model) const
|
||||
|
@ -4221,6 +4405,12 @@ void GLCanvas3D::_zoom_to_box(const BoundingBoxf3& box)
|
|||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
void GLCanvas3D::_update_camera_zoom(double zoom)
|
||||
{
|
||||
m_camera.update_zoom(zoom);
|
||||
m_dirty = true;
|
||||
}
|
||||
|
||||
void GLCanvas3D::_refresh_if_shown_on_screen()
|
||||
{
|
||||
if (_is_shown_on_screen())
|
||||
|
|
|
@ -81,6 +81,8 @@ template <size_t N> using Vec2dsEvent = ArrayEvent<Vec2d, N>;
|
|||
using Vec3dEvent = Event<Vec3d>;
|
||||
template <size_t N> using Vec3dsEvent = ArrayEvent<Vec3d, N>;
|
||||
|
||||
using HeightProfileSmoothEvent = Event<HeightProfileSmoothingParams>;
|
||||
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_INIT, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_SCHEDULE_BACKGROUND_PROCESS, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_RIGHT_CLICK, RBtnEvent);
|
||||
|
@ -104,6 +106,11 @@ wxDECLARE_EVENT(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, wxKeyEvent);
|
|||
wxDECLARE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, SimpleEvent);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, Event<float>);
|
||||
wxDECLARE_EVENT(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, HeightProfileSmoothEvent);
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
class GLCanvas3D
|
||||
{
|
||||
|
@ -153,13 +160,17 @@ private:
|
|||
|
||||
private:
|
||||
static const float THICKNESS_BAR_WIDTH;
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
static const float THICKNESS_RESET_BUTTON_HEIGHT;
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
bool m_enabled;
|
||||
Shader m_shader;
|
||||
unsigned int m_z_texture_id;
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
mutable GLTexture m_tooltip_texture;
|
||||
mutable GLTexture m_reset_texture;
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
// Not owned by LayersEditing.
|
||||
const DynamicPrintConfig *m_config;
|
||||
// ModelObject for the currently selected object (Model::objects[last_object_id]).
|
||||
|
@ -171,6 +182,11 @@ private:
|
|||
std::vector<coordf_t> m_layer_height_profile;
|
||||
bool m_layer_height_profile_modified;
|
||||
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
mutable float m_adaptive_cusp;
|
||||
mutable HeightProfileSmoothingParams m_smooth_params;
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
class LayersTexture
|
||||
{
|
||||
public:
|
||||
|
@ -217,28 +233,42 @@ private:
|
|||
void adjust_layer_height_profile();
|
||||
void accept_changes(GLCanvas3D& canvas);
|
||||
void reset_layer_height_profile(GLCanvas3D& canvas);
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
void adaptive_layer_height_profile(GLCanvas3D& canvas, float cusp);
|
||||
void smooth_layer_height_profile(GLCanvas3D& canvas, const HeightProfileSmoothingParams& smoothing_paramsn);
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
static float get_cursor_z_relative(const GLCanvas3D& canvas);
|
||||
static bool bar_rect_contains(const GLCanvas3D& canvas, float x, float y);
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
static bool reset_rect_contains(const GLCanvas3D& canvas, float x, float y);
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
static Rect get_bar_rect_screen(const GLCanvas3D& canvas);
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
static Rect get_reset_rect_screen(const GLCanvas3D& canvas);
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
static Rect get_bar_rect_viewport(const GLCanvas3D& canvas);
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
static Rect get_reset_rect_viewport(const GLCanvas3D& canvas);
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
float object_max_z() const { return m_object_max_z; }
|
||||
|
||||
private:
|
||||
bool _is_initialized() const;
|
||||
bool is_initialized() const;
|
||||
void generate_layer_height_texture();
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
void _render_tooltip_texture(const GLCanvas3D& canvas, const Rect& bar_rect, const Rect& reset_rect) const;
|
||||
void _render_reset_texture(const Rect& reset_rect) const;
|
||||
void _render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const;
|
||||
void _render_profile(const Rect& bar_rect) const;
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
void render_active_object_annotations(const GLCanvas3D& canvas, const Rect& bar_rect) const;
|
||||
void render_profile(const Rect& bar_rect) const;
|
||||
void update_slicing_parameters();
|
||||
|
||||
static float thickness_bar_width(const GLCanvas3D &canvas);
|
||||
#if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
static float reset_button_height(const GLCanvas3D &canvas);
|
||||
#endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
};
|
||||
|
||||
struct Mouse
|
||||
|
@ -493,6 +523,7 @@ public:
|
|||
void set_color_by(const std::string& value);
|
||||
|
||||
const Camera& get_camera() const { return m_camera; }
|
||||
Camera& get_camera() { return m_camera; }
|
||||
|
||||
BoundingBoxf3 volumes_bounding_box() const;
|
||||
BoundingBoxf3 scene_bounding_box() const;
|
||||
|
@ -500,6 +531,12 @@ public:
|
|||
bool is_layers_editing_enabled() const;
|
||||
bool is_layers_editing_allowed() const;
|
||||
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
void reset_layer_height_profile();
|
||||
void adaptive_layer_height_profile(float cusp);
|
||||
void smooth_layer_height_profile(const HeightProfileSmoothingParams& smoothing_params);
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
bool is_reload_delayed() const;
|
||||
|
||||
void enable_layers_editing(bool enable);
|
||||
|
@ -576,8 +613,6 @@ public:
|
|||
void do_flatten(const Vec3d& normal, const std::string& snapshot_type);
|
||||
void do_mirror(const std::string& snapshot_type);
|
||||
|
||||
void set_camera_zoom(double zoom);
|
||||
|
||||
void update_gizmos_on_off_state();
|
||||
void reset_all_gizmos() { m_gizmos.reset_all_states(); }
|
||||
|
||||
|
@ -655,6 +690,7 @@ private:
|
|||
#else
|
||||
void _zoom_to_box(const BoundingBoxf3& box);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
void _update_camera_zoom(double zoom);
|
||||
|
||||
void _refresh_if_shown_on_screen();
|
||||
|
||||
|
|
|
@ -1126,7 +1126,6 @@ void GUI_App::gcode_thumbnails_debug()
|
|||
}
|
||||
else if (reading_image && boost::starts_with(gcode_line, END_MASK))
|
||||
{
|
||||
#if ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
std::string out_filename = out_path + std::to_string(width) + "x" + std::to_string(height) + ".png";
|
||||
boost::nowide::ofstream out_file(out_filename.c_str(), std::ios::binary);
|
||||
if (out_file.good())
|
||||
|
@ -1138,46 +1137,6 @@ void GUI_App::gcode_thumbnails_debug()
|
|||
out_file.write(decoded.c_str(), decoded.size());
|
||||
out_file.close();
|
||||
}
|
||||
#else
|
||||
if (!row.empty())
|
||||
{
|
||||
rows.push_back(row);
|
||||
row.clear();
|
||||
}
|
||||
|
||||
if ((unsigned int)rows.size() == height)
|
||||
{
|
||||
std::vector<unsigned char> thumbnail(4 * width * height, 0);
|
||||
for (unsigned int r = 0; r < (unsigned int)rows.size(); ++r)
|
||||
{
|
||||
std::string decoded_row;
|
||||
decoded_row.resize(boost::beast::detail::base64::decoded_size(rows[r].size()));
|
||||
decoded_row.resize(boost::beast::detail::base64::decode((void*)&decoded_row[0], rows[r].data(), rows[r].size()).first);
|
||||
|
||||
if ((unsigned int)decoded_row.size() == width * 4)
|
||||
{
|
||||
void* image_ptr = (void*)(thumbnail.data() + r * width * 4);
|
||||
::memcpy(image_ptr, (const void*)decoded_row.c_str(), width * 4);
|
||||
}
|
||||
}
|
||||
|
||||
wxImage image(width, height);
|
||||
image.InitAlpha();
|
||||
|
||||
for (unsigned int r = 0; r < height; ++r)
|
||||
{
|
||||
unsigned int rr = r * width;
|
||||
for (unsigned int c = 0; c < width; ++c)
|
||||
{
|
||||
unsigned char* px = thumbnail.data() + 4 * (rr + c);
|
||||
image.SetRGB((int)c, (int)r, px[0], px[1], px[2]);
|
||||
image.SetAlpha((int)c, (int)r, px[3]);
|
||||
}
|
||||
}
|
||||
|
||||
image.SaveFile(out_path + std::to_string(width) + "x" + std::to_string(height) + ".png", wxBITMAP_TYPE_PNG);
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
|
||||
reading_image = false;
|
||||
width = 0;
|
||||
|
@ -1185,19 +1144,9 @@ void GUI_App::gcode_thumbnails_debug()
|
|||
rows.clear();
|
||||
}
|
||||
else if (reading_image)
|
||||
{
|
||||
#if !ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
if (!row.empty() && (gcode_line[1] == ' '))
|
||||
{
|
||||
rows.push_back(row);
|
||||
row.clear();
|
||||
}
|
||||
#endif // !ENABLE_THUMBNAIL_GENERATOR_PNG_TO_GCODE
|
||||
|
||||
row += gcode_line.substr(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
in_file.close();
|
||||
}
|
||||
|
|
|
@ -528,6 +528,9 @@ void ImGuiWrapper::init_style()
|
|||
// Slider
|
||||
set_color(ImGuiCol_SliderGrab, COL_ORANGE_DARK);
|
||||
set_color(ImGuiCol_SliderGrabActive, COL_ORANGE_LIGHT);
|
||||
|
||||
// Separator
|
||||
set_color(ImGuiCol_Separator, COL_ORANGE_LIGHT);
|
||||
}
|
||||
|
||||
void ImGuiWrapper::render_draw_data(ImDrawData *draw_data)
|
||||
|
|
|
@ -157,6 +157,7 @@ void KBShortcutsDialog::fill_shortcuts()
|
|||
plater_shortcuts.push_back(Shortcut("Z", L("Zoom to selected object")));
|
||||
plater_shortcuts.push_back(Shortcut("I", L("Zoom in")));
|
||||
plater_shortcuts.push_back(Shortcut("O", L("Zoom out")));
|
||||
plater_shortcuts.push_back(Shortcut(ctrl+"M", L("Show/Hide 3Dconnexion devices settings dialog")));
|
||||
plater_shortcuts.push_back(Shortcut("ESC", L("Unselect gizmo / Clear selection")));
|
||||
#if ENABLE_RENDER_PICKING_PASS
|
||||
// Don't localize debugging texts.
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "PrintHostDialogs.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
#include "Mouse3DController.hpp"
|
||||
#include "I18N.hpp"
|
||||
|
||||
#include <fstream>
|
||||
|
|
822
src/slic3r/GUI/Mouse3DController.cpp
Normal file
822
src/slic3r/GUI/Mouse3DController.cpp
Normal file
|
@ -0,0 +1,822 @@
|
|||
#include "libslic3r/libslic3r.h"
|
||||
#include "Mouse3DController.hpp"
|
||||
|
||||
#include "Camera.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "AppConfig.hpp"
|
||||
|
||||
#include <wx/glcanvas.h>
|
||||
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include "I18N.hpp"
|
||||
|
||||
#include <bitset>
|
||||
|
||||
// WARN: If updating these lists, please also update resources/udev/90-3dconnexion.rules
|
||||
|
||||
static const std::vector<int> _3DCONNEXION_VENDORS =
|
||||
{
|
||||
0x046d, // LOGITECH = 1133 // Logitech (3Dconnexion is made by Logitech)
|
||||
0x256F // 3DCONNECTION = 9583 // 3Dconnexion
|
||||
};
|
||||
|
||||
// See: https://github.com/FreeSpacenav/spacenavd/blob/a9eccf34e7cac969ee399f625aef827f4f4aaec6/src/dev.c#L202
|
||||
static const std::vector<int> _3DCONNEXION_DEVICES =
|
||||
{
|
||||
0xc603, /* 50691 spacemouse plus XT */
|
||||
0xc605, /* 50693 cadman */
|
||||
0xc606, /* 50694 spacemouse classic */
|
||||
0xc621, /* 50721 spaceball 5000 */
|
||||
0xc623, /* 50723 space traveller */
|
||||
0xc625, /* 50725 space pilot */
|
||||
0xc626, /* 50726 space navigator *TESTED* */
|
||||
0xc627, /* 50727 space explorer */
|
||||
0xc628, /* 50728 space navigator for notebooks*/
|
||||
0xc629, /* 50729 space pilot pro*/
|
||||
0xc62b, /* 50731 space mouse pro*/
|
||||
0xc62e, /* 50734 spacemouse wireless (USB cable) *TESTED* */
|
||||
0xc62f, /* 50735 spacemouse wireless receiver */
|
||||
0xc631, /* 50737 spacemouse pro wireless *TESTED* */
|
||||
0xc632, /* 50738 spacemouse pro wireless receiver */
|
||||
0xc633, /* 50739 spacemouse enterprise */
|
||||
0xc635, /* 50741 spacemouse compact *TESTED* */
|
||||
0xc636, /* 50742 spacemouse module */
|
||||
0xc640, /* 50752 nulooq */
|
||||
0xc652, /* 50770 3Dconnexion universal receiver *TESTED* */
|
||||
};
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
const double Mouse3DController::State::DefaultTranslationScale = 2.5;
|
||||
const double Mouse3DController::State::MaxTranslationDeadzone = 0.2;
|
||||
const double Mouse3DController::State::DefaultTranslationDeadzone = 0.5 * Mouse3DController::State::MaxTranslationDeadzone;
|
||||
const float Mouse3DController::State::DefaultRotationScale = 1.0f;
|
||||
const float Mouse3DController::State::MaxRotationDeadzone = (float)Mouse3DController::State::MaxTranslationDeadzone;
|
||||
const float Mouse3DController::State::DefaultRotationDeadzone = 0.5f * Mouse3DController::State::MaxRotationDeadzone;
|
||||
|
||||
Mouse3DController::State::State()
|
||||
: m_buttons_enabled(false)
|
||||
, m_translation_params(DefaultTranslationScale, DefaultTranslationDeadzone)
|
||||
, m_rotation_params(DefaultRotationScale, DefaultRotationDeadzone)
|
||||
, m_mouse_wheel_counter(0)
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
, m_translation_queue_max_size(0)
|
||||
, m_rotation_queue_max_size(0)
|
||||
, m_buttons_queue_max_size(0)
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
{
|
||||
}
|
||||
|
||||
void Mouse3DController::State::append_translation(const Vec3d& translation)
|
||||
{
|
||||
while (m_translation.queue.size() >= m_translation.max_size)
|
||||
{
|
||||
m_translation.queue.pop();
|
||||
}
|
||||
m_translation.queue.push(translation);
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
m_translation_queue_max_size = std::max(m_translation_queue_max_size, m_translation.queue.size());
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
}
|
||||
|
||||
void Mouse3DController::State::append_rotation(const Vec3f& rotation)
|
||||
{
|
||||
while (m_rotation.queue.size() >= m_rotation.max_size)
|
||||
{
|
||||
m_rotation.queue.pop();
|
||||
}
|
||||
m_rotation.queue.push(rotation);
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
m_rotation_queue_max_size = std::max(m_rotation_queue_max_size, m_rotation.queue.size());
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
if (rotation(0) != 0.0f)
|
||||
++m_mouse_wheel_counter;
|
||||
}
|
||||
|
||||
void Mouse3DController::State::append_button(unsigned int id)
|
||||
{
|
||||
if (!m_buttons_enabled)
|
||||
return;
|
||||
|
||||
m_buttons.push(id);
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
m_buttons_queue_max_size = std::max(m_buttons_queue_max_size, m_buttons.size());
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
}
|
||||
|
||||
bool Mouse3DController::State::process_mouse_wheel()
|
||||
{
|
||||
if (m_mouse_wheel_counter == 0)
|
||||
return false;
|
||||
else if (!m_rotation.queue.empty())
|
||||
{
|
||||
--m_mouse_wheel_counter;
|
||||
return true;
|
||||
}
|
||||
|
||||
m_mouse_wheel_counter = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Mouse3DController::State::set_queues_max_size(size_t size)
|
||||
{
|
||||
if (size > 0)
|
||||
{
|
||||
m_translation.max_size = size;
|
||||
m_rotation.max_size = size;
|
||||
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
m_translation_queue_max_size = 0;
|
||||
m_rotation_queue_max_size = 0;
|
||||
m_buttons_queue_max_size = 0;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
}
|
||||
}
|
||||
|
||||
bool Mouse3DController::State::apply(Camera& camera)
|
||||
{
|
||||
if (!wxGetApp().IsActive())
|
||||
return false;
|
||||
|
||||
bool ret = false;
|
||||
|
||||
if (has_translation())
|
||||
{
|
||||
const Vec3d& translation = m_translation.queue.front();
|
||||
camera.set_target(camera.get_target() + m_translation_params.scale * (translation(0) * camera.get_dir_right() + translation(1) * camera.get_dir_forward() + translation(2) * camera.get_dir_up()));
|
||||
m_translation.queue.pop();
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (has_rotation())
|
||||
{
|
||||
const Vec3f& rotation = m_rotation.queue.front();
|
||||
float theta = m_rotation_params.scale * rotation(0);
|
||||
float phi = m_rotation_params.scale * rotation(2);
|
||||
float sign = camera.inverted_phi ? -1.0f : 1.0f;
|
||||
camera.phi += sign * phi;
|
||||
camera.set_theta(camera.get_theta() + theta, wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() != ptSLA);
|
||||
m_rotation.queue.pop();
|
||||
ret = true;
|
||||
}
|
||||
|
||||
if (m_buttons_enabled && has_button())
|
||||
{
|
||||
unsigned int button = m_buttons.front();
|
||||
switch (button)
|
||||
{
|
||||
case 0: { camera.update_zoom(1.0); break; }
|
||||
case 1: { camera.update_zoom(-1.0); break; }
|
||||
default: { break; }
|
||||
}
|
||||
m_buttons.pop();
|
||||
ret = true;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Mouse3DController::Mouse3DController()
|
||||
: m_initialized(false)
|
||||
, m_device(nullptr)
|
||||
, m_device_str("")
|
||||
, m_running(false)
|
||||
, m_settings_dialog(false)
|
||||
{
|
||||
m_last_time = std::chrono::high_resolution_clock::now();
|
||||
}
|
||||
|
||||
void Mouse3DController::init()
|
||||
{
|
||||
if (m_initialized)
|
||||
return;
|
||||
|
||||
// Initialize the hidapi library
|
||||
int res = hid_init();
|
||||
if (res != 0)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Unable to initialize hidapi library";
|
||||
return;
|
||||
}
|
||||
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
void Mouse3DController::shutdown()
|
||||
{
|
||||
if (!m_initialized)
|
||||
return;
|
||||
|
||||
stop();
|
||||
disconnect_device();
|
||||
|
||||
// Finalize the hidapi library
|
||||
hid_exit();
|
||||
m_initialized = false;
|
||||
}
|
||||
|
||||
bool Mouse3DController::apply(Camera& camera)
|
||||
{
|
||||
if (!m_initialized)
|
||||
return false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
// check if the user unplugged the device
|
||||
if (!m_running && is_device_connected())
|
||||
{
|
||||
disconnect_device();
|
||||
// hides the settings dialog if the user re-plug the device
|
||||
m_settings_dialog = false;
|
||||
}
|
||||
|
||||
// check if the user plugged the device
|
||||
if (connect_device())
|
||||
start();
|
||||
|
||||
return is_device_connected() ? m_state.apply(camera) : false;
|
||||
}
|
||||
|
||||
void Mouse3DController::render_settings_dialog(unsigned int canvas_width, unsigned int canvas_height) const
|
||||
{
|
||||
if (!m_running || !m_settings_dialog)
|
||||
return;
|
||||
|
||||
ImGuiWrapper& imgui = *wxGetApp().imgui();
|
||||
|
||||
imgui.set_next_window_pos(0.5f * (float)canvas_width, 0.5f * (float)canvas_height, ImGuiCond_Always, 0.5f, 0.5f);
|
||||
imgui.set_next_window_bg_alpha(0.5f);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
|
||||
imgui.begin(_(L("3Dconnexion settings")), ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
const ImVec4& color = ImGui::GetStyleColorVec4(ImGuiCol_Separator);
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text(_(L("Device:")));
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::SameLine();
|
||||
imgui.text(m_device_str);
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text(_(L("Speed:")));
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
float translation_scale = (float)m_state.get_translation_scale() / State::DefaultTranslationScale;
|
||||
if (ImGui::SliderFloat(_(L("Translation##1")), &translation_scale, 0.5f, 2.0f, "%.1f"))
|
||||
m_state.set_translation_scale(State::DefaultTranslationScale * (double)translation_scale);
|
||||
|
||||
float rotation_scale = m_state.get_rotation_scale() / State::DefaultRotationScale;
|
||||
if (ImGui::SliderFloat(_(L("Rotation##1")), &rotation_scale, 0.5f, 2.0f, "%.1f"))
|
||||
m_state.set_rotation_scale(State::DefaultRotationScale * rotation_scale);
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text(_(L("Deadzone:")));
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
float translation_deadzone = (float)m_state.get_translation_deadzone();
|
||||
if (ImGui::SliderFloat(_(L("Translation##2")), &translation_deadzone, 0.0f, (float)State::MaxTranslationDeadzone, "%.2f"))
|
||||
m_state.set_translation_deadzone((double)translation_deadzone);
|
||||
|
||||
float rotation_deadzone = m_state.get_rotation_deadzone();
|
||||
if (ImGui::SliderFloat(_(L("Rotation##2")), &rotation_deadzone, 0.0f, State::MaxRotationDeadzone, "%.2f"))
|
||||
m_state.set_rotation_deadzone(rotation_deadzone);
|
||||
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
ImGui::Separator();
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text("DEBUG:");
|
||||
imgui.text("Vectors:");
|
||||
ImGui::PopStyleColor();
|
||||
Vec3f translation = m_state.get_translation().cast<float>();
|
||||
Vec3f rotation = m_state.get_rotation();
|
||||
ImGui::InputFloat3("Translation##3", translation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::InputFloat3("Rotation##3", rotation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text("Queue size:");
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
int translation_size[2] = { (int)m_state.get_translation_queue_size(), (int)m_state.get_translation_queue_max_size() };
|
||||
int rotation_size[2] = { (int)m_state.get_rotation_queue_size(), (int)m_state.get_rotation_queue_max_size() };
|
||||
int buttons_size[2] = { (int)m_state.get_buttons_queue_size(), (int)m_state.get_buttons_queue_max_size() };
|
||||
|
||||
ImGui::InputInt2("Translation##4", translation_size, ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::InputInt2("Rotation##4", rotation_size, ImGuiInputTextFlags_ReadOnly);
|
||||
ImGui::InputInt2("Buttons", buttons_size, ImGuiInputTextFlags_ReadOnly);
|
||||
|
||||
int queue_size = (int)m_state.get_queues_max_size();
|
||||
if (ImGui::InputInt("Max size", &queue_size, 1, 1, ImGuiInputTextFlags_ReadOnly))
|
||||
{
|
||||
if (queue_size > 0)
|
||||
m_state.set_queues_max_size(queue_size);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, color);
|
||||
imgui.text("Camera:");
|
||||
ImGui::PopStyleColor();
|
||||
Vec3f target = wxGetApp().plater()->get_camera().get_target().cast<float>();
|
||||
ImGui::InputFloat3("Target", target.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
|
||||
imgui.end();
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
bool Mouse3DController::connect_device()
|
||||
{
|
||||
static const long long DETECTION_TIME = 2; // seconds
|
||||
|
||||
if (is_device_connected())
|
||||
return false;
|
||||
|
||||
// check time since last detection took place
|
||||
auto now = std::chrono::high_resolution_clock::now();
|
||||
if (std::chrono::duration_cast<std::chrono::seconds>(now - m_last_time).count() < DETECTION_TIME)
|
||||
return false;
|
||||
|
||||
m_last_time = now;
|
||||
|
||||
// Enumerates devices
|
||||
hid_device_info* devices = hid_enumerate(0, 0);
|
||||
if (devices == nullptr)
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "Unable to enumerate HID devices";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Searches for 1st connected 3Dconnexion device
|
||||
struct DeviceData
|
||||
{
|
||||
std::string path;
|
||||
unsigned short usage_page;
|
||||
unsigned short usage;
|
||||
|
||||
DeviceData()
|
||||
: path(""), usage_page(0), usage(0)
|
||||
{}
|
||||
DeviceData(const std::string& path, unsigned short usage_page, unsigned short usage)
|
||||
: path(path), usage_page(usage_page), usage(usage)
|
||||
{}
|
||||
|
||||
bool has_valid_usage() const { return (usage_page == 1) && (usage == 8); }
|
||||
};
|
||||
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
hid_device_info* cur = devices;
|
||||
std::cout << std::endl << "======================================================================================================================================" << std::endl;
|
||||
std::cout << "Detected devices:" << std::endl;
|
||||
while (cur != nullptr)
|
||||
{
|
||||
std::cout << "\"";
|
||||
std::wcout << ((cur->manufacturer_string != nullptr) ? cur->manufacturer_string : L"Unknown");
|
||||
std::cout << "/";
|
||||
std::wcout << ((cur->product_string != nullptr) ? cur->product_string : L"Unknown");
|
||||
std::cout << "\" code: " << cur->vendor_id << "/" << cur->product_id << " (" << std::hex << cur->vendor_id << "/" << cur->product_id << std::dec << ")";
|
||||
std::cout << " serial number: '";
|
||||
std::wcout << ((cur->serial_number != nullptr) ? cur->serial_number : L"Unknown");
|
||||
std::cout << "' usage page: " << cur->usage_page << " usage: " << cur->usage << " interface number: " << cur->interface_number << std::endl;
|
||||
|
||||
cur = cur->next;
|
||||
}
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
|
||||
// When using 3Dconnexion universal receiver, multiple devices are detected sharing the same vendor_id and product_id.
|
||||
// To choose from them the right one we use:
|
||||
// On Windows and Mac: usage_page == 1 and usage == 8
|
||||
// On Linux: as usage_page and usage are not defined (see hidapi.h) we try all detected devices until one is succesfully open
|
||||
// When only a single device is detected, as for wired connections, vendor_id and product_id are enough
|
||||
|
||||
// First we count all the valid devices from the enumerated list,
|
||||
|
||||
hid_device_info* current = devices;
|
||||
typedef std::pair<unsigned short, unsigned short> DeviceIds;
|
||||
typedef std::vector<DeviceData> DeviceDataList;
|
||||
typedef std::map<DeviceIds, DeviceDataList> DetectedDevices;
|
||||
DetectedDevices detected_devices;
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << std::endl << "Detected 3D connexion devices:" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
while (current != nullptr)
|
||||
{
|
||||
unsigned short vendor_id = 0;
|
||||
unsigned short product_id = 0;
|
||||
|
||||
for (size_t i = 0; i < _3DCONNEXION_VENDORS.size(); ++i)
|
||||
{
|
||||
if (_3DCONNEXION_VENDORS[i] == current->vendor_id)
|
||||
{
|
||||
vendor_id = current->vendor_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (vendor_id != 0)
|
||||
{
|
||||
for (size_t i = 0; i < _3DCONNEXION_DEVICES.size(); ++i)
|
||||
{
|
||||
if (_3DCONNEXION_DEVICES[i] == current->product_id)
|
||||
{
|
||||
product_id = current->product_id;
|
||||
DeviceIds detected_device(vendor_id, product_id);
|
||||
DetectedDevices::iterator it = detected_devices.find(detected_device);
|
||||
if (it == detected_devices.end())
|
||||
it = detected_devices.insert(DetectedDevices::value_type(detected_device, DeviceDataList())).first;
|
||||
|
||||
it->second.emplace_back(current->path, current->usage_page, current->usage);
|
||||
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::wcout << "\"" << ((current->manufacturer_string != nullptr) ? current->manufacturer_string : L"Unknown");
|
||||
std::cout << "/";
|
||||
std::wcout << ((current->product_string != nullptr) ? current->product_string : L"Unknown");
|
||||
std::cout << "\" code: " << current->vendor_id << "/" << current->product_id << " (" << std::hex << current->vendor_id << "/" << current->product_id << std::dec << ")";
|
||||
std::cout << " serial number: '";
|
||||
std::wcout << ((current->serial_number != nullptr) ? current->serial_number : L"Unknown");
|
||||
std::cout << "' usage page: " << current->usage_page << " usage: " << current->usage << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
// Free enumerated devices
|
||||
hid_free_enumeration(devices);
|
||||
|
||||
if (detected_devices.empty())
|
||||
return false;
|
||||
|
||||
std::string path = "";
|
||||
unsigned short vendor_id = 0;
|
||||
unsigned short product_id = 0;
|
||||
|
||||
// Then we'll decide the choosing logic to apply in dependence of the device count and operating system
|
||||
|
||||
for (const DetectedDevices::value_type& device : detected_devices)
|
||||
{
|
||||
if (device.second.size() == 1)
|
||||
{
|
||||
#ifdef __linux__
|
||||
hid_device* test_device = hid_open(device.first.first, device.first.second, nullptr);
|
||||
if (test_device != nullptr)
|
||||
{
|
||||
hid_close(test_device);
|
||||
#else
|
||||
if (device.second.front().has_valid_usage())
|
||||
{
|
||||
#endif // __linux__
|
||||
vendor_id = device.first.first;
|
||||
product_id = device.first.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bool found = false;
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
for (const DeviceData& data : device.second)
|
||||
{
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << "Test device: " << std::hex << device.first.first << std::dec << "/" << std::hex << device.first.second << std::dec << " \"" << data.path << "\"";
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
#ifdef __linux__
|
||||
hid_device* test_device = hid_open_path(data.path.c_str());
|
||||
if (test_device != nullptr)
|
||||
{
|
||||
path = data.path;
|
||||
vendor_id = device.first.first;
|
||||
product_id = device.first.second;
|
||||
found = true;
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << "-> PASSED" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
hid_close(test_device);
|
||||
break;
|
||||
}
|
||||
#else
|
||||
if (data.has_valid_usage())
|
||||
{
|
||||
path = data.path;
|
||||
vendor_id = device.first.first;
|
||||
product_id = device.first.second;
|
||||
found = true;
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << "-> PASSED" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
break;
|
||||
}
|
||||
#endif // __linux__
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
else
|
||||
std::cout << "-> NOT PASSED" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
}
|
||||
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (path.empty())
|
||||
{
|
||||
if ((vendor_id != 0) && (product_id != 0))
|
||||
{
|
||||
// Open the 3Dconnexion device using vendor_id and product_id
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << std::endl << "Opening device: " << std::hex << vendor_id << std::dec << "/" << std::hex << product_id << std::dec << " using hid_open()" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
m_device = hid_open(vendor_id, product_id, nullptr);
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Open the 3Dconnexion device using the device path
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << std::endl << "Opening device: " << std::hex << vendor_id << std::dec << "/" << std::hex << product_id << std::dec << "\"" << path << "\" using hid_open_path()" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
m_device = hid_open_path(path.c_str());
|
||||
}
|
||||
|
||||
if (m_device != nullptr)
|
||||
{
|
||||
std::vector<wchar_t> manufacturer(1024, 0);
|
||||
hid_get_manufacturer_string(m_device, manufacturer.data(), 1024);
|
||||
m_device_str = boost::nowide::narrow(manufacturer.data());
|
||||
|
||||
std::vector<wchar_t> product(1024, 0);
|
||||
hid_get_product_string(m_device, product.data(), 1024);
|
||||
m_device_str += "/" + boost::nowide::narrow(product.data());
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Connected device: " << m_device_str;
|
||||
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << std::endl << "Connected device:" << std::endl;
|
||||
std::cout << "Manufacturer/product: " << m_device_str << std::endl;
|
||||
std::cout << "Manufacturer id.....: " << vendor_id << " (" << std::hex << vendor_id << std::dec << ")" << std::endl;
|
||||
std::cout << "Product id..........: " << product_id << " (" << std::hex << product_id << std::dec << ")" << std::endl;
|
||||
std::cout << "Path................: '" << path << "'" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
|
||||
// get device parameters from the config, if present
|
||||
double translation_speed = 1.0;
|
||||
float rotation_speed = 1.0;
|
||||
double translation_deadzone = State::DefaultTranslationDeadzone;
|
||||
float rotation_deadzone = State::DefaultRotationDeadzone;
|
||||
wxGetApp().app_config->get_mouse_device_translation_speed(m_device_str, translation_speed);
|
||||
wxGetApp().app_config->get_mouse_device_translation_deadzone(m_device_str, translation_deadzone);
|
||||
wxGetApp().app_config->get_mouse_device_rotation_speed(m_device_str, rotation_speed);
|
||||
wxGetApp().app_config->get_mouse_device_rotation_deadzone(m_device_str, rotation_deadzone);
|
||||
// clamp to valid values
|
||||
m_state.set_translation_scale(State::DefaultTranslationScale * std::max(0.5, std::min(2.0, translation_speed)));
|
||||
m_state.set_translation_deadzone(std::max(0.0, std::min(State::MaxTranslationDeadzone, translation_deadzone)));
|
||||
m_state.set_rotation_scale(State::DefaultRotationScale * std::max(0.5f, std::min(2.0f, rotation_speed)));
|
||||
m_state.set_rotation_deadzone(std::max(0.0f, std::min(State::MaxRotationDeadzone, rotation_deadzone)));
|
||||
}
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
else
|
||||
{
|
||||
std::cout << std::endl << "Unable to connect to device:" << std::endl;
|
||||
std::cout << "Manufacturer/product: " << m_device_str << std::endl;
|
||||
std::cout << "Manufacturer id.....: " << vendor_id << " (" << std::hex << vendor_id << std::dec << ")" << std::endl;
|
||||
std::cout << "Product id..........: " << product_id << " (" << std::hex << product_id << std::dec << ")" << std::endl;
|
||||
std::cout << "Path................: '" << path << "'" << std::endl;
|
||||
}
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
|
||||
return (m_device != nullptr);
|
||||
}
|
||||
|
||||
void Mouse3DController::disconnect_device()
|
||||
{
|
||||
if (!is_device_connected())
|
||||
return;
|
||||
|
||||
// Stop the secondary thread, if running
|
||||
if (m_thread.joinable())
|
||||
m_thread.join();
|
||||
|
||||
// Store current device parameters into the config
|
||||
wxGetApp().app_config->set_mouse_device(m_device_str, m_state.get_translation_scale() / State::DefaultTranslationScale, m_state.get_translation_deadzone(),
|
||||
m_state.get_rotation_scale() / State::DefaultRotationScale, m_state.get_rotation_deadzone());
|
||||
wxGetApp().app_config->save();
|
||||
|
||||
// Close the 3Dconnexion device
|
||||
hid_close(m_device);
|
||||
m_device = nullptr;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Disconnected device: " << m_device_str;
|
||||
|
||||
m_device_str = "";
|
||||
}
|
||||
|
||||
void Mouse3DController::start()
|
||||
{
|
||||
if (!is_device_connected() || m_running)
|
||||
return;
|
||||
|
||||
m_thread = std::thread(&Mouse3DController::run, this);
|
||||
}
|
||||
|
||||
void Mouse3DController::run()
|
||||
{
|
||||
m_running = true;
|
||||
while (m_running)
|
||||
{
|
||||
collect_input();
|
||||
}
|
||||
}
|
||||
|
||||
void Mouse3DController::collect_input()
|
||||
{
|
||||
DataPacket packet = { 0 };
|
||||
int res = hid_read_timeout(m_device, packet.data(), packet.size(), 100);
|
||||
if (res < 0)
|
||||
{
|
||||
// An error occourred (device detached from pc ?)
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wxGetApp().IsActive())
|
||||
return;
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
|
||||
bool updated = false;
|
||||
|
||||
if (res == 7)
|
||||
updated = handle_packet(packet);
|
||||
else if (res == 13)
|
||||
updated = handle_wireless_packet(packet);
|
||||
else if ((res == 3) && (packet[0] == 3))
|
||||
// On Mac button packets can be 3 bytes long
|
||||
updated = handle_packet(packet);
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
else if (res > 0)
|
||||
std::cout << "Got unknown data packet of length: " << res << ", code:" << (int)packet[0] << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
|
||||
if (updated)
|
||||
// ask for an idle event to update 3D scene
|
||||
wxWakeUpIdle();
|
||||
}
|
||||
|
||||
bool Mouse3DController::handle_packet(const DataPacket& packet)
|
||||
{
|
||||
switch (packet[0])
|
||||
{
|
||||
case 1: // Translation
|
||||
{
|
||||
if (handle_packet_translation(packet))
|
||||
return true;
|
||||
|
||||
break;
|
||||
}
|
||||
case 2: // Rotation
|
||||
{
|
||||
if (handle_packet_rotation(packet, 1))
|
||||
return true;
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: // Button
|
||||
{
|
||||
if (handle_packet_button(packet, packet.size() - 1))
|
||||
return true;
|
||||
|
||||
break;
|
||||
}
|
||||
case 23: // Battery charge
|
||||
{
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << m_device_str << " - battery level: " << (int)packet[1] << " percent" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << "Got unknown data packet of code: " << (int)packet[0] << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Mouse3DController::handle_wireless_packet(const DataPacket& packet)
|
||||
{
|
||||
switch (packet[0])
|
||||
{
|
||||
case 1: // Translation + Rotation
|
||||
{
|
||||
bool updated = handle_packet_translation(packet);
|
||||
updated |= handle_packet_rotation(packet, 7);
|
||||
|
||||
if (updated)
|
||||
return true;
|
||||
|
||||
break;
|
||||
}
|
||||
case 3: // Button
|
||||
{
|
||||
if (handle_packet_button(packet, 12))
|
||||
return true;
|
||||
|
||||
break;
|
||||
}
|
||||
case 23: // Battery charge
|
||||
{
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << m_device_str << " - battery level: " << (int)packet[1] << " percent" << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
std::cout << "Got unknown data packet of code: " << (int)packet[0] << std::endl;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
double convert_input(unsigned char first, unsigned char second, double deadzone)
|
||||
{
|
||||
short value = first | second << 8;
|
||||
double ret = (double)value / 350.0;
|
||||
return (std::abs(ret) > deadzone) ? ret : 0.0;
|
||||
}
|
||||
|
||||
bool Mouse3DController::handle_packet_translation(const DataPacket& packet)
|
||||
{
|
||||
double deadzone = m_state.get_translation_deadzone();
|
||||
Vec3d translation(-convert_input(packet[1], packet[2], deadzone),
|
||||
convert_input(packet[3], packet[4], deadzone),
|
||||
convert_input(packet[5], packet[6], deadzone));
|
||||
|
||||
if (!translation.isApprox(Vec3d::Zero()))
|
||||
{
|
||||
m_state.append_translation(translation);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Mouse3DController::handle_packet_rotation(const DataPacket& packet, unsigned int first_byte)
|
||||
{
|
||||
double deadzone = (double)m_state.get_rotation_deadzone();
|
||||
Vec3f rotation(-(float)convert_input(packet[first_byte + 0], packet[first_byte + 1], deadzone),
|
||||
(float)convert_input(packet[first_byte + 2], packet[first_byte + 3], deadzone),
|
||||
-(float)convert_input(packet[first_byte + 4], packet[first_byte + 5], deadzone));
|
||||
|
||||
if (!rotation.isApprox(Vec3f::Zero()))
|
||||
{
|
||||
m_state.append_rotation(rotation);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Mouse3DController::handle_packet_button(const DataPacket& packet, unsigned int packet_size)
|
||||
{
|
||||
unsigned int data = 0;
|
||||
for (unsigned int i = 1; i < packet_size; ++i)
|
||||
{
|
||||
data |= packet[i] << 8 * (i - 1);
|
||||
}
|
||||
|
||||
const std::bitset<32> data_bits{ data };
|
||||
for (size_t i = 0; i < data_bits.size(); ++i)
|
||||
{
|
||||
if (data_bits.test(i))
|
||||
{
|
||||
m_state.append_button((unsigned int)i);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
175
src/slic3r/GUI/Mouse3DController.hpp
Normal file
175
src/slic3r/GUI/Mouse3DController.hpp
Normal file
|
@ -0,0 +1,175 @@
|
|||
#ifndef slic3r_Mouse3DController_hpp_
|
||||
#define slic3r_Mouse3DController_hpp_
|
||||
|
||||
// Enabled debug output to console and extended imgui dialog
|
||||
#define ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT 0
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
#include "hidapi.h"
|
||||
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
struct Camera;
|
||||
|
||||
class Mouse3DController
|
||||
{
|
||||
class State
|
||||
{
|
||||
public:
|
||||
static const double DefaultTranslationScale;
|
||||
static const double MaxTranslationDeadzone;
|
||||
static const double DefaultTranslationDeadzone;
|
||||
static const float DefaultRotationScale;
|
||||
static const float MaxRotationDeadzone;
|
||||
static const float DefaultRotationDeadzone;
|
||||
|
||||
private:
|
||||
template <typename Number>
|
||||
struct CustomParameters
|
||||
{
|
||||
Number scale;
|
||||
Number deadzone;
|
||||
|
||||
CustomParameters(Number scale, Number deadzone) : scale(scale), deadzone(deadzone) {}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct InputQueue
|
||||
{
|
||||
size_t max_size;
|
||||
std::queue<T> queue;
|
||||
|
||||
// The default value of 5 for max_size seems to work fine on all platforms
|
||||
// The effects of changing this value can be tested by setting ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT to 1
|
||||
// and playing with the imgui dialog which shows by pressing CTRL+M
|
||||
InputQueue() : max_size(5) {}
|
||||
};
|
||||
|
||||
InputQueue<Vec3d> m_translation;
|
||||
InputQueue<Vec3f> m_rotation;
|
||||
std::queue<unsigned int> m_buttons;
|
||||
|
||||
bool m_buttons_enabled;
|
||||
|
||||
CustomParameters<double> m_translation_params;
|
||||
CustomParameters<float> m_rotation_params;
|
||||
|
||||
// When the 3Dconnexion driver is running the system gets, by default, mouse wheel events when rotations around the X axis are detected.
|
||||
// We want to filter these out because we are getting the data directly from the device, bypassing the driver, and those mouse wheel events interfere
|
||||
// by triggering unwanted zoom in/out of the scene
|
||||
// The following variable is used to count the potential mouse wheel events triggered and is updated by:
|
||||
// Mouse3DController::collect_input() through the call to the append_rotation() method
|
||||
// GLCanvas3D::on_mouse_wheel() through the call to the process_mouse_wheel() method
|
||||
// GLCanvas3D::on_idle() through the call to the apply() method
|
||||
unsigned int m_mouse_wheel_counter;
|
||||
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
size_t m_translation_queue_max_size;
|
||||
size_t m_rotation_queue_max_size;
|
||||
size_t m_buttons_queue_max_size;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
|
||||
public:
|
||||
State();
|
||||
|
||||
void append_translation(const Vec3d& translation);
|
||||
void append_rotation(const Vec3f& rotation);
|
||||
void append_button(unsigned int id);
|
||||
|
||||
bool has_translation() const { return !m_translation.queue.empty(); }
|
||||
bool has_rotation() const { return !m_rotation.queue.empty(); }
|
||||
bool has_button() const { return !m_buttons.empty(); }
|
||||
|
||||
bool process_mouse_wheel();
|
||||
|
||||
double get_translation_scale() const { return m_translation_params.scale; }
|
||||
void set_translation_scale(double scale) { m_translation_params.scale = scale; }
|
||||
|
||||
float get_rotation_scale() const { return m_rotation_params.scale; }
|
||||
void set_rotation_scale(float scale) { m_rotation_params.scale = scale; }
|
||||
|
||||
double get_translation_deadzone() const { return m_translation_params.deadzone; }
|
||||
void set_translation_deadzone(double deadzone) { m_translation_params.deadzone = deadzone; }
|
||||
|
||||
float get_rotation_deadzone() const { return m_rotation_params.deadzone; }
|
||||
void set_rotation_deadzone(float deadzone) { m_rotation_params.deadzone = deadzone; }
|
||||
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
Vec3d get_translation() const { return has_translation() ? m_translation.queue.front() : Vec3d::Zero(); }
|
||||
Vec3f get_rotation() const { return has_rotation() ? m_rotation.queue.front() : Vec3f::Zero(); }
|
||||
unsigned int get_button() const { return has_button() ? m_buttons.front() : 0; }
|
||||
|
||||
unsigned int get_translation_queue_size() const { return (unsigned int)m_translation.queue.size(); }
|
||||
unsigned int get_rotation_queue_size() const { return (unsigned int)m_rotation.queue.size(); }
|
||||
unsigned int get_buttons_queue_size() const { return (unsigned int)m_buttons.size(); }
|
||||
|
||||
size_t get_translation_queue_max_size() const { return m_translation_queue_max_size; }
|
||||
size_t get_rotation_queue_max_size() const { return m_rotation_queue_max_size; }
|
||||
size_t get_buttons_queue_max_size() const { return m_buttons_queue_max_size; }
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
|
||||
size_t get_queues_max_size() const { return m_translation.max_size; }
|
||||
void set_queues_max_size(size_t size);
|
||||
|
||||
// return true if any change to the camera took place
|
||||
bool apply(Camera& camera);
|
||||
};
|
||||
|
||||
bool m_initialized;
|
||||
mutable State m_state;
|
||||
std::mutex m_mutex;
|
||||
std::thread m_thread;
|
||||
hid_device* m_device;
|
||||
std::string m_device_str;
|
||||
bool m_running;
|
||||
bool m_settings_dialog;
|
||||
std::chrono::time_point<std::chrono::steady_clock> m_last_time;
|
||||
|
||||
public:
|
||||
Mouse3DController();
|
||||
|
||||
void init();
|
||||
void shutdown();
|
||||
|
||||
bool is_device_connected() const { return m_device != nullptr; }
|
||||
bool is_running() const { return m_running; }
|
||||
|
||||
bool process_mouse_wheel() { std::lock_guard<std::mutex> lock(m_mutex); return m_state.process_mouse_wheel(); }
|
||||
|
||||
bool apply(Camera& camera);
|
||||
|
||||
bool is_settings_dialog_shown() const { return m_settings_dialog; }
|
||||
void show_settings_dialog(bool show) { m_settings_dialog = show && is_running(); }
|
||||
void render_settings_dialog(unsigned int canvas_width, unsigned int canvas_height) const;
|
||||
|
||||
private:
|
||||
bool connect_device();
|
||||
void disconnect_device();
|
||||
void start();
|
||||
void stop() { m_running = false; }
|
||||
|
||||
// secondary thread methods
|
||||
void run();
|
||||
void collect_input();
|
||||
|
||||
typedef std::array<unsigned char, 13> DataPacket;
|
||||
bool handle_packet(const DataPacket& packet);
|
||||
bool handle_wireless_packet(const DataPacket& packet);
|
||||
bool handle_packet_translation(const DataPacket& packet);
|
||||
bool handle_packet_rotation(const DataPacket& packet, unsigned int first_byte);
|
||||
bool handle_packet_button(const DataPacket& packet, unsigned int packet_size);
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_Mouse3DController_hpp_
|
||||
|
|
@ -65,6 +65,7 @@
|
|||
#include "GUI_Preview.hpp"
|
||||
#include "3DBed.hpp"
|
||||
#include "Camera.hpp"
|
||||
#include "Mouse3DController.hpp"
|
||||
#include "Tab.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "BackgroundSlicingProcess.hpp"
|
||||
|
@ -1387,9 +1388,6 @@ struct Plater::priv
|
|||
Slic3r::Model model;
|
||||
PrinterTechnology printer_technology = ptFFF;
|
||||
Slic3r::GCodePreviewData gcode_preview_data;
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
std::vector<Slic3r::ThumbnailData> thumbnail_data;
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
// GUI elements
|
||||
wxSizer* panel_sizer{ nullptr };
|
||||
|
@ -1398,6 +1396,7 @@ struct Plater::priv
|
|||
Sidebar *sidebar;
|
||||
Bed3D bed;
|
||||
Camera camera;
|
||||
Mouse3DController mouse3d_controller;
|
||||
View3D* view3D;
|
||||
GLToolbar view_toolbar;
|
||||
Preview *preview;
|
||||
|
@ -1946,6 +1945,7 @@ struct Plater::priv
|
|||
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
void generate_thumbnail(ThumbnailData& data, unsigned int w, unsigned int h, bool printable_only, bool parts_only, bool transparent_background);
|
||||
void generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool transparent_background);
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
void msw_rescale_object_menu();
|
||||
|
@ -2016,7 +2016,15 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||
background_process.set_sla_print(&sla_print);
|
||||
background_process.set_gcode_preview_data(&gcode_preview_data);
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
background_process.set_thumbnail_data(&thumbnail_data);
|
||||
background_process.set_thumbnail_cb([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool transparent_background)
|
||||
{
|
||||
std::packaged_task<void(ThumbnailsList&, const Vec2ds&, bool, bool, bool)> task([this](ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool transparent_background) {
|
||||
generate_thumbnails(thumbnails, sizes, printable_only, parts_only, transparent_background);
|
||||
});
|
||||
std::future<void> result = task.get_future();
|
||||
wxTheApp->CallAfter([&]() { task(thumbnails, sizes, printable_only, parts_only, transparent_background); });
|
||||
result.wait();
|
||||
});
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED);
|
||||
background_process.set_finished_event(EVT_PROCESS_COMPLETED);
|
||||
|
@ -2087,6 +2095,11 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||
view3D_canvas->Bind(EVT_GLCANVAS_RESETGIZMOS, [this](SimpleEvent&) { reset_all_gizmos(); });
|
||||
view3D_canvas->Bind(EVT_GLCANVAS_UNDO, [this](SimpleEvent&) { this->undo(); });
|
||||
view3D_canvas->Bind(EVT_GLCANVAS_REDO, [this](SimpleEvent&) { this->redo(); });
|
||||
#if ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
view3D_canvas->Bind(EVT_GLCANVAS_RESET_LAYER_HEIGHT_PROFILE, [this](SimpleEvent&) { this->view3D->get_canvas3d()->reset_layer_height_profile(); });
|
||||
view3D_canvas->Bind(EVT_GLCANVAS_ADAPTIVE_LAYER_HEIGHT_PROFILE, [this](Event<float>& evt) { this->view3D->get_canvas3d()->adaptive_layer_height_profile(evt.data); });
|
||||
view3D_canvas->Bind(EVT_GLCANVAS_SMOOTH_LAYER_HEIGHT_PROFILE, [this](HeightProfileSmoothEvent& evt) { this->view3D->get_canvas3d()->smooth_layer_height_profile(evt.data); });
|
||||
#endif // ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
|
||||
|
||||
// 3DScene/Toolbar:
|
||||
view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this);
|
||||
|
@ -2136,12 +2149,16 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||
// updates camera type from .ini file
|
||||
camera.set_type(get_config("use_perspective_camera"));
|
||||
|
||||
mouse3d_controller.init();
|
||||
|
||||
// Initialize the Undo / Redo stack with a first snapshot.
|
||||
this->take_snapshot(_(L("New Project")));
|
||||
}
|
||||
|
||||
Plater::priv::~priv()
|
||||
{
|
||||
mouse3d_controller.shutdown();
|
||||
|
||||
if (config != nullptr)
|
||||
delete config;
|
||||
}
|
||||
|
@ -3062,37 +3079,6 @@ bool Plater::priv::restart_background_process(unsigned int state)
|
|||
( ((state & UPDATE_BACKGROUND_PROCESS_FORCE_RESTART) != 0 && ! this->background_process.finished()) ||
|
||||
(state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) != 0 ||
|
||||
(state & UPDATE_BACKGROUND_PROCESS_RESTART) != 0 ) ) {
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
if (((state & UPDATE_BACKGROUND_PROCESS_FORCE_EXPORT) == 0) &&
|
||||
(this->background_process.state() != BackgroundSlicingProcess::STATE_RUNNING))
|
||||
{
|
||||
// update thumbnail data
|
||||
const std::vector<Vec2d> &thumbnail_sizes = this->background_process.current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values;
|
||||
if (this->printer_technology == ptFFF)
|
||||
{
|
||||
// for ptFFF we need to generate the thumbnails before the export of gcode starts
|
||||
this->thumbnail_data.clear();
|
||||
for (const Vec2d &sized : thumbnail_sizes)
|
||||
{
|
||||
this->thumbnail_data.push_back(ThumbnailData());
|
||||
Point size(sized); // round to ints
|
||||
generate_thumbnail(this->thumbnail_data.back(), size.x(), size.y(), true, true, false);
|
||||
}
|
||||
}
|
||||
else if (this->printer_technology == ptSLA)
|
||||
{
|
||||
// for ptSLA generate thumbnails without supports and pad (not yet calculated)
|
||||
// to render also supports and pad see on_slicing_update()
|
||||
this->thumbnail_data.clear();
|
||||
for (const Vec2d &sized : thumbnail_sizes)
|
||||
{
|
||||
this->thumbnail_data.push_back(ThumbnailData());
|
||||
Point size(sized); // round to ints
|
||||
generate_thumbnail(this->thumbnail_data.back(), size.x(), size.y(), true, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
// The print is valid and it can be started.
|
||||
if (this->background_process.start()) {
|
||||
this->statusbar()->set_cancel_callback([this]() {
|
||||
|
@ -3339,6 +3325,7 @@ void Plater::priv::set_current_panel(wxPanel* panel)
|
|||
} else
|
||||
view3D->reload_scene(true);
|
||||
}
|
||||
|
||||
// sets the canvas as dirty to force a render at the 1st idle event (wxWidgets IsShownOnScreen() is buggy and cannot be used reliably)
|
||||
view3D->set_as_dirty();
|
||||
view_toolbar.select_item("3D");
|
||||
|
@ -3353,6 +3340,7 @@ void Plater::priv::set_current_panel(wxPanel* panel)
|
|||
this->q->reslice();
|
||||
// keeps current gcode preview, if any
|
||||
preview->reload_print(true);
|
||||
|
||||
preview->set_canvas_as_dirty();
|
||||
view_toolbar.select_item("Preview");
|
||||
}
|
||||
|
@ -3430,25 +3418,6 @@ void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
|
|||
} else if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SLA_PREVIEW) {
|
||||
// Update the SLA preview. Only called if not RELOAD_SLA_SUPPORT_POINTS, as the block above will refresh the preview anyways.
|
||||
this->preview->reload_print();
|
||||
|
||||
// uncomment the following lines if you want to render into the thumbnail also supports and pad for SLA printer
|
||||
/*
|
||||
#if ENABLE_THUMBNAIL_GENERATOR
|
||||
// update thumbnail data
|
||||
// for ptSLA generate the thumbnail after supports and pad have been calculated to have them rendered
|
||||
if ((this->printer_technology == ptSLA) && (evt.status.percent == -3))
|
||||
{
|
||||
const std::vector<Vec2d>& thumbnail_sizes = this->background_process.current_print()->full_print_config().option<ConfigOptionPoints>("thumbnails")->values;
|
||||
this->thumbnail_data.clear();
|
||||
for (const Vec2d &sized : thumbnail_sizes)
|
||||
{
|
||||
this->thumbnail_data.push_back(ThumbnailData());
|
||||
Point size(sized); // round to ints
|
||||
generate_thumbnail(this->thumbnail_data.back(), size.x(), size.y(), true, false, false);
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3679,6 +3648,19 @@ void Plater::priv::generate_thumbnail(ThumbnailData& data, unsigned int w, unsig
|
|||
{
|
||||
view3D->get_canvas3d()->render_thumbnail(data, w, h, printable_only, parts_only, transparent_background);
|
||||
}
|
||||
|
||||
void Plater::priv::generate_thumbnails(ThumbnailsList& thumbnails, const Vec2ds& sizes, bool printable_only, bool parts_only, bool transparent_background)
|
||||
{
|
||||
thumbnails.clear();
|
||||
for (const Vec2d& size : sizes)
|
||||
{
|
||||
thumbnails.push_back(ThumbnailData());
|
||||
Point isize(size); // round to ints
|
||||
generate_thumbnail(thumbnails.back(), isize.x(), isize.y(), printable_only, parts_only, transparent_background);
|
||||
if (!thumbnails.back().is_valid())
|
||||
thumbnails.pop_back();
|
||||
}
|
||||
}
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
|
||||
void Plater::priv::msw_rescale_object_menu()
|
||||
|
@ -5241,6 +5223,16 @@ const Camera& Plater::get_camera() const
|
|||
return p->camera;
|
||||
}
|
||||
|
||||
const Mouse3DController& Plater::get_mouse3d_controller() const
|
||||
{
|
||||
return p->mouse3d_controller;
|
||||
}
|
||||
|
||||
Mouse3DController& Plater::get_mouse3d_controller()
|
||||
{
|
||||
return p->mouse3d_controller;
|
||||
}
|
||||
|
||||
bool Plater::can_delete() const { return p->can_delete(); }
|
||||
bool Plater::can_delete_all() const { return p->can_delete_all(); }
|
||||
bool Plater::can_increase_instances() const { return p->can_increase_instances(); }
|
||||
|
|
|
@ -41,6 +41,7 @@ class ObjectSettings;
|
|||
class ObjectLayers;
|
||||
class ObjectList;
|
||||
class GLCanvas3D;
|
||||
class Mouse3DController;
|
||||
|
||||
using t_optgroups = std::vector <std::shared_ptr<ConfigOptionsGroup>>;
|
||||
|
||||
|
@ -260,6 +261,8 @@ public:
|
|||
void msw_rescale();
|
||||
|
||||
const Camera& get_camera() const;
|
||||
const Mouse3DController& get_mouse3d_controller() const;
|
||||
Mouse3DController& get_mouse3d_controller();
|
||||
|
||||
// ROII wrapper for suppressing the Undo / Redo snapshot to be taken.
|
||||
class SuppressSnapshots
|
||||
|
|
|
@ -263,6 +263,42 @@ SCENARIO("Path chaining", "[Geometry]") {
|
|||
}
|
||||
}
|
||||
}
|
||||
GIVEN("Gyroid infill end points") {
|
||||
Polylines polylines = {
|
||||
{ {28122608, 3221037}, {27919139, 56036027} },
|
||||
{ {33642863, 3400772}, {30875220, 56450360} },
|
||||
{ {34579315, 3599827}, {35049758, 55971572} },
|
||||
{ {26483070, 3374004}, {23971830, 55763598} },
|
||||
{ {38931405, 4678879}, {38740053, 55077714} },
|
||||
{ {20311895, 5015778}, {20079051, 54551952} },
|
||||
{ {16463068, 6773342}, {18823514, 53992958} },
|
||||
{ {44433771, 7424951}, {42629462, 53346059} },
|
||||
{ {15697614, 7329492}, {15350896, 52089991} },
|
||||
{ {48085792, 10147132}, {46435427, 50792118} },
|
||||
{ {48828819, 10972330}, {49126582, 48368374} },
|
||||
{ {9654526, 12656711}, {10264020, 47691584} },
|
||||
{ {5726905, 18648632}, {8070762, 45082416} },
|
||||
{ {54818187, 39579970}, {52974912, 43271272} },
|
||||
{ {4464342, 37371742}, {5027890, 39106220} },
|
||||
{ {54139746, 18417661}, {55177987, 38472580} },
|
||||
{ {56527590, 32058461}, {56316456, 34067185} },
|
||||
{ {3303988, 29215290}, {3569863, 32985633} },
|
||||
{ {56255666, 25025857}, {56478310, 27144087} },
|
||||
{ {4300034, 22805361}, {3667946, 25752601} },
|
||||
{ {8266122, 14250611}, {6244813, 17751595} },
|
||||
{ {12177955, 9886741}, {10703348, 11491900} }
|
||||
};
|
||||
Polylines chained = chain_polylines(polylines);
|
||||
THEN("Chained taking the shortest path") {
|
||||
double connection_length = 0.;
|
||||
for (size_t i = 1; i < chained.size(); ++i) {
|
||||
const Polyline &pl1 = chained[i - 1];
|
||||
const Polyline &pl2 = chained[i];
|
||||
connection_length += (pl2.first_point() - pl1.last_point()).cast<double>().norm();
|
||||
}
|
||||
REQUIRE(connection_length < 85206000.);
|
||||
}
|
||||
}
|
||||
GIVEN("Loop pieces") {
|
||||
Point a { 2185796, 19058485 };
|
||||
Point b { 3957902, 18149382 };
|
||||
|
|
Loading…
Reference in a new issue