mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 12:41:20 -06:00 
			
		
		
		
	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,10 +184,21 @@ 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) | ||||
| { | ||||
| 	for (uint32_t i_face = 0; i_face < stl->stats.number_of_facets; ++ i_face) { | ||||
|     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) { | ||||
| 			stl_vertex &v_dst = face.vertex[i_vertex]; | ||||
|  | @ -196,21 +207,18 @@ 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); | ||||
| 	for (size_t i = 0; i < stl->stats.number_of_facets; ++ i) { | ||||
|     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) | ||||
| 			f.vertex[j] = (t * f.vertex[j].template cast<T>()).template cast<float>().eval(); | ||||
|  | @ -223,12 +231,13 @@ 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) | ||||
| { | ||||
| 	for (size_t i = 0; i < stl->stats.number_of_facets; ++ i) { | ||||
|     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,10 +817,12 @@ void mark_boundary_segments_touching_infill( | |||
| 				Vec2d vperp(-v.y(), v.x()); | ||||
| 				Vec2d a = pt1 - v - vperp; | ||||
| 				Vec2d b = pt1 + v - vperp; | ||||
| 				grid.visit_cells_intersecting_line(a.cast<coord_t>(), b.cast<coord_t>(), visitor); | ||||
| 				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; | ||||
| 				grid.visit_cells_intersecting_line(a.cast<coord_t>(), b.cast<coord_t>(), visitor); | ||||
| 				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(); | ||||
|  | @ -812,46 +807,46 @@ void GCode::_do_export(Print &print, FILE *file) | |||
|     // shall be adjusted as well to produce a G-code block compatible with the particular firmware flavor.
 | ||||
|     if (print.config().gcode_flavor.value == gcfMarlin) { | ||||
|         m_normal_time_estimator.set_max_acceleration((float)print.config().machine_max_acceleration_extruding.values[0]); | ||||
| 		m_normal_time_estimator.set_retract_acceleration((float)print.config().machine_max_acceleration_retracting.values[0]); | ||||
| 		m_normal_time_estimator.set_minimum_feedrate((float)print.config().machine_min_extruding_rate.values[0]); | ||||
| 		m_normal_time_estimator.set_minimum_travel_feedrate((float)print.config().machine_min_travel_rate.values[0]); | ||||
| 		m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)print.config().machine_max_acceleration_x.values[0]); | ||||
| 		m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)print.config().machine_max_acceleration_y.values[0]); | ||||
| 		m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)print.config().machine_max_acceleration_z.values[0]); | ||||
| 		m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)print.config().machine_max_acceleration_e.values[0]); | ||||
| 		m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)print.config().machine_max_feedrate_x.values[0]); | ||||
| 		m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)print.config().machine_max_feedrate_y.values[0]); | ||||
| 		m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)print.config().machine_max_feedrate_z.values[0]); | ||||
| 		m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)print.config().machine_max_feedrate_e.values[0]); | ||||
| 		m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)print.config().machine_max_jerk_x.values[0]); | ||||
| 		m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)print.config().machine_max_jerk_y.values[0]); | ||||
| 		m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)print.config().machine_max_jerk_z.values[0]); | ||||
| 		m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)print.config().machine_max_jerk_e.values[0]); | ||||
|         m_normal_time_estimator.set_retract_acceleration((float)print.config().machine_max_acceleration_retracting.values[0]); | ||||
|         m_normal_time_estimator.set_minimum_feedrate((float)print.config().machine_min_extruding_rate.values[0]); | ||||
|         m_normal_time_estimator.set_minimum_travel_feedrate((float)print.config().machine_min_travel_rate.values[0]); | ||||
|         m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)print.config().machine_max_acceleration_x.values[0]); | ||||
|         m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)print.config().machine_max_acceleration_y.values[0]); | ||||
|         m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)print.config().machine_max_acceleration_z.values[0]); | ||||
|         m_normal_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)print.config().machine_max_acceleration_e.values[0]); | ||||
|         m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)print.config().machine_max_feedrate_x.values[0]); | ||||
|         m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)print.config().machine_max_feedrate_y.values[0]); | ||||
|         m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)print.config().machine_max_feedrate_z.values[0]); | ||||
|         m_normal_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)print.config().machine_max_feedrate_e.values[0]); | ||||
|         m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)print.config().machine_max_jerk_x.values[0]); | ||||
|         m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)print.config().machine_max_jerk_y.values[0]); | ||||
|         m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)print.config().machine_max_jerk_z.values[0]); | ||||
|         m_normal_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)print.config().machine_max_jerk_e.values[0]); | ||||
| 
 | ||||
|         if (m_silent_time_estimator_enabled) | ||||
|         { | ||||
|             m_silent_time_estimator.reset(); | ||||
|             m_silent_time_estimator.set_dialect(print.config().gcode_flavor); | ||||
|             /* "Stealth mode" values can be just a copy of "normal mode" values 
 | ||||
|             /* "Stealth mode" values can be just a copy of "normal mode" values
 | ||||
|             * (when they aren't input for a printer preset). | ||||
|             * Thus, use back value from values, instead of second one, which could be absent | ||||
|             */ | ||||
| 			m_silent_time_estimator.set_max_acceleration((float)print.config().machine_max_acceleration_extruding.values.back()); | ||||
| 			m_silent_time_estimator.set_retract_acceleration((float)print.config().machine_max_acceleration_retracting.values.back()); | ||||
| 			m_silent_time_estimator.set_minimum_feedrate((float)print.config().machine_min_extruding_rate.values.back()); | ||||
| 			m_silent_time_estimator.set_minimum_travel_feedrate((float)print.config().machine_min_travel_rate.values.back()); | ||||
| 			m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)print.config().machine_max_acceleration_x.values.back()); | ||||
| 			m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)print.config().machine_max_acceleration_y.values.back()); | ||||
| 			m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)print.config().machine_max_acceleration_z.values.back()); | ||||
| 			m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)print.config().machine_max_acceleration_e.values.back()); | ||||
| 			m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)print.config().machine_max_feedrate_x.values.back()); | ||||
| 			m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)print.config().machine_max_feedrate_y.values.back()); | ||||
| 			m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)print.config().machine_max_feedrate_z.values.back()); | ||||
| 			m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)print.config().machine_max_feedrate_e.values.back()); | ||||
| 			m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)print.config().machine_max_jerk_x.values.back()); | ||||
| 			m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)print.config().machine_max_jerk_y.values.back()); | ||||
| 			m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)print.config().machine_max_jerk_z.values.back()); | ||||
| 			m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)print.config().machine_max_jerk_e.values.back()); | ||||
|             m_silent_time_estimator.set_max_acceleration((float)print.config().machine_max_acceleration_extruding.values.back()); | ||||
|             m_silent_time_estimator.set_retract_acceleration((float)print.config().machine_max_acceleration_retracting.values.back()); | ||||
|             m_silent_time_estimator.set_minimum_feedrate((float)print.config().machine_min_extruding_rate.values.back()); | ||||
|             m_silent_time_estimator.set_minimum_travel_feedrate((float)print.config().machine_min_travel_rate.values.back()); | ||||
|             m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::X, (float)print.config().machine_max_acceleration_x.values.back()); | ||||
|             m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Y, (float)print.config().machine_max_acceleration_y.values.back()); | ||||
|             m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::Z, (float)print.config().machine_max_acceleration_z.values.back()); | ||||
|             m_silent_time_estimator.set_axis_max_acceleration(GCodeTimeEstimator::E, (float)print.config().machine_max_acceleration_e.values.back()); | ||||
|             m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::X, (float)print.config().machine_max_feedrate_x.values.back()); | ||||
|             m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Y, (float)print.config().machine_max_feedrate_y.values.back()); | ||||
|             m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::Z, (float)print.config().machine_max_feedrate_z.values.back()); | ||||
|             m_silent_time_estimator.set_axis_max_feedrate(GCodeTimeEstimator::E, (float)print.config().machine_max_feedrate_e.values.back()); | ||||
|             m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::X, (float)print.config().machine_max_jerk_x.values.back()); | ||||
|             m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Y, (float)print.config().machine_max_jerk_y.values.back()); | ||||
|             m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::Z, (float)print.config().machine_max_jerk_z.values.back()); | ||||
|             m_silent_time_estimator.set_axis_max_jerk(GCodeTimeEstimator::E, (float)print.config().machine_max_jerk_e.values.back()); | ||||
|             if (print.config().single_extruder_multi_material) { | ||||
|                 // As of now the fields are shown at the UI dialog in the same combo box as the ramming values, so they
 | ||||
|                 // are considered to be active for the single extruder multi-material printers only.
 | ||||
|  | @ -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()) { | ||||
|  | @ -927,7 +923,7 @@ void GCode::_do_export(Print &print, FILE *file) | |||
|     m_enable_cooling_markers = true; | ||||
|     this->apply_print_config(print.config()); | ||||
|     this->set_extruders(print.extruders()); | ||||
|      | ||||
| 
 | ||||
|     // Initialize colorprint.
 | ||||
|     m_colorprint_heights = cast<float>(print.config().colorprint_heights.values); | ||||
| 
 | ||||
|  | @ -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 
 | ||||
|  | @ -973,7 +969,7 @@ void GCode::_do_export(Print &print, FILE *file) | |||
|         } | ||||
|     } | ||||
|     print.throw_if_canceled(); | ||||
|      | ||||
| 
 | ||||
|     m_cooling_buffer = make_unique<CoolingBuffer>(*this); | ||||
|     if (print.config().spiral_vase.value) | ||||
|         m_spiral_vase = make_unique<SpiralVase>(print.config()); | ||||
|  | @ -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 | ||||
| 	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()); | ||||
| #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()); | ||||
| 	} | ||||
| 	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 | ||||
| 	// Mark as removed from the queue.
 | ||||
| 	m_index_setter(m_heap.front(), std::numeric_limits<size_t>::max()); | ||||
| #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()); | ||||
| 	} | ||||
| 	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 | ||||
| 	// Mark as removed from the queue.
 | ||||
| 	m_index_setter(m_heap[idx], std::numeric_limits<size_t>::max()); | ||||
| #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()); | ||||
| 	} | ||||
| 	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
 | ||||
|  |  | |||
|  | @ -1522,9 +1522,9 @@ bool PrintObject::update_layer_height_profile(const ModelObject &model_object, c | |||
|         layer_height_profile.clear(); | ||||
| 
 | ||||
|     if (layer_height_profile.empty()) { | ||||
|             //layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_config_ranges, model_object.volumes);
 | ||||
|             layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_config_ranges); | ||||
|        	updated = true; | ||||
|         //layer_height_profile = layer_height_profile_adaptive(slicing_parameters, model_object.layer_config_ranges, model_object.volumes);
 | ||||
|         layer_height_profile = layer_height_profile_from_ranges(slicing_parameters, model_object.layer_config_ranges); | ||||
|         updated = true; | ||||
|     } | ||||
|     return updated; | ||||
| } | ||||
|  |  | |||
|  | @ -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; | ||||
| } | ||||
| 
 | ||||
| #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.
 | ||||
| // #define DEBUG_SVG_OUTPUT
 | ||||
| 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); | ||||
| 			assert(z >= lo && z <= hi); | ||||
| #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_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,21 +27,42 @@ std::pair<float, float> face_z_span(const stl_facet *f) | |||
| 
 | ||||
| void SlicingAdaptive::prepare() | ||||
| { | ||||
| 	// 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) | ||||
| #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) | ||||
| 			m_faces.emplace_back(&face); | ||||
|     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) | ||||
|             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) { | ||||
| 		std::pair<float, float> span1 = face_z_span(f1); | ||||
|         std::pair<float, float> span1 = face_z_span(f1); | ||||
| 		std::pair<float, float> span2 = face_z_span(f2); | ||||
| 		return span1 < span2; | ||||
| 	}); | ||||
|         return span1 < span2; | ||||
|     }); | ||||
| 
 | ||||
| 	// 3) Generate Z components of the facet normals.
 | ||||
| 	m_face_normal_z.assign(m_faces.size(), 0.f); | ||||
|  | @ -45,14 +72,14 @@ 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
 | ||||
| 	int ordered_id = current_facet; | ||||
| 	for (; ordered_id < int(m_faces.size()); ++ ordered_id) { | ||||
| 		std::pair<float, float> zspan = face_z_span(m_faces[ordered_id]); | ||||
| 		// facet's minimum is higher than slice_z -> end loop
 | ||||
|         std::pair<float, float> zspan = face_z_span(m_faces[ordered_id]); | ||||
|         // facet's minimum is higher than slice_z -> end loop
 | ||||
| 		if (zspan.first >= z) | ||||
| 			break; | ||||
| 		// facet's maximum is higher than slice_z -> store the first event for next cusp_height call to begin at this point
 | ||||
|  | @ -77,8 +104,8 @@ float SlicingAdaptive::cusp_height(float z, float cusp_value, int ¤t_facet | |||
| 	// check for sloped facets inside the determined layer and correct height if necessary
 | ||||
| 	if (height > m_slicing_params.min_layer_height) { | ||||
| 		for (; ordered_id < int(m_faces.size()); ++ ordered_id) { | ||||
| 			std::pair<float, float> zspan = face_z_span(m_faces[ordered_id]); | ||||
| 			// facet's minimum is higher than slice_z + height -> end loop
 | ||||
|             std::pair<float, float> zspan = face_z_span(m_faces[ordered_id]); | ||||
|             // facet's minimum is higher than slice_z + height -> end loop
 | ||||
| 			if (zspan.first >= z + height) | ||||
| 				break; | ||||
| 
 | ||||
|  | @ -117,24 +144,25 @@ 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) | ||||
| { | ||||
| 	for (size_t i = 0; i < m_faces.size(); ++ i) { | ||||
| 		std::pair<float, float> zspan = face_z_span(m_faces[i]); | ||||
| 		// facet's minimum is higher than max forward distance -> end loop
 | ||||
|         std::pair<float, float> zspan = face_z_span(m_faces[i]); | ||||
|         // facet's minimum is higher than max forward distance -> end loop
 | ||||
| 		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,29 +5,49 @@ | |||
| 
 | ||||
| #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: | ||||
| 	void clear(); | ||||
| 	void set_slicing_parameters(SlicingParameters params) { m_slicing_params = params; } | ||||
| 	void add_mesh(const TriangleMesh *mesh) { m_meshes.push_back(mesh); } | ||||
| 	void prepare(); | ||||
| #if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|     void clear(); | ||||
| #endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
|     void set_slicing_parameters(SlicingParameters params) { m_slicing_params = params; } | ||||
| #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); | ||||
| 	float horizontal_facet_distance(float z); | ||||
| #if !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE | ||||
|     float horizontal_facet_distance(float z); | ||||
| #endif // !ENABLE_ADAPTIVE_LAYER_HEIGHT_PROFILE
 | ||||
| 
 | ||||
| protected: | ||||
| 	SlicingParameters 					m_slicing_params; | ||||
| 
 | ||||
| 	std::vector<const TriangleMesh*>	m_meshes; | ||||
| 	// Collected faces of all meshes, sorted by raising Z of the bottom most face.
 | ||||
| #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.
 | ||||
|     // Z component of face normals, normalized.
 | ||||
| 	std::vector<float>					m_face_normal_z; | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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,17 +1144,7 @@ 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); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 YuSanka
						YuSanka