Merge branch 'lm_tm_hollowing'
This commit is contained in:
commit
ef30270048
115 changed files with 17393 additions and 2919 deletions
|
@ -104,7 +104,7 @@ list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules/)
|
|||
|
||||
enable_testing ()
|
||||
|
||||
# Enable C++11 language standard.
|
||||
# Enable C++17 language standard.
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
|
@ -426,6 +426,12 @@ if(SLIC3R_STATIC)
|
|||
set(USE_BLOSC TRUE)
|
||||
endif()
|
||||
|
||||
find_package(OpenVDB 5.0 REQUIRED COMPONENTS openvdb)
|
||||
if(OpenVDB_FOUND)
|
||||
slic3r_remap_configs(IlmBase::Half RelWithDebInfo Release)
|
||||
slic3r_remap_configs(Blosc::blosc RelWithDebInfo Release)
|
||||
endif()
|
||||
|
||||
set(TOP_LEVEL_PROJECT_DIR ${PROJECT_SOURCE_DIR})
|
||||
function(prusaslicer_copy_dlls target)
|
||||
if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
|
||||
|
@ -449,8 +455,6 @@ function(prusaslicer_copy_dlls target)
|
|||
|
||||
endfunction()
|
||||
|
||||
#find_package(OpenVDB 5.0 COMPONENTS openvdb)
|
||||
#slic3r_remap_configs(IlmBase::Half RelWithDebInfo Release)
|
||||
|
||||
# libslic3r, PrusaSlicer GUI and the PrusaSlicer executable.
|
||||
add_subdirectory(src)
|
||||
|
|
106
cmake/modules/CheckAtomic.cmake
Normal file
106
cmake/modules/CheckAtomic.cmake
Normal file
|
@ -0,0 +1,106 @@
|
|||
# atomic builtins are required for threading support.
|
||||
|
||||
INCLUDE(CheckCXXSourceCompiles)
|
||||
INCLUDE(CheckLibraryExists)
|
||||
|
||||
# Sometimes linking against libatomic is required for atomic ops, if
|
||||
# the platform doesn't support lock-free atomics.
|
||||
|
||||
function(check_working_cxx_atomics varname)
|
||||
set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
|
||||
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++11")
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#include <atomic>
|
||||
std::atomic<int> x;
|
||||
int main() {
|
||||
return x;
|
||||
}
|
||||
" ${varname})
|
||||
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||
endfunction(check_working_cxx_atomics)
|
||||
|
||||
function(check_working_cxx_atomics64 varname)
|
||||
set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
|
||||
set(CMAKE_REQUIRED_FLAGS "-std=c++11 ${CMAKE_REQUIRED_FLAGS}")
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
std::atomic<uint64_t> x (0);
|
||||
int main() {
|
||||
uint64_t i = x.load(std::memory_order_relaxed);
|
||||
return 0;
|
||||
}
|
||||
" ${varname})
|
||||
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||
endfunction(check_working_cxx_atomics64)
|
||||
|
||||
|
||||
# This isn't necessary on MSVC, so avoid command-line switch annoyance
|
||||
# by only running on GCC-like hosts.
|
||||
if (LLVM_COMPILER_IS_GCC_COMPATIBLE)
|
||||
# First check if atomics work without the library.
|
||||
check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITHOUT_LIB)
|
||||
# If not, check if the library exists, and atomics work with it.
|
||||
if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB)
|
||||
check_library_exists(atomic __atomic_fetch_add_4 "" HAVE_LIBATOMIC)
|
||||
if( HAVE_LIBATOMIC )
|
||||
list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic")
|
||||
check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITH_LIB)
|
||||
if (NOT HAVE_CXX_ATOMICS_WITH_LIB)
|
||||
message(FATAL_ERROR "Host compiler must support std::atomic!")
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "Host compiler appears to require libatomic, but cannot find it.")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Check for 64 bit atomic operations.
|
||||
if(MSVC)
|
||||
set(HAVE_CXX_ATOMICS64_WITHOUT_LIB True)
|
||||
else()
|
||||
check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITHOUT_LIB)
|
||||
endif()
|
||||
|
||||
# If not, check if the library exists, and atomics work with it.
|
||||
if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB)
|
||||
check_library_exists(atomic __atomic_load_8 "" HAVE_CXX_LIBATOMICS64)
|
||||
if(HAVE_CXX_LIBATOMICS64)
|
||||
list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic")
|
||||
check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB)
|
||||
if (NOT HAVE_CXX_ATOMICS64_WITH_LIB)
|
||||
message(FATAL_ERROR "Host compiler must support 64-bit std::atomic!")
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "Host compiler appears to require libatomic for 64-bit operations, but cannot find it.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
## TODO: This define is only used for the legacy atomic operations in
|
||||
## llvm's Atomic.h, which should be replaced. Other code simply
|
||||
## assumes C++11 <atomic> works.
|
||||
CHECK_CXX_SOURCE_COMPILES("
|
||||
#ifdef _MSC_VER
|
||||
#include <windows.h>
|
||||
#endif
|
||||
int main() {
|
||||
#ifdef _MSC_VER
|
||||
volatile LONG val = 1;
|
||||
MemoryBarrier();
|
||||
InterlockedCompareExchange(&val, 0, 1);
|
||||
InterlockedIncrement(&val);
|
||||
InterlockedDecrement(&val);
|
||||
#else
|
||||
volatile unsigned long val = 1;
|
||||
__sync_synchronize();
|
||||
__sync_val_compare_and_swap(&val, 1, 0);
|
||||
__sync_add_and_fetch(&val, 1);
|
||||
__sync_sub_and_fetch(&val, 1);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
" LLVM_HAS_ATOMICS)
|
||||
|
||||
if( NOT LLVM_HAS_ATOMICS )
|
||||
message(STATUS "Warning: LLVM will be built thread-unsafe because atomic builtins are missing")
|
||||
endif()
|
|
@ -108,6 +108,18 @@ if(POLICY CMP0074)
|
|||
cmake_policy(SET CMP0074 NEW)
|
||||
endif()
|
||||
|
||||
if(OpenVDB_FIND_QUIETLY)
|
||||
set (_quiet "QUIET")
|
||||
else()
|
||||
set (_quiet "")
|
||||
endif()
|
||||
|
||||
if(OpenVDB_FIND_REQUIRED)
|
||||
set (_required "REQUIRED")
|
||||
else()
|
||||
set (_required "")
|
||||
endif()
|
||||
|
||||
# Include utility functions for version information
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/OpenVDBUtils.cmake)
|
||||
|
||||
|
@ -146,7 +158,7 @@ set(_OPENVDB_ROOT_SEARCH_DIR "")
|
|||
|
||||
# Additionally try and use pkconfig to find OpenVDB
|
||||
|
||||
find_package(PkgConfig)
|
||||
find_package(PkgConfig ${_quiet} )
|
||||
pkg_check_modules(PC_OpenVDB QUIET OpenVDB)
|
||||
|
||||
# ------------------------------------------------------------------------
|
||||
|
@ -230,12 +242,14 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
|
|||
else()
|
||||
set(OpenVDB_${COMPONENT}_FOUND FALSE)
|
||||
endif()
|
||||
|
||||
set(OpenVDB_${COMPONENT}_LIBRARY ${OpenVDB_${COMPONENT}_LIBRARY_RELEASE})
|
||||
else ()
|
||||
string(TOUPPER "${CMAKE_BUILD_TYPE}" _BUILD_TYPE)
|
||||
|
||||
set(OpenVDB_${COMPONENT}_LIBRARY ${OpenVDB_${COMPONENT}_LIBRARY_${_BUILD_TYPE}})
|
||||
|
||||
if (NOT MSVC AND NOT OpenVDB_${COMPONENT}_LIBRARY)
|
||||
if (NOT OpenVDB_${COMPONENT}_LIBRARY)
|
||||
set(OpenVDB_${COMPONENT}_LIBRARY ${OpenVDB_${COMPONENT}_LIBRARY_RELEASE})
|
||||
endif ()
|
||||
|
||||
|
@ -247,6 +261,7 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
|
|||
set(OpenVDB_${COMPONENT}_FOUND FALSE)
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
endforeach()
|
||||
|
||||
if(UNIX AND OPENVDB_USE_STATIC_LIBS)
|
||||
|
@ -280,7 +295,7 @@ OPENVDB_ABI_VERSION_FROM_PRINT(
|
|||
ABI OpenVDB_ABI
|
||||
)
|
||||
|
||||
if(NOT OpenVDB_FIND_QUIET)
|
||||
if(NOT OpenVDB_FIND_QUIETLY)
|
||||
if(NOT OpenVDB_ABI)
|
||||
message(WARNING "Unable to determine OpenVDB ABI version from OpenVDB "
|
||||
"installation. The library major version \"${OpenVDB_MAJOR_VERSION}\" "
|
||||
|
@ -298,7 +313,17 @@ endif()
|
|||
|
||||
# Add standard dependencies
|
||||
|
||||
find_package(IlmBase COMPONENTS Half)
|
||||
macro(just_fail msg)
|
||||
set(OpenVDB_FOUND FALSE)
|
||||
if(OpenVDB_FIND_REQUIRED)
|
||||
message(FATAL_ERROR ${msg})
|
||||
elseif(NOT OpenVDB_FIND_QUIETLY)
|
||||
message(WARNING ${msg})
|
||||
endif()
|
||||
return()
|
||||
endmacro()
|
||||
|
||||
find_package(IlmBase QUIET COMPONENTS Half)
|
||||
if(NOT IlmBase_FOUND)
|
||||
pkg_check_modules(IlmBase QUIET IlmBase)
|
||||
endif()
|
||||
|
@ -306,20 +331,20 @@ if (IlmBase_FOUND AND NOT TARGET IlmBase::Half)
|
|||
message(STATUS "Falling back to IlmBase found by pkg-config...")
|
||||
|
||||
find_library(IlmHalf_LIBRARY NAMES Half)
|
||||
if(IlmHalf_LIBRARY-NOTFOUND)
|
||||
message(FATAL_ERROR "IlmBase::Half can not be found!")
|
||||
if(IlmHalf_LIBRARY-NOTFOUND OR NOT IlmBase_INCLUDE_DIRS)
|
||||
just_fail("IlmBase::Half can not be found!")
|
||||
endif()
|
||||
|
||||
add_library(IlmBase::Half UNKNOWN IMPORTED)
|
||||
set_target_properties(IlmBase::Half PROPERTIES
|
||||
IMPORTED_LOCATION "${IlmHalf_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${IlmBase_INCLUDE_DIRS})
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${IlmBase_INCLUDE_DIRS}")
|
||||
elseif(NOT IlmBase_FOUND)
|
||||
message(FATAL_ERROR "IlmBase::Half can not be found!")
|
||||
just_fail("IlmBase::Half can not be found!")
|
||||
endif()
|
||||
find_package(TBB REQUIRED COMPONENTS tbb)
|
||||
find_package(ZLIB REQUIRED)
|
||||
find_package(Boost REQUIRED COMPONENTS iostreams system)
|
||||
find_package(TBB ${_quiet} ${_required} COMPONENTS tbb)
|
||||
find_package(ZLIB ${_quiet} ${_required})
|
||||
find_package(Boost ${_quiet} ${_required} COMPONENTS iostreams system )
|
||||
|
||||
# Use GetPrerequisites to see which libraries this OpenVDB lib has linked to
|
||||
# which we can query for optional deps. This basically runs ldd/otoll/objdump
|
||||
|
@ -380,7 +405,7 @@ unset(_OPENVDB_PREREQUISITE_LIST)
|
|||
unset(_HAS_DEP)
|
||||
|
||||
if(OpenVDB_USES_BLOSC)
|
||||
find_package(Blosc )
|
||||
find_package(Blosc QUIET)
|
||||
if(NOT Blosc_FOUND OR NOT TARGET Blosc::blosc)
|
||||
message(STATUS "find_package could not find Blosc. Using fallback blosc search...")
|
||||
find_path(Blosc_INCLUDE_DIR blosc.h)
|
||||
|
@ -392,25 +417,25 @@ if(OpenVDB_USES_BLOSC)
|
|||
IMPORTED_LOCATION "${Blosc_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES ${Blosc_INCLUDE_DIR})
|
||||
elseif()
|
||||
message(FATAL_ERROR "Blosc library can not be found!")
|
||||
just_fail("Blosc library can not be found!")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(OpenVDB_USES_LOG4CPLUS)
|
||||
find_package(Log4cplus REQUIRED)
|
||||
find_package(Log4cplus ${_quiet} ${_required})
|
||||
endif()
|
||||
|
||||
if(OpenVDB_USES_ILM)
|
||||
find_package(IlmBase REQUIRED)
|
||||
find_package(IlmBase ${_quiet} ${_required})
|
||||
endif()
|
||||
|
||||
if(OpenVDB_USES_EXR)
|
||||
find_package(OpenEXR REQUIRED)
|
||||
find_package(OpenEXR ${_quiet} ${_required})
|
||||
endif()
|
||||
|
||||
if(UNIX)
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(Threads ${_quiet} ${_required})
|
||||
endif()
|
||||
|
||||
# Set deps. Note that the order here is important. If we're building against
|
||||
|
@ -500,18 +525,15 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
|
|||
IMPORTED_LINK_DEPENDENT_LIBRARIES "${_OPENVDB_HIDDEN_DEPENDENCIES}" # non visible deps
|
||||
INTERFACE_LINK_LIBRARIES "${_OPENVDB_VISIBLE_DEPENDENCIES}" # visible deps (headers)
|
||||
INTERFACE_COMPILE_FEATURES cxx_std_11
|
||||
IMPORTED_LOCATION "${OpenVDB_${COMPONENT}_LIBRARY}"
|
||||
)
|
||||
|
||||
if (_is_multi)
|
||||
set_target_properties(OpenVDB::${COMPONENT} PROPERTIES
|
||||
IMPORTED_LOCATION_RELEASE "${OpenVDB_${COMPONENT}_LIBRARY_RELEASE}"
|
||||
IMPORTED_LOCATION_DEBUG "${OpenVDB_${COMPONENT}_LIBRARY_DEBUG}"
|
||||
)
|
||||
else ()
|
||||
set_target_properties(OpenVDB::${COMPONENT} PROPERTIES
|
||||
IMPORTED_LOCATION "${OpenVDB_${COMPONENT}_LIBRARY}"
|
||||
)
|
||||
endif ()
|
||||
if (_is_multi)
|
||||
set_target_properties(OpenVDB::${COMPONENT} PROPERTIES
|
||||
IMPORTED_LOCATION_RELEASE "${OpenVDB_${COMPONENT}_LIBRARY_RELEASE}"
|
||||
IMPORTED_LOCATION_DEBUG "${OpenVDB_${COMPONENT}_LIBRARY_DEBUG}"
|
||||
)
|
||||
endif ()
|
||||
|
||||
if (OPENVDB_USE_STATIC_LIBS)
|
||||
set_target_properties(OpenVDB::${COMPONENT} PROPERTIES
|
||||
|
@ -521,7 +543,7 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
|
|||
endif()
|
||||
endforeach()
|
||||
|
||||
if(OpenVDB_FOUND AND NOT ${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY)
|
||||
if(OpenVDB_FOUND AND NOT OpenVDB_FIND_QUIETLY)
|
||||
message(STATUS "OpenVDB libraries: ${OpenVDB_LIBRARIES}")
|
||||
endif()
|
||||
|
||||
|
|
|
@ -125,7 +125,9 @@ function(OPENVDB_ABI_VERSION_FROM_PRINT OPENVDB_PRINT)
|
|||
cmake_parse_arguments(_VDB "QUIET" "ABI" "" ${ARGN})
|
||||
|
||||
if(NOT EXISTS ${OPENVDB_PRINT})
|
||||
message(WARNING "vdb_print not found! ${OPENVDB_PRINT}")
|
||||
if(NOT OpenVDB_FIND_QUIETLY)
|
||||
message(WARNING "vdb_print not found! ${OPENVDB_PRINT}")
|
||||
endif()
|
||||
return()
|
||||
endif()
|
||||
|
||||
|
@ -148,7 +150,9 @@ function(OPENVDB_ABI_VERSION_FROM_PRINT OPENVDB_PRINT)
|
|||
endif()
|
||||
|
||||
if(${_VDB_PRINT_RETURN_STATUS})
|
||||
message(WARNING "vdb_print returned with status ${_VDB_PRINT_RETURN_STATUS}")
|
||||
if(NOT OpenVDB_FIND_QUIETLY)
|
||||
message(WARNING "vdb_print returned with status ${_VDB_PRINT_RETURN_STATUS}")
|
||||
endif()
|
||||
return()
|
||||
endif()
|
||||
|
||||
|
|
2
deps/CGAL/CGAL.cmake
vendored
2
deps/CGAL/CGAL.cmake
vendored
|
@ -17,4 +17,4 @@ ExternalProject_Add_Step(dep_CGAL dep_CGAL_relocation_fix
|
|||
DEPENDEES install
|
||||
COMMAND ${CMAKE_COMMAND} -E remove CGALConfig-installation-dirs.cmake
|
||||
WORKING_DIRECTORY "${DESTDIR}/usr/local/${CMAKE_INSTALL_LIBDIR}/cmake/CGAL"
|
||||
)
|
||||
)
|
||||
|
|
3
deps/GMP/GMP.cmake
vendored
3
deps/GMP/GMP.cmake
vendored
|
@ -18,7 +18,8 @@ if (MSVC)
|
|||
|
||||
else ()
|
||||
ExternalProject_Add(dep_GMP
|
||||
URL https://gmplib.org/download/gmp/gmp-6.1.2.tar.bz2
|
||||
# URL https://gmplib.org/download/gmp/gmp-6.1.2.tar.bz2
|
||||
URL https://gmplib.org/download/gmp/gmp-6.2.0.tar.lz
|
||||
BUILD_IN_SOURCE ON
|
||||
CONFIGURE_COMMAND ./configure --enable-shared=no --enable-cxx=yes --enable-static=yes "--prefix=${DESTDIR}/usr/local" --with-pic
|
||||
BUILD_COMMAND make -j
|
||||
|
|
657
deps/openvdb-mods.patch
vendored
657
deps/openvdb-mods.patch
vendored
|
@ -1,24 +1,25 @@
|
|||
From dbe038fce8a15ddc9a5c83ec5156d7bc9e178015 Mon Sep 17 00:00:00 2001
|
||||
From d359098d9989ac7dbd149611d6ac941529fb4157 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
|
||||
Date: Thu, 23 Jan 2020 17:17:36 +0100
|
||||
Subject: [PATCH] openvdb-mods
|
||||
|
||||
Signed-off-by: tamasmeszaros <meszaros.q@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 3 -
|
||||
cmake/FindBlosc.cmake | 218 ---------------
|
||||
cmake/CheckAtomic.cmake | 106 ++++++
|
||||
cmake/FindBlosc.cmake | 218 ------------
|
||||
cmake/FindCppUnit.cmake | 4 +-
|
||||
cmake/FindIlmBase.cmake | 337 ----------------------
|
||||
cmake/FindOpenEXR.cmake | 329 ----------------------
|
||||
cmake/FindIlmBase.cmake | 337 ------------------
|
||||
cmake/FindOpenEXR.cmake | 329 ------------------
|
||||
cmake/FindOpenVDB.cmake | 19 +-
|
||||
cmake/FindTBB.cmake | 605 ++++++++++++++++++++--------------------
|
||||
openvdb/CMakeLists.txt | 13 +-
|
||||
cmake/FindTBB.cmake | 599 ++++++++++++++++----------------
|
||||
openvdb/CMakeLists.txt | 16 +-
|
||||
openvdb/Grid.cc | 3 +
|
||||
openvdb/PlatformConfig.h | 9 +-
|
||||
openvdb/cmd/CMakeLists.txt | 4 +-
|
||||
openvdb/unittest/CMakeLists.txt | 3 +-
|
||||
openvdb/unittest/TestFile.cc | 2 +-
|
||||
13 files changed, 336 insertions(+), 1213 deletions(-)
|
||||
14 files changed, 442 insertions(+), 1210 deletions(-)
|
||||
create mode 100644 cmake/CheckAtomic.cmake
|
||||
delete mode 100644 cmake/FindBlosc.cmake
|
||||
delete mode 100644 cmake/FindIlmBase.cmake
|
||||
delete mode 100644 cmake/FindOpenEXR.cmake
|
||||
|
@ -40,6 +41,119 @@ index 580b353..6d364c1 100644
|
|||
cmake/FindOpenVDB.cmake
|
||||
cmake/FindTBB.cmake
|
||||
cmake/OpenVDBGLFW3Setup.cmake
|
||||
diff --git a/cmake/CheckAtomic.cmake b/cmake/CheckAtomic.cmake
|
||||
new file mode 100644
|
||||
index 0000000..c045e30
|
||||
--- /dev/null
|
||||
+++ b/cmake/CheckAtomic.cmake
|
||||
@@ -0,0 +1,106 @@
|
||||
+# atomic builtins are required for threading support.
|
||||
+
|
||||
+INCLUDE(CheckCXXSourceCompiles)
|
||||
+INCLUDE(CheckLibraryExists)
|
||||
+
|
||||
+# Sometimes linking against libatomic is required for atomic ops, if
|
||||
+# the platform doesn't support lock-free atomics.
|
||||
+
|
||||
+function(check_working_cxx_atomics varname)
|
||||
+ set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
|
||||
+ set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++11")
|
||||
+ CHECK_CXX_SOURCE_COMPILES("
|
||||
+#include <atomic>
|
||||
+std::atomic<int> x;
|
||||
+int main() {
|
||||
+ return x;
|
||||
+}
|
||||
+" ${varname})
|
||||
+ set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||
+endfunction(check_working_cxx_atomics)
|
||||
+
|
||||
+function(check_working_cxx_atomics64 varname)
|
||||
+ set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
|
||||
+ set(CMAKE_REQUIRED_FLAGS "-std=c++11 ${CMAKE_REQUIRED_FLAGS}")
|
||||
+ CHECK_CXX_SOURCE_COMPILES("
|
||||
+#include <atomic>
|
||||
+#include <cstdint>
|
||||
+std::atomic<uint64_t> x (0);
|
||||
+int main() {
|
||||
+ uint64_t i = x.load(std::memory_order_relaxed);
|
||||
+ return 0;
|
||||
+}
|
||||
+" ${varname})
|
||||
+ set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||
+endfunction(check_working_cxx_atomics64)
|
||||
+
|
||||
+
|
||||
+# This isn't necessary on MSVC, so avoid command-line switch annoyance
|
||||
+# by only running on GCC-like hosts.
|
||||
+if (LLVM_COMPILER_IS_GCC_COMPATIBLE)
|
||||
+ # First check if atomics work without the library.
|
||||
+ check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITHOUT_LIB)
|
||||
+ # If not, check if the library exists, and atomics work with it.
|
||||
+ if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB)
|
||||
+ check_library_exists(atomic __atomic_fetch_add_4 "" HAVE_LIBATOMIC)
|
||||
+ if( HAVE_LIBATOMIC )
|
||||
+ list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic")
|
||||
+ check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITH_LIB)
|
||||
+ if (NOT HAVE_CXX_ATOMICS_WITH_LIB)
|
||||
+ message(FATAL_ERROR "Host compiler must support std::atomic!")
|
||||
+ endif()
|
||||
+ else()
|
||||
+ message(FATAL_ERROR "Host compiler appears to require libatomic, but cannot find it.")
|
||||
+ endif()
|
||||
+ endif()
|
||||
+endif()
|
||||
+
|
||||
+# Check for 64 bit atomic operations.
|
||||
+if(MSVC)
|
||||
+ set(HAVE_CXX_ATOMICS64_WITHOUT_LIB True)
|
||||
+else()
|
||||
+ check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITHOUT_LIB)
|
||||
+endif()
|
||||
+
|
||||
+# If not, check if the library exists, and atomics work with it.
|
||||
+if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB)
|
||||
+ check_library_exists(atomic __atomic_load_8 "" HAVE_CXX_LIBATOMICS64)
|
||||
+ if(HAVE_CXX_LIBATOMICS64)
|
||||
+ list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic")
|
||||
+ check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB)
|
||||
+ if (NOT HAVE_CXX_ATOMICS64_WITH_LIB)
|
||||
+ message(FATAL_ERROR "Host compiler must support 64-bit std::atomic!")
|
||||
+ endif()
|
||||
+ else()
|
||||
+ message(FATAL_ERROR "Host compiler appears to require libatomic for 64-bit operations, but cannot find it.")
|
||||
+ endif()
|
||||
+endif()
|
||||
+
|
||||
+## TODO: This define is only used for the legacy atomic operations in
|
||||
+## llvm's Atomic.h, which should be replaced. Other code simply
|
||||
+## assumes C++11 <atomic> works.
|
||||
+CHECK_CXX_SOURCE_COMPILES("
|
||||
+#ifdef _MSC_VER
|
||||
+#include <windows.h>
|
||||
+#endif
|
||||
+int main() {
|
||||
+#ifdef _MSC_VER
|
||||
+ volatile LONG val = 1;
|
||||
+ MemoryBarrier();
|
||||
+ InterlockedCompareExchange(&val, 0, 1);
|
||||
+ InterlockedIncrement(&val);
|
||||
+ InterlockedDecrement(&val);
|
||||
+#else
|
||||
+ volatile unsigned long val = 1;
|
||||
+ __sync_synchronize();
|
||||
+ __sync_val_compare_and_swap(&val, 1, 0);
|
||||
+ __sync_add_and_fetch(&val, 1);
|
||||
+ __sync_sub_and_fetch(&val, 1);
|
||||
+#endif
|
||||
+ return 0;
|
||||
+ }
|
||||
+" LLVM_HAS_ATOMICS)
|
||||
+
|
||||
+if( NOT LLVM_HAS_ATOMICS )
|
||||
+ message(STATUS "Warning: LLVM will be built thread-unsafe because atomic builtins are missing")
|
||||
+endif()
|
||||
\ No newline at end of file
|
||||
diff --git a/cmake/FindBlosc.cmake b/cmake/FindBlosc.cmake
|
||||
deleted file mode 100644
|
||||
index 5aacfdd..0000000
|
||||
|
@ -965,7 +1079,7 @@ index 339c1a2..0000000
|
|||
- message(FATAL_ERROR "Unable to find OpenEXR")
|
||||
-endif()
|
||||
diff --git a/cmake/FindOpenVDB.cmake b/cmake/FindOpenVDB.cmake
|
||||
index 63a2eda..6211071 100644
|
||||
index 63a2eda..d9f6d07 100644
|
||||
--- a/cmake/FindOpenVDB.cmake
|
||||
+++ b/cmake/FindOpenVDB.cmake
|
||||
@@ -244,7 +244,7 @@ set(OpenVDB_LIB_COMPONENTS "")
|
||||
|
@ -1004,7 +1118,7 @@ index 63a2eda..6211071 100644
|
|||
)
|
||||
+
|
||||
+ if (OPENVDB_USE_STATIC_LIBS)
|
||||
+ set_target_properties(OpenVDB::${COMPONENT} PROPERTIES
|
||||
+ set_target_properties(OpenVDB::${COMPONENT} PROPERTIES
|
||||
+ INTERFACE_COMPILE_DEFINITIONS "OPENVDB_STATICLIB;OPENVDB_OPENEXR_STATICLIB"
|
||||
+ )
|
||||
+ endif()
|
||||
|
@ -1012,7 +1126,7 @@ index 63a2eda..6211071 100644
|
|||
endforeach()
|
||||
|
||||
diff --git a/cmake/FindTBB.cmake b/cmake/FindTBB.cmake
|
||||
index bdf9c81..c6bdec9 100644
|
||||
index bdf9c81..06093a4 100644
|
||||
--- a/cmake/FindTBB.cmake
|
||||
+++ b/cmake/FindTBB.cmake
|
||||
@@ -1,333 +1,332 @@
|
||||
|
@ -1022,35 +1136,21 @@ index bdf9c81..c6bdec9 100644
|
|||
-# All rights reserved. This software is distributed under the
|
||||
-# Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ )
|
||||
+# Copyright (c) 2015 Justus Calvin
|
||||
+#
|
||||
#
|
||||
-# Redistributions of source code must retain the above copyright
|
||||
-# and license notice and the following restrictions and disclaimer.
|
||||
+# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
+# of this software and associated documentation files (the "Software"), to deal
|
||||
+# in the Software without restriction, including without limitation the rights
|
||||
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
+# copies of the Software, and to permit persons to whom the Software is
|
||||
+# furnished to do so, subject to the following conditions:
|
||||
+#
|
||||
+# The above copyright notice and this permission notice shall be included in all
|
||||
+# copies or substantial portions of the Software.
|
||||
+#
|
||||
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
+# SOFTWARE.
|
||||
+
|
||||
#
|
||||
-# Redistributions of source code must retain the above copyright
|
||||
-# and license notice and the following restrictions and disclaimer.
|
||||
+# FindTBB
|
||||
+# -------
|
||||
#
|
||||
-# * Neither the name of DreamWorks Animation nor the names of
|
||||
-# its contributors may be used to endorse or promote products derived
|
||||
-# from this software without specific prior written permission.
|
||||
+# Find TBB include directories and libraries.
|
||||
+# The above copyright notice and this permission notice shall be included in all
|
||||
+# copies or substantial portions of the Software.
|
||||
#
|
||||
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
|
@ -1065,7 +1165,14 @@ index bdf9c81..c6bdec9 100644
|
|||
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
-# IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE
|
||||
-# LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00.
|
||||
+# Usage:
|
||||
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
+# SOFTWARE.
|
||||
+
|
||||
#
|
||||
-#[=======================================================================[.rst:
|
||||
-
|
||||
|
@ -1142,19 +1249,26 @@ index bdf9c81..c6bdec9 100644
|
|||
-if(POLICY CMP0057)
|
||||
- cmake_policy(SET CMP0057 NEW)
|
||||
-endif()
|
||||
+# FindTBB
|
||||
+# -------
|
||||
+#
|
||||
+# Find TBB include directories and libraries.
|
||||
+#
|
||||
+# Usage:
|
||||
+#
|
||||
+# find_package(TBB [major[.minor]] [EXACT]
|
||||
+# [QUIET] [REQUIRED]
|
||||
+# [[COMPONENTS] [components...]]
|
||||
+# [OPTIONAL_COMPONENTS components...])
|
||||
+# [OPTIONAL_COMPONENTS components...])
|
||||
+#
|
||||
+# where the allowed components are tbbmalloc and tbb_preview. Users may modify
|
||||
+# where the allowed components are tbbmalloc and tbb_preview. Users may modify
|
||||
+# the behavior of this module with the following variables:
|
||||
+#
|
||||
+# * TBB_ROOT_DIR - The base directory the of TBB installation.
|
||||
+# * TBB_INCLUDE_DIR - The directory that contains the TBB headers files.
|
||||
+# * TBB_LIBRARY - The directory that contains the TBB library files.
|
||||
+# * TBB_<library>_LIBRARY - The path of the TBB the corresponding TBB library.
|
||||
+# These libraries, if specified, override the
|
||||
+# * TBB_<library>_LIBRARY - The path of the TBB the corresponding TBB library.
|
||||
+# These libraries, if specified, override the
|
||||
+# corresponding library search results, where <library>
|
||||
+# may be tbb, tbb_debug, tbbmalloc, tbbmalloc_debug,
|
||||
+# tbb_preview, or tbb_preview_debug.
|
||||
|
@ -1167,7 +1281,7 @@ index bdf9c81..c6bdec9 100644
|
|||
+# Users may modify the behavior of this module with the following environment
|
||||
+# variables:
|
||||
+#
|
||||
+# * TBB_INSTALL_DIR
|
||||
+# * TBB_INSTALL_DIR
|
||||
+# * TBBROOT
|
||||
+# * LIBRARY_PATH
|
||||
+#
|
||||
|
@ -1180,15 +1294,15 @@ index bdf9c81..c6bdec9 100644
|
|||
+# * TBB_VERSION - The full version string
|
||||
+# * TBB_VERSION_MAJOR - The major version
|
||||
+# * TBB_VERSION_MINOR - The minor version
|
||||
+# * TBB_INTERFACE_VERSION - The interface version number defined in
|
||||
+# * TBB_INTERFACE_VERSION - The interface version number defined in
|
||||
+# tbb/tbb_stddef.h.
|
||||
+# * TBB_<library>_LIBRARY_RELEASE - The path of the TBB release version of
|
||||
+# * TBB_<library>_LIBRARY_RELEASE - The path of the TBB release version of
|
||||
+# <library>, where <library> may be tbb, tbb_debug,
|
||||
+# tbbmalloc, tbbmalloc_debug, tbb_preview, or
|
||||
+# tbbmalloc, tbbmalloc_debug, tbb_preview, or
|
||||
+# tbb_preview_debug.
|
||||
+# * TBB_<library>_LIBRARY_DEGUG - The path of the TBB release version of
|
||||
+# * TBB_<library>_LIBRARY_DEGUG - The path of the TBB release version of
|
||||
+# <library>, where <library> may be tbb, tbb_debug,
|
||||
+# tbbmalloc, tbbmalloc_debug, tbb_preview, or
|
||||
+# tbbmalloc, tbbmalloc_debug, tbb_preview, or
|
||||
+# tbb_preview_debug.
|
||||
+#
|
||||
+# The following varibles should be used to build and link with TBB:
|
||||
|
@ -1244,12 +1358,10 @@ index bdf9c81..c6bdec9 100644
|
|||
- set(TBB_FIND_COMPONENTS ${_TBB_COMPONENT_LIST})
|
||||
-endif()
|
||||
+include(FindPackageHandleStandardArgs)
|
||||
+
|
||||
+find_package(Threads QUIET REQUIRED)
|
||||
|
||||
-# Append TBB_ROOT or $ENV{TBB_ROOT} if set (prioritize the direct cmake var)
|
||||
-set(_TBB_ROOT_SEARCH_DIR "")
|
||||
+if(NOT TBB_FOUND)
|
||||
+find_package(Threads QUIET REQUIRED)
|
||||
|
||||
-if(TBB_ROOT)
|
||||
- list(APPEND _TBB_ROOT_SEARCH_DIR ${TBB_ROOT})
|
||||
|
@ -1257,41 +1369,9 @@ index bdf9c81..c6bdec9 100644
|
|||
- set(_ENV_TBB_ROOT $ENV{TBB_ROOT})
|
||||
- if(_ENV_TBB_ROOT)
|
||||
- list(APPEND _TBB_ROOT_SEARCH_DIR ${_ENV_TBB_ROOT})
|
||||
+ ##################################
|
||||
+ # Check the build type
|
||||
+ ##################################
|
||||
+
|
||||
+ if(NOT DEFINED TBB_USE_DEBUG_BUILD)
|
||||
+ if(CMAKE_BUILD_TYPE MATCHES "(Debug|DEBUG|debug)")
|
||||
+ set(TBB_BUILD_TYPE DEBUG)
|
||||
+ else()
|
||||
+ set(TBB_BUILD_TYPE RELEASE)
|
||||
+ endif()
|
||||
+ elseif(TBB_USE_DEBUG_BUILD)
|
||||
+ set(TBB_BUILD_TYPE DEBUG)
|
||||
+ else()
|
||||
+ set(TBB_BUILD_TYPE RELEASE)
|
||||
endif()
|
||||
- endif()
|
||||
-endif()
|
||||
+
|
||||
+ ##################################
|
||||
+ # Set the TBB search directories
|
||||
+ ##################################
|
||||
+
|
||||
+ # Define search paths based on user input and environment variables
|
||||
+ set(TBB_SEARCH_DIR ${TBB_ROOT_DIR} $ENV{TBB_INSTALL_DIR} $ENV{TBBROOT})
|
||||
+
|
||||
+ # Define the search directories based on the current platform
|
||||
+ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
+ set(TBB_DEFAULT_SEARCH_DIR "C:/Program Files/Intel/TBB"
|
||||
+ "C:/Program Files (x86)/Intel/TBB")
|
||||
+
|
||||
+ # Set the target architecture
|
||||
+ if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
+ set(TBB_ARCHITECTURE "intel64")
|
||||
+ else()
|
||||
+ set(TBB_ARCHITECTURE "ia32")
|
||||
+ endif()
|
||||
+if(NOT TBB_FOUND)
|
||||
|
||||
-# Additionally try and use pkconfig to find Tbb
|
||||
-
|
||||
|
@ -1339,6 +1419,57 @@ index bdf9c81..c6bdec9 100644
|
|||
-
|
||||
- set(Tbb_VERSION ${Tbb_VERSION_MAJOR}.${Tbb_VERSION_MINOR})
|
||||
-endif()
|
||||
+ ##################################
|
||||
+ # Check the build type
|
||||
+ ##################################
|
||||
+
|
||||
+ if(NOT DEFINED TBB_USE_DEBUG_BUILD)
|
||||
+ if(CMAKE_BUILD_TYPE MATCHES "(Debug|DEBUG|debug)")
|
||||
+ set(TBB_BUILD_TYPE DEBUG)
|
||||
+ else()
|
||||
+ set(TBB_BUILD_TYPE RELEASE)
|
||||
+ endif()
|
||||
+ elseif(TBB_USE_DEBUG_BUILD)
|
||||
+ set(TBB_BUILD_TYPE DEBUG)
|
||||
+ else()
|
||||
+ set(TBB_BUILD_TYPE RELEASE)
|
||||
+ endif()
|
||||
|
||||
-# ------------------------------------------------------------------------
|
||||
-# Search for TBB lib DIR
|
||||
-# ------------------------------------------------------------------------
|
||||
+ ##################################
|
||||
+ # Set the TBB search directories
|
||||
+ ##################################
|
||||
|
||||
-set(_TBB_LIBRARYDIR_SEARCH_DIRS "")
|
||||
+ # Define search paths based on user input and environment variables
|
||||
+ set(TBB_SEARCH_DIR ${TBB_ROOT_DIR} $ENV{TBB_INSTALL_DIR} $ENV{TBBROOT})
|
||||
|
||||
-# Append to _TBB_LIBRARYDIR_SEARCH_DIRS in priority order
|
||||
+ # Define the search directories based on the current platform
|
||||
+ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||
+ set(TBB_DEFAULT_SEARCH_DIR "C:/Program Files/Intel/TBB"
|
||||
+ "C:/Program Files (x86)/Intel/TBB")
|
||||
|
||||
-set(_TBB_LIBRARYDIR_SEARCH_DIRS "")
|
||||
-list(APPEND _TBB_LIBRARYDIR_SEARCH_DIRS
|
||||
- ${TBB_LIBRARYDIR}
|
||||
- ${_TBB_ROOT_SEARCH_DIR}
|
||||
- ${PC_Tbb_LIBRARY_DIRS}
|
||||
- ${SYSTEM_LIBRARY_PATHS}
|
||||
-)
|
||||
+ # Set the target architecture
|
||||
+ if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
+ set(TBB_ARCHITECTURE "intel64")
|
||||
+ else()
|
||||
+ set(TBB_ARCHITECTURE "ia32")
|
||||
+ endif()
|
||||
|
||||
-set(TBB_PATH_SUFFIXES
|
||||
- lib64
|
||||
- lib
|
||||
-)
|
||||
+ # Set the TBB search library path search suffix based on the version of VC
|
||||
+ if(WINDOWS_STORE)
|
||||
+ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11_ui")
|
||||
|
@ -1352,104 +1483,16 @@ index bdf9c81..c6bdec9 100644
|
|||
+ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc10")
|
||||
+ endif()
|
||||
|
||||
-# ------------------------------------------------------------------------
|
||||
-# Search for TBB lib DIR
|
||||
-# ------------------------------------------------------------------------
|
||||
-# platform branching
|
||||
+ # Add the library path search suffix for the VC independent version of TBB
|
||||
+ list(APPEND TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc_mt")
|
||||
+
|
||||
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
+ # OS X
|
||||
+ set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb")
|
||||
+
|
||||
+ # TODO: Check to see which C++ library is being used by the compiler.
|
||||
+ if(NOT ${CMAKE_SYSTEM_VERSION} VERSION_LESS 13.0)
|
||||
+ # The default C++ library on OS X 10.9 and later is libc++
|
||||
+ set(TBB_LIB_PATH_SUFFIX "lib/libc++" "lib")
|
||||
+ else()
|
||||
+ set(TBB_LIB_PATH_SUFFIX "lib")
|
||||
+ endif()
|
||||
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
+ # Linux
|
||||
+ set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb")
|
||||
+
|
||||
+ # TODO: Check compiler version to see the suffix should be <arch>/gcc4.1 or
|
||||
+ # <arch>/gcc4.1. For now, assume that the compiler is more recent than
|
||||
+ # gcc 4.4.x or later.
|
||||
+ if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
|
||||
+ set(TBB_LIB_PATH_SUFFIX "lib/intel64/gcc4.4")
|
||||
+ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$")
|
||||
+ set(TBB_LIB_PATH_SUFFIX "lib/ia32/gcc4.4")
|
||||
+ endif()
|
||||
+ endif()
|
||||
+
|
||||
+ ##################################
|
||||
+ # Find the TBB include dir
|
||||
+ ##################################
|
||||
+
|
||||
+ find_path(TBB_INCLUDE_DIRS tbb/tbb.h
|
||||
+ HINTS ${TBB_INCLUDE_DIR} ${TBB_SEARCH_DIR}
|
||||
+ PATHS ${TBB_DEFAULT_SEARCH_DIR}
|
||||
+ PATH_SUFFIXES include)
|
||||
+
|
||||
+ ##################################
|
||||
+ # Set version strings
|
||||
+ ##################################
|
||||
+
|
||||
+ if(TBB_INCLUDE_DIRS)
|
||||
+ file(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _tbb_version_file)
|
||||
+ string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1"
|
||||
+ TBB_VERSION_MAJOR "${_tbb_version_file}")
|
||||
+ string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1"
|
||||
+ TBB_VERSION_MINOR "${_tbb_version_file}")
|
||||
+ string(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1"
|
||||
+ TBB_INTERFACE_VERSION "${_tbb_version_file}")
|
||||
+ set(TBB_VERSION "${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR}")
|
||||
+ endif()
|
||||
|
||||
-set(_TBB_LIBRARYDIR_SEARCH_DIRS "")
|
||||
+ ##################################
|
||||
+ # Find TBB components
|
||||
+ ##################################
|
||||
|
||||
-# Append to _TBB_LIBRARYDIR_SEARCH_DIRS in priority order
|
||||
+ if(TBB_VERSION VERSION_LESS 4.3)
|
||||
+ set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc tbb)
|
||||
+ else()
|
||||
+ set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc_proxy tbbmalloc tbb)
|
||||
+ endif()
|
||||
|
||||
-set(_TBB_LIBRARYDIR_SEARCH_DIRS "")
|
||||
-list(APPEND _TBB_LIBRARYDIR_SEARCH_DIRS
|
||||
- ${TBB_LIBRARYDIR}
|
||||
- ${_TBB_ROOT_SEARCH_DIR}
|
||||
- ${PC_Tbb_LIBRARY_DIRS}
|
||||
- ${SYSTEM_LIBRARY_PATHS}
|
||||
-)
|
||||
+ if(TBB_STATIC)
|
||||
+ set(TBB_STATIC_SUFFIX "_static")
|
||||
+ endif()
|
||||
|
||||
-set(TBB_PATH_SUFFIXES
|
||||
- lib64
|
||||
- lib
|
||||
-)
|
||||
+ # Find each component
|
||||
+ foreach(_comp ${TBB_SEARCH_COMPOMPONENTS})
|
||||
+ if(";${TBB_FIND_COMPONENTS};tbb;" MATCHES ";${_comp};")
|
||||
|
||||
-# platform branching
|
||||
+ unset(TBB_${_comp}_LIBRARY_DEBUG CACHE)
|
||||
+ unset(TBB_${_comp}_LIBRARY_RELEASE CACHE)
|
||||
|
||||
-if(UNIX)
|
||||
- list(INSERT TBB_PATH_SUFFIXES 0 lib/x86_64-linux-gnu)
|
||||
-endif()
|
||||
+ # Search for the libraries
|
||||
+ find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp}${TBB_STATIC_SUFFIX}
|
||||
+ HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
|
||||
+ PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
|
||||
+ PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
|
||||
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||
+ # OS X
|
||||
+ set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb")
|
||||
|
||||
-if(APPLE)
|
||||
- if(TBB_FOR_CLANG)
|
||||
|
@ -1471,29 +1514,33 @@ index bdf9c81..c6bdec9 100644
|
|||
- list(GET GCC_VERSION_COMPONENTS 0 GCC_MAJOR)
|
||||
- list(GET GCC_VERSION_COMPONENTS 1 GCC_MINOR)
|
||||
- list(INSERT TBB_PATH_SUFFIXES 0 lib/intel64/gcc${GCC_MAJOR}.${GCC_MINOR})
|
||||
- else()
|
||||
+ # TODO: Check to see which C++ library is being used by the compiler.
|
||||
+ if(NOT ${CMAKE_SYSTEM_VERSION} VERSION_LESS 13.0)
|
||||
+ # The default C++ library on OS X 10.9 and later is libc++
|
||||
+ set(TBB_LIB_PATH_SUFFIX "lib/libc++" "lib")
|
||||
else()
|
||||
- list(INSERT TBB_PATH_SUFFIXES 0 lib/intel64/gcc4.4)
|
||||
- endif()
|
||||
- endif()
|
||||
+ set(TBB_LIB_PATH_SUFFIX "lib")
|
||||
+ endif()
|
||||
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
+ # Linux
|
||||
+ set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb")
|
||||
+
|
||||
+ # TODO: Check compiler version to see the suffix should be <arch>/gcc4.1 or
|
||||
+ # <arch>/gcc4.1. For now, assume that the compiler is more recent than
|
||||
+ # gcc 4.4.x or later.
|
||||
+ if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
|
||||
+ set(TBB_LIB_PATH_SUFFIX "lib/intel64/gcc4.4")
|
||||
+ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$")
|
||||
+ set(TBB_LIB_PATH_SUFFIX "lib/ia32/gcc4.4")
|
||||
endif()
|
||||
endif()
|
||||
-endif()
|
||||
+ find_library(TBB_${_comp}_LIBRARY_DEBUG ${_comp}${TBB_STATIC_SUFFIX}_debug
|
||||
+ HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
|
||||
+ PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
|
||||
+ PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
|
||||
|
||||
-
|
||||
-if(UNIX AND TBB_USE_STATIC_LIBS)
|
||||
- set(_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
|
||||
- set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
|
||||
-endif()
|
||||
+ if(TBB_${_comp}_LIBRARY_DEBUG)
|
||||
+ list(APPEND TBB_LIBRARIES_DEBUG "${TBB_${_comp}_LIBRARY_DEBUG}")
|
||||
+ endif()
|
||||
+ if(TBB_${_comp}_LIBRARY_RELEASE)
|
||||
+ list(APPEND TBB_LIBRARIES_RELEASE "${TBB_${_comp}_LIBRARY_RELEASE}")
|
||||
+ endif()
|
||||
+ if(TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE} AND NOT TBB_${_comp}_LIBRARY)
|
||||
+ set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE}}")
|
||||
+ endif()
|
||||
|
||||
-set(Tbb_LIB_COMPONENTS "")
|
||||
-
|
||||
|
@ -1516,39 +1563,44 @@ index bdf9c81..c6bdec9 100644
|
|||
- # Extract the directory and apply the matched text (in brackets)
|
||||
- get_filename_component(Tbb_${COMPONENT}_DIR "${Tbb_${COMPONENT}_LIBRARY}" DIRECTORY)
|
||||
- set(Tbb_${COMPONENT}_LIBRARY "${Tbb_${COMPONENT}_DIR}/${CMAKE_MATCH_1}")
|
||||
+ if(TBB_${_comp}_LIBRARY AND EXISTS "${TBB_${_comp}_LIBRARY}")
|
||||
+ set(TBB_${_comp}_FOUND TRUE)
|
||||
+ else()
|
||||
+ set(TBB_${_comp}_FOUND FALSE)
|
||||
endif()
|
||||
- endif()
|
||||
- endif()
|
||||
+ ##################################
|
||||
+ # Find the TBB include dir
|
||||
+ ##################################
|
||||
+
|
||||
+ # Mark internal variables as advanced
|
||||
+ mark_as_advanced(TBB_${_comp}_LIBRARY_RELEASE)
|
||||
+ mark_as_advanced(TBB_${_comp}_LIBRARY_DEBUG)
|
||||
+ mark_as_advanced(TBB_${_comp}_LIBRARY)
|
||||
+ find_path(TBB_INCLUDE_DIRS tbb/tbb.h
|
||||
+ HINTS ${TBB_INCLUDE_DIR} ${TBB_SEARCH_DIR}
|
||||
+ PATHS ${TBB_DEFAULT_SEARCH_DIR}
|
||||
+ PATH_SUFFIXES include)
|
||||
+
|
||||
endif()
|
||||
- endif()
|
||||
+ endforeach()
|
||||
+ ##################################
|
||||
+ # Set version strings
|
||||
+ ##################################
|
||||
+
|
||||
+ if(TBB_INCLUDE_DIRS)
|
||||
+ file(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _tbb_version_file)
|
||||
+ string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1"
|
||||
+ TBB_VERSION_MAJOR "${_tbb_version_file}")
|
||||
+ string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1"
|
||||
+ TBB_VERSION_MINOR "${_tbb_version_file}")
|
||||
+ string(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1"
|
||||
+ TBB_INTERFACE_VERSION "${_tbb_version_file}")
|
||||
+ set(TBB_VERSION "${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR}")
|
||||
endif()
|
||||
|
||||
- list(APPEND Tbb_LIB_COMPONENTS ${Tbb_${COMPONENT}_LIBRARY})
|
||||
+ ##################################
|
||||
+ # Set compile flags and libraries
|
||||
+ # Find TBB components
|
||||
+ ##################################
|
||||
|
||||
- if(Tbb_${COMPONENT}_LIBRARY)
|
||||
- set(TBB_${COMPONENT}_FOUND TRUE)
|
||||
- else()
|
||||
+ if(TBB_VERSION VERSION_LESS 4.3)
|
||||
+ set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc tbb)
|
||||
else()
|
||||
- set(TBB_${COMPONENT}_FOUND FALSE)
|
||||
+ set(TBB_DEFINITIONS_RELEASE "")
|
||||
+ set(TBB_DEFINITIONS_DEBUG "TBB_USE_DEBUG=1")
|
||||
+
|
||||
+ if(TBB_LIBRARIES_${TBB_BUILD_TYPE})
|
||||
+ set(TBB_LIBRARIES "${TBB_LIBRARIES_${TBB_BUILD_TYPE}}")
|
||||
+ endif()
|
||||
+
|
||||
+ if(NOT MSVC AND NOT TBB_LIBRARIES)
|
||||
+ set(TBB_LIBRARIES ${TBB_LIBRARIES_RELEASE})
|
||||
+ set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc_proxy tbbmalloc tbb)
|
||||
endif()
|
||||
-endforeach()
|
||||
|
||||
|
@ -1556,61 +1608,51 @@ index bdf9c81..c6bdec9 100644
|
|||
- set(CMAKE_FIND_LIBRARY_SUFFIXES ${_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES})
|
||||
- unset(_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES)
|
||||
-endif()
|
||||
+ set(TBB_DEFINITIONS "")
|
||||
+ if (MSVC AND TBB_STATIC)
|
||||
+ set(TBB_DEFINITIONS __TBB_NO_IMPLICIT_LINKAGE)
|
||||
+ endif ()
|
||||
+
|
||||
+ unset (TBB_STATIC_SUFFIX)
|
||||
+
|
||||
+ find_package_handle_standard_args(TBB
|
||||
+ REQUIRED_VARS TBB_INCLUDE_DIRS TBB_LIBRARIES
|
||||
+ FAIL_MESSAGE "TBB library cannot be found. Consider set TBBROOT environment variable."
|
||||
+ HANDLE_COMPONENTS
|
||||
+ VERSION_VAR TBB_VERSION)
|
||||
+
|
||||
+ ##################################
|
||||
+ # Create targets
|
||||
+ ##################################
|
||||
+
|
||||
+ if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND)
|
||||
+ add_library(TBB::tbb UNKNOWN IMPORTED)
|
||||
+ set_target_properties(TBB::tbb PROPERTIES
|
||||
+ INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS}"
|
||||
+ INTERFACE_LINK_LIBRARIES "Threads::Threads;${CMAKE_DL_LIBS}"
|
||||
+ INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS}
|
||||
+ IMPORTED_LOCATION ${TBB_LIBRARIES})
|
||||
+ if(TBB_LIBRARIES_RELEASE AND TBB_LIBRARIES_DEBUG)
|
||||
+ set_target_properties(TBB::tbb PROPERTIES
|
||||
+ INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS};$<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:${TBB_DEFINITIONS_DEBUG}>;$<$<CONFIG:Release>:${TBB_DEFINITIONS_RELEASE}>"
|
||||
+ IMPORTED_LOCATION_DEBUG ${TBB_LIBRARIES_DEBUG}
|
||||
+ IMPORTED_LOCATION_RELWITHDEBINFO ${TBB_LIBRARIES_RELEASE}
|
||||
+ IMPORTED_LOCATION_RELEASE ${TBB_LIBRARIES_RELEASE}
|
||||
+ IMPORTED_LOCATION_MINSIZEREL ${TBB_LIBRARIES_RELEASE}
|
||||
+ )
|
||||
+ endif()
|
||||
+ if(TBB_STATIC)
|
||||
+ set(TBB_STATIC_SUFFIX "_static")
|
||||
+ endif()
|
||||
|
||||
-# ------------------------------------------------------------------------
|
||||
-# Cache and set TBB_FOUND
|
||||
-# ------------------------------------------------------------------------
|
||||
+ mark_as_advanced(TBB_INCLUDE_DIRS TBB_LIBRARIES)
|
||||
+ # Find each component
|
||||
+ foreach(_comp ${TBB_SEARCH_COMPOMPONENTS})
|
||||
+ if(";${TBB_FIND_COMPONENTS};tbb;" MATCHES ";${_comp};")
|
||||
+
|
||||
+ unset(TBB_ARCHITECTURE)
|
||||
+ unset(TBB_BUILD_TYPE)
|
||||
+ unset(TBB_LIB_PATH_SUFFIX)
|
||||
+ unset(TBB_DEFAULT_SEARCH_DIR)
|
||||
+ unset(TBB_${_comp}_LIBRARY_DEBUG CACHE)
|
||||
+ unset(TBB_${_comp}_LIBRARY_RELEASE CACHE)
|
||||
+
|
||||
+ if(TBB_DEBUG)
|
||||
+ message(STATUS " TBB_FOUND = ${TBB_FOUND}")
|
||||
+ message(STATUS " TBB_INCLUDE_DIRS = ${TBB_INCLUDE_DIRS}")
|
||||
+ message(STATUS " TBB_DEFINITIONS = ${TBB_DEFINITIONS}")
|
||||
+ message(STATUS " TBB_LIBRARIES = ${TBB_LIBRARIES}")
|
||||
+ message(STATUS " TBB_DEFINITIONS_DEBUG = ${TBB_DEFINITIONS_DEBUG}")
|
||||
+ message(STATUS " TBB_LIBRARIES_DEBUG = ${TBB_LIBRARIES_DEBUG}")
|
||||
+ message(STATUS " TBB_DEFINITIONS_RELEASE = ${TBB_DEFINITIONS_RELEASE}")
|
||||
+ message(STATUS " TBB_LIBRARIES_RELEASE = ${TBB_LIBRARIES_RELEASE}")
|
||||
+ endif()
|
||||
+ # Search for the libraries
|
||||
+ find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp}${TBB_STATIC_SUFFIX}
|
||||
+ HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
|
||||
+ PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
|
||||
+ PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
|
||||
+
|
||||
+ find_library(TBB_${_comp}_LIBRARY_DEBUG ${_comp}${TBB_STATIC_SUFFIX}_debug
|
||||
+ HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
|
||||
+ PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
|
||||
+ PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
|
||||
+
|
||||
+ if(TBB_${_comp}_LIBRARY_DEBUG)
|
||||
+ list(APPEND TBB_LIBRARIES_DEBUG "${TBB_${_comp}_LIBRARY_DEBUG}")
|
||||
+ endif()
|
||||
+ if(TBB_${_comp}_LIBRARY_RELEASE)
|
||||
+ list(APPEND TBB_LIBRARIES_RELEASE "${TBB_${_comp}_LIBRARY_RELEASE}")
|
||||
+ endif()
|
||||
+ if(TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE} AND NOT TBB_${_comp}_LIBRARY)
|
||||
+ set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE}}")
|
||||
+ endif()
|
||||
+
|
||||
+ if(TBB_${_comp}_LIBRARY AND EXISTS "${TBB_${_comp}_LIBRARY}")
|
||||
+ set(TBB_${_comp}_FOUND TRUE)
|
||||
+ else()
|
||||
+ set(TBB_${_comp}_FOUND FALSE)
|
||||
+ endif()
|
||||
+
|
||||
+ # Mark internal variables as advanced
|
||||
+ mark_as_advanced(TBB_${_comp}_LIBRARY_RELEASE)
|
||||
+ mark_as_advanced(TBB_${_comp}_LIBRARY_DEBUG)
|
||||
+ mark_as_advanced(TBB_${_comp}_LIBRARY)
|
||||
|
||||
-include(FindPackageHandleStandardArgs)
|
||||
-find_package_handle_standard_args(TBB
|
||||
|
@ -1646,13 +1688,82 @@ index bdf9c81..c6bdec9 100644
|
|||
- INTERFACE_COMPILE_OPTIONS "${Tbb_DEFINITIONS}"
|
||||
- INTERFACE_INCLUDE_DIRECTORIES "${Tbb_INCLUDE_DIR}"
|
||||
- )
|
||||
- endif()
|
||||
- endforeach()
|
||||
endif()
|
||||
endforeach()
|
||||
-elseif(TBB_FIND_REQUIRED)
|
||||
- message(FATAL_ERROR "Unable to find TBB")
|
||||
+
|
||||
+ ##################################
|
||||
+ # Set compile flags and libraries
|
||||
+ ##################################
|
||||
+
|
||||
+ set(TBB_DEFINITIONS_RELEASE "")
|
||||
+ set(TBB_DEFINITIONS_DEBUG "TBB_USE_DEBUG=1")
|
||||
+
|
||||
+ if(TBB_LIBRARIES_${TBB_BUILD_TYPE})
|
||||
+ set(TBB_LIBRARIES "${TBB_LIBRARIES_${TBB_BUILD_TYPE}}")
|
||||
+ endif()
|
||||
+
|
||||
+ if(NOT MSVC AND NOT TBB_LIBRARIES)
|
||||
+ set(TBB_LIBRARIES ${TBB_LIBRARIES_RELEASE})
|
||||
+ endif()
|
||||
+
|
||||
+ set(TBB_DEFINITIONS "")
|
||||
+ if (MSVC AND TBB_STATIC)
|
||||
+ set(TBB_DEFINITIONS __TBB_NO_IMPLICIT_LINKAGE)
|
||||
+ endif ()
|
||||
+
|
||||
+ unset (TBB_STATIC_SUFFIX)
|
||||
+
|
||||
+ find_package_handle_standard_args(TBB
|
||||
+ REQUIRED_VARS TBB_INCLUDE_DIRS TBB_LIBRARIES
|
||||
+ FAIL_MESSAGE "TBB library cannot be found. Consider set TBBROOT environment variable."
|
||||
+ HANDLE_COMPONENTS
|
||||
+ VERSION_VAR TBB_VERSION)
|
||||
+
|
||||
+ ##################################
|
||||
+ # Create targets
|
||||
+ ##################################
|
||||
+
|
||||
+ if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND)
|
||||
+ add_library(TBB::tbb UNKNOWN IMPORTED)
|
||||
+ set_target_properties(TBB::tbb PROPERTIES
|
||||
+ INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS}"
|
||||
+ INTERFACE_LINK_LIBRARIES "Threads::Threads;${CMAKE_DL_LIBS}"
|
||||
+ INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS}
|
||||
+ IMPORTED_LOCATION ${TBB_LIBRARIES})
|
||||
+ if(TBB_LIBRARIES_RELEASE AND TBB_LIBRARIES_DEBUG)
|
||||
+ set_target_properties(TBB::tbb PROPERTIES
|
||||
+ INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS};$<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:${TBB_DEFINITIONS_DEBUG}>;$<$<CONFIG:Release>:${TBB_DEFINITIONS_RELEASE}>"
|
||||
+ IMPORTED_LOCATION_DEBUG ${TBB_LIBRARIES_DEBUG}
|
||||
+ IMPORTED_LOCATION_RELWITHDEBINFO ${TBB_LIBRARIES_RELEASE}
|
||||
+ IMPORTED_LOCATION_RELEASE ${TBB_LIBRARIES_RELEASE}
|
||||
+ IMPORTED_LOCATION_MINSIZEREL ${TBB_LIBRARIES_RELEASE}
|
||||
+ )
|
||||
+ endif()
|
||||
+ endif()
|
||||
+
|
||||
+ mark_as_advanced(TBB_INCLUDE_DIRS TBB_LIBRARIES)
|
||||
+
|
||||
+ unset(TBB_ARCHITECTURE)
|
||||
+ unset(TBB_BUILD_TYPE)
|
||||
+ unset(TBB_LIB_PATH_SUFFIX)
|
||||
+ unset(TBB_DEFAULT_SEARCH_DIR)
|
||||
+
|
||||
+ if(TBB_DEBUG)
|
||||
+ message(STATUS " TBB_FOUND = ${TBB_FOUND}")
|
||||
+ message(STATUS " TBB_INCLUDE_DIRS = ${TBB_INCLUDE_DIRS}")
|
||||
+ message(STATUS " TBB_DEFINITIONS = ${TBB_DEFINITIONS}")
|
||||
+ message(STATUS " TBB_LIBRARIES = ${TBB_LIBRARIES}")
|
||||
+ message(STATUS " TBB_DEFINITIONS_DEBUG = ${TBB_DEFINITIONS_DEBUG}")
|
||||
+ message(STATUS " TBB_LIBRARIES_DEBUG = ${TBB_LIBRARIES_DEBUG}")
|
||||
+ message(STATUS " TBB_DEFINITIONS_RELEASE = ${TBB_DEFINITIONS_RELEASE}")
|
||||
+ message(STATUS " TBB_LIBRARIES_RELEASE = ${TBB_LIBRARIES_RELEASE}")
|
||||
+ endif()
|
||||
+
|
||||
endif()
|
||||
diff --git a/openvdb/CMakeLists.txt b/openvdb/CMakeLists.txt
|
||||
index 89301bd..df27aae 100644
|
||||
index 89301bd..6a3c90c 100644
|
||||
--- a/openvdb/CMakeLists.txt
|
||||
+++ b/openvdb/CMakeLists.txt
|
||||
@@ -78,7 +78,7 @@ else()
|
||||
|
@ -1664,7 +1775,21 @@ index 89301bd..df27aae 100644
|
|||
message(DEPRECATION "Support for TBB versions < ${FUTURE_MINIMUM_TBB_VERSION} "
|
||||
"is deprecated and will be removed.")
|
||||
endif()
|
||||
@@ -185,11 +185,6 @@ if(WIN32)
|
||||
@@ -129,10 +129,13 @@ endif()
|
||||
# include paths from shared installs (including houdini) may pull in the wrong
|
||||
# headers
|
||||
|
||||
+include (CheckAtomic)
|
||||
+
|
||||
set(OPENVDB_CORE_DEPENDENT_LIBS
|
||||
Boost::iostreams
|
||||
Boost::system
|
||||
IlmBase::Half
|
||||
+ ${CMAKE_REQUIRED_LIBRARIES}
|
||||
)
|
||||
|
||||
if(USE_EXR)
|
||||
@@ -185,11 +188,6 @@ if(WIN32)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
@ -1676,7 +1801,7 @@ index 89301bd..df27aae 100644
|
|||
##### Core library configuration
|
||||
|
||||
set(OPENVDB_LIBRARY_SOURCE_FILES
|
||||
@@ -374,10 +369,16 @@ set(OPENVDB_LIBRARY_UTIL_INCLUDE_FILES
|
||||
@@ -374,10 +372,16 @@ set(OPENVDB_LIBRARY_UTIL_INCLUDE_FILES
|
||||
|
||||
if(OPENVDB_CORE_SHARED)
|
||||
add_library(openvdb_shared SHARED ${OPENVDB_LIBRARY_SOURCE_FILES})
|
||||
|
@ -1779,5 +1904,5 @@ index df51830..0ab0c12 100644
|
|||
/// @todo This changes the compressor setting globally.
|
||||
if (blosc_set_compressor(compname) < 0) continue;
|
||||
--
|
||||
2.16.2.windows.1
|
||||
2.17.1
|
||||
|
||||
|
|
80
resources/icons/hollow.svg
Normal file
80
resources/icons/hollow.svg
Normal file
|
@ -0,0 +1,80 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
|
||||
<g id="hollow">
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M64,121.87c-0.39,0-0.77-0.11-1.11-0.34l-17.93-11.95c-0.92-0.61-1.17-1.85-0.55-2.77
|
||||
s1.86-1.17,2.77-0.55L64,117.47l17.03-11.35c0.92-0.61,2.16-0.36,2.77,0.55c0.61,0.92,0.36,2.16-0.55,2.77l-18.14,12.09
|
||||
C64.77,121.76,64.39,121.87,64,121.87z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M90.96,103.89c-0.65,0-1.28-0.31-1.67-0.89c-0.61-0.92-0.36-2.16,0.55-2.77l20.04-13.36V41.13L64,10.53
|
||||
L18.11,41.13v45.75l20.03,13.36c0.92,0.61,1.17,1.85,0.55,2.77c-0.61,0.92-1.85,1.17-2.77,0.55L15,89.61
|
||||
c-0.56-0.37-0.89-1-0.89-1.66V40.06c0-0.67,0.33-1.29,0.89-1.66L62.89,6.47c0.67-0.45,1.55-0.45,2.22,0L113,38.39
|
||||
c0.56,0.37,0.89,1,0.89,1.66v47.89c0,0.67-0.33,1.29-0.89,1.66l-20.93,13.95C91.73,103.79,91.34,103.89,90.96,103.89z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#ED6B21" d="M67.33,23.81c-0.38,0-0.77-0.11-1.11-0.34L64,22l-2.22,1.48c-0.92,0.61-2.16,0.36-2.77-0.55
|
||||
c-0.61-0.92-0.36-2.16,0.55-2.77l3.33-2.22c0.67-0.45,1.55-0.45,2.22,0l3.33,2.22c0.92,0.61,1.17,1.85,0.55,2.77
|
||||
C68.61,23.5,67.97,23.81,67.33,23.81z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#ED6B21" d="M36.09,40.2c-0.65,0-1.28-0.31-1.67-0.89c-0.61-0.92-0.36-2.16,0.55-2.77l5.46-3.64
|
||||
c0.92-0.61,2.16-0.36,2.77,0.55c0.61,0.92,0.36,2.16-0.55,2.77l-5.46,3.64C36.86,40.09,36.48,40.2,36.09,40.2z M48.38,32.01
|
||||
c-0.65,0-1.28-0.31-1.67-0.89c-0.61-0.92-0.36-2.16,0.55-2.77l5.46-3.64c0.92-0.61,2.16-0.36,2.77,0.55s0.36,2.16-0.55,2.77
|
||||
l-5.46,3.64C49.15,31.9,48.77,32.01,48.38,32.01z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#ED6B21" d="M25.94,50.97c-1.1,0-2-0.9-2-2v-4c0-0.67,0.33-1.29,0.89-1.66l3.33-2.22c0.92-0.62,2.16-0.36,2.77,0.55
|
||||
s0.36,2.16-0.55,2.77l-2.44,1.62v2.93C27.94,50.07,27.04,50.97,25.94,50.97z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#ED6B21" d="M25.94,70.29c-1.1,0-2-0.9-2-2v-8.59c0-1.1,0.9-2,2-2s2,0.9,2,2v8.59C27.94,69.4,27.04,70.29,25.94,70.29
|
||||
z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#ED6B21" d="M29.26,87.25c-0.38,0-0.77-0.11-1.11-0.34l-3.33-2.22c-0.56-0.37-0.89-1-0.89-1.66v-4c0-1.1,0.9-2,2-2
|
||||
s2,0.9,2,2v2.93l2.44,1.62c0.92,0.61,1.17,1.85,0.55,2.77C30.54,86.94,29.91,87.25,29.26,87.25z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#ED6B21" d="M53.84,103.64c-0.38,0-0.77-0.11-1.11-0.34l-5.46-3.64c-0.92-0.61-1.17-1.85-0.55-2.77
|
||||
c0.61-0.92,1.85-1.17,2.77-0.55l5.46,3.64c0.92,0.61,1.17,1.85,0.55,2.77C55.12,103.32,54.49,103.64,53.84,103.64z M41.55,95.44
|
||||
c-0.38,0-0.77-0.11-1.11-0.34l-5.46-3.64c-0.92-0.61-1.17-1.85-0.55-2.77c0.61-0.92,1.86-1.17,2.77-0.55l5.46,3.64
|
||||
c0.92,0.61,1.17,1.85,0.55,2.77C42.83,95.13,42.2,95.44,41.55,95.44z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#ED6B21" d="M64,110.41c-0.39,0-0.77-0.11-1.11-0.34l-3.33-2.22c-0.92-0.61-1.17-1.85-0.55-2.77
|
||||
c0.61-0.92,1.85-1.17,2.77-0.55L64,106l2.22-1.48c0.92-0.61,2.16-0.37,2.77,0.55c0.61,0.92,0.36,2.16-0.55,2.77l-3.33,2.22
|
||||
C64.77,110.29,64.39,110.41,64,110.41z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#ED6B21" d="M74.16,103.64c-0.65,0-1.28-0.31-1.67-0.89c-0.61-0.92-0.36-2.16,0.55-2.77l5.46-3.64
|
||||
c0.92-0.61,2.16-0.36,2.77,0.55c0.61,0.92,0.36,2.16-0.55,2.77l-5.46,3.64C74.92,103.53,74.54,103.64,74.16,103.64z M86.45,95.44
|
||||
c-0.65,0-1.28-0.31-1.67-0.89c-0.61-0.92-0.36-2.16,0.55-2.77l5.46-3.64c0.92-0.61,2.16-0.36,2.77,0.55
|
||||
c0.61,0.92,0.36,2.16-0.55,2.77l-5.46,3.64C87.21,95.33,86.83,95.44,86.45,95.44z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#ED6B21" d="M98.74,87.25c-0.65,0-1.28-0.31-1.67-0.89c-0.61-0.92-0.36-2.16,0.55-2.77l2.44-1.62v-2.93
|
||||
c0-1.1,0.9-2,2-2s2,0.9,2,2v4c0,0.67-0.33,1.29-0.89,1.66l-3.33,2.22C99.5,87.14,99.12,87.25,98.74,87.25z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#ED6B21" d="M102.06,70.29c-1.1,0-2-0.9-2-2v-8.59c0-1.1,0.9-2,2-2s2,0.9,2,2v8.59
|
||||
C104.06,69.4,103.17,70.29,102.06,70.29z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#ED6B21" d="M102.06,50.97c-1.1,0-2-0.9-2-2v-2.93l-2.44-1.62c-0.92-0.61-1.17-1.85-0.55-2.77s1.85-1.17,2.77-0.55
|
||||
l3.33,2.22c0.56,0.37,0.89,1,0.89,1.66v4C104.06,50.07,103.17,50.97,102.06,50.97z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path fill="#ED6B21" d="M91.91,40.2c-0.38,0-0.77-0.11-1.11-0.34l-5.46-3.64c-0.92-0.61-1.17-1.85-0.55-2.77
|
||||
c0.61-0.92,1.85-1.17,2.77-0.55l5.46,3.64c0.92,0.61,1.17,1.85,0.55,2.77C93.19,39.89,92.55,40.2,91.91,40.2z M79.62,32.01
|
||||
c-0.38,0-0.77-0.11-1.11-0.34l-5.46-3.64c-0.92-0.61-1.17-1.85-0.55-2.77s1.85-1.17,2.77-0.55l5.46,3.64
|
||||
c0.92,0.61,1.17,1.85,0.55,2.77C80.9,31.69,80.26,32.01,79.62,32.01z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.6 KiB |
25
resources/icons/hollowing.svg
Normal file
25
resources/icons/hollowing.svg
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<g id="support">
|
||||
<polygon fill="none" stroke="#808080" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="8,1
|
||||
2.31,4.79 2.31,8.57 2.31,8.79 2.31,10.47 8,14.25 13.69,10.47 13.69,8.79 13.69,8.57 13.69,4.79 "/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="12.69" y1="15" x2="12.69" y2="12.44"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="13.87" y1="15" x2="13.87" y2="11.64"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="2.13" y1="15" x2="2.13" y2="11.64"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="3.33" y1="15" x2="3.33" y2="12.44"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="4.51" y1="15" x2="4.51" y2="13.22"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="5.66" y1="15" x2="5.66" y2="14"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="10.34" y1="15" x2="10.34" y2="14"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="11.5" y1="15" x2="11.5" y2="13.22"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
25
resources/icons/white/hollowing.svg
Normal file
25
resources/icons/white/hollowing.svg
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||
<g id="support">
|
||||
<polygon fill="none" stroke="#808080" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="8,1
|
||||
2.31,4.79 2.31,8.57 2.31,8.79 2.31,10.47 8,14.25 13.69,10.47 13.69,8.79 13.69,8.57 13.69,4.79 "/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="12.69" y1="15" x2="12.69" y2="12.44"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="13.87" y1="15" x2="13.87" y2="11.64"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="2.13" y1="15" x2="2.13" y2="11.64"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="3.33" y1="15" x2="3.33" y2="12.44"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="4.51" y1="15" x2="4.51" y2="13.22"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="5.66" y1="15" x2="5.66" y2="14"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="10.34" y1="15" x2="10.34" y2="14"/>
|
||||
|
||||
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="11.5" y1="15" x2="11.5" y2="13.22"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -1,3 +1,4 @@
|
|||
#add_subdirectory(slasupporttree)
|
||||
#add_subdirectory(openvdb)
|
||||
add_subdirectory(meshboolean)
|
||||
add_subdirectory(opencsg)
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
if (SLIC3R_STATIC)
|
||||
set(CGAL_Boost_USE_STATIC_LIBS ON)
|
||||
endif ()
|
||||
|
||||
find_package(CGAL REQUIRED)
|
||||
|
||||
add_executable(meshboolean MeshBoolean.cpp)
|
||||
|
||||
target_link_libraries(meshboolean libslic3r CGAL::CGAL)
|
||||
target_link_libraries(meshboolean libslic3r)
|
||||
|
||||
if (WIN32)
|
||||
prusaslicer_copy_dlls(meshboolean)
|
||||
|
|
|
@ -1,85 +1,76 @@
|
|||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#undef PI
|
||||
#include <igl/readOFF.h>
|
||||
//#undef IGL_STATIC_LIBRARY
|
||||
#include <igl/copyleft/cgal/mesh_boolean.h>
|
||||
|
||||
#include <Eigen/Core>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <admesh/stl.h>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/Model.hpp>
|
||||
#include <libslic3r/SLAPrint.hpp>
|
||||
#include <libslic3r/SLAPrintSteps.hpp>
|
||||
#include <libslic3r/MeshBoolean.hpp>
|
||||
|
||||
#include <libnest2d/tools/benchmark.h>
|
||||
|
||||
#include <boost/nowide/cstdio.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
bool its_write_obj(const Eigen::MatrixXd &V, Eigen::MatrixXi &F, const char *file)
|
||||
{
|
||||
|
||||
FILE *fp = boost::nowide::fopen(file, "w");
|
||||
if (fp == nullptr) {
|
||||
BOOST_LOG_TRIVIAL(error) << "stl_write_obj: Couldn't open " << file << " for writing";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < V.rows(); ++ i)
|
||||
fprintf(fp, "v %lf %lf %lf\n", V(i, 0), V(i, 1), V(i, 2));
|
||||
for (size_t i = 0; i < F.rows(); ++ i)
|
||||
fprintf(fp, "f %d %d %d\n", F(i, 0) + 1, F(i, 1) + 1, F(i, 2) + 1);
|
||||
fclose(fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
void mesh_boolean_test(const std::string &fname)
|
||||
{
|
||||
using namespace Eigen;
|
||||
using namespace std;
|
||||
// igl::readOFF(TUTORIAL_SHARED_PATH "/cheburashka.off",VA,FA);
|
||||
// igl::readOFF(TUTORIAL_SHARED_PATH "/decimated-knight.off",VB,FB);
|
||||
// Plot the mesh with pseudocolors
|
||||
// igl::opengl::glfw::Viewer viewer;
|
||||
|
||||
// Initialize
|
||||
// update(viewer);
|
||||
|
||||
//igl::copyleft::cgal::mesh_boolean(VA,FA,VB,FB,boolean_type,VC,FC,J);
|
||||
|
||||
|
||||
std::cout << fname.c_str() << std::endl;
|
||||
TriangleMesh mesh;
|
||||
|
||||
mesh.ReadSTLFile(fname.c_str());
|
||||
mesh.repair(true);
|
||||
its_write_obj(mesh.its, (fname + "-imported0.obj").c_str());
|
||||
|
||||
|
||||
Eigen::MatrixXd VA,VB,VC;
|
||||
Eigen::VectorXi J,I;
|
||||
Eigen::MatrixXi FA,FB,FC;
|
||||
igl::MeshBooleanType boolean_type(igl::MESH_BOOLEAN_TYPE_UNION);
|
||||
|
||||
|
||||
typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
|
||||
typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
|
||||
|
||||
Eigen::MatrixXd V = MapMatrixXfUnaligned(mesh.its.vertices.front().data(), mesh.its.vertices.size(), 3).cast<double>();
|
||||
Eigen::MatrixXi F = MapMatrixXiUnaligned(mesh.its.indices.front().data(), mesh.its.indices.size(), 3);
|
||||
|
||||
its_write_obj(V, F, (fname + "-imported.obj").c_str());
|
||||
// Self-union to clean up
|
||||
igl::copyleft::cgal::mesh_boolean(V, F, Eigen::MatrixXd(), Eigen::MatrixXi(), boolean_type, VC, FC);
|
||||
|
||||
its_write_obj(VC, FC, (fname + "-fixed.obj").c_str());
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
int main(const int argc, const char * argv[])
|
||||
{
|
||||
if (argc < 1) return -1;
|
||||
using namespace Slic3r;
|
||||
|
||||
Slic3r::mesh_boolean_test(argv[1]);
|
||||
if (argc <= 1) return EXIT_FAILURE;
|
||||
|
||||
DynamicPrintConfig cfg;
|
||||
auto model = Model::read_from_file(argv[1], &cfg);
|
||||
|
||||
if (model.objects.empty()) return EXIT_SUCCESS;
|
||||
|
||||
SLAPrint print;
|
||||
print.apply(model, cfg);
|
||||
PrintBase::TaskParams task;
|
||||
task.to_object_step = slaposHollowing;
|
||||
|
||||
print.set_task(task);
|
||||
print.process();
|
||||
|
||||
Benchmark bench;
|
||||
|
||||
for (SLAPrintObject *po : print.objects()) {
|
||||
TriangleMesh holes;
|
||||
sla::DrainHoles holepts = po->transformed_drainhole_points();
|
||||
|
||||
for (auto &hole: holepts)
|
||||
holes.merge(sla::to_triangle_mesh(hole.to_mesh()));
|
||||
|
||||
TriangleMesh hollowed_mesh = po->transformed_mesh();
|
||||
hollowed_mesh.merge(po->hollowed_interior_mesh());
|
||||
|
||||
hollowed_mesh.require_shared_vertices();
|
||||
holes.require_shared_vertices();
|
||||
|
||||
TriangleMesh drilled_mesh_igl = hollowed_mesh;
|
||||
bench.start();
|
||||
MeshBoolean::minus(drilled_mesh_igl, holes);
|
||||
bench.stop();
|
||||
|
||||
std::cout << "Mesh boolean duration with IGL: " << bench.getElapsedSec() << std::endl;
|
||||
|
||||
TriangleMesh drilled_mesh_cgal = hollowed_mesh;
|
||||
bench.start();
|
||||
MeshBoolean::cgal::self_union(drilled_mesh_cgal);
|
||||
MeshBoolean::cgal::minus(drilled_mesh_cgal, holes);
|
||||
bench.stop();
|
||||
|
||||
std::cout << "Mesh boolean duration with CGAL: " << bench.getElapsedSec() << std::endl;
|
||||
|
||||
std::string name("obj"), outf;
|
||||
outf = name + "igl" + std::to_string(po->model_object()->id().id) + ".obj";
|
||||
drilled_mesh_igl.WriteOBJFile(outf.c_str());
|
||||
|
||||
outf = name + "cgal" + std::to_string(po->model_object()->id().id) + ".obj";
|
||||
drilled_mesh_cgal.WriteOBJFile(outf.c_str());
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
29
sandboxes/opencsg/CMakeLists.txt
Normal file
29
sandboxes/opencsg/CMakeLists.txt
Normal file
|
@ -0,0 +1,29 @@
|
|||
cmake_minimum_required(VERSION 3.0)
|
||||
|
||||
project(OpenCSG-example)
|
||||
|
||||
add_executable(opencsg_example WIN32
|
||||
main.cpp
|
||||
Engine.hpp Engine.cpp
|
||||
ShaderCSGDisplay.hpp ShaderCSGDisplay.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/ProgressStatusBar.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/I18N.hpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/I18N.cpp)
|
||||
|
||||
find_package(wxWidgets 3.1 REQUIRED COMPONENTS core base gl html)
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(GLEW REQUIRED)
|
||||
find_package(OpenCSG REQUIRED)
|
||||
include(${wxWidgets_USE_FILE})
|
||||
|
||||
target_link_libraries(opencsg_example libslic3r)
|
||||
target_include_directories(opencsg_example PRIVATE ${wxWidgets_INCLUDE_DIRS})
|
||||
target_compile_definitions(opencsg_example PRIVATE ${wxWidgets_DEFINITIONS})
|
||||
|
||||
slic3r_remap_configs(OpenCSG::opencsg RelWithDebInfo Release)
|
||||
target_link_libraries(opencsg_example ${wxWidgets_LIBRARIES}
|
||||
OpenCSG::opencsg
|
||||
GLEW::GLEW
|
||||
OpenGL::GL
|
||||
#-lXrandr -lXext -lX11
|
||||
)
|
498
sandboxes/opencsg/Engine.cpp
Normal file
498
sandboxes/opencsg/Engine.cpp
Normal file
|
@ -0,0 +1,498 @@
|
|||
#include "Engine.hpp"
|
||||
#include <libslic3r/Utils.hpp>
|
||||
#include <libslic3r/SLAPrint.hpp>
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define HAS_GLSAFE
|
||||
#endif
|
||||
|
||||
#ifdef HAS_GLSAFE
|
||||
extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name);
|
||||
inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); }
|
||||
#define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
|
||||
#define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
|
||||
|
||||
void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name)
|
||||
{
|
||||
GLenum err = glGetError();
|
||||
if (err == GL_NO_ERROR)
|
||||
return;
|
||||
const char *sErr = 0;
|
||||
switch (err) {
|
||||
case GL_INVALID_ENUM: sErr = "Invalid Enum"; break;
|
||||
case GL_INVALID_VALUE: sErr = "Invalid Value"; break;
|
||||
// be aware that GL_INVALID_OPERATION is generated if glGetError is executed between the execution of glBegin and the corresponding execution of glEnd
|
||||
case GL_INVALID_OPERATION: sErr = "Invalid Operation"; break;
|
||||
case GL_STACK_OVERFLOW: sErr = "Stack Overflow"; break;
|
||||
case GL_STACK_UNDERFLOW: sErr = "Stack Underflow"; break;
|
||||
case GL_OUT_OF_MEMORY: sErr = "Out Of Memory"; break;
|
||||
default: sErr = "Unknown"; break;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(error) << "OpenGL error in " << file_name << ":" << line << ", function " << function_name << "() : " << (int)err << " - " << sErr;
|
||||
assert(false);
|
||||
}
|
||||
|
||||
#else
|
||||
inline void glAssertRecentCall() { }
|
||||
#define glsafe(cmd) cmd
|
||||
#define glcheck()
|
||||
#endif
|
||||
|
||||
namespace Slic3r { namespace GL {
|
||||
|
||||
Scene::Scene() = default;
|
||||
Scene::~Scene() = default;
|
||||
|
||||
void CSGDisplay::render_scene()
|
||||
{
|
||||
GLfloat color[] = {1.f, 1.f, 0.f, 0.f};
|
||||
glsafe(::glColor4fv(color));
|
||||
|
||||
if (m_csgsettings.is_enabled()) {
|
||||
OpenCSG::render(m_scene_cache.primitives_csg);
|
||||
glDepthFunc(GL_EQUAL);
|
||||
}
|
||||
|
||||
for (auto& p : m_scene_cache.primitives_csg) p->render();
|
||||
if (m_csgsettings.is_enabled()) glDepthFunc(GL_LESS);
|
||||
|
||||
for (auto& p : m_scene_cache.primitives_free) p->render();
|
||||
|
||||
glFlush();
|
||||
}
|
||||
|
||||
void Scene::set_print(uqptr<SLAPrint> &&print)
|
||||
{
|
||||
m_print = std::move(print);
|
||||
|
||||
// Notify displays
|
||||
call(&Listener::on_scene_updated, m_listeners, *this);
|
||||
}
|
||||
|
||||
BoundingBoxf3 Scene::get_bounding_box() const
|
||||
{
|
||||
return m_print->model().bounding_box();
|
||||
}
|
||||
|
||||
void CSGDisplay::SceneCache::clear()
|
||||
{
|
||||
primitives_csg.clear();
|
||||
primitives_free.clear();
|
||||
primitives.clear();
|
||||
}
|
||||
|
||||
shptr<Primitive> CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh)
|
||||
{
|
||||
auto p = std::make_shared<Primitive>();
|
||||
p->load_mesh(mesh);
|
||||
primitives.emplace_back(p);
|
||||
primitives_free.emplace_back(p.get());
|
||||
return p;
|
||||
}
|
||||
|
||||
shptr<Primitive> CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh,
|
||||
OpenCSG::Operation o,
|
||||
unsigned c)
|
||||
{
|
||||
auto p = std::make_shared<Primitive>(o, c);
|
||||
p->load_mesh(mesh);
|
||||
primitives.emplace_back(p);
|
||||
primitives_csg.emplace_back(p.get());
|
||||
return p;
|
||||
}
|
||||
|
||||
void IndexedVertexArray::push_geometry(float x, float y, float z, float nx, float ny, float nz)
|
||||
{
|
||||
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
|
||||
if (this->vertices_and_normals_interleaved_VBO_id != 0)
|
||||
return;
|
||||
|
||||
if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity())
|
||||
this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6));
|
||||
this->vertices_and_normals_interleaved.emplace_back(nx);
|
||||
this->vertices_and_normals_interleaved.emplace_back(ny);
|
||||
this->vertices_and_normals_interleaved.emplace_back(nz);
|
||||
this->vertices_and_normals_interleaved.emplace_back(x);
|
||||
this->vertices_and_normals_interleaved.emplace_back(y);
|
||||
this->vertices_and_normals_interleaved.emplace_back(z);
|
||||
|
||||
this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size();
|
||||
}
|
||||
|
||||
void IndexedVertexArray::push_triangle(int idx1, int idx2, int idx3) {
|
||||
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
|
||||
if (this->vertices_and_normals_interleaved_VBO_id != 0)
|
||||
return;
|
||||
|
||||
if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity())
|
||||
this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3));
|
||||
this->triangle_indices.emplace_back(idx1);
|
||||
this->triangle_indices.emplace_back(idx2);
|
||||
this->triangle_indices.emplace_back(idx3);
|
||||
this->triangle_indices_size = this->triangle_indices.size();
|
||||
}
|
||||
|
||||
void IndexedVertexArray::load_mesh(const TriangleMesh &mesh)
|
||||
{
|
||||
assert(triangle_indices.empty() && vertices_and_normals_interleaved_size == 0);
|
||||
assert(quad_indices.empty() && triangle_indices_size == 0);
|
||||
assert(vertices_and_normals_interleaved.size() % 6 == 0 && quad_indices_size == vertices_and_normals_interleaved.size());
|
||||
|
||||
this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count());
|
||||
|
||||
int vertices_count = 0;
|
||||
for (size_t i = 0; i < mesh.stl.stats.number_of_facets; ++i) {
|
||||
const stl_facet &facet = mesh.stl.facet_start[i];
|
||||
for (int j = 0; j < 3; ++j)
|
||||
this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2));
|
||||
|
||||
this->push_triangle(vertices_count, vertices_count + 1, vertices_count + 2);
|
||||
vertices_count += 3;
|
||||
}
|
||||
}
|
||||
|
||||
void IndexedVertexArray::finalize_geometry()
|
||||
{
|
||||
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
|
||||
assert(this->triangle_indices_VBO_id == 0);
|
||||
assert(this->quad_indices_VBO_id == 0);
|
||||
|
||||
if (!this->vertices_and_normals_interleaved.empty()) {
|
||||
glsafe(
|
||||
::glGenBuffers(1, &this->vertices_and_normals_interleaved_VBO_id));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER,
|
||||
this->vertices_and_normals_interleaved_VBO_id));
|
||||
glsafe(
|
||||
::glBufferData(GL_ARRAY_BUFFER,
|
||||
GLsizeiptr(
|
||||
this->vertices_and_normals_interleaved.size() *
|
||||
4),
|
||||
this->vertices_and_normals_interleaved.data(),
|
||||
GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
this->vertices_and_normals_interleaved.clear();
|
||||
}
|
||||
if (!this->triangle_indices.empty()) {
|
||||
glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_id));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
||||
this->triangle_indices_VBO_id));
|
||||
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
||||
GLsizeiptr(this->triangle_indices.size() * 4),
|
||||
this->triangle_indices.data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
this->triangle_indices.clear();
|
||||
}
|
||||
if (!this->quad_indices.empty()) {
|
||||
glsafe(::glGenBuffers(1, &this->quad_indices_VBO_id));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
||||
this->quad_indices_VBO_id));
|
||||
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
||||
GLsizeiptr(this->quad_indices.size() * 4),
|
||||
this->quad_indices.data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
this->quad_indices.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void IndexedVertexArray::release_geometry()
|
||||
{
|
||||
if (this->vertices_and_normals_interleaved_VBO_id) {
|
||||
glsafe(
|
||||
::glDeleteBuffers(1,
|
||||
&this->vertices_and_normals_interleaved_VBO_id));
|
||||
this->vertices_and_normals_interleaved_VBO_id = 0;
|
||||
}
|
||||
if (this->triangle_indices_VBO_id) {
|
||||
glsafe(::glDeleteBuffers(1, &this->triangle_indices_VBO_id));
|
||||
this->triangle_indices_VBO_id = 0;
|
||||
}
|
||||
if (this->quad_indices_VBO_id) {
|
||||
glsafe(::glDeleteBuffers(1, &this->quad_indices_VBO_id));
|
||||
this->quad_indices_VBO_id = 0;
|
||||
}
|
||||
this->clear();
|
||||
}
|
||||
|
||||
void IndexedVertexArray::render() const
|
||||
{
|
||||
assert(this->vertices_and_normals_interleaved_VBO_id != 0);
|
||||
assert(this->triangle_indices_VBO_id != 0 ||
|
||||
this->quad_indices_VBO_id != 0);
|
||||
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER,
|
||||
this->vertices_and_normals_interleaved_VBO_id));
|
||||
glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float),
|
||||
reinterpret_cast<const void *>(3 * sizeof(float))));
|
||||
glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr));
|
||||
|
||||
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
||||
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
|
||||
|
||||
// Render using the Vertex Buffer Objects.
|
||||
if (this->triangle_indices_size > 0) {
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
||||
this->triangle_indices_VBO_id));
|
||||
glsafe(::glDrawElements(GL_TRIANGLES,
|
||||
GLsizei(this->triangle_indices_size),
|
||||
GL_UNSIGNED_INT, nullptr));
|
||||
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
}
|
||||
if (this->quad_indices_size > 0) {
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
||||
this->quad_indices_VBO_id));
|
||||
glsafe(::glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size),
|
||||
GL_UNSIGNED_INT, nullptr));
|
||||
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
}
|
||||
|
||||
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
|
||||
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
|
||||
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
}
|
||||
|
||||
void IndexedVertexArray::clear() {
|
||||
this->vertices_and_normals_interleaved.clear();
|
||||
this->triangle_indices.clear();
|
||||
this->quad_indices.clear();
|
||||
vertices_and_normals_interleaved_size = 0;
|
||||
triangle_indices_size = 0;
|
||||
quad_indices_size = 0;
|
||||
}
|
||||
|
||||
void IndexedVertexArray::shrink_to_fit() {
|
||||
this->vertices_and_normals_interleaved.shrink_to_fit();
|
||||
this->triangle_indices.shrink_to_fit();
|
||||
this->quad_indices.shrink_to_fit();
|
||||
}
|
||||
|
||||
void Volume::render()
|
||||
{
|
||||
glsafe(::glPushMatrix());
|
||||
glsafe(::glMultMatrixd(m_trafo.get_matrix().data()));
|
||||
m_geom.render();
|
||||
glsafe(::glPopMatrix());
|
||||
}
|
||||
|
||||
void Display::clear_screen()
|
||||
{
|
||||
glViewport(0, 0, GLsizei(m_size.x()), GLsizei(m_size.y()));
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||
}
|
||||
|
||||
Display::~Display()
|
||||
{
|
||||
OpenCSG::freeResources();
|
||||
}
|
||||
|
||||
void Display::set_active(long width, long height)
|
||||
{
|
||||
if (!m_initialized) {
|
||||
glewInit();
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
// gray background
|
||||
glClearColor(0.9f, 0.9f, 0.9f, 1.0f);
|
||||
|
||||
// Enable two OpenGL lights
|
||||
GLfloat light_diffuse[] = { 1.0f, 1.0f, 0.0f, 1.0f}; // White diffuse light
|
||||
GLfloat light_position0[] = {-1.0f, -1.0f, -1.0f, 0.0f}; // Infinite light location
|
||||
GLfloat light_position1[] = { 1.0f, 1.0f, 1.0f, 0.0f}; // Infinite light location
|
||||
|
||||
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
|
||||
glLightfv(GL_LIGHT0, GL_POSITION, light_position0);
|
||||
glEnable(GL_LIGHT0);
|
||||
glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse);
|
||||
glLightfv(GL_LIGHT1, GL_POSITION, light_position1);
|
||||
glEnable(GL_LIGHT1);
|
||||
glEnable(GL_LIGHTING);
|
||||
glEnable(GL_NORMALIZE);
|
||||
|
||||
// Use depth buffering for hidden surface elimination
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
|
||||
set_screen_size(width, height);
|
||||
}
|
||||
|
||||
void Display::set_screen_size(long width, long height)
|
||||
{
|
||||
if (m_size.x() != width || m_size.y() != height)
|
||||
m_camera->set_screen(width, height);
|
||||
|
||||
m_size = {width, height};
|
||||
}
|
||||
|
||||
void Display::repaint()
|
||||
{
|
||||
clear_screen();
|
||||
|
||||
m_camera->view();
|
||||
render_scene();
|
||||
|
||||
m_fps_counter.update();
|
||||
|
||||
swap_buffers();
|
||||
}
|
||||
|
||||
void Controller::on_scene_updated(const Scene &scene)
|
||||
{
|
||||
const SLAPrint *print = scene.get_print();
|
||||
if (!print) return;
|
||||
|
||||
auto bb = scene.get_bounding_box();
|
||||
double d = std::max(std::max(bb.size().x(), bb.size().y()), bb.size().z());
|
||||
m_wheel_pos = long(2 * d);
|
||||
|
||||
call_cameras(&Camera::set_zoom, m_wheel_pos);
|
||||
call(&Display::on_scene_updated, m_displays, scene);
|
||||
}
|
||||
|
||||
void Controller::on_scroll(long v, long d, MouseInput::WheelAxis /*wa*/)
|
||||
{
|
||||
m_wheel_pos += v / d;
|
||||
|
||||
call_cameras(&Camera::set_zoom, m_wheel_pos);
|
||||
call(&Display::repaint, m_displays);
|
||||
}
|
||||
|
||||
void Controller::on_moved_to(long x, long y)
|
||||
{
|
||||
if (m_left_btn) {
|
||||
call_cameras(&Camera::rotate, (Vec2i{x, y} - m_mouse_pos).cast<float>());
|
||||
call(&Display::repaint, m_displays);
|
||||
}
|
||||
|
||||
m_mouse_pos = {x, y};
|
||||
}
|
||||
|
||||
void CSGDisplay::apply_csgsettings(const CSGSettings &settings)
|
||||
{
|
||||
using namespace OpenCSG;
|
||||
|
||||
bool needupdate = m_csgsettings.get_convexity() != settings.get_convexity();
|
||||
|
||||
m_csgsettings = settings;
|
||||
setOption(AlgorithmSetting, m_csgsettings.get_algo());
|
||||
setOption(DepthComplexitySetting, m_csgsettings.get_depth_algo());
|
||||
setOption(DepthBoundsOptimization, m_csgsettings.get_optimization());
|
||||
|
||||
if (needupdate) {
|
||||
for (OpenCSG::Primitive * p : m_scene_cache.primitives_csg)
|
||||
if (p->getConvexity() > 1)
|
||||
p->setConvexity(m_csgsettings.get_convexity());
|
||||
}
|
||||
}
|
||||
|
||||
void CSGDisplay::on_scene_updated(const Scene &scene)
|
||||
{
|
||||
const SLAPrint *print = scene.get_print();
|
||||
if (!print) return;
|
||||
|
||||
m_scene_cache.clear();
|
||||
|
||||
for (const SLAPrintObject *po : print->objects()) {
|
||||
const ModelObject *mo = po->model_object();
|
||||
TriangleMesh msh = mo->raw_mesh();
|
||||
|
||||
sla::DrainHoles holedata = mo->sla_drain_holes;
|
||||
|
||||
for (const ModelInstance *mi : mo->instances) {
|
||||
|
||||
TriangleMesh mshinst = msh;
|
||||
auto interior = po->hollowed_interior_mesh();
|
||||
interior.transform(po->trafo().inverse());
|
||||
|
||||
mshinst.merge(interior);
|
||||
mshinst.require_shared_vertices();
|
||||
|
||||
mi->transform_mesh(&mshinst);
|
||||
|
||||
auto bb = mshinst.bounding_box();
|
||||
auto center = bb.center().cast<float>();
|
||||
mshinst.translate(-center);
|
||||
|
||||
mshinst.require_shared_vertices();
|
||||
m_scene_cache.add_mesh(mshinst, OpenCSG::Intersection,
|
||||
m_csgsettings.get_convexity());
|
||||
}
|
||||
|
||||
for (const sla::DrainHole &holept : holedata) {
|
||||
TriangleMesh holemesh = sla::to_triangle_mesh(holept.to_mesh());
|
||||
holemesh.require_shared_vertices();
|
||||
m_scene_cache.add_mesh(holemesh, OpenCSG::Subtraction, 1);
|
||||
}
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
void Camera::view()
|
||||
{
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
glLoadIdentity();
|
||||
gluLookAt(0.0, m_zoom, 0.0, /* eye is at (0,zoom,0) */
|
||||
m_referene.x(), m_referene.y(), m_referene.z(),
|
||||
0.0, 0.0, 1.0); /* up is in positive Y direction */
|
||||
|
||||
// TODO Could have been set in prevoius gluLookAt in first argument
|
||||
glRotatef(m_rot.y(), 1.0, 0.0, 0.0);
|
||||
glRotatef(m_rot.x(), 0.0, 0.0, 1.0);
|
||||
|
||||
if (m_clip_z > 0.) {
|
||||
GLdouble plane[] = {0., 0., 1., m_clip_z};
|
||||
glClipPlane(GL_CLIP_PLANE0, plane);
|
||||
glEnable(GL_CLIP_PLANE0);
|
||||
} else {
|
||||
glDisable(GL_CLIP_PLANE0);
|
||||
}
|
||||
}
|
||||
|
||||
void PerspectiveCamera::set_screen(long width, long height)
|
||||
{
|
||||
// Setup the view of the CSG shape
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
gluPerspective(45.0, width / double(height), .1, 200.0);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
}
|
||||
|
||||
bool enable_multisampling(bool e)
|
||||
{
|
||||
if (!e) { glDisable(GL_MULTISAMPLE); return false; }
|
||||
|
||||
GLint is_ms_context;
|
||||
glGetIntegerv(GL_SAMPLE_BUFFERS, &is_ms_context);
|
||||
|
||||
if (is_ms_context) { glEnable(GL_MULTISAMPLE); return true; }
|
||||
else return false;
|
||||
}
|
||||
|
||||
MouseInput::Listener::~Listener() = default;
|
||||
|
||||
void FpsCounter::update()
|
||||
{
|
||||
++m_frames;
|
||||
|
||||
TimePoint msec = Clock::now();
|
||||
|
||||
double seconds_window = to_sec(msec - m_window);
|
||||
m_fps = 0.5 * m_fps + 0.5 * (m_frames / seconds_window);
|
||||
|
||||
if (to_sec(msec - m_last) >= m_resolution) {
|
||||
m_last = msec;
|
||||
for (auto &l : m_listeners) l(m_fps);
|
||||
}
|
||||
|
||||
if (seconds_window >= m_window_size) {
|
||||
m_frames = 0;
|
||||
m_window = msec;
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GL
|
493
sandboxes/opencsg/Engine.hpp
Normal file
493
sandboxes/opencsg/Engine.hpp
Normal file
|
@ -0,0 +1,493 @@
|
|||
#ifndef SLIC3R_OCSG_EXMP_ENGINE_HPP
|
||||
#define SLIC3R_OCSG_EXMP_ENGINE_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <chrono>
|
||||
|
||||
#include <libslic3r/Geometry.hpp>
|
||||
#include <libslic3r/Model.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
#include <opencsg/opencsg.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class SLAPrint;
|
||||
|
||||
namespace GL {
|
||||
|
||||
// Simple shorthands for smart pointers
|
||||
template<class T> using shptr = std::shared_ptr<T>;
|
||||
template<class T> using uqptr = std::unique_ptr<T>;
|
||||
template<class T> using wkptr = std::weak_ptr<T>;
|
||||
|
||||
template<class T, class A = std::allocator<T>> using vector = std::vector<T, A>;
|
||||
|
||||
// remove empty weak pointers from a vector
|
||||
template<class L> inline void cleanup(vector<std::weak_ptr<L>> &listeners) {
|
||||
auto it = std::remove_if(listeners.begin(), listeners.end(),
|
||||
[](auto &l) { return !l.lock(); });
|
||||
listeners.erase(it, listeners.end());
|
||||
}
|
||||
|
||||
// Call a class method on each element of a vector of objects (weak pointers)
|
||||
// of the same type.
|
||||
template<class F, class L, class...Args>
|
||||
inline void call(F &&f, vector<std::weak_ptr<L>> &listeners, Args&&... args) {
|
||||
for (auto &l : listeners)
|
||||
if (auto p = l.lock()) ((p.get())->*f)(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// A representation of a mouse input for the engine.
|
||||
class MouseInput
|
||||
{
|
||||
public:
|
||||
enum WheelAxis { waVertical, waHorizontal };
|
||||
|
||||
// Interface to implement if an object wants to receive notifications
|
||||
// about mouse events.
|
||||
class Listener {
|
||||
public:
|
||||
virtual ~Listener();
|
||||
|
||||
virtual void on_left_click_down() {}
|
||||
virtual void on_left_click_up() {}
|
||||
virtual void on_right_click_down() {}
|
||||
virtual void on_right_click_up() {}
|
||||
virtual void on_double_click() {}
|
||||
virtual void on_scroll(long /*v*/, long /*delta*/, WheelAxis ) {}
|
||||
virtual void on_moved_to(long /*x*/, long /*y*/) {}
|
||||
};
|
||||
|
||||
private:
|
||||
vector<wkptr<Listener>> m_listeners;
|
||||
|
||||
public:
|
||||
virtual ~MouseInput() = default;
|
||||
|
||||
virtual void left_click_down()
|
||||
{
|
||||
call(&Listener::on_left_click_down, m_listeners);
|
||||
}
|
||||
virtual void left_click_up()
|
||||
{
|
||||
call(&Listener::on_left_click_up, m_listeners);
|
||||
}
|
||||
virtual void right_click_down()
|
||||
{
|
||||
call(&Listener::on_right_click_down, m_listeners);
|
||||
}
|
||||
virtual void right_click_up()
|
||||
{
|
||||
call(&Listener::on_right_click_up, m_listeners);
|
||||
}
|
||||
virtual void double_click()
|
||||
{
|
||||
call(&Listener::on_double_click, m_listeners);
|
||||
}
|
||||
virtual void scroll(long v, long d, WheelAxis wa)
|
||||
{
|
||||
call(&Listener::on_scroll, m_listeners, v, d, wa);
|
||||
}
|
||||
virtual void move_to(long x, long y)
|
||||
{
|
||||
call(&Listener::on_moved_to, m_listeners, x, y);
|
||||
}
|
||||
|
||||
void add_listener(shptr<Listener> listener)
|
||||
{
|
||||
m_listeners.emplace_back(listener);
|
||||
cleanup(m_listeners);
|
||||
}
|
||||
};
|
||||
|
||||
// This is a stripped down version of Slic3r::IndexedVertexArray
|
||||
class IndexedVertexArray {
|
||||
public:
|
||||
~IndexedVertexArray() { release_geometry(); }
|
||||
|
||||
// Vertices and their normals, interleaved to be used by void
|
||||
// glInterleavedArrays(GL_N3F_V3F, 0, x)
|
||||
vector<float> vertices_and_normals_interleaved;
|
||||
vector<int> triangle_indices;
|
||||
vector<int> quad_indices;
|
||||
|
||||
// When the geometry data is loaded into the graphics card as Vertex
|
||||
// Buffer Objects, the above mentioned std::vectors are cleared and the
|
||||
// following variables keep their original length.
|
||||
size_t vertices_and_normals_interleaved_size{ 0 };
|
||||
size_t triangle_indices_size{ 0 };
|
||||
size_t quad_indices_size{ 0 };
|
||||
|
||||
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
|
||||
// Zero if the VBOs are not sent to GPU yet.
|
||||
unsigned int vertices_and_normals_interleaved_VBO_id{ 0 };
|
||||
unsigned int triangle_indices_VBO_id{ 0 };
|
||||
unsigned int quad_indices_VBO_id{ 0 };
|
||||
|
||||
|
||||
void push_geometry(float x, float y, float z, float nx, float ny, float nz);
|
||||
|
||||
inline void push_geometry(
|
||||
double x, double y, double z, double nx, double ny, double nz)
|
||||
{
|
||||
push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz));
|
||||
}
|
||||
|
||||
inline void push_geometry(const Vec3d &p, const Vec3d &n)
|
||||
{
|
||||
push_geometry(p(0), p(1), p(2), n(0), n(1), n(2));
|
||||
}
|
||||
|
||||
void push_triangle(int idx1, int idx2, int idx3);
|
||||
|
||||
void load_mesh(const TriangleMesh &mesh);
|
||||
|
||||
inline bool has_VBOs() const
|
||||
{
|
||||
return vertices_and_normals_interleaved_VBO_id != 0;
|
||||
}
|
||||
|
||||
// Finalize the initialization of the geometry & indices,
|
||||
// upload the geometry and indices to OpenGL VBO objects
|
||||
// and shrink the allocated data, possibly relasing it if it has been
|
||||
// loaded into the VBOs.
|
||||
void finalize_geometry();
|
||||
// Release the geometry data, release OpenGL VBOs.
|
||||
void release_geometry();
|
||||
|
||||
void render() const;
|
||||
|
||||
// Is there any geometry data stored?
|
||||
bool empty() const { return vertices_and_normals_interleaved_size == 0; }
|
||||
|
||||
void clear();
|
||||
|
||||
// Shrink the internal storage to tighly fit the data stored.
|
||||
void shrink_to_fit();
|
||||
};
|
||||
|
||||
// Try to enable or disable multisampling.
|
||||
bool enable_multisampling(bool e = true);
|
||||
|
||||
class Volume {
|
||||
IndexedVertexArray m_geom;
|
||||
Geometry::Transformation m_trafo;
|
||||
|
||||
public:
|
||||
|
||||
void render();
|
||||
|
||||
void translation(const Vec3d &offset) { m_trafo.set_offset(offset); }
|
||||
void rotation(const Vec3d &rot) { m_trafo.set_rotation(rot); }
|
||||
void scale(const Vec3d &scaleing) { m_trafo.set_scaling_factor(scaleing); }
|
||||
void scale(double s) { scale({s, s, s}); }
|
||||
|
||||
inline void load_mesh(const TriangleMesh &mesh)
|
||||
{
|
||||
m_geom.load_mesh(mesh);
|
||||
m_geom.finalize_geometry();
|
||||
}
|
||||
};
|
||||
|
||||
// A primitive that can be used with OpenCSG rendering algorithms.
|
||||
// Does a similar job to GLVolume.
|
||||
class Primitive : public Volume, public OpenCSG::Primitive
|
||||
{
|
||||
public:
|
||||
using OpenCSG::Primitive::Primitive;
|
||||
|
||||
Primitive() : OpenCSG::Primitive(OpenCSG::Intersection, 1) {}
|
||||
|
||||
void render() override { Volume::render(); }
|
||||
};
|
||||
|
||||
// A simple representation of a camera in a 3D scene
|
||||
class Camera {
|
||||
protected:
|
||||
Vec2f m_rot = {0., 0.};
|
||||
Vec3d m_referene = {0., 0., 0.};
|
||||
double m_zoom = 0.;
|
||||
double m_clip_z = 0.;
|
||||
public:
|
||||
|
||||
virtual ~Camera() = default;
|
||||
|
||||
virtual void view();
|
||||
virtual void set_screen(long width, long height) = 0;
|
||||
|
||||
void set_rotation(const Vec2f &rotation) { m_rot = rotation; }
|
||||
void rotate(const Vec2f &rotation) { m_rot += rotation; }
|
||||
void set_zoom(double z) { m_zoom = z; }
|
||||
void set_reference_point(const Vec3d &p) { m_referene = p; }
|
||||
void set_clip_z(double z) { m_clip_z = z; }
|
||||
};
|
||||
|
||||
// Reset a camera object
|
||||
inline void reset(Camera &cam)
|
||||
{
|
||||
cam.set_rotation({0., 0.});
|
||||
cam.set_zoom(0.);
|
||||
cam.set_reference_point({0., 0., 0.});
|
||||
cam.set_clip_z(0.);
|
||||
}
|
||||
|
||||
// Specialization of a camera which shows in perspective projection
|
||||
class PerspectiveCamera: public Camera {
|
||||
public:
|
||||
|
||||
void set_screen(long width, long height) override;
|
||||
};
|
||||
|
||||
// A simple counter of FPS. Subscribed objects will receive updates of the
|
||||
// current fps.
|
||||
class FpsCounter {
|
||||
vector<std::function<void(double)>> m_listeners;
|
||||
|
||||
using Clock = std::chrono::high_resolution_clock;
|
||||
using Duration = Clock::duration;
|
||||
using TimePoint = Clock::time_point;
|
||||
|
||||
int m_frames = 0;
|
||||
TimePoint m_last = Clock::now(), m_window = m_last;
|
||||
|
||||
double m_resolution = 0.1, m_window_size = 1.0;
|
||||
double m_fps = 0.;
|
||||
|
||||
static double to_sec(Duration d)
|
||||
{
|
||||
return d.count() * double(Duration::period::num) / Duration::period::den;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
void update();
|
||||
|
||||
void add_listener(std::function<void(double)> lst)
|
||||
{
|
||||
m_listeners.emplace_back(lst);
|
||||
}
|
||||
|
||||
void clear_listeners() { m_listeners = {}; }
|
||||
|
||||
void set_notification_interval(double seconds);
|
||||
void set_measure_window_size(double seconds);
|
||||
|
||||
double get_notification_interval() const { return m_resolution; }
|
||||
double get_mesure_window_size() const { return m_window_size; }
|
||||
};
|
||||
|
||||
// Collection of the used OpenCSG library settings.
|
||||
class CSGSettings {
|
||||
public:
|
||||
static const constexpr unsigned DEFAULT_CONVEXITY = 10;
|
||||
|
||||
private:
|
||||
OpenCSG::Algorithm m_csgalg = OpenCSG::Algorithm::Automatic;
|
||||
OpenCSG::DepthComplexityAlgorithm m_depth_algo = OpenCSG::NoDepthComplexitySampling;
|
||||
OpenCSG::Optimization m_optim = OpenCSG::OptimizationDefault;
|
||||
bool m_enable = true;
|
||||
unsigned int m_convexity = DEFAULT_CONVEXITY;
|
||||
|
||||
public:
|
||||
int get_algo() const { return int(m_csgalg); }
|
||||
void set_algo(int alg)
|
||||
{
|
||||
if (alg < OpenCSG::Algorithm::AlgorithmUnused)
|
||||
m_csgalg = OpenCSG::Algorithm(alg);
|
||||
}
|
||||
|
||||
int get_depth_algo() const { return int(m_depth_algo); }
|
||||
void set_depth_algo(int alg)
|
||||
{
|
||||
if (alg < OpenCSG::DepthComplexityAlgorithmUnused)
|
||||
m_depth_algo = OpenCSG::DepthComplexityAlgorithm(alg);
|
||||
}
|
||||
|
||||
int get_optimization() const { return int(m_optim); }
|
||||
void set_optimization(int o)
|
||||
{
|
||||
if (o < OpenCSG::Optimization::OptimizationUnused)
|
||||
m_optim = OpenCSG::Optimization(o);
|
||||
}
|
||||
|
||||
void enable_csg(bool en = true) { m_enable = en; }
|
||||
bool is_enabled() const { return m_enable; }
|
||||
|
||||
unsigned get_convexity() const { return m_convexity; }
|
||||
void set_convexity(unsigned c) { m_convexity = c; }
|
||||
};
|
||||
|
||||
// The scene is a wrapper around SLAPrint which holds the data to be visualized.
|
||||
class Scene
|
||||
{
|
||||
uqptr<SLAPrint> m_print;
|
||||
public:
|
||||
|
||||
// Subscribers will be notified if the model is changed. This might be a
|
||||
// display which will have to load the meshes and repaint itself when
|
||||
// the scene data changes.
|
||||
// eg. We load a new 3mf through the UI, this will notify the controller
|
||||
// associated with the scene and all the displays that the controller is
|
||||
// connected with.
|
||||
class Listener {
|
||||
public:
|
||||
virtual ~Listener() = default;
|
||||
virtual void on_scene_updated(const Scene &scene) = 0;
|
||||
};
|
||||
|
||||
Scene();
|
||||
~Scene();
|
||||
|
||||
void set_print(uqptr<SLAPrint> &&print);
|
||||
const SLAPrint * get_print() const { return m_print.get(); }
|
||||
|
||||
BoundingBoxf3 get_bounding_box() const;
|
||||
|
||||
void add_listener(shptr<Listener> listener)
|
||||
{
|
||||
m_listeners.emplace_back(listener);
|
||||
cleanup(m_listeners);
|
||||
}
|
||||
|
||||
private:
|
||||
vector<wkptr<Listener>> m_listeners;
|
||||
};
|
||||
|
||||
// The basic Display. This is almost just an interface but will do all the
|
||||
// initialization and show the fps values. Overriding the render_scene is
|
||||
// needed to show the scene content. The specific method of displaying the
|
||||
// scene is up the the particular implementation (OpenCSG or other screen space
|
||||
// boolean algorithms)
|
||||
class Display : public Scene::Listener
|
||||
{
|
||||
protected:
|
||||
Vec2i m_size;
|
||||
bool m_initialized = false;
|
||||
|
||||
shptr<Camera> m_camera;
|
||||
FpsCounter m_fps_counter;
|
||||
|
||||
public:
|
||||
|
||||
explicit Display(shptr<Camera> camera = nullptr)
|
||||
: m_camera(camera ? camera : std::make_shared<PerspectiveCamera>())
|
||||
{}
|
||||
|
||||
~Display() override;
|
||||
|
||||
shptr<const Camera> get_camera() const { return m_camera; }
|
||||
shptr<Camera> get_camera() { return m_camera; }
|
||||
void set_camera(shptr<Camera> cam) { m_camera = cam; }
|
||||
|
||||
virtual void swap_buffers() = 0;
|
||||
virtual void set_active(long width, long height);
|
||||
virtual void set_screen_size(long width, long height);
|
||||
Vec2i get_screen_size() const { return m_size; }
|
||||
|
||||
virtual void repaint();
|
||||
|
||||
bool is_initialized() const { return m_initialized; }
|
||||
|
||||
virtual void clear_screen();
|
||||
virtual void render_scene() {}
|
||||
|
||||
template<class _FpsCounter> void set_fps_counter(_FpsCounter &&fpsc)
|
||||
{
|
||||
m_fps_counter = std::forward<_FpsCounter>(fpsc);
|
||||
}
|
||||
|
||||
const FpsCounter &get_fps_counter() const { return m_fps_counter; }
|
||||
FpsCounter &get_fps_counter() { return m_fps_counter; }
|
||||
};
|
||||
|
||||
// Special dispaly using OpenCSG for rendering the scene.
|
||||
class CSGDisplay : public Display {
|
||||
protected:
|
||||
CSGSettings m_csgsettings;
|
||||
|
||||
// Cache the renderable primitives. These will be fetched when the scene
|
||||
// is modified.
|
||||
struct SceneCache {
|
||||
vector<shptr<Primitive>> primitives;
|
||||
vector<Primitive *> primitives_free;
|
||||
vector<OpenCSG::Primitive *> primitives_csg;
|
||||
|
||||
void clear();
|
||||
|
||||
shptr<Primitive> add_mesh(const TriangleMesh &mesh);
|
||||
shptr<Primitive> add_mesh(const TriangleMesh &mesh,
|
||||
OpenCSG::Operation op,
|
||||
unsigned covexity);
|
||||
} m_scene_cache;
|
||||
|
||||
public:
|
||||
|
||||
// Receive or apply the new settings.
|
||||
const CSGSettings & get_csgsettings() const { return m_csgsettings; }
|
||||
void apply_csgsettings(const CSGSettings &settings);
|
||||
|
||||
void render_scene() override;
|
||||
|
||||
void on_scene_updated(const Scene &scene) override;
|
||||
};
|
||||
|
||||
|
||||
// The controller is a hub which dispatches mouse events to the connected
|
||||
// displays. It keeps track of the mouse wheel position, the states whether
|
||||
// the mouse is being held, dragged, etc... All the connected displays will
|
||||
// mirror the camera movement (if there is more than one display).
|
||||
class Controller : public std::enable_shared_from_this<Controller>,
|
||||
public MouseInput::Listener,
|
||||
public Scene::Listener
|
||||
{
|
||||
long m_wheel_pos = 0;
|
||||
Vec2i m_mouse_pos, m_mouse_pos_rprev, m_mouse_pos_lprev;
|
||||
bool m_left_btn = false, m_right_btn = false;
|
||||
|
||||
shptr<Scene> m_scene;
|
||||
vector<wkptr<Display>> m_displays;
|
||||
|
||||
// Call a method of Camera on all the cameras of the attached displays
|
||||
template<class F, class...Args>
|
||||
void call_cameras(F &&f, Args&&... args) {
|
||||
for (wkptr<Display> &l : m_displays)
|
||||
if (auto disp = l.lock()) if (auto cam = disp->get_camera())
|
||||
(cam.get()->*f)(std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
// Set the scene that will be controlled.
|
||||
void set_scene(shptr<Scene> scene)
|
||||
{
|
||||
m_scene = scene;
|
||||
m_scene->add_listener(shared_from_this());
|
||||
}
|
||||
|
||||
const Scene * get_scene() const { return m_scene.get(); }
|
||||
|
||||
void add_display(shptr<Display> disp)
|
||||
{
|
||||
m_displays.emplace_back(disp);
|
||||
cleanup(m_displays);
|
||||
}
|
||||
|
||||
void remove_displays() { m_displays = {}; }
|
||||
|
||||
void on_scene_updated(const Scene &scene) override;
|
||||
|
||||
void on_left_click_down() override { m_left_btn = true; }
|
||||
void on_left_click_up() override { m_left_btn = false; }
|
||||
void on_right_click_down() override { m_right_btn = true; }
|
||||
void on_right_click_up() override { m_right_btn = false; }
|
||||
|
||||
void on_scroll(long v, long d, MouseInput::WheelAxis wa) override;
|
||||
void on_moved_to(long x, long y) override;
|
||||
|
||||
void move_clip_plane(double z) { call_cameras(&Camera::set_clip_z, z); }
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::GL
|
||||
#endif // SLIC3R_OCSG_EXMP_ENGINE_HPP
|
68
sandboxes/opencsg/ShaderCSGDisplay.cpp
Normal file
68
sandboxes/opencsg/ShaderCSGDisplay.cpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
#include "ShaderCSGDisplay.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
#include <GL/glew.h>
|
||||
|
||||
namespace Slic3r { namespace GL {
|
||||
|
||||
void ShaderCSGDisplay::add_mesh(const TriangleMesh &mesh)
|
||||
{
|
||||
auto v = std::make_shared<CSGVolume>();
|
||||
v->load_mesh(mesh);
|
||||
m_volumes.emplace_back(v);
|
||||
}
|
||||
|
||||
void ShaderCSGDisplay::render_scene()
|
||||
{
|
||||
GLfloat color[] = {1.f, 1.f, 0.f, 0.f};
|
||||
glColor4fv(color);
|
||||
glDepthFunc(GL_LESS);
|
||||
for (auto &v : m_volumes) v->render();
|
||||
glFlush();
|
||||
}
|
||||
|
||||
void ShaderCSGDisplay::on_scene_updated(const Scene &scene)
|
||||
{
|
||||
// TriangleMesh mesh = print->objects().front()->hollowed_interior_mesh();
|
||||
// Look at CSGDisplay::on_scene_updated to see how its done there.
|
||||
|
||||
const SLAPrint *print = scene.get_print();
|
||||
if (!print) return;
|
||||
|
||||
m_volumes.clear();
|
||||
|
||||
for (const SLAPrintObject *po : print->objects()) {
|
||||
const ModelObject *mo = po->model_object();
|
||||
TriangleMesh msh = mo->raw_mesh();
|
||||
|
||||
sla::DrainHoles holedata = mo->sla_drain_holes;
|
||||
|
||||
for (const ModelInstance *mi : mo->instances) {
|
||||
|
||||
TriangleMesh mshinst = msh;
|
||||
auto interior = po->hollowed_interior_mesh();
|
||||
interior.transform(po->trafo().inverse());
|
||||
|
||||
mshinst.merge(interior);
|
||||
mshinst.require_shared_vertices();
|
||||
|
||||
mi->transform_mesh(&mshinst);
|
||||
|
||||
auto bb = mshinst.bounding_box();
|
||||
auto center = bb.center().cast<float>();
|
||||
mshinst.translate(-center);
|
||||
|
||||
mshinst.require_shared_vertices();
|
||||
add_mesh(mshinst);
|
||||
}
|
||||
|
||||
for (const sla::DrainHole &holept : holedata) {
|
||||
TriangleMesh holemesh = sla::to_triangle_mesh(holept.to_mesh());
|
||||
holemesh.require_shared_vertices();
|
||||
add_mesh(holemesh);
|
||||
}
|
||||
}
|
||||
|
||||
repaint();
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::GL
|
27
sandboxes/opencsg/ShaderCSGDisplay.hpp
Normal file
27
sandboxes/opencsg/ShaderCSGDisplay.hpp
Normal file
|
@ -0,0 +1,27 @@
|
|||
#ifndef SHADERCSGDISPLAY_HPP
|
||||
#define SHADERCSGDISPLAY_HPP
|
||||
|
||||
#include "Engine.hpp"
|
||||
|
||||
namespace Slic3r { namespace GL {
|
||||
|
||||
class CSGVolume: public Volume
|
||||
{
|
||||
// Extend...
|
||||
};
|
||||
|
||||
class ShaderCSGDisplay: public Display {
|
||||
protected:
|
||||
vector<shptr<CSGVolume>> m_volumes;
|
||||
|
||||
void add_mesh(const TriangleMesh &mesh);
|
||||
public:
|
||||
|
||||
void render_scene() override;
|
||||
|
||||
void on_scene_updated(const Scene &scene) override;
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
#endif // SHADERCSGDISPLAY_HPP
|
734
sandboxes/opencsg/main.cpp
Normal file
734
sandboxes/opencsg/main.cpp
Normal file
|
@ -0,0 +1,734 @@
|
|||
#include <iostream>
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
|
||||
#include "Engine.hpp"
|
||||
#include "ShaderCSGDisplay.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#include <opencsg/opencsg.h>
|
||||
// For compilers that support precompilation, includes "wx/wx.h".
|
||||
#include <wx/wxprec.h>
|
||||
#ifndef WX_PRECOMP
|
||||
#include <wx/wx.h>
|
||||
#endif
|
||||
|
||||
#include <wx/slider.h>
|
||||
#include <wx/tglbtn.h>
|
||||
#include <wx/combobox.h>
|
||||
#include <wx/spinctrl.h>
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/glcanvas.h>
|
||||
#include <wx/cmdline.h>
|
||||
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Format/3mf.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
|
||||
#include "slic3r/GUI/Job.hpp"
|
||||
#include "slic3r/GUI/ProgressStatusBar.hpp"
|
||||
|
||||
using namespace Slic3r::GL;
|
||||
|
||||
class Renderer {
|
||||
protected:
|
||||
wxGLCanvas *m_canvas;
|
||||
shptr<wxGLContext> m_context;
|
||||
public:
|
||||
|
||||
Renderer(wxGLCanvas *c): m_canvas{c} {
|
||||
auto ctx = new wxGLContext(m_canvas);
|
||||
if (!ctx || !ctx->IsOK()) {
|
||||
wxMessageBox("Could not create OpenGL context.", "Error",
|
||||
wxOK | wxICON_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
m_context.reset(ctx);
|
||||
}
|
||||
|
||||
wxGLContext * context() { return m_context.get(); }
|
||||
const wxGLContext * context() const { return m_context.get(); }
|
||||
};
|
||||
|
||||
// Tell the CSGDisplay how to swap buffers and set the gl context.
|
||||
class OCSGRenderer: public Renderer, public Slic3r::GL::CSGDisplay {
|
||||
public:
|
||||
|
||||
OCSGRenderer(wxGLCanvas *c): Renderer{c} {}
|
||||
|
||||
void set_active(long w, long h) override
|
||||
{
|
||||
m_canvas->SetCurrent(*m_context);
|
||||
Slic3r::GL::Display::set_active(w, h);
|
||||
}
|
||||
|
||||
void swap_buffers() override { m_canvas->SwapBuffers(); }
|
||||
};
|
||||
|
||||
// Tell the CSGDisplay how to swap buffers and set the gl context.
|
||||
class ShaderCSGRenderer : public Renderer, public Slic3r::GL::ShaderCSGDisplay {
|
||||
public:
|
||||
|
||||
ShaderCSGRenderer(wxGLCanvas *c): Renderer{c} {}
|
||||
|
||||
void set_active(long w, long h) override
|
||||
{
|
||||
m_canvas->SetCurrent(*m_context);
|
||||
Slic3r::GL::Display::set_active(w, h);
|
||||
}
|
||||
|
||||
void swap_buffers() override { m_canvas->SwapBuffers(); }
|
||||
};
|
||||
|
||||
// The opengl rendering facility. Here we implement the rendering objects.
|
||||
class Canvas: public wxGLCanvas
|
||||
{
|
||||
// One display is active at a time, the OCSGRenderer by default.
|
||||
shptr<Slic3r::GL::Display> m_display;
|
||||
|
||||
public:
|
||||
|
||||
template<class...Args>
|
||||
Canvas(Args &&...args): wxGLCanvas(std::forward<Args>(args)...) {}
|
||||
|
||||
shptr<Slic3r::GL::Display> get_display() const { return m_display; }
|
||||
|
||||
void set_display(shptr<Slic3r::GL::Display> d) { m_display = d; }
|
||||
};
|
||||
|
||||
// Enumerate possible mouse events, we will record them.
|
||||
enum EEvents { LCLK_U, RCLK_U, LCLK_D, RCLK_D, DDCLK, SCRL, MV };
|
||||
struct Event
|
||||
{
|
||||
EEvents type;
|
||||
long a, b;
|
||||
Event(EEvents t, long x = 0, long y = 0) : type{t}, a{x}, b{y} {}
|
||||
};
|
||||
|
||||
// Create a special mouse input adapter, which can store (record) the received
|
||||
// mouse signals into a file and play back the stored events later.
|
||||
class RecorderMouseInput: public MouseInput {
|
||||
std::vector<Event> m_events;
|
||||
bool m_recording = false, m_playing = false;
|
||||
|
||||
public:
|
||||
void left_click_down() override
|
||||
{
|
||||
if (m_recording) m_events.emplace_back(LCLK_D);
|
||||
if (!m_playing) MouseInput::left_click_down();
|
||||
}
|
||||
void left_click_up() override
|
||||
{
|
||||
if (m_recording) m_events.emplace_back(LCLK_U);
|
||||
if (!m_playing) MouseInput::left_click_up();
|
||||
}
|
||||
void right_click_down() override
|
||||
{
|
||||
if (m_recording) m_events.emplace_back(RCLK_D);
|
||||
if (!m_playing) MouseInput::right_click_down();
|
||||
}
|
||||
void right_click_up() override
|
||||
{
|
||||
if (m_recording) m_events.emplace_back(RCLK_U);
|
||||
if (!m_playing) MouseInput::right_click_up();
|
||||
}
|
||||
void double_click() override
|
||||
{
|
||||
if (m_recording) m_events.emplace_back(DDCLK);
|
||||
if (!m_playing) MouseInput::double_click();
|
||||
}
|
||||
void scroll(long v, long d, WheelAxis wa) override
|
||||
{
|
||||
if (m_recording) m_events.emplace_back(SCRL, v, d);
|
||||
if (!m_playing) MouseInput::scroll(v, d, wa);
|
||||
}
|
||||
void move_to(long x, long y) override
|
||||
{
|
||||
if (m_recording) m_events.emplace_back(MV, x, y);
|
||||
if (!m_playing) MouseInput::move_to(x, y);
|
||||
}
|
||||
|
||||
void save(std::ostream &stream)
|
||||
{
|
||||
for (const Event &evt : m_events)
|
||||
stream << evt.type << " " << evt.a << " " << evt.b << std::endl;
|
||||
}
|
||||
|
||||
void load(std::istream &stream)
|
||||
{
|
||||
m_events.clear();
|
||||
while (stream.good()) {
|
||||
int type; long a, b;
|
||||
stream >> type >> a >> b;
|
||||
m_events.emplace_back(EEvents(type), a, b);
|
||||
}
|
||||
}
|
||||
|
||||
void record(bool r) { m_recording = r; if (r) m_events.clear(); }
|
||||
|
||||
void play()
|
||||
{
|
||||
m_playing = true;
|
||||
for (const Event &evt : m_events) {
|
||||
switch (evt.type) {
|
||||
case LCLK_U: MouseInput::left_click_up(); break;
|
||||
case LCLK_D: MouseInput::left_click_down(); break;
|
||||
case RCLK_U: MouseInput::right_click_up(); break;
|
||||
case RCLK_D: MouseInput::right_click_down(); break;
|
||||
case DDCLK: MouseInput::double_click(); break;
|
||||
case SCRL: MouseInput::scroll(evt.a, evt.b, WheelAxis::waVertical); break;
|
||||
case MV: MouseInput::move_to(evt.a, evt.b); break;
|
||||
}
|
||||
|
||||
wxTheApp->Yield();
|
||||
if (!m_playing)
|
||||
break;
|
||||
}
|
||||
m_playing = false;
|
||||
}
|
||||
|
||||
void stop() { m_playing = false; }
|
||||
bool is_playing() const { return m_playing; }
|
||||
};
|
||||
|
||||
// The top level frame of the application.
|
||||
class MyFrame: public wxFrame
|
||||
{
|
||||
// Instantiate the 3D engine.
|
||||
shptr<Scene> m_scene; // Model
|
||||
shptr<Canvas> m_canvas; // Views store
|
||||
shptr<OCSGRenderer> m_ocsgdisplay; // View
|
||||
shptr<ShaderCSGRenderer> m_shadercsg_display; // Another view
|
||||
shptr<Controller> m_ctl; // Controller
|
||||
|
||||
// Add a status bar with progress indication.
|
||||
shptr<Slic3r::GUI::ProgressStatusBar> m_stbar;
|
||||
|
||||
RecorderMouseInput m_mouse;
|
||||
|
||||
// When loading a Model from 3mf and preparing it, we use a separate thread.
|
||||
class SLAJob: public Slic3r::GUI::Job {
|
||||
MyFrame *m_parent;
|
||||
std::unique_ptr<Slic3r::SLAPrint> m_print;
|
||||
std::string m_fname;
|
||||
|
||||
public:
|
||||
SLAJob(MyFrame *frame, const std::string &fname)
|
||||
: Slic3r::GUI::Job{frame->m_stbar}
|
||||
, m_parent{frame}
|
||||
, m_fname{fname}
|
||||
{}
|
||||
|
||||
// Runs in separate thread
|
||||
void process() override;
|
||||
|
||||
const std::string & get_project_fname() const { return m_fname; }
|
||||
|
||||
protected:
|
||||
|
||||
// Runs in the UI thread.
|
||||
void finalize() override
|
||||
{
|
||||
m_parent->m_scene->set_print(std::move(m_print));
|
||||
m_parent->m_stbar->set_status_text(
|
||||
wxString::Format("Model %s loaded.", m_fname));
|
||||
}
|
||||
};
|
||||
|
||||
uqptr<SLAJob> m_ui_job;
|
||||
|
||||
// To keep track of the running average of measured fps values.
|
||||
double m_fps_avg = 0.;
|
||||
|
||||
// We need the record button across methods
|
||||
wxToggleButton *m_record_btn;
|
||||
wxComboBox * m_alg_select;
|
||||
wxComboBox * m_depth_select;
|
||||
wxComboBox * m_optimization_select;
|
||||
wxSpinCtrl * m_convexity_spin;
|
||||
wxToggleButton *m_csg_toggle;
|
||||
wxToggleButton *m_ms_toggle;
|
||||
wxStaticText *m_fpstext;
|
||||
|
||||
CSGSettings m_csg_settings;
|
||||
|
||||
void read_csg_settings(const wxCmdLineParser &parser);
|
||||
|
||||
void set_renderer_algorithm(const wxString &alg);
|
||||
|
||||
void activate_canvas_display();
|
||||
|
||||
public:
|
||||
MyFrame(const wxString & title,
|
||||
const wxPoint & pos,
|
||||
const wxSize & size,
|
||||
const wxCmdLineParser &parser);
|
||||
|
||||
// Grab a 3mf and load (hollow it out) within the UI job.
|
||||
void load_model(const std::string &fname) {
|
||||
m_ui_job = std::make_unique<SLAJob>(this, fname);
|
||||
m_ui_job->start();
|
||||
}
|
||||
|
||||
// Load a previously stored mouse event log and play it back.
|
||||
void play_back_mouse(const std::string &events_fname)
|
||||
{
|
||||
std::fstream stream(events_fname, std::fstream::in);
|
||||
|
||||
if (stream.good()) {
|
||||
std::string model_name;
|
||||
std::getline(stream, model_name);
|
||||
load_model(model_name);
|
||||
|
||||
while (!m_ui_job->is_finalized())
|
||||
wxTheApp->Yield();;
|
||||
|
||||
int w, h;
|
||||
stream >> w >> h;
|
||||
SetSize(w, h);
|
||||
|
||||
m_mouse.load(stream);
|
||||
if (m_record_btn) m_record_btn->Disable();
|
||||
m_mouse.play();
|
||||
}
|
||||
}
|
||||
|
||||
Canvas * canvas() { return m_canvas.get(); }
|
||||
const Canvas * canvas() const { return m_canvas.get(); }
|
||||
|
||||
// Bind the canvas mouse events to a class implementing MouseInput interface
|
||||
void bind_canvas_events(MouseInput &msinput);
|
||||
|
||||
double get_fps_average() const { return m_fps_avg; }
|
||||
};
|
||||
|
||||
// Possible OpenCSG configuration values. Will be used on the command line and
|
||||
// on the UI widgets.
|
||||
static const std::vector<wxString> CSG_ALGS = {"Auto", "Goldfeather", "SCS", "EnricoShader"};
|
||||
static const std::vector<wxString> CSG_DEPTH = {"Off", "OcclusionQuery", "On"};
|
||||
static const std::vector<wxString> CSG_OPT = { "Default", "ForceOn", "On", "Off" };
|
||||
|
||||
inline long get_idx(const wxString &a, const std::vector<wxString> &v)
|
||||
{
|
||||
auto it = std::find(v.begin(), v.end(), a.ToStdString());
|
||||
return it - v.begin();
|
||||
};
|
||||
|
||||
class App : public wxApp {
|
||||
MyFrame *m_frame = nullptr;
|
||||
wxString m_fname;
|
||||
public:
|
||||
bool OnInit() override {
|
||||
|
||||
wxCmdLineParser parser(argc, argv);
|
||||
|
||||
parser.AddOption("p", "play", "play back file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
|
||||
parser.AddOption("a", "algorithm", "OpenCSG algorithm [Auto|Goldfeather|SCS]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
|
||||
parser.AddOption("d", "depth", "OpenCSG depth strategy [Off|OcclusionQuery|On]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
|
||||
parser.AddOption("o", "optimization", "OpenCSG optimization strategy [Default|ForceOn|On|Off]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
|
||||
parser.AddOption("c", "convexity", "OpenCSG convexity parameter for generic meshes", wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL);
|
||||
parser.AddSwitch("", "disable-csg", "Disable csg rendering", wxCMD_LINE_PARAM_OPTIONAL);
|
||||
|
||||
parser.Parse();
|
||||
|
||||
bool is_play = parser.Found("play", &m_fname);
|
||||
|
||||
m_frame = new MyFrame("PrusaSlicer OpenCSG Demo", wxDefaultPosition, wxSize(1024, 768), parser);
|
||||
|
||||
if (is_play) {
|
||||
Bind(wxEVT_IDLE, &App::Play, this);
|
||||
m_frame->Show( true );
|
||||
} else m_frame->Show( true );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Play(wxIdleEvent &) {
|
||||
Unbind(wxEVT_IDLE, &App::Play, this);
|
||||
m_frame->play_back_mouse(m_fname.ToStdString());
|
||||
m_frame->Destroy();
|
||||
}
|
||||
};
|
||||
|
||||
wxIMPLEMENT_APP(App);
|
||||
|
||||
void MyFrame::read_csg_settings(const wxCmdLineParser &parser)
|
||||
{
|
||||
wxString alg;
|
||||
parser.Found("algorithm", &alg);
|
||||
|
||||
wxString depth;
|
||||
parser.Found("depth", &depth);
|
||||
|
||||
wxString opt;
|
||||
parser.Found("optimization", &opt);
|
||||
|
||||
long convexity = 1;
|
||||
parser.Found("convexity", &convexity);
|
||||
|
||||
bool csg_off = parser.Found("disable-csg");
|
||||
|
||||
if (auto a = get_idx(alg, CSG_ALGS) < OpenCSG::AlgorithmUnused)
|
||||
m_csg_settings.set_algo(OpenCSG::Algorithm(a));
|
||||
|
||||
if (auto a = get_idx(depth, CSG_DEPTH) < OpenCSG::DepthComplexityAlgorithmUnused)
|
||||
m_csg_settings.set_depth_algo(OpenCSG::DepthComplexityAlgorithm(a));
|
||||
|
||||
if (auto a = get_idx(opt, CSG_OPT) < OpenCSG::OptimizationUnused)
|
||||
m_csg_settings.set_optimization(OpenCSG::Optimization(a));
|
||||
|
||||
m_csg_settings.set_convexity(unsigned(convexity));
|
||||
m_csg_settings.enable_csg(!csg_off);
|
||||
|
||||
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
||||
}
|
||||
|
||||
void MyFrame::set_renderer_algorithm(const wxString &alg)
|
||||
{
|
||||
long alg_idx = get_idx(alg, CSG_ALGS);
|
||||
if (alg_idx < 0 || alg_idx >= long(CSG_ALGS.size())) return;
|
||||
|
||||
// If there is a valid display in place, save its camera.
|
||||
auto cam = m_canvas->get_display() ?
|
||||
m_canvas->get_display()->get_camera() : nullptr;
|
||||
|
||||
if (alg == "EnricoShader") {
|
||||
m_alg_select->SetSelection(int(alg_idx));
|
||||
m_depth_select->Disable();
|
||||
m_optimization_select->Disable();
|
||||
m_csg_toggle->Disable();
|
||||
|
||||
m_ocsgdisplay.reset();
|
||||
canvas()->set_display(nullptr);
|
||||
m_shadercsg_display = std::make_shared<ShaderCSGRenderer>(canvas());
|
||||
canvas()->set_display(m_shadercsg_display);
|
||||
} else {
|
||||
if (m_csg_settings.get_algo() > 0) m_depth_select->Enable(true);
|
||||
m_alg_select->SetSelection(m_csg_settings.get_algo());
|
||||
m_depth_select->SetSelection(m_csg_settings.get_depth_algo());
|
||||
m_optimization_select->SetSelection(m_csg_settings.get_optimization());
|
||||
m_convexity_spin->SetValue(int(m_csg_settings.get_convexity()));
|
||||
m_csg_toggle->SetValue(m_csg_settings.is_enabled());
|
||||
m_optimization_select->Enable();
|
||||
m_csg_toggle->Enable();
|
||||
|
||||
m_shadercsg_display.reset();
|
||||
canvas()->set_display(nullptr);
|
||||
m_ocsgdisplay = std::make_shared<OCSGRenderer>(canvas());
|
||||
m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
||||
canvas()->set_display(m_ocsgdisplay);
|
||||
}
|
||||
|
||||
if (cam)
|
||||
m_canvas->get_display()->set_camera(cam);
|
||||
|
||||
m_ctl->remove_displays();
|
||||
m_ctl->add_display(m_canvas->get_display());
|
||||
m_canvas->get_display()->get_fps_counter().add_listener([this](double fps) {
|
||||
m_fpstext->SetLabel(wxString::Format("fps: %.2f", fps));
|
||||
m_fps_avg = 0.9 * m_fps_avg + 0.1 * fps;
|
||||
});
|
||||
|
||||
if (IsShown()) {
|
||||
activate_canvas_display();
|
||||
m_canvas->get_display()->on_scene_updated(*m_scene);
|
||||
}
|
||||
}
|
||||
|
||||
void MyFrame::activate_canvas_display()
|
||||
{
|
||||
const wxSize ClientSize = m_canvas->GetClientSize();
|
||||
m_canvas->get_display()->set_active(ClientSize.x, ClientSize.y);
|
||||
enable_multisampling(m_ms_toggle->GetValue());
|
||||
|
||||
m_canvas->Bind(wxEVT_PAINT, [this](wxPaintEvent &) {
|
||||
// This is required even though dc is not used otherwise.
|
||||
wxPaintDC dc(m_canvas.get());
|
||||
const wxSize csize = m_canvas->GetClientSize();
|
||||
m_canvas->get_display()->set_screen_size(csize.x, csize.y);
|
||||
m_canvas->get_display()->repaint();
|
||||
});
|
||||
|
||||
m_canvas->Bind(wxEVT_SIZE, [this](wxSizeEvent &) {
|
||||
const wxSize csize = m_canvas->GetClientSize();
|
||||
m_canvas->get_display()->set_screen_size(csize.x, csize.y);
|
||||
m_canvas->get_display()->repaint();
|
||||
});
|
||||
|
||||
// Do the repaint continuously
|
||||
m_canvas->Bind(wxEVT_IDLE, [this](wxIdleEvent &evt) {
|
||||
m_canvas->get_display()->repaint();
|
||||
evt.RequestMore();
|
||||
});
|
||||
|
||||
bind_canvas_events(m_mouse);
|
||||
}
|
||||
|
||||
MyFrame::MyFrame(const wxString &title, const wxPoint &pos, const wxSize &size,
|
||||
const wxCmdLineParser &parser):
|
||||
wxFrame(nullptr, wxID_ANY, title, pos, size)
|
||||
{
|
||||
wxMenu *menuFile = new wxMenu;
|
||||
menuFile->Append(wxID_OPEN);
|
||||
menuFile->Append(wxID_EXIT);
|
||||
wxMenuBar *menuBar = new wxMenuBar;
|
||||
menuBar->Append( menuFile, "&File" );
|
||||
SetMenuBar( menuBar );
|
||||
|
||||
m_stbar = std::make_shared<Slic3r::GUI::ProgressStatusBar>(this);
|
||||
m_stbar->embed(this);
|
||||
|
||||
SetStatusText( "Welcome to wxWidgets!" );
|
||||
|
||||
int attribList[] =
|
||||
{WX_GL_RGBA, WX_GL_DOUBLEBUFFER,
|
||||
// RGB channels each should be allocated with 8 bit depth. One
|
||||
// should almost certainly get these bit depths by default.
|
||||
WX_GL_MIN_RED, 8, WX_GL_MIN_GREEN, 8, WX_GL_MIN_BLUE, 8,
|
||||
// Requesting an 8 bit alpha channel. Interestingly, the NVIDIA
|
||||
// drivers would most likely work with some alpha plane, but
|
||||
// glReadPixels would not return the alpha channel on NVIDIA if
|
||||
// not requested when the GL context is created.
|
||||
WX_GL_MIN_ALPHA, 8, WX_GL_DEPTH_SIZE, 8, WX_GL_STENCIL_SIZE, 8,
|
||||
WX_GL_SAMPLE_BUFFERS, GL_TRUE, WX_GL_SAMPLES, 4, 0};
|
||||
|
||||
m_scene = std::make_shared<Scene>();
|
||||
m_ctl = std::make_shared<Controller>();
|
||||
m_ctl->set_scene(m_scene);
|
||||
|
||||
m_canvas = std::make_shared<Canvas>(this, wxID_ANY, attribList,
|
||||
wxDefaultPosition, wxDefaultSize,
|
||||
wxWANTS_CHARS | wxFULL_REPAINT_ON_RESIZE);
|
||||
|
||||
read_csg_settings(parser);
|
||||
|
||||
wxPanel *control_panel = new wxPanel(this);
|
||||
|
||||
auto controlsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
auto slider_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
auto console_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
|
||||
auto slider = new wxSlider(control_panel, wxID_ANY, 0, 0, 100,
|
||||
wxDefaultPosition, wxDefaultSize,
|
||||
wxSL_VERTICAL);
|
||||
slider_sizer->Add(slider, 1, wxEXPAND);
|
||||
|
||||
m_ms_toggle = new wxToggleButton(control_panel, wxID_ANY, "Multisampling");
|
||||
console_sizer->Add(m_ms_toggle, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
m_csg_toggle = new wxToggleButton(control_panel, wxID_ANY, "CSG");
|
||||
m_csg_toggle->SetValue(true);
|
||||
console_sizer->Add(m_csg_toggle, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
auto add_combobox = [control_panel, console_sizer]
|
||||
(const wxString &label, const std::vector<wxString> &list)
|
||||
{
|
||||
auto widget = new wxComboBox(control_panel, wxID_ANY, list[0],
|
||||
wxDefaultPosition, wxDefaultSize,
|
||||
int(list.size()), list.data());
|
||||
|
||||
auto sz = new wxBoxSizer(wxHORIZONTAL);
|
||||
sz->Add(new wxStaticText(control_panel, wxID_ANY, label), 0,
|
||||
wxALL | wxALIGN_CENTER, 5);
|
||||
sz->Add(widget, 1, wxALL | wxEXPAND, 5);
|
||||
console_sizer->Add(sz, 0, wxEXPAND);
|
||||
return widget;
|
||||
};
|
||||
|
||||
auto add_spinctl = [control_panel, console_sizer]
|
||||
(const wxString &label, int initial, int min, int max)
|
||||
{
|
||||
auto widget = new wxSpinCtrl(
|
||||
control_panel, wxID_ANY,
|
||||
wxString::Format("%d", initial),
|
||||
wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, min, max,
|
||||
initial);
|
||||
|
||||
auto sz = new wxBoxSizer(wxHORIZONTAL);
|
||||
sz->Add(new wxStaticText(control_panel, wxID_ANY, label), 0,
|
||||
wxALL | wxALIGN_CENTER, 5);
|
||||
sz->Add(widget, 1, wxALL | wxEXPAND, 5);
|
||||
console_sizer->Add(sz, 0, wxEXPAND);
|
||||
return widget;
|
||||
};
|
||||
|
||||
m_convexity_spin = add_spinctl("Convexity", CSGSettings::DEFAULT_CONVEXITY, 0, 100);
|
||||
|
||||
m_alg_select = add_combobox("Algorithm", CSG_ALGS);
|
||||
m_depth_select = add_combobox("Depth Complexity", CSG_DEPTH);
|
||||
m_optimization_select = add_combobox("Optimization", CSG_OPT);
|
||||
|
||||
m_fpstext = new wxStaticText(control_panel, wxID_ANY, "");
|
||||
console_sizer->Add(m_fpstext, 0, wxALL, 5);
|
||||
|
||||
m_record_btn = new wxToggleButton(control_panel, wxID_ANY, "Record");
|
||||
console_sizer->Add(m_record_btn, 0, wxALL | wxEXPAND, 5);
|
||||
|
||||
controlsizer->Add(slider_sizer, 0, wxEXPAND);
|
||||
controlsizer->Add(console_sizer, 1, wxEXPAND);
|
||||
|
||||
control_panel->SetSizer(controlsizer);
|
||||
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(m_canvas.get(), 1, wxEXPAND);
|
||||
sizer->Add(control_panel, 0, wxEXPAND);
|
||||
SetSizer(sizer);
|
||||
|
||||
wxString alg;
|
||||
if (!parser.Found("algorithm", &alg)) alg = "Auto";
|
||||
|
||||
set_renderer_algorithm(alg);
|
||||
|
||||
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt){
|
||||
if (m_canvas) RemoveChild(m_canvas.get());
|
||||
m_canvas.reset();
|
||||
if (!m_mouse.is_playing()) evt.Skip();
|
||||
else m_mouse.stop();
|
||||
});
|
||||
|
||||
Bind(wxEVT_MENU, [this](wxCommandEvent &) {
|
||||
wxFileDialog dlg(this, "Select project file", wxEmptyString,
|
||||
wxEmptyString, "*.3mf", wxFD_OPEN|wxFD_FILE_MUST_EXIST);
|
||||
|
||||
if (dlg.ShowModal() == wxID_OK) load_model(dlg.GetPath().ToStdString());
|
||||
}, wxID_OPEN);
|
||||
|
||||
Bind(wxEVT_MENU, [this](wxCommandEvent &) { Close(true); }, wxID_EXIT);
|
||||
|
||||
Bind(wxEVT_SHOW, [this](wxShowEvent &) {
|
||||
activate_canvas_display();
|
||||
});
|
||||
|
||||
Bind(wxEVT_SLIDER, [this, slider](wxCommandEvent &) {
|
||||
m_ctl->move_clip_plane(double(slider->GetValue()));
|
||||
});
|
||||
|
||||
m_ms_toggle->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &){
|
||||
enable_multisampling(m_ms_toggle->GetValue());
|
||||
m_canvas->get_display()->repaint();
|
||||
});
|
||||
|
||||
m_csg_toggle->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &){
|
||||
CSGSettings stt = m_ocsgdisplay->get_csgsettings();
|
||||
stt.enable_csg(m_csg_toggle->GetValue());
|
||||
m_ocsgdisplay->apply_csgsettings(stt);
|
||||
});
|
||||
|
||||
m_alg_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
|
||||
wxString alg = m_alg_select->GetValue();
|
||||
int sel = m_alg_select->GetSelection();
|
||||
m_csg_settings.set_algo(sel);
|
||||
set_renderer_algorithm(alg);
|
||||
});
|
||||
|
||||
m_depth_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
|
||||
int sel = m_depth_select->GetSelection();
|
||||
m_csg_settings.set_depth_algo(sel);
|
||||
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
||||
});
|
||||
|
||||
m_optimization_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
|
||||
int sel = m_optimization_select->GetSelection();
|
||||
m_csg_settings.set_optimization(sel);
|
||||
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
||||
});
|
||||
|
||||
m_convexity_spin->Bind(wxEVT_SPINCTRL, [this](wxSpinEvent &) {
|
||||
int c = m_convexity_spin->GetValue();
|
||||
if (c > 0) {
|
||||
m_csg_settings.set_convexity(unsigned(c));
|
||||
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
||||
}
|
||||
});
|
||||
|
||||
m_record_btn->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &) {
|
||||
if (!m_ui_job) {
|
||||
m_stbar->set_status_text("No project loaded!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_record_btn->GetValue()) {
|
||||
if (auto c = m_canvas->get_display()->get_camera()) reset(*c);
|
||||
m_ctl->on_scene_updated(*m_scene);
|
||||
m_mouse.record(true);
|
||||
} else {
|
||||
m_mouse.record(false);
|
||||
wxFileDialog dlg(this, "Select output file",
|
||||
wxEmptyString, wxEmptyString, "*.events",
|
||||
wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
|
||||
|
||||
if (dlg.ShowModal() == wxID_OK) {
|
||||
std::fstream stream(dlg.GetPath().ToStdString(),
|
||||
std::fstream::out);
|
||||
|
||||
if (stream.good()) {
|
||||
stream << m_ui_job->get_project_fname() << "\n";
|
||||
wxSize winsize = GetSize();
|
||||
stream << winsize.x << " " << winsize.y << "\n";
|
||||
m_mouse.save(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void MyFrame::bind_canvas_events(MouseInput &ms)
|
||||
{
|
||||
m_canvas->Bind(wxEVT_MOUSEWHEEL, [&ms](wxMouseEvent &evt) {
|
||||
ms.scroll(evt.GetWheelRotation(), evt.GetWheelDelta(),
|
||||
evt.GetWheelAxis() == wxMOUSE_WHEEL_VERTICAL ?
|
||||
Slic3r::GL::MouseInput::waVertical :
|
||||
Slic3r::GL::MouseInput::waHorizontal);
|
||||
});
|
||||
|
||||
m_canvas->Bind(wxEVT_MOTION, [&ms](wxMouseEvent &evt) {
|
||||
ms.move_to(evt.GetPosition().x, evt.GetPosition().y);
|
||||
});
|
||||
|
||||
m_canvas->Bind(wxEVT_RIGHT_DOWN, [&ms](wxMouseEvent & /*evt*/) {
|
||||
ms.right_click_down();
|
||||
});
|
||||
|
||||
m_canvas->Bind(wxEVT_RIGHT_UP, [&ms](wxMouseEvent & /*evt*/) {
|
||||
ms.right_click_up();
|
||||
});
|
||||
|
||||
m_canvas->Bind(wxEVT_LEFT_DOWN, [&ms](wxMouseEvent & /*evt*/) {
|
||||
ms.left_click_down();
|
||||
});
|
||||
|
||||
m_canvas->Bind(wxEVT_LEFT_UP, [&ms](wxMouseEvent & /*evt*/) {
|
||||
ms.left_click_up();
|
||||
});
|
||||
|
||||
ms.add_listener(m_ctl);
|
||||
}
|
||||
|
||||
void MyFrame::SLAJob::process()
|
||||
{
|
||||
using SlStatus = Slic3r::PrintBase::SlicingStatus;
|
||||
|
||||
Slic3r::DynamicPrintConfig cfg;
|
||||
auto model = Slic3r::Model::read_from_file(m_fname, &cfg);
|
||||
|
||||
m_print = std::make_unique<Slic3r::SLAPrint>();
|
||||
m_print->apply(model, cfg);
|
||||
|
||||
Slic3r::PrintBase::TaskParams params;
|
||||
params.to_object_step = Slic3r::slaposHollowing;
|
||||
m_print->set_task(params);
|
||||
|
||||
m_print->set_status_callback([this](const SlStatus &status) {
|
||||
update_status(status.percent, status.text);
|
||||
});
|
||||
|
||||
try {
|
||||
m_print->process();
|
||||
} catch(std::exception &e) {
|
||||
update_status(0, wxString("Exception during processing: ") + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
//int main() {}
|
|
@ -1,2 +1,7 @@
|
|||
add_executable(openvdb_example openvdb_example.cpp)
|
||||
target_link_libraries(openvdb_example libslic3r)
|
||||
if(TARGET OpenVDB::openvdb)
|
||||
add_executable(openvdb_example openvdb_example.cpp)
|
||||
|
||||
target_link_libraries(openvdb_example libslic3r)
|
||||
target_link_libraries(openvdb_example OpenVDB::openvdb)
|
||||
endif()
|
||||
|
||||
|
|
|
@ -9,6 +9,11 @@ if (MINGW)
|
|||
add_compile_options(-Wa,-mbig-obj)
|
||||
endif ()
|
||||
|
||||
set(OpenVDBUtils_SOURCES "")
|
||||
if (TARGET OpenVDB::openvdb)
|
||||
set(OpenVDBUtils_SOURCES OpenVDBUtils.cpp OpenVDBUtils.hpp)
|
||||
endif()
|
||||
|
||||
add_library(libslic3r STATIC
|
||||
pchheader.cpp
|
||||
pchheader.hpp
|
||||
|
@ -149,9 +154,9 @@ add_library(libslic3r STATIC
|
|||
ShortestPath.cpp
|
||||
ShortestPath.hpp
|
||||
SLAPrint.cpp
|
||||
SLAPrintSteps.cpp
|
||||
SLAPrintSteps.hpp
|
||||
SLAPrint.hpp
|
||||
SLA/SLAAutoSupports.hpp
|
||||
SLA/SLAAutoSupports.cpp
|
||||
Slicing.cpp
|
||||
Slicing.hpp
|
||||
SlicingAdaptive.cpp
|
||||
|
@ -180,35 +185,65 @@ add_library(libslic3r STATIC
|
|||
MinAreaBoundingBox.cpp
|
||||
miniz_extension.hpp
|
||||
miniz_extension.cpp
|
||||
SLA/SLACommon.hpp
|
||||
SLA/SLABoilerPlate.hpp
|
||||
SLA/SLAPad.hpp
|
||||
SLA/SLAPad.cpp
|
||||
SLA/SLASupportTreeBuilder.hpp
|
||||
SLA/SLASupportTreeBuildsteps.hpp
|
||||
SLA/SLASupportTreeBuildsteps.cpp
|
||||
SLA/SLASupportTreeBuilder.cpp
|
||||
SLA/SLAConcurrency.hpp
|
||||
SLA/SLASupportTree.hpp
|
||||
SLA/SLASupportTree.cpp
|
||||
SLA/SLASupportTreeIGL.cpp
|
||||
SLA/SLARotfinder.hpp
|
||||
SLA/SLARotfinder.cpp
|
||||
SLA/SLABoostAdapter.hpp
|
||||
SLA/SLASpatIndex.hpp
|
||||
SLA/SLARaster.hpp
|
||||
SLA/SLARaster.cpp
|
||||
SLA/SLARasterWriter.hpp
|
||||
SLA/SLARasterWriter.cpp
|
||||
SimplifyMesh.hpp
|
||||
SimplifyMeshImpl.hpp
|
||||
SimplifyMesh.cpp
|
||||
${OpenVDBUtils_SOURCES}
|
||||
SLA/Common.hpp
|
||||
SLA/Common.cpp
|
||||
SLA/Pad.hpp
|
||||
SLA/Pad.cpp
|
||||
SLA/SupportTreeBuilder.hpp
|
||||
SLA/SupportTreeBuildsteps.hpp
|
||||
SLA/SupportTreeBuildsteps.cpp
|
||||
SLA/SupportTreeBuilder.cpp
|
||||
SLA/Concurrency.hpp
|
||||
SLA/SupportTree.hpp
|
||||
SLA/SupportTree.cpp
|
||||
# SLA/SupportTreeIGL.cpp
|
||||
SLA/Rotfinder.hpp
|
||||
SLA/Rotfinder.cpp
|
||||
SLA/BoostAdapter.hpp
|
||||
SLA/SpatIndex.hpp
|
||||
SLA/Raster.hpp
|
||||
SLA/Raster.cpp
|
||||
SLA/RasterWriter.hpp
|
||||
SLA/RasterWriter.cpp
|
||||
SLA/ConcaveHull.hpp
|
||||
SLA/ConcaveHull.cpp
|
||||
SLA/Hollowing.hpp
|
||||
SLA/Hollowing.cpp
|
||||
SLA/JobController.hpp
|
||||
SLA/SupportPoint.hpp
|
||||
SLA/SupportPointGenerator.hpp
|
||||
SLA/SupportPointGenerator.cpp
|
||||
SLA/Contour3D.hpp
|
||||
SLA/Contour3D.cpp
|
||||
SLA/EigenMesh3D.hpp
|
||||
SLA/Clustering.hpp
|
||||
)
|
||||
|
||||
encoding_check(libslic3r)
|
||||
|
||||
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
|
||||
add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE)
|
||||
if (SLIC3R_STATIC)
|
||||
set(CGAL_Boost_USE_STATIC_LIBS ON CACHE BOOL "" FORCE)
|
||||
endif ()
|
||||
set(CGAL_DO_NOT_WARN_ABOUT_CMAKE_BUILD_TYPE ON CACHE BOOL "" FORCE)
|
||||
|
||||
cmake_policy(PUSH)
|
||||
cmake_policy(SET CMP0011 NEW)
|
||||
find_package(CGAL REQUIRED)
|
||||
cmake_policy(POP)
|
||||
|
||||
add_library(libslic3r_cgal OBJECT MeshBoolean.cpp MeshBoolean.hpp)
|
||||
target_include_directories(libslic3r_cgal PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
$<TARGET_PROPERTY:libigl,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
$<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||
target_compile_definitions(libslic3r_cgal PRIVATE
|
||||
$<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_COMPILE_DEFINITIONS>)
|
||||
target_compile_options(libslic3r_cgal PRIVATE
|
||||
$<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_COMPILE_OPTIONS>)
|
||||
|
||||
encoding_check(libslic3r)
|
||||
|
||||
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0)
|
||||
target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${LIBNEST2D_INCLUDES} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
@ -228,10 +263,14 @@ target_link_libraries(libslic3r
|
|||
qhull
|
||||
semver
|
||||
TBB::tbb
|
||||
# OpenVDB::openvdb
|
||||
$<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_LINK_LIBRARIES>
|
||||
${CMAKE_DL_LIBS}
|
||||
)
|
||||
|
||||
if (TARGET OpenVDB::openvdb)
|
||||
target_link_libraries(libslic3r OpenVDB::openvdb)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
target_link_libraries(libslic3r Psapi.lib)
|
||||
endif()
|
||||
|
@ -239,3 +278,9 @@ endif()
|
|||
if(SLIC3R_PROFILE)
|
||||
target_link_libraries(slic3r Shiny)
|
||||
endif()
|
||||
|
||||
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
|
||||
add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE)
|
||||
endif ()
|
||||
|
||||
target_sources(libslic3r PRIVATE $<TARGET_OBJECTS:libslic3r_cgal>)
|
||||
|
|
|
@ -55,6 +55,7 @@ const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config";
|
|||
const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt";
|
||||
const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml";
|
||||
const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt";
|
||||
const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt";
|
||||
const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml";
|
||||
|
||||
const char* MODEL_TAG = "model";
|
||||
|
@ -385,6 +386,7 @@ namespace Slic3r {
|
|||
typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
|
||||
typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap;
|
||||
typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap;
|
||||
typedef std::map<int, std::vector<sla::DrainHole>> IdToSlaDrainHolesMap;
|
||||
|
||||
// Version of the 3mf file
|
||||
unsigned int m_version;
|
||||
|
@ -403,6 +405,7 @@ namespace Slic3r {
|
|||
IdToLayerHeightsProfileMap m_layer_heights_profiles;
|
||||
IdToLayerConfigRangesMap m_layer_config_ranges;
|
||||
IdToSlaSupportPointsMap m_sla_support_points;
|
||||
IdToSlaDrainHolesMap m_sla_drain_holes;
|
||||
std::string m_curr_metadata_name;
|
||||
std::string m_curr_characters;
|
||||
std::string m_name;
|
||||
|
@ -422,6 +425,7 @@ namespace Slic3r {
|
|||
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
|
||||
void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||
|
||||
|
@ -629,6 +633,11 @@ namespace Slic3r {
|
|||
// extract sla support points file
|
||||
_extract_sla_support_points_from_archive(archive, stat);
|
||||
}
|
||||
else if (boost::algorithm::iequals(name, SLA_DRAIN_HOLES_FILE))
|
||||
{
|
||||
// extract sla support points file
|
||||
_extract_sla_drain_holes_from_archive(archive, stat);
|
||||
}
|
||||
else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE))
|
||||
{
|
||||
// extract slic3r print config file
|
||||
|
@ -683,6 +692,11 @@ namespace Slic3r {
|
|||
model_object->sla_support_points = obj_sla_support_points->second;
|
||||
model_object->sla_points_status = sla::PointsStatus::UserModified;
|
||||
}
|
||||
|
||||
IdToSlaDrainHolesMap::iterator obj_drain_holes = m_sla_drain_holes.find(object.second + 1);
|
||||
if (obj_drain_holes != m_sla_drain_holes.end() && !obj_drain_holes->second.empty()) {
|
||||
model_object->sla_drain_holes = obj_drain_holes->second;
|
||||
}
|
||||
|
||||
IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first);
|
||||
if (obj_metadata != m_objects_metadata.end())
|
||||
|
@ -955,8 +969,9 @@ namespace Slic3r {
|
|||
|
||||
// Info on format versioning - see 3mf.hpp
|
||||
int version = 0;
|
||||
if (!objects.empty() && objects[0].find("support_points_format_version=") != std::string::npos) {
|
||||
objects[0].erase(objects[0].begin(), objects[0].begin() + 30); // removes the string
|
||||
std::string key("support_points_format_version=");
|
||||
if (!objects.empty() && objects[0].find(key) != std::string::npos) {
|
||||
objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string
|
||||
version = std::stoi(objects[0]);
|
||||
objects.erase(objects.begin()); // pop the header
|
||||
}
|
||||
|
@ -1022,6 +1037,90 @@ namespace Slic3r {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _3MF_Importer::_extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
|
||||
{
|
||||
if (stat.m_uncomp_size > 0)
|
||||
{
|
||||
std::string buffer(size_t(stat.m_uncomp_size), 0);
|
||||
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
|
||||
if (res == 0)
|
||||
{
|
||||
add_error("Error while reading sla support points data to buffer");
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer.back() == '\n')
|
||||
buffer.pop_back();
|
||||
|
||||
std::vector<std::string> objects;
|
||||
boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off);
|
||||
|
||||
// Info on format versioning - see 3mf.hpp
|
||||
int version = 0;
|
||||
std::string key("drain_holes_format_version=");
|
||||
if (!objects.empty() && objects[0].find(key) != std::string::npos) {
|
||||
objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string
|
||||
version = std::stoi(objects[0]);
|
||||
objects.erase(objects.begin()); // pop the header
|
||||
}
|
||||
|
||||
for (const std::string& object : objects)
|
||||
{
|
||||
std::vector<std::string> object_data;
|
||||
boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off);
|
||||
|
||||
if (object_data.size() != 2)
|
||||
{
|
||||
add_error("Error while reading object data");
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<std::string> object_data_id;
|
||||
boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off);
|
||||
if (object_data_id.size() != 2)
|
||||
{
|
||||
add_error("Error while reading object id");
|
||||
continue;
|
||||
}
|
||||
|
||||
int object_id = std::atoi(object_data_id[1].c_str());
|
||||
if (object_id == 0)
|
||||
{
|
||||
add_error("Found invalid object id");
|
||||
continue;
|
||||
}
|
||||
|
||||
IdToSlaDrainHolesMap::iterator object_item = m_sla_drain_holes.find(object_id);
|
||||
if (object_item != m_sla_drain_holes.end())
|
||||
{
|
||||
add_error("Found duplicated SLA drain holes");
|
||||
continue;
|
||||
}
|
||||
|
||||
std::vector<std::string> object_data_points;
|
||||
boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off);
|
||||
|
||||
sla::DrainHoles sla_drain_holes;
|
||||
|
||||
if (version == 1) {
|
||||
for (unsigned int i=0; i<object_data_points.size(); i+=8)
|
||||
sla_drain_holes.emplace_back(Vec3f{float(std::atof(object_data_points[i+0].c_str())),
|
||||
float(std::atof(object_data_points[i+1].c_str())),
|
||||
float(std::atof(object_data_points[i+2].c_str()))},
|
||||
Vec3f{float(std::atof(object_data_points[i+3].c_str())),
|
||||
float(std::atof(object_data_points[i+4].c_str())),
|
||||
float(std::atof(object_data_points[i+5].c_str()))},
|
||||
float(std::atof(object_data_points[i+6].c_str())),
|
||||
float(std::atof(object_data_points[i+7].c_str())));
|
||||
}
|
||||
|
||||
if (!sla_drain_holes.empty())
|
||||
m_sla_drain_holes.insert(IdToSlaDrainHolesMap::value_type(object_id, sla_drain_holes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool _3MF_Importer::_extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model)
|
||||
|
@ -1904,6 +2003,7 @@ namespace Slic3r {
|
|||
bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||
bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||
bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||
bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||
bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config);
|
||||
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data);
|
||||
bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||
|
@ -2025,6 +2125,14 @@ namespace Slic3r {
|
|||
boost::filesystem::remove(filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_add_sla_drain_holes_file_to_archive(archive, model))
|
||||
{
|
||||
close_zip_writer(&archive);
|
||||
boost::filesystem::remove(filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml").
|
||||
// All custom gcode per height of whole Model are stored here
|
||||
|
@ -2482,6 +2590,50 @@ namespace Slic3r {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _3MF_Exporter::_add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model)
|
||||
{
|
||||
const char *const fmt = "object_id=%d|";
|
||||
std::string out;
|
||||
|
||||
unsigned int count = 0;
|
||||
for (const ModelObject* object : model.objects)
|
||||
{
|
||||
++count;
|
||||
auto& drain_holes = object->sla_drain_holes;
|
||||
if (!drain_holes.empty())
|
||||
{
|
||||
out += string_printf(fmt, count);
|
||||
|
||||
// Store the layer height profile as a single space separated list.
|
||||
for (size_t i = 0; i < drain_holes.size(); ++i)
|
||||
out += string_printf((i == 0 ? "%f %f %f %f %f %f %f %f" : " %f %f %f %f %f %f %f %f"),
|
||||
drain_holes[i].pos(0),
|
||||
drain_holes[i].pos(1),
|
||||
drain_holes[i].pos(2),
|
||||
drain_holes[i].normal(0),
|
||||
drain_holes[i].normal(1),
|
||||
drain_holes[i].normal(2),
|
||||
drain_holes[i].radius,
|
||||
drain_holes[i].height);
|
||||
|
||||
out += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!out.empty())
|
||||
{
|
||||
// Adds version header at the beginning:
|
||||
out = std::string("drain_holes_format_version=") + std::to_string(drain_holes_format_version) + std::string("\n") + out;
|
||||
|
||||
if (!mz_zip_writer_add_mem(&archive, SLA_DRAIN_HOLES_FILE.c_str(), static_cast<const void*>(out.data()), out.length(), mz_uint(MZ_DEFAULT_COMPRESSION)))
|
||||
{
|
||||
add_error("Unable to add sla support points file to archive");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config)
|
||||
{
|
||||
|
|
|
@ -19,6 +19,10 @@ namespace Slic3r {
|
|||
enum {
|
||||
support_points_format_version = 1
|
||||
};
|
||||
|
||||
enum {
|
||||
drain_holes_format_version = 1
|
||||
};
|
||||
|
||||
class Model;
|
||||
class DynamicPrintConfig;
|
||||
|
|
|
@ -355,6 +355,35 @@ bool objparse(const char *path, ObjData &data)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool objparse(std::istream &stream, ObjData &data)
|
||||
{
|
||||
try {
|
||||
char buf[65536 * 2];
|
||||
size_t len = 0;
|
||||
size_t lenPrev = 0;
|
||||
while ((len = size_t(stream.read(buf + lenPrev, 65536).gcount())) != 0) {
|
||||
len += lenPrev;
|
||||
size_t lastLine = 0;
|
||||
for (size_t i = 0; i < len; ++ i)
|
||||
if (buf[i] == '\r' || buf[i] == '\n') {
|
||||
buf[i] = 0;
|
||||
char *c = buf + lastLine;
|
||||
while (*c == ' ' || *c == '\t')
|
||||
++ c;
|
||||
obj_parseline(c, data);
|
||||
lastLine = i + 1;
|
||||
}
|
||||
lenPrev = len - lastLine;
|
||||
memmove(buf, buf + lastLine, lenPrev);
|
||||
}
|
||||
}
|
||||
catch (std::bad_alloc&) {
|
||||
printf("Out of memory\r\n");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool savevector(FILE *pFile, const std::vector<T> &v)
|
||||
{
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <istream>
|
||||
|
||||
namespace ObjParser {
|
||||
|
||||
|
@ -97,6 +98,7 @@ struct ObjData {
|
|||
};
|
||||
|
||||
extern bool objparse(const char *path, ObjData &data);
|
||||
extern bool objparse(std::istream &stream, ObjData &data);
|
||||
|
||||
extern bool objbinsave(const char *path, const ObjData &data);
|
||||
|
||||
|
|
170
src/libslic3r/MeshBoolean.cpp
Normal file
170
src/libslic3r/MeshBoolean.cpp
Normal file
|
@ -0,0 +1,170 @@
|
|||
#include "MeshBoolean.hpp"
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#undef PI
|
||||
|
||||
// Include igl first. It defines "L" macro which then clashes with our localization
|
||||
#include <igl/copyleft/cgal/mesh_boolean.h>
|
||||
#undef L
|
||||
|
||||
// CGAL headers
|
||||
#include <CGAL/Polygon_mesh_processing/corefinement.h>
|
||||
#include <CGAL/Exact_integer.h>
|
||||
#include <CGAL/Surface_mesh.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace MeshBoolean {
|
||||
|
||||
typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
|
||||
typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
|
||||
|
||||
typedef std::pair<Eigen::MatrixXd, Eigen::MatrixXi> EigenMesh;
|
||||
|
||||
static TriangleMesh eigen_to_triangle_mesh(const Eigen::MatrixXd& VC, const Eigen::MatrixXi& FC)
|
||||
{
|
||||
Pointf3s points(size_t(VC.rows()));
|
||||
std::vector<Vec3crd> facets(size_t(FC.rows()));
|
||||
|
||||
for (Eigen::Index i = 0; i < VC.rows(); ++i)
|
||||
points[size_t(i)] = VC.row(i);
|
||||
|
||||
for (Eigen::Index i = 0; i < FC.rows(); ++i)
|
||||
facets[size_t(i)] = FC.row(i);
|
||||
|
||||
TriangleMesh out{points, facets};
|
||||
out.require_shared_vertices();
|
||||
return out;
|
||||
}
|
||||
|
||||
static EigenMesh triangle_mesh_to_eigen_mesh(const TriangleMesh &mesh)
|
||||
{
|
||||
EigenMesh emesh;
|
||||
emesh.first = MapMatrixXfUnaligned(mesh.its.vertices.front().data(),
|
||||
Eigen::Index(mesh.its.vertices.size()),
|
||||
3).cast<double>();
|
||||
|
||||
emesh.second = MapMatrixXiUnaligned(mesh.its.indices.front().data(),
|
||||
Eigen::Index(mesh.its.indices.size()),
|
||||
3);
|
||||
return emesh;
|
||||
}
|
||||
|
||||
void minus(TriangleMesh& A, const TriangleMesh& B)
|
||||
{
|
||||
auto [VA, FA] = triangle_mesh_to_eigen_mesh(A);
|
||||
auto [VB, FB] = triangle_mesh_to_eigen_mesh(B);
|
||||
|
||||
Eigen::MatrixXd VC;
|
||||
Eigen::MatrixXi FC;
|
||||
igl::MeshBooleanType boolean_type(igl::MESH_BOOLEAN_TYPE_MINUS);
|
||||
igl::copyleft::cgal::mesh_boolean(VA, FA, VB, FB, boolean_type, VC, FC);
|
||||
|
||||
A = eigen_to_triangle_mesh(VC, FC);
|
||||
}
|
||||
|
||||
void self_union(TriangleMesh& mesh)
|
||||
{
|
||||
auto [V, F] = triangle_mesh_to_eigen_mesh(mesh);
|
||||
|
||||
Eigen::MatrixXd VC;
|
||||
Eigen::MatrixXi FC;
|
||||
|
||||
igl::MeshBooleanType boolean_type(igl::MESH_BOOLEAN_TYPE_UNION);
|
||||
igl::copyleft::cgal::mesh_boolean(V, F, Eigen::MatrixXd(), Eigen::MatrixXi(), boolean_type, VC, FC);
|
||||
|
||||
mesh = eigen_to_triangle_mesh(VC, FC);
|
||||
}
|
||||
|
||||
namespace cgal {
|
||||
|
||||
namespace CGALProc = CGAL::Polygon_mesh_processing;
|
||||
namespace CGALParams = CGAL::Polygon_mesh_processing::parameters;
|
||||
|
||||
using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel;
|
||||
using _CGALMesh = CGAL::Surface_mesh<Kernel::Point_3>;
|
||||
|
||||
struct CGALMesh { _CGALMesh m; };
|
||||
|
||||
static void triangle_mesh_to_cgal(const TriangleMesh &M, _CGALMesh &out)
|
||||
{
|
||||
for (const Vec3f &v : M.its.vertices)
|
||||
out.add_vertex(_CGALMesh::Point(v.x(), v.y(), v.z()));
|
||||
|
||||
for (const Vec3crd &face : M.its.indices) {
|
||||
auto f = face.cast<CGAL::SM_Vertex_index>();
|
||||
out.add_face(f(0), f(1), f(2));
|
||||
}
|
||||
}
|
||||
|
||||
static TriangleMesh cgal_to_triangle_mesh(const _CGALMesh &cgalmesh)
|
||||
{
|
||||
Pointf3s points;
|
||||
std::vector<Vec3crd> facets;
|
||||
points.reserve(cgalmesh.num_vertices());
|
||||
facets.reserve(cgalmesh.num_faces());
|
||||
|
||||
for (auto &vi : cgalmesh.vertices()) {
|
||||
auto &v = cgalmesh.point(vi); // Don't ask...
|
||||
points.emplace_back(v.x(), v.y(), v.z());
|
||||
}
|
||||
|
||||
for (auto &face : cgalmesh.faces()) {
|
||||
auto vtc = cgalmesh.vertices_around_face(cgalmesh.halfedge(face));
|
||||
int i = 0;
|
||||
Vec3crd trface;
|
||||
for (auto v : vtc) trface(i++) = int(v.idx());
|
||||
facets.emplace_back(trface);
|
||||
}
|
||||
|
||||
TriangleMesh out{points, facets};
|
||||
out.require_shared_vertices();
|
||||
return out;
|
||||
}
|
||||
|
||||
std::unique_ptr<CGALMesh> triangle_mesh_to_cgal(const TriangleMesh &M)
|
||||
{
|
||||
auto out = std::make_unique<CGALMesh>();
|
||||
triangle_mesh_to_cgal(M, out->m);
|
||||
return out;
|
||||
}
|
||||
|
||||
void cgal_to_triangle_mesh(const CGALMesh &cgalmesh, TriangleMesh &out)
|
||||
{
|
||||
out = cgal_to_triangle_mesh(cgalmesh.m);
|
||||
}
|
||||
|
||||
void minus(CGALMesh &A, CGALMesh &B)
|
||||
{
|
||||
CGALProc::corefine_and_compute_difference(A.m, B.m, A.m);
|
||||
}
|
||||
|
||||
void self_union(CGALMesh &A)
|
||||
{
|
||||
CGALProc::corefine(A.m, A.m);
|
||||
}
|
||||
|
||||
void minus(TriangleMesh &A, const TriangleMesh &B)
|
||||
{
|
||||
CGALMesh meshA;
|
||||
CGALMesh meshB;
|
||||
triangle_mesh_to_cgal(A, meshA.m);
|
||||
triangle_mesh_to_cgal(B, meshB.m);
|
||||
|
||||
CGALMesh meshResult;
|
||||
CGALProc::corefine_and_compute_difference(meshA.m, meshB.m, meshResult.m);
|
||||
|
||||
A = cgal_to_triangle_mesh(meshResult.m);
|
||||
}
|
||||
|
||||
void self_union(TriangleMesh &m)
|
||||
{
|
||||
_CGALMesh cgalmesh;
|
||||
triangle_mesh_to_cgal(m, cgalmesh);
|
||||
CGALProc::corefine(cgalmesh, cgalmesh);
|
||||
|
||||
m = cgal_to_triangle_mesh(cgalmesh);
|
||||
}
|
||||
|
||||
} // namespace cgal
|
||||
|
||||
} // namespace MeshBoolean
|
||||
} // namespace Slic3r
|
38
src/libslic3r/MeshBoolean.hpp
Normal file
38
src/libslic3r/MeshBoolean.hpp
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef libslic3r_MeshBoolean_hpp_
|
||||
#define libslic3r_MeshBoolean_hpp_
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
|
||||
namespace MeshBoolean {
|
||||
|
||||
void minus(TriangleMesh& A, const TriangleMesh& B);
|
||||
void self_union(TriangleMesh& mesh);
|
||||
|
||||
namespace cgal {
|
||||
|
||||
struct CGALMesh;
|
||||
|
||||
std::unique_ptr<CGALMesh> triangle_mesh_to_cgal(const TriangleMesh &M);
|
||||
void cgal_to_triangle_mesh(const CGALMesh &cgalmesh, TriangleMesh &out);
|
||||
|
||||
// Do boolean mesh difference with CGAL bypassing igl.
|
||||
void minus(TriangleMesh &A, const TriangleMesh &B);
|
||||
|
||||
// Do self union only with CGAL.
|
||||
void self_union(TriangleMesh& mesh);
|
||||
|
||||
// does A = A - B
|
||||
// CGAL takes non-const objects as arguments. I suppose it doesn't change B but
|
||||
// there is no official garantee.
|
||||
void minus(CGALMesh &A, CGALMesh &B);
|
||||
void self_union(CGALMesh &A);
|
||||
|
||||
}
|
||||
|
||||
} // namespace MeshBoolean
|
||||
} // namespace Slic3r
|
||||
#endif // libslic3r_MeshBoolean_hpp_
|
|
@ -620,6 +620,7 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs)
|
|||
assert(this->config.id() == rhs.config.id());
|
||||
this->sla_support_points = rhs.sla_support_points;
|
||||
this->sla_points_status = rhs.sla_points_status;
|
||||
this->sla_drain_holes = rhs.sla_drain_holes;
|
||||
this->layer_config_ranges = rhs.layer_config_ranges; // #ys_FIXME_experiment
|
||||
this->layer_height_profile = rhs.layer_height_profile;
|
||||
this->printable = rhs.printable;
|
||||
|
@ -660,6 +661,7 @@ ModelObject& ModelObject::assign_copy(ModelObject &&rhs)
|
|||
assert(this->config.id() == rhs.config.id());
|
||||
this->sla_support_points = std::move(rhs.sla_support_points);
|
||||
this->sla_points_status = std::move(rhs.sla_points_status);
|
||||
this->sla_drain_holes = std::move(rhs.sla_drain_holes);
|
||||
this->layer_config_ranges = std::move(rhs.layer_config_ranges); // #ys_FIXME_experiment
|
||||
this->layer_height_profile = std::move(rhs.layer_height_profile);
|
||||
this->origin_translation = std::move(rhs.origin_translation);
|
||||
|
@ -1113,6 +1115,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
|||
if (keep_upper) {
|
||||
upper->set_model(nullptr);
|
||||
upper->sla_support_points.clear();
|
||||
lower->sla_drain_holes.clear();
|
||||
upper->sla_points_status = sla::PointsStatus::NoPoints;
|
||||
upper->clear_volumes();
|
||||
upper->input_file = "";
|
||||
|
@ -1121,6 +1124,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
|||
if (keep_lower) {
|
||||
lower->set_model(nullptr);
|
||||
lower->sla_support_points.clear();
|
||||
lower->sla_drain_holes.clear();
|
||||
lower->sla_points_status = sla::PointsStatus::NoPoints;
|
||||
lower->clear_volumes();
|
||||
lower->input_file = "";
|
||||
|
@ -1156,7 +1160,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
|||
if (keep_upper) { upper->add_volume(*volume); }
|
||||
if (keep_lower) { lower->add_volume(*volume); }
|
||||
}
|
||||
else {
|
||||
else if (! volume->mesh().empty()) {
|
||||
|
||||
TriangleMesh upper_mesh, lower_mesh;
|
||||
|
||||
// Transform the mesh by the combined transformation matrix.
|
||||
|
@ -1164,7 +1169,9 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
|||
TriangleMesh mesh(volume->mesh());
|
||||
mesh.transform(instance_matrix * volume_matrix, true);
|
||||
volume->reset_mesh();
|
||||
|
||||
|
||||
mesh.require_shared_vertices();
|
||||
|
||||
// Perform cut
|
||||
TriangleMeshSlicer tms(&mesh);
|
||||
tms.cut(float(z), &upper_mesh, &lower_mesh);
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
#include "Point.hpp"
|
||||
#include "PrintConfig.hpp"
|
||||
#include "Slicing.hpp"
|
||||
#include "SLA/SLACommon.hpp"
|
||||
#include "SLA/SupportPoint.hpp"
|
||||
#include "SLA/Hollowing.hpp"
|
||||
#include "TriangleMesh.hpp"
|
||||
#include "Arrange.hpp"
|
||||
#include "CustomGCode.hpp"
|
||||
|
@ -199,10 +200,13 @@ public:
|
|||
// This vector holds position of selected support points for SLA. The data are
|
||||
// saved in mesh coordinates to allow using them for several instances.
|
||||
// The format is (x, y, z, point_size, supports_island)
|
||||
std::vector<sla::SupportPoint> sla_support_points;
|
||||
sla::SupportPoints sla_support_points;
|
||||
// To keep track of where the points came from (used for synchronization between
|
||||
// the SLA gizmo and the backend).
|
||||
sla::PointsStatus sla_points_status = sla::PointsStatus::NoPoints;
|
||||
sla::PointsStatus sla_points_status = sla::PointsStatus::NoPoints;
|
||||
|
||||
// Holes to be drilled into the object so resin can flow out
|
||||
sla::DrainHoles sla_drain_holes;
|
||||
|
||||
/* This vector accumulates the total translation applied to the object by the
|
||||
center_around_origin() method. Callers might want to apply the same translation
|
||||
|
@ -373,7 +377,7 @@ private:
|
|||
template<class Archive> void serialize(Archive &ar) {
|
||||
ar(cereal::base_class<ObjectBase>(this));
|
||||
Internal::StaticSerializationWrapper<ModelConfig> config_wrapper(config);
|
||||
ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, printable, origin_translation,
|
||||
ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation,
|
||||
m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid);
|
||||
}
|
||||
};
|
||||
|
|
135
src/libslic3r/OpenVDBUtils.cpp
Normal file
135
src/libslic3r/OpenVDBUtils.cpp
Normal file
|
@ -0,0 +1,135 @@
|
|||
#define NOMINMAX
|
||||
#include "OpenVDBUtils.hpp"
|
||||
#include <openvdb/tools/MeshToVolume.h>
|
||||
#include <openvdb/tools/VolumeToMesh.h>
|
||||
#include <openvdb/tools/LevelSetRebuild.h>
|
||||
|
||||
//#include "MTUtils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMeshDataAdapter {
|
||||
public:
|
||||
const TriangleMesh &mesh;
|
||||
|
||||
size_t polygonCount() const { return mesh.its.indices.size(); }
|
||||
size_t pointCount() const { return mesh.its.vertices.size(); }
|
||||
size_t vertexCount(size_t) const { return 3; }
|
||||
|
||||
// Return position pos in local grid index space for polygon n and vertex v
|
||||
void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const;
|
||||
};
|
||||
|
||||
class Contour3DDataAdapter {
|
||||
public:
|
||||
const sla::Contour3D &mesh;
|
||||
|
||||
size_t polygonCount() const { return mesh.faces3.size() + mesh.faces4.size(); }
|
||||
size_t pointCount() const { return mesh.points.size(); }
|
||||
size_t vertexCount(size_t n) const { return n < mesh.faces3.size() ? 3 : 4; }
|
||||
|
||||
// Return position pos in local grid index space for polygon n and vertex v
|
||||
void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const;
|
||||
};
|
||||
|
||||
void TriangleMeshDataAdapter::getIndexSpacePoint(size_t n,
|
||||
size_t v,
|
||||
openvdb::Vec3d &pos) const
|
||||
{
|
||||
auto vidx = size_t(mesh.its.indices[n](Eigen::Index(v)));
|
||||
Slic3r::Vec3d p = mesh.its.vertices[vidx].cast<double>();
|
||||
pos = {p.x(), p.y(), p.z()};
|
||||
}
|
||||
|
||||
void Contour3DDataAdapter::getIndexSpacePoint(size_t n,
|
||||
size_t v,
|
||||
openvdb::Vec3d &pos) const
|
||||
{
|
||||
size_t vidx = 0;
|
||||
if (n < mesh.faces3.size()) vidx = size_t(mesh.faces3[n](Eigen::Index(v)));
|
||||
else vidx = size_t(mesh.faces4[n - mesh.faces3.size()](Eigen::Index(v)));
|
||||
|
||||
Slic3r::Vec3d p = mesh.points[vidx];
|
||||
pos = {p.x(), p.y(), p.z()};
|
||||
}
|
||||
|
||||
|
||||
// TODO: Do I need to call initialize? Seems to work without it as well but the
|
||||
// docs say it should be called ones. It does a mutex lock-unlock sequence all
|
||||
// even if was called previously.
|
||||
|
||||
openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh,
|
||||
const openvdb::math::Transform &tr,
|
||||
float exteriorBandWidth,
|
||||
float interiorBandWidth,
|
||||
int flags)
|
||||
{
|
||||
openvdb::initialize();
|
||||
return openvdb::tools::meshToVolume<openvdb::FloatGrid>(
|
||||
TriangleMeshDataAdapter{mesh}, tr, exteriorBandWidth,
|
||||
interiorBandWidth, flags);
|
||||
}
|
||||
|
||||
openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &mesh,
|
||||
const openvdb::math::Transform &tr,
|
||||
float exteriorBandWidth,
|
||||
float interiorBandWidth,
|
||||
int flags)
|
||||
{
|
||||
openvdb::initialize();
|
||||
return openvdb::tools::meshToVolume<openvdb::FloatGrid>(
|
||||
Contour3DDataAdapter{mesh}, tr, exteriorBandWidth, interiorBandWidth,
|
||||
flags);
|
||||
}
|
||||
|
||||
template<class Grid>
|
||||
sla::Contour3D _volumeToMesh(const Grid &grid,
|
||||
double isovalue,
|
||||
double adaptivity,
|
||||
bool relaxDisorientedTriangles)
|
||||
{
|
||||
openvdb::initialize();
|
||||
|
||||
std::vector<openvdb::Vec3s> points;
|
||||
std::vector<openvdb::Vec3I> triangles;
|
||||
std::vector<openvdb::Vec4I> quads;
|
||||
|
||||
openvdb::tools::volumeToMesh(grid, points, triangles, quads, isovalue,
|
||||
adaptivity, relaxDisorientedTriangles);
|
||||
|
||||
sla::Contour3D ret;
|
||||
ret.points.reserve(points.size());
|
||||
ret.faces3.reserve(triangles.size());
|
||||
ret.faces4.reserve(quads.size());
|
||||
|
||||
for (auto &v : points) ret.points.emplace_back(to_vec3d(v));
|
||||
for (auto &v : triangles) ret.faces3.emplace_back(to_vec3i(v));
|
||||
for (auto &v : quads) ret.faces4.emplace_back(to_vec4i(v));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
TriangleMesh grid_to_mesh(const openvdb::FloatGrid &grid,
|
||||
double isovalue,
|
||||
double adaptivity,
|
||||
bool relaxDisorientedTriangles)
|
||||
{
|
||||
return to_triangle_mesh(
|
||||
_volumeToMesh(grid, isovalue, adaptivity, relaxDisorientedTriangles));
|
||||
}
|
||||
|
||||
sla::Contour3D grid_to_contour3d(const openvdb::FloatGrid &grid,
|
||||
double isovalue,
|
||||
double adaptivity,
|
||||
bool relaxDisorientedTriangles)
|
||||
{
|
||||
return _volumeToMesh(grid, isovalue, adaptivity,
|
||||
relaxDisorientedTriangles);
|
||||
}
|
||||
|
||||
openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, double iso, double er, double ir)
|
||||
{
|
||||
return openvdb::tools::levelSetRebuild(grid, float(iso), float(er), float(ir));
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
45
src/libslic3r/OpenVDBUtils.hpp
Normal file
45
src/libslic3r/OpenVDBUtils.hpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#ifndef OPENVDBUTILS_HPP
|
||||
#define OPENVDBUTILS_HPP
|
||||
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <openvdb/openvdb.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
inline Vec3f to_vec3f(const openvdb::Vec3s &v) { return Vec3f{v.x(), v.y(), v.z()}; }
|
||||
inline Vec3d to_vec3d(const openvdb::Vec3s &v) { return to_vec3f(v).cast<double>(); }
|
||||
inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[0]), int(v[1]), int(v[2])}; }
|
||||
inline Vec4i to_vec4i(const openvdb::Vec4I &v) { return Vec4i{int(v[0]), int(v[1]), int(v[2]), int(v[3])}; }
|
||||
|
||||
openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh & mesh,
|
||||
const openvdb::math::Transform &tr = {},
|
||||
float exteriorBandWidth = 3.0f,
|
||||
float interiorBandWidth = 3.0f,
|
||||
int flags = 0);
|
||||
|
||||
openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D & mesh,
|
||||
const openvdb::math::Transform &tr = {},
|
||||
float exteriorBandWidth = 3.0f,
|
||||
float interiorBandWidth = 3.0f,
|
||||
int flags = 0);
|
||||
|
||||
sla::Contour3D grid_to_contour3d(const openvdb::FloatGrid &grid,
|
||||
double isovalue,
|
||||
double adaptivity,
|
||||
bool relaxDisorientedTriangles = true);
|
||||
|
||||
TriangleMesh grid_to_mesh(const openvdb::FloatGrid &grid,
|
||||
double isovalue = 0.0,
|
||||
double adaptivity = 0.0,
|
||||
bool relaxDisorientedTriangles = true);
|
||||
|
||||
openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid,
|
||||
double iso,
|
||||
double ext_range = 3.,
|
||||
double int_range = 3.);
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // OPENVDBUTILS_HPP
|
|
@ -2873,6 +2873,41 @@ void PrintConfigDef::init_sla_params()
|
|||
def->min = 0;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(0.3));
|
||||
|
||||
def = this->add("hollowing_enable", coBool);
|
||||
def->label = L("Enable hollowing");
|
||||
def->category = L("Hollowing");
|
||||
def->tooltip = L("Hollow out a model to have an empty interior");
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
def = this->add("hollowing_min_thickness", coFloat);
|
||||
def->label = L("Hollowing thickness");
|
||||
def->category = L("Hollowing");
|
||||
def->tooltip = L("Minimum wall thickness of a hollowed model.");
|
||||
def->sidetext = L("mm");
|
||||
def->min = 1;
|
||||
def->max = 10;
|
||||
def->mode = comSimple;
|
||||
def->set_default_value(new ConfigOptionFloat(3.));
|
||||
|
||||
def = this->add("hollowing_quality", coFloat);
|
||||
def->label = L("Hollowing accuracy");
|
||||
def->category = L("Hollowing");
|
||||
def->tooltip = L("Performance vs accuracy of calculation. Lower values may produce unwanted artifacts.");
|
||||
def->min = 0;
|
||||
def->max = 1;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(0.5));
|
||||
|
||||
def = this->add("hollowing_closing_distance", coFloat);
|
||||
def->label = L("Hollowing closing distance");
|
||||
def->category = L("Hollowing");
|
||||
def->tooltip = L("");
|
||||
def->min = 0;
|
||||
def->max = 10;
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionFloat(2.0));
|
||||
}
|
||||
|
||||
void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value)
|
||||
|
|
|
@ -1017,7 +1017,7 @@ public:
|
|||
ConfigOptionFloat support_base_height /*= 1.0*/;
|
||||
|
||||
// The minimum distance of the pillar base from the model in mm.
|
||||
ConfigOptionFloat support_base_safety_distance; /*= 1.0*/;
|
||||
ConfigOptionFloat support_base_safety_distance; /*= 1.0*/
|
||||
|
||||
// The default angle for connecting support sticks and junctions.
|
||||
ConfigOptionFloat support_critical_angle /*= 45*/;
|
||||
|
@ -1062,7 +1062,7 @@ public:
|
|||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Zero elevation mode parameters:
|
||||
// - The object pad will be derived from the the model geometry.
|
||||
// - The object pad will be derived from the model geometry.
|
||||
// - There will be a gap between the object pad and the generated pad
|
||||
// according to the support_base_safety_distance parameter.
|
||||
// - The two pads will be connected with tiny connector sticks
|
||||
|
@ -1084,6 +1084,28 @@ public:
|
|||
|
||||
// How much should the tiny connectors penetrate into the model body
|
||||
ConfigOptionFloat pad_object_connector_penetration;
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
// Model hollowing parameters:
|
||||
// - Models can be hollowed out as part of the SLA print process
|
||||
// - Thickness of the hollowed model walls can be adjusted
|
||||
// -
|
||||
// - Additional holes will be drilled into the hollow model to allow for
|
||||
// - resin removal.
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ConfigOptionBool hollowing_enable;
|
||||
|
||||
// The minimum thickness of the model walls to maintain. Note that the
|
||||
// resulting walls may be thicker due to smoothing out fine cavities where
|
||||
// resin could stuck.
|
||||
ConfigOptionFloat hollowing_min_thickness;
|
||||
|
||||
// Indirectly controls the voxel size (resolution) used by openvdb
|
||||
ConfigOptionFloat hollowing_quality;
|
||||
|
||||
// Indirectly controls the minimum size of created cavities.
|
||||
ConfigOptionFloat hollowing_closing_distance;
|
||||
|
||||
protected:
|
||||
void initialize(StaticCacheBase &cache, const char *base_ptr)
|
||||
|
@ -1121,6 +1143,10 @@ protected:
|
|||
OPT_PTR(pad_object_connector_stride);
|
||||
OPT_PTR(pad_object_connector_width);
|
||||
OPT_PTR(pad_object_connector_penetration);
|
||||
OPT_PTR(hollowing_enable);
|
||||
OPT_PTR(hollowing_min_thickness);
|
||||
OPT_PTR(hollowing_quality);
|
||||
OPT_PTR(hollowing_closing_distance);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef SLABOOSTADAPTER_HPP
|
||||
#define SLABOOSTADAPTER_HPP
|
||||
#ifndef SLA_BOOSTADAPTER_HPP
|
||||
#define SLA_BOOSTADAPTER_HPP
|
||||
|
||||
#include "SLA/SLABoilerPlate.hpp"
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <boost/geometry.hpp>
|
||||
|
||||
namespace boost {
|
30
src/libslic3r/SLA/Clustering.hpp
Normal file
30
src/libslic3r/SLA/Clustering.hpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
#ifndef SLA_CLUSTERING_HPP
|
||||
#define SLA_CLUSTERING_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
using ClusterEl = std::vector<unsigned>;
|
||||
using ClusteredPoints = std::vector<ClusterEl>;
|
||||
|
||||
// Clustering a set of points by the given distance.
|
||||
ClusteredPoints cluster(const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
double dist,
|
||||
unsigned max_points);
|
||||
|
||||
ClusteredPoints cluster(const PointSet& points,
|
||||
double dist,
|
||||
unsigned max_points);
|
||||
|
||||
ClusteredPoints cluster(
|
||||
const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
|
||||
unsigned max_points);
|
||||
|
||||
}}
|
||||
#endif // CLUSTERING_HPP
|
741
src/libslic3r/SLA/Common.cpp
Normal file
741
src/libslic3r/SLA/Common.cpp
Normal file
|
@ -0,0 +1,741 @@
|
|||
#include <cmath>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Concurrency.hpp>
|
||||
#include <libslic3r/SLA/SupportTree.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <libslic3r/SLA/Clustering.hpp>
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
|
||||
|
||||
// Workaround: IGL signed_distance.h will define PI in the igl namespace.
|
||||
#undef PI
|
||||
|
||||
// HEAVY headers... takes eternity to compile
|
||||
|
||||
// for concave hull merging decisions
|
||||
#include <libslic3r/SLA/BoostAdapter.hpp>
|
||||
#include "boost/geometry/index/rtree.hpp"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4244)
|
||||
#pragma warning(disable: 4267)
|
||||
#endif
|
||||
#include <igl/ray_mesh_intersect.h>
|
||||
#include <igl/point_mesh_squared_distance.h>
|
||||
#include <igl/remove_duplicate_vertices.h>
|
||||
#include <igl/signed_distance.h>
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
#include "ClipperUtils.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
// Bring back PI from the igl namespace
|
||||
using igl::PI;
|
||||
|
||||
/* **************************************************************************
|
||||
* PointIndex implementation
|
||||
* ************************************************************************** */
|
||||
|
||||
class PointIndex::Impl {
|
||||
public:
|
||||
using BoostIndex = boost::geometry::index::rtree< PointIndexEl,
|
||||
boost::geometry::index::rstar<16, 4> /* ? */ >;
|
||||
|
||||
BoostIndex m_store;
|
||||
};
|
||||
|
||||
PointIndex::PointIndex(): m_impl(new Impl()) {}
|
||||
PointIndex::~PointIndex() {}
|
||||
|
||||
PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
|
||||
PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
|
||||
|
||||
PointIndex& PointIndex::operator=(const PointIndex &cpy)
|
||||
{
|
||||
m_impl.reset(new Impl(*cpy.m_impl));
|
||||
return *this;
|
||||
}
|
||||
|
||||
PointIndex& PointIndex::operator=(PointIndex &&cpy)
|
||||
{
|
||||
m_impl.swap(cpy.m_impl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void PointIndex::insert(const PointIndexEl &el)
|
||||
{
|
||||
m_impl->m_store.insert(el);
|
||||
}
|
||||
|
||||
bool PointIndex::remove(const PointIndexEl& el)
|
||||
{
|
||||
return m_impl->m_store.remove(el) == 1;
|
||||
}
|
||||
|
||||
std::vector<PointIndexEl>
|
||||
PointIndex::query(std::function<bool(const PointIndexEl &)> fn) const
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
|
||||
std::vector<PointIndexEl> ret;
|
||||
m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) const
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
std::vector<PointIndexEl> ret; ret.reserve(k);
|
||||
m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t PointIndex::size() const
|
||||
{
|
||||
return m_impl->m_store.size();
|
||||
}
|
||||
|
||||
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn)
|
||||
{
|
||||
for(auto& el : m_impl->m_store) fn(el);
|
||||
}
|
||||
|
||||
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) const
|
||||
{
|
||||
for(const auto &el : m_impl->m_store) fn(el);
|
||||
}
|
||||
|
||||
/* **************************************************************************
|
||||
* BoxIndex implementation
|
||||
* ************************************************************************** */
|
||||
|
||||
class BoxIndex::Impl {
|
||||
public:
|
||||
using BoostIndex = boost::geometry::index::
|
||||
rtree<BoxIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>;
|
||||
|
||||
BoostIndex m_store;
|
||||
};
|
||||
|
||||
BoxIndex::BoxIndex(): m_impl(new Impl()) {}
|
||||
BoxIndex::~BoxIndex() {}
|
||||
|
||||
BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
|
||||
BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
|
||||
|
||||
BoxIndex& BoxIndex::operator=(const BoxIndex &cpy)
|
||||
{
|
||||
m_impl.reset(new Impl(*cpy.m_impl));
|
||||
return *this;
|
||||
}
|
||||
|
||||
BoxIndex& BoxIndex::operator=(BoxIndex &&cpy)
|
||||
{
|
||||
m_impl.swap(cpy.m_impl);
|
||||
return *this;
|
||||
}
|
||||
|
||||
void BoxIndex::insert(const BoxIndexEl &el)
|
||||
{
|
||||
m_impl->m_store.insert(el);
|
||||
}
|
||||
|
||||
bool BoxIndex::remove(const BoxIndexEl& el)
|
||||
{
|
||||
return m_impl->m_store.remove(el) == 1;
|
||||
}
|
||||
|
||||
std::vector<BoxIndexEl> BoxIndex::query(const BoundingBox &qrbb,
|
||||
BoxIndex::QueryType qt)
|
||||
{
|
||||
namespace bgi = boost::geometry::index;
|
||||
|
||||
std::vector<BoxIndexEl> ret; ret.reserve(m_impl->m_store.size());
|
||||
|
||||
switch (qt) {
|
||||
case qtIntersects:
|
||||
m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret));
|
||||
break;
|
||||
case qtWithin:
|
||||
m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t BoxIndex::size() const
|
||||
{
|
||||
return m_impl->m_store.size();
|
||||
}
|
||||
|
||||
void BoxIndex::foreach(std::function<void (const BoxIndexEl &)> fn)
|
||||
{
|
||||
for(auto& el : m_impl->m_store) fn(el);
|
||||
}
|
||||
|
||||
|
||||
/* ****************************************************************************
|
||||
* EigenMesh3D implementation
|
||||
* ****************************************************************************/
|
||||
|
||||
class EigenMesh3D::AABBImpl: public igl::AABB<Eigen::MatrixXd, 3> {
|
||||
public:
|
||||
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
||||
igl::WindingNumberAABB<Vec3d, Eigen::MatrixXd, Eigen::MatrixXi> windtree;
|
||||
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
||||
};
|
||||
|
||||
EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) {
|
||||
static const double dEPS = 1e-6;
|
||||
|
||||
const stl_file& stl = tmesh.stl;
|
||||
|
||||
auto&& bb = tmesh.bounding_box();
|
||||
m_ground_level += bb.min(Z);
|
||||
|
||||
Eigen::MatrixXd V;
|
||||
Eigen::MatrixXi F;
|
||||
|
||||
V.resize(3*stl.stats.number_of_facets, 3);
|
||||
F.resize(stl.stats.number_of_facets, 3);
|
||||
for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) {
|
||||
const stl_facet &facet = stl.facet_start[i];
|
||||
V.block<1, 3>(3 * i + 0, 0) = facet.vertex[0].cast<double>();
|
||||
V.block<1, 3>(3 * i + 1, 0) = facet.vertex[1].cast<double>();
|
||||
V.block<1, 3>(3 * i + 2, 0) = facet.vertex[2].cast<double>();
|
||||
F(i, 0) = int(3*i+0);
|
||||
F(i, 1) = int(3*i+1);
|
||||
F(i, 2) = int(3*i+2);
|
||||
}
|
||||
|
||||
// We will convert this to a proper 3d mesh with no duplicate points.
|
||||
Eigen::VectorXi SVI, SVJ;
|
||||
igl::remove_duplicate_vertices(V, F, dEPS, m_V, SVI, SVJ, m_F);
|
||||
|
||||
// Build the AABB accelaration tree
|
||||
m_aabb->init(m_V, m_F);
|
||||
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
||||
m_aabb->windtree.set_mesh(m_V, m_F);
|
||||
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
||||
}
|
||||
|
||||
EigenMesh3D::~EigenMesh3D() {}
|
||||
|
||||
EigenMesh3D::EigenMesh3D(const EigenMesh3D &other):
|
||||
m_V(other.m_V), m_F(other.m_F), m_ground_level(other.m_ground_level),
|
||||
m_aabb( new AABBImpl(*other.m_aabb) ) {}
|
||||
|
||||
EigenMesh3D::EigenMesh3D(const Contour3D &other)
|
||||
{
|
||||
m_V.resize(Eigen::Index(other.points.size()), 3);
|
||||
m_F.resize(Eigen::Index(other.faces3.size() + 2 * other.faces4.size()), 3);
|
||||
|
||||
for (Eigen::Index i = 0; i < Eigen::Index(other.points.size()); ++i)
|
||||
m_V.row(i) = other.points[size_t(i)];
|
||||
|
||||
for (Eigen::Index i = 0; i < Eigen::Index(other.faces3.size()); ++i)
|
||||
m_F.row(i) = other.faces3[size_t(i)];
|
||||
|
||||
size_t N = other.faces3.size() + 2 * other.faces4.size();
|
||||
for (size_t i = other.faces3.size(); i < N; i += 2) {
|
||||
size_t quad_idx = (i - other.faces3.size()) / 2;
|
||||
auto & quad = other.faces4[quad_idx];
|
||||
m_F.row(Eigen::Index(i)) = Vec3i{quad(0), quad(1), quad(2)};
|
||||
m_F.row(Eigen::Index(i + 1)) = Vec3i{quad(2), quad(3), quad(0)};
|
||||
}
|
||||
}
|
||||
|
||||
EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other)
|
||||
{
|
||||
m_V = other.m_V;
|
||||
m_F = other.m_F;
|
||||
m_ground_level = other.m_ground_level;
|
||||
m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this;
|
||||
}
|
||||
|
||||
EigenMesh3D::hit_result
|
||||
EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
|
||||
{
|
||||
assert(is_approx(dir.norm(), 1.));
|
||||
igl::Hit hit;
|
||||
hit.t = std::numeric_limits<float>::infinity();
|
||||
|
||||
if (m_holes.empty()) {
|
||||
m_aabb->intersect_ray(m_V, m_F, s, dir, hit);
|
||||
hit_result ret(*this);
|
||||
ret.m_t = double(hit.t);
|
||||
ret.m_dir = dir;
|
||||
ret.m_source = s;
|
||||
if(!std::isinf(hit.t) && !std::isnan(hit.t))
|
||||
ret.m_normal = this->normal_by_face_id(hit.id);
|
||||
|
||||
return ret;
|
||||
}
|
||||
else {
|
||||
// If there are holes, the hit_results will be made by
|
||||
// query_ray_hits (object) and filter_hits (holes):
|
||||
return filter_hits(query_ray_hits(s, dir));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<EigenMesh3D::hit_result>
|
||||
EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
|
||||
{
|
||||
std::vector<EigenMesh3D::hit_result> outs;
|
||||
std::vector<igl::Hit> hits;
|
||||
m_aabb->intersect_ray(m_V, m_F, s, dir, hits);
|
||||
|
||||
// The sort is necessary, the hits are not always sorted.
|
||||
std::sort(hits.begin(), hits.end(),
|
||||
[](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
|
||||
|
||||
// Remove duplicates. They sometimes appear, for example when the ray is cast
|
||||
// along an axis of a cube due to floating-point approximations in igl (?)
|
||||
hits.erase(std::unique(hits.begin(), hits.end(),
|
||||
[](const igl::Hit& a, const igl::Hit& b)
|
||||
{ return a.t == b.t; }),
|
||||
hits.end());
|
||||
|
||||
// Convert the igl::Hit into hit_result
|
||||
outs.reserve(hits.size());
|
||||
for (const igl::Hit& hit : hits) {
|
||||
outs.emplace_back(EigenMesh3D::hit_result(*this));
|
||||
outs.back().m_t = double(hit.t);
|
||||
outs.back().m_dir = dir;
|
||||
outs.back().m_source = s;
|
||||
if(!std::isinf(hit.t) && !std::isnan(hit.t))
|
||||
outs.back().m_normal = this->normal_by_face_id(hit.id);
|
||||
}
|
||||
|
||||
return outs;
|
||||
}
|
||||
|
||||
EigenMesh3D::hit_result EigenMesh3D::filter_hits(
|
||||
const std::vector<EigenMesh3D::hit_result>& object_hits) const
|
||||
{
|
||||
assert(! m_holes.empty());
|
||||
hit_result out(*this);
|
||||
|
||||
if (object_hits.empty())
|
||||
return out;
|
||||
|
||||
const Vec3d& s = object_hits.front().source();
|
||||
const Vec3d& dir = object_hits.front().direction();
|
||||
|
||||
// A helper struct to save an intersetion with a hole
|
||||
struct HoleHit {
|
||||
HoleHit(float t_p, const Vec3d& normal_p, bool entry_p) :
|
||||
t(t_p), normal(normal_p), entry(entry_p) {}
|
||||
float t;
|
||||
Vec3d normal;
|
||||
bool entry;
|
||||
};
|
||||
std::vector<HoleHit> hole_isects;
|
||||
hole_isects.reserve(m_holes.size());
|
||||
|
||||
auto sf = s.cast<float>();
|
||||
auto dirf = dir.cast<float>();
|
||||
|
||||
// Collect hits on all holes, preserve information about entry/exit
|
||||
for (const sla::DrainHole& hole : m_holes) {
|
||||
std::array<std::pair<float, Vec3d>, 2> isects;
|
||||
if (hole.get_intersections(sf, dirf, isects)) {
|
||||
// Ignore hole hits behind the source
|
||||
if (isects[0].first > 0.f) hole_isects.emplace_back(isects[0].first, isects[0].second, true);
|
||||
if (isects[1].first > 0.f) hole_isects.emplace_back(isects[1].first, isects[1].second, false);
|
||||
}
|
||||
}
|
||||
|
||||
// Holes can intersect each other, sort the hits by t
|
||||
std::sort(hole_isects.begin(), hole_isects.end(),
|
||||
[](const HoleHit& a, const HoleHit& b) { return a.t < b.t; });
|
||||
|
||||
// Now inspect the intersections with object and holes, in the order of
|
||||
// increasing distance. Keep track how deep are we nested in mesh/holes and
|
||||
// pick the correct intersection.
|
||||
// This needs to be done twice - first to find out how deep in the structure
|
||||
// the source is, then to pick the correct intersection.
|
||||
int hole_nested = 0;
|
||||
int object_nested = 0;
|
||||
for (int dry_run=1; dry_run>=0; --dry_run) {
|
||||
hole_nested = -hole_nested;
|
||||
object_nested = -object_nested;
|
||||
|
||||
bool is_hole = false;
|
||||
bool is_entry = false;
|
||||
const HoleHit* next_hole_hit = hole_isects.empty() ? nullptr : &hole_isects.front();
|
||||
const hit_result* next_mesh_hit = &object_hits.front();
|
||||
|
||||
while (next_hole_hit || next_mesh_hit) {
|
||||
if (next_hole_hit && next_mesh_hit) // still have hole and obj hits
|
||||
is_hole = (next_hole_hit->t < next_mesh_hit->m_t);
|
||||
else
|
||||
is_hole = next_hole_hit; // one or the other ran out
|
||||
|
||||
// Is this entry or exit hit?
|
||||
is_entry = is_hole ? next_hole_hit->entry : ! next_mesh_hit->is_inside();
|
||||
|
||||
if (! dry_run) {
|
||||
if (! is_hole && hole_nested == 0) {
|
||||
// This is a valid object hit
|
||||
return *next_mesh_hit;
|
||||
}
|
||||
if (is_hole && ! is_entry && object_nested != 0) {
|
||||
// This holehit is the one we seek
|
||||
out.m_t = next_hole_hit->t;
|
||||
out.m_normal = next_hole_hit->normal;
|
||||
out.m_source = s;
|
||||
out.m_dir = dir;
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
// Increase/decrease the counter
|
||||
(is_hole ? hole_nested : object_nested) += (is_entry ? 1 : -1);
|
||||
|
||||
// Advance the respective pointer
|
||||
if (is_hole && next_hole_hit++ == &hole_isects.back())
|
||||
next_hole_hit = nullptr;
|
||||
if (! is_hole && next_mesh_hit++ == &object_hits.back())
|
||||
next_mesh_hit = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// if we got here, the ray ended up in infinity
|
||||
return out;
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
||||
EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const {
|
||||
double sign = 0; double sqdst = 0; int i = 0; Vec3d c;
|
||||
igl::signed_distance_winding_number(*m_aabb, m_V, m_F, m_aabb->windtree,
|
||||
p, sign, sqdst, i, c);
|
||||
|
||||
return si_result(sign * std::sqrt(sqdst), i, c);
|
||||
}
|
||||
|
||||
bool EigenMesh3D::inside(const Vec3d &p) const {
|
||||
return m_aabb->windtree.inside(p);
|
||||
}
|
||||
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
||||
|
||||
double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
|
||||
double sqdst = 0;
|
||||
Eigen::Matrix<double, 1, 3> pp = p;
|
||||
Eigen::Matrix<double, 1, 3> cc;
|
||||
sqdst = m_aabb->squared_distance(m_V, m_F, pp, i, cc);
|
||||
c = cc;
|
||||
return sqdst;
|
||||
}
|
||||
|
||||
/* ****************************************************************************
|
||||
* Misc functions
|
||||
* ****************************************************************************/
|
||||
|
||||
namespace {
|
||||
|
||||
bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
|
||||
double eps = 0.05)
|
||||
{
|
||||
using Line3D = Eigen::ParametrizedLine<double, 3>;
|
||||
|
||||
auto line = Line3D::Through(e1, e2);
|
||||
double d = line.distance(p);
|
||||
return std::abs(d) < eps;
|
||||
}
|
||||
|
||||
template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
|
||||
auto p = pp2 - pp1;
|
||||
return std::sqrt(p.transpose() * p);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
PointSet normals(const PointSet& points,
|
||||
const EigenMesh3D& mesh,
|
||||
double eps,
|
||||
std::function<void()> thr, // throw on cancel
|
||||
const std::vector<unsigned>& pt_indices)
|
||||
{
|
||||
if (points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0)
|
||||
return {};
|
||||
|
||||
std::vector<unsigned> range = pt_indices;
|
||||
if (range.empty()) {
|
||||
range.resize(size_t(points.rows()), 0);
|
||||
std::iota(range.begin(), range.end(), 0);
|
||||
}
|
||||
|
||||
PointSet ret(range.size(), 3);
|
||||
|
||||
// for (size_t ridx = 0; ridx < range.size(); ++ridx)
|
||||
ccr::enumerate(
|
||||
range.begin(), range.end(),
|
||||
[&ret, &mesh, &points, thr, eps](unsigned el, size_t ridx) {
|
||||
thr();
|
||||
auto eidx = Eigen::Index(el);
|
||||
int faceid = 0;
|
||||
Vec3d p;
|
||||
|
||||
mesh.squared_distance(points.row(eidx), faceid, p);
|
||||
|
||||
auto trindex = mesh.F().row(faceid);
|
||||
|
||||
const Vec3d &p1 = mesh.V().row(trindex(0));
|
||||
const Vec3d &p2 = mesh.V().row(trindex(1));
|
||||
const Vec3d &p3 = mesh.V().row(trindex(2));
|
||||
|
||||
// We should check if the point lies on an edge of the hosting
|
||||
// triangle. If it does then all the other triangles using the
|
||||
// same two points have to be searched and the final normal should
|
||||
// be some kind of aggregation of the participating triangle
|
||||
// normals. We should also consider the cases where the support
|
||||
// point lies right on a vertex of its triangle. The procedure is
|
||||
// the same, get the neighbor triangles and calculate an average
|
||||
// normal.
|
||||
|
||||
// mark the vertex indices of the edge. ia and ib marks and edge
|
||||
// ic will mark a single vertex.
|
||||
int ia = -1, ib = -1, ic = -1;
|
||||
|
||||
if (std::abs(distance(p, p1)) < eps) {
|
||||
ic = trindex(0);
|
||||
} else if (std::abs(distance(p, p2)) < eps) {
|
||||
ic = trindex(1);
|
||||
} else if (std::abs(distance(p, p3)) < eps) {
|
||||
ic = trindex(2);
|
||||
} else if (point_on_edge(p, p1, p2, eps)) {
|
||||
ia = trindex(0);
|
||||
ib = trindex(1);
|
||||
} else if (point_on_edge(p, p2, p3, eps)) {
|
||||
ia = trindex(1);
|
||||
ib = trindex(2);
|
||||
} else if (point_on_edge(p, p1, p3, eps)) {
|
||||
ia = trindex(0);
|
||||
ib = trindex(2);
|
||||
}
|
||||
|
||||
// vector for the neigboring triangles including the detected one.
|
||||
std::vector<Vec3i> neigh;
|
||||
if (ic >= 0) { // The point is right on a vertex of the triangle
|
||||
for (int n = 0; n < mesh.F().rows(); ++n) {
|
||||
thr();
|
||||
Vec3i ni = mesh.F().row(n);
|
||||
if ((ni(X) == ic || ni(Y) == ic || ni(Z) == ic))
|
||||
neigh.emplace_back(ni);
|
||||
}
|
||||
} else if (ia >= 0 && ib >= 0) { // the point is on and edge
|
||||
// now get all the neigboring triangles
|
||||
for (int n = 0; n < mesh.F().rows(); ++n) {
|
||||
thr();
|
||||
Vec3i ni = mesh.F().row(n);
|
||||
if ((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) &&
|
||||
(ni(X) == ib || ni(Y) == ib || ni(Z) == ib))
|
||||
neigh.emplace_back(ni);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the normals for the neighboring triangles
|
||||
std::vector<Vec3d> neighnorms;
|
||||
neighnorms.reserve(neigh.size());
|
||||
for (const Vec3i &tri : neigh) {
|
||||
const Vec3d & pt1 = mesh.V().row(tri(0));
|
||||
const Vec3d & pt2 = mesh.V().row(tri(1));
|
||||
const Vec3d & pt3 = mesh.V().row(tri(2));
|
||||
Eigen::Vector3d U = pt2 - pt1;
|
||||
Eigen::Vector3d V = pt3 - pt1;
|
||||
neighnorms.emplace_back(U.cross(V).normalized());
|
||||
}
|
||||
|
||||
// Throw out duplicates. They would cause trouble with summing. We
|
||||
// will use std::unique which works on sorted ranges. We will sort
|
||||
// by the coefficient-wise sum of the normals. It should force the
|
||||
// same elements to be consecutive.
|
||||
std::sort(neighnorms.begin(), neighnorms.end(),
|
||||
[](const Vec3d &v1, const Vec3d &v2) {
|
||||
return v1.sum() < v2.sum();
|
||||
});
|
||||
|
||||
auto lend = std::unique(neighnorms.begin(), neighnorms.end(),
|
||||
[](const Vec3d &n1, const Vec3d &n2) {
|
||||
// Compare normals for equivalence.
|
||||
// This is controvers stuff.
|
||||
auto deq = [](double a, double b) {
|
||||
return std::abs(a - b) < 1e-3;
|
||||
};
|
||||
return deq(n1(X), n2(X)) &&
|
||||
deq(n1(Y), n2(Y)) &&
|
||||
deq(n1(Z), n2(Z));
|
||||
});
|
||||
|
||||
if (!neighnorms.empty()) { // there were neighbors to count with
|
||||
// sum up the normals and then normalize the result again.
|
||||
// This unification seems to be enough.
|
||||
Vec3d sumnorm(0, 0, 0);
|
||||
sumnorm = std::accumulate(neighnorms.begin(), lend, sumnorm);
|
||||
sumnorm.normalize();
|
||||
ret.row(long(ridx)) = sumnorm;
|
||||
} else { // point lies safely within its triangle
|
||||
Eigen::Vector3d U = p2 - p1;
|
||||
Eigen::Vector3d V = p3 - p1;
|
||||
ret.row(long(ridx)) = U.cross(V).normalized();
|
||||
}
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
namespace bgi = boost::geometry::index;
|
||||
using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >;
|
||||
|
||||
namespace {
|
||||
|
||||
bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2)
|
||||
{
|
||||
return e1.second < e2.second;
|
||||
};
|
||||
|
||||
ClusteredPoints cluster(Index3D &sindex,
|
||||
unsigned max_points,
|
||||
std::function<std::vector<PointIndexEl>(
|
||||
const Index3D &, const PointIndexEl &)> qfn)
|
||||
{
|
||||
using Elems = std::vector<PointIndexEl>;
|
||||
|
||||
// Recursive function for visiting all the points in a given distance to
|
||||
// each other
|
||||
std::function<void(Elems&, Elems&)> group =
|
||||
[&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster)
|
||||
{
|
||||
for(auto& p : pts) {
|
||||
std::vector<PointIndexEl> tmp = qfn(sindex, p);
|
||||
|
||||
std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements);
|
||||
|
||||
Elems newpts;
|
||||
std::set_difference(tmp.begin(), tmp.end(),
|
||||
cluster.begin(), cluster.end(),
|
||||
std::back_inserter(newpts), cmp_ptidx_elements);
|
||||
|
||||
int c = max_points && newpts.size() + cluster.size() > max_points?
|
||||
int(max_points - cluster.size()) : int(newpts.size());
|
||||
|
||||
cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c);
|
||||
std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements);
|
||||
|
||||
if(!newpts.empty() && (!max_points || cluster.size() < max_points))
|
||||
group(newpts, cluster);
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<Elems> clusters;
|
||||
for(auto it = sindex.begin(); it != sindex.end();) {
|
||||
Elems cluster = {};
|
||||
Elems pts = {*it};
|
||||
group(pts, cluster);
|
||||
|
||||
for(auto& c : cluster) sindex.remove(c);
|
||||
it = sindex.begin();
|
||||
|
||||
clusters.emplace_back(cluster);
|
||||
}
|
||||
|
||||
ClusteredPoints result;
|
||||
for(auto& cluster : clusters) {
|
||||
result.emplace_back();
|
||||
for(auto c : cluster) result.back().emplace_back(c.second);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
|
||||
const PointIndexEl& p,
|
||||
double dist,
|
||||
unsigned max_points)
|
||||
{
|
||||
std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
|
||||
sindex.query(
|
||||
bgi::nearest(p.first, max_points),
|
||||
std::back_inserter(tmp)
|
||||
);
|
||||
|
||||
for(auto it = tmp.begin(); it < tmp.end(); ++it)
|
||||
if(distance(p.first, it->first) > dist) it = tmp.erase(it);
|
||||
|
||||
return tmp;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Clustering a set of points by the given criteria
|
||||
ClusteredPoints cluster(
|
||||
const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
double dist,
|
||||
unsigned max_points)
|
||||
{
|
||||
// A spatial index for querying the nearest points
|
||||
Index3D sindex;
|
||||
|
||||
// Build the index
|
||||
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
|
||||
|
||||
return cluster(sindex, max_points,
|
||||
[dist, max_points](const Index3D& sidx, const PointIndexEl& p)
|
||||
{
|
||||
return distance_queryfn(sidx, p, dist, max_points);
|
||||
});
|
||||
}
|
||||
|
||||
// Clustering a set of points by the given criteria
|
||||
ClusteredPoints cluster(
|
||||
const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
|
||||
unsigned max_points)
|
||||
{
|
||||
// A spatial index for querying the nearest points
|
||||
Index3D sindex;
|
||||
|
||||
// Build the index
|
||||
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
|
||||
|
||||
return cluster(sindex, max_points,
|
||||
[max_points, predicate](const Index3D& sidx, const PointIndexEl& p)
|
||||
{
|
||||
std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
|
||||
sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){
|
||||
return predicate(p, e);
|
||||
}), std::back_inserter(tmp));
|
||||
return tmp;
|
||||
});
|
||||
}
|
||||
|
||||
ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points)
|
||||
{
|
||||
// A spatial index for querying the nearest points
|
||||
Index3D sindex;
|
||||
|
||||
// Build the index
|
||||
for(Eigen::Index i = 0; i < pts.rows(); i++)
|
||||
sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i)));
|
||||
|
||||
return cluster(sindex, max_points,
|
||||
[dist, max_points](const Index3D& sidx, const PointIndexEl& p)
|
||||
{
|
||||
return distance_queryfn(sidx, p, dist, max_points);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
32
src/libslic3r/SLA/Common.hpp
Normal file
32
src/libslic3r/SLA/Common.hpp
Normal file
|
@ -0,0 +1,32 @@
|
|||
#ifndef SLA_COMMON_HPP
|
||||
#define SLA_COMMON_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <numeric>
|
||||
#include <functional>
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
//#include "SLASpatIndex.hpp"
|
||||
|
||||
//#include <libslic3r/ExPolygon.hpp>
|
||||
//#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
// #define SLIC3R_SLA_NEEDS_WINDTREE
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Typedefs from Point.hpp
|
||||
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f;
|
||||
typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d;
|
||||
typedef Eigen::Matrix<int, 4, 1, Eigen::DontAlign> Vec4i;
|
||||
|
||||
namespace sla {
|
||||
|
||||
using PointSet = Eigen::MatrixXd;
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
||||
|
||||
#endif // SLASUPPORTTREE_HPP
|
|
@ -1,7 +1,9 @@
|
|||
#include "ConcaveHull.hpp"
|
||||
#include <libslic3r/SLA/ConcaveHull.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
|
||||
#include <libslic3r/MTUtils.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
#include "SLASpatIndex.hpp"
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -40,9 +42,9 @@ Point ConcaveHull::centroid(const Points &pp)
|
|||
|
||||
// As it shows, the current offset_ex in ClipperUtils hangs if used in jtRound
|
||||
// mode
|
||||
ClipperLib::Paths fast_offset(const ClipperLib::Paths &paths,
|
||||
coord_t delta,
|
||||
ClipperLib::JoinType jointype)
|
||||
static ClipperLib::Paths fast_offset(const ClipperLib::Paths &paths,
|
||||
coord_t delta,
|
||||
ClipperLib::JoinType jointype)
|
||||
{
|
||||
using ClipperLib::ClipperOffset;
|
||||
using ClipperLib::etClosedPolygon;
|
||||
|
@ -73,7 +75,7 @@ Points ConcaveHull::calculate_centroids() const
|
|||
Points centroids = reserve_vector<Point>(m_polys.size());
|
||||
std::transform(m_polys.begin(), m_polys.end(),
|
||||
std::back_inserter(centroids),
|
||||
[this](const Polygon &poly) { return centroid(poly); });
|
||||
[](const Polygon &poly) { return centroid(poly); });
|
||||
|
||||
return centroids;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef CONCAVEHULL_HPP
|
||||
#define CONCAVEHULL_HPP
|
||||
#ifndef SLA_CONCAVEHULL_HPP
|
||||
#define SLA_CONCAVEHULL_HPP
|
||||
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef SLACONCURRENCY_H
|
||||
#define SLACONCURRENCY_H
|
||||
#ifndef SLA_CONCURRENCY_H
|
||||
#define SLA_CONCURRENCY_H
|
||||
|
||||
#include <tbb/spin_mutex.h>
|
||||
#include <tbb/mutex.h>
|
149
src/libslic3r/SLA/Contour3D.cpp
Normal file
149
src/libslic3r/SLA/Contour3D.cpp
Normal file
|
@ -0,0 +1,149 @@
|
|||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
|
||||
#include <libslic3r/Format/objparser.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
Contour3D::Contour3D(const TriangleMesh &trmesh)
|
||||
{
|
||||
points.reserve(trmesh.its.vertices.size());
|
||||
faces3.reserve(trmesh.its.indices.size());
|
||||
|
||||
for (auto &v : trmesh.its.vertices)
|
||||
points.emplace_back(v.cast<double>());
|
||||
|
||||
std::copy(trmesh.its.indices.begin(), trmesh.its.indices.end(),
|
||||
std::back_inserter(faces3));
|
||||
}
|
||||
|
||||
Contour3D::Contour3D(TriangleMesh &&trmesh)
|
||||
{
|
||||
points.reserve(trmesh.its.vertices.size());
|
||||
|
||||
for (auto &v : trmesh.its.vertices)
|
||||
points.emplace_back(v.cast<double>());
|
||||
|
||||
faces3.swap(trmesh.its.indices);
|
||||
}
|
||||
|
||||
Contour3D::Contour3D(const EigenMesh3D &emesh) {
|
||||
points.reserve(size_t(emesh.V().rows()));
|
||||
faces3.reserve(size_t(emesh.F().rows()));
|
||||
|
||||
for (int r = 0; r < emesh.V().rows(); r++)
|
||||
points.emplace_back(emesh.V().row(r).cast<double>());
|
||||
|
||||
for (int i = 0; i < emesh.F().rows(); i++)
|
||||
faces3.emplace_back(emesh.F().row(i));
|
||||
}
|
||||
|
||||
Contour3D &Contour3D::merge(const Contour3D &ctr)
|
||||
{
|
||||
auto N = coord_t(points.size());
|
||||
auto N_f3 = faces3.size();
|
||||
auto N_f4 = faces4.size();
|
||||
|
||||
points.insert(points.end(), ctr.points.begin(), ctr.points.end());
|
||||
faces3.insert(faces3.end(), ctr.faces3.begin(), ctr.faces3.end());
|
||||
faces4.insert(faces4.end(), ctr.faces4.begin(), ctr.faces4.end());
|
||||
|
||||
for(size_t n = N_f3; n < faces3.size(); n++) {
|
||||
auto& idx = faces3[n]; idx.x() += N; idx.y() += N; idx.z() += N;
|
||||
}
|
||||
|
||||
for(size_t n = N_f4; n < faces4.size(); n++) {
|
||||
auto& idx = faces4[n]; for (int k = 0; k < 4; k++) idx(k) += N;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Contour3D &Contour3D::merge(const Pointf3s &triangles)
|
||||
{
|
||||
const size_t offs = points.size();
|
||||
points.insert(points.end(), triangles.begin(), triangles.end());
|
||||
faces3.reserve(faces3.size() + points.size() / 3);
|
||||
|
||||
for(int i = int(offs); i < int(points.size()); i += 3)
|
||||
faces3.emplace_back(i, i + 1, i + 2);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Contour3D::to_obj(std::ostream &stream)
|
||||
{
|
||||
for(auto& p : points)
|
||||
stream << "v " << p.transpose() << "\n";
|
||||
|
||||
for(auto& f : faces3)
|
||||
stream << "f " << (f + Vec3i(1, 1, 1)).transpose() << "\n";
|
||||
|
||||
for(auto& f : faces4)
|
||||
stream << "f " << (f + Vec4i(1, 1, 1, 1)).transpose() << "\n";
|
||||
}
|
||||
|
||||
void Contour3D::from_obj(std::istream &stream)
|
||||
{
|
||||
ObjParser::ObjData data;
|
||||
ObjParser::objparse(stream, data);
|
||||
|
||||
points.reserve(data.coordinates.size() / 4 + 1);
|
||||
auto &coords = data.coordinates;
|
||||
for (size_t i = 0; i < coords.size(); i += 4)
|
||||
points.emplace_back(coords[i], coords[i + 1], coords[i + 2]);
|
||||
|
||||
Vec3i triangle;
|
||||
Vec4i quad;
|
||||
size_t v = 0;
|
||||
while(v < data.vertices.size()) {
|
||||
size_t N = 0;
|
||||
size_t i = v;
|
||||
while (data.vertices[v++].coordIdx != -1) ++N;
|
||||
|
||||
std::function<void(int, int)> setfn;
|
||||
if (N < 3 || N > 4) continue;
|
||||
else if (N == 3) setfn = [&triangle](int k, int f) { triangle(k) = f; };
|
||||
else setfn = [&quad](int k, int f) { quad(k) = f; };
|
||||
|
||||
for (size_t j = 0; j < N; ++j)
|
||||
setfn(int(j), data.vertices[i + j].coordIdx);
|
||||
}
|
||||
}
|
||||
|
||||
TriangleMesh to_triangle_mesh(const Contour3D &ctour) {
|
||||
if (ctour.faces4.empty()) return {ctour.points, ctour.faces3};
|
||||
|
||||
std::vector<Vec3i> triangles;
|
||||
|
||||
triangles.reserve(ctour.faces3.size() + 2 * ctour.faces4.size());
|
||||
std::copy(ctour.faces3.begin(), ctour.faces3.end(),
|
||||
std::back_inserter(triangles));
|
||||
|
||||
for (auto &quad : ctour.faces4) {
|
||||
triangles.emplace_back(quad(0), quad(1), quad(2));
|
||||
triangles.emplace_back(quad(2), quad(3), quad(0));
|
||||
}
|
||||
|
||||
return {ctour.points, std::move(triangles)};
|
||||
}
|
||||
|
||||
TriangleMesh to_triangle_mesh(Contour3D &&ctour) {
|
||||
if (ctour.faces4.empty())
|
||||
return {std::move(ctour.points), std::move(ctour.faces3)};
|
||||
|
||||
std::vector<Vec3i> triangles;
|
||||
|
||||
triangles.reserve(ctour.faces3.size() + 2 * ctour.faces4.size());
|
||||
std::copy(ctour.faces3.begin(), ctour.faces3.end(),
|
||||
std::back_inserter(triangles));
|
||||
|
||||
for (auto &quad : ctour.faces4) {
|
||||
triangles.emplace_back(quad(0), quad(1), quad(2));
|
||||
triangles.emplace_back(quad(2), quad(3), quad(0));
|
||||
}
|
||||
|
||||
return {std::move(ctour.points), std::move(triangles)};
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
45
src/libslic3r/SLA/Contour3D.hpp
Normal file
45
src/libslic3r/SLA/Contour3D.hpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
#ifndef SLA_CONTOUR3D_HPP
|
||||
#define SLA_CONTOUR3D_HPP
|
||||
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
class EigenMesh3D;
|
||||
|
||||
/// Dumb vertex mesh consisting of triangles (or) quads. Capable of merging with
|
||||
/// other meshes of this type and converting to and from other mesh formats.
|
||||
struct Contour3D {
|
||||
std::vector<Vec3d> points;
|
||||
std::vector<Vec3i> faces3;
|
||||
std::vector<Vec4i> faces4;
|
||||
|
||||
Contour3D() = default;
|
||||
Contour3D(const TriangleMesh &trmesh);
|
||||
Contour3D(TriangleMesh &&trmesh);
|
||||
Contour3D(const EigenMesh3D &emesh);
|
||||
|
||||
Contour3D& merge(const Contour3D& ctr);
|
||||
Contour3D& merge(const Pointf3s& triangles);
|
||||
|
||||
// Write the index triangle structure to OBJ file for debugging purposes.
|
||||
void to_obj(std::ostream& stream);
|
||||
void from_obj(std::istream &stream);
|
||||
|
||||
inline bool empty() const
|
||||
{
|
||||
return points.empty() || (faces4.empty() && faces3.empty());
|
||||
}
|
||||
};
|
||||
|
||||
/// Mesh from an existing contour.
|
||||
TriangleMesh to_triangle_mesh(const Contour3D& ctour);
|
||||
|
||||
/// Mesh from an evaporating 3D contour
|
||||
TriangleMesh to_triangle_mesh(Contour3D&& ctour);
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // CONTOUR3D_HPP
|
156
src/libslic3r/SLA/EigenMesh3D.hpp
Normal file
156
src/libslic3r/SLA/EigenMesh3D.hpp
Normal file
|
@ -0,0 +1,156 @@
|
|||
#ifndef SLA_EIGENMESH3D_H
|
||||
#define SLA_EIGENMESH3D_H
|
||||
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include "libslic3r/SLA/Hollowing.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
|
||||
namespace sla {
|
||||
|
||||
struct Contour3D;
|
||||
|
||||
/// An index-triangle structure for libIGL functions. Also serves as an
|
||||
/// alternative (raw) input format for the SLASupportTree.
|
||||
// Implemented in libslic3r/SLA/Common.cpp
|
||||
class EigenMesh3D {
|
||||
class AABBImpl;
|
||||
|
||||
Eigen::MatrixXd m_V;
|
||||
Eigen::MatrixXi m_F;
|
||||
double m_ground_level = 0, m_gnd_offset = 0;
|
||||
|
||||
std::unique_ptr<AABBImpl> m_aabb;
|
||||
|
||||
// This holds a copy of holes in the mesh. Initialized externally
|
||||
// by load_mesh setter.
|
||||
std::vector<DrainHole> m_holes;
|
||||
|
||||
public:
|
||||
|
||||
EigenMesh3D(const TriangleMesh&);
|
||||
EigenMesh3D(const EigenMesh3D& other);
|
||||
EigenMesh3D(const Contour3D &other);
|
||||
EigenMesh3D& operator=(const EigenMesh3D&);
|
||||
|
||||
~EigenMesh3D();
|
||||
|
||||
inline double ground_level() const { return m_ground_level + m_gnd_offset; }
|
||||
inline void ground_level_offset(double o) { m_gnd_offset = o; }
|
||||
inline double ground_level_offset() const { return m_gnd_offset; }
|
||||
|
||||
inline const Eigen::MatrixXd& V() const { return m_V; }
|
||||
inline const Eigen::MatrixXi& F() const { return m_F; }
|
||||
|
||||
// Result of a raycast
|
||||
class hit_result {
|
||||
// m_t holds a distance from m_source to the intersection.
|
||||
double m_t = infty();
|
||||
const EigenMesh3D *m_mesh = nullptr;
|
||||
Vec3d m_dir;
|
||||
Vec3d m_source;
|
||||
Vec3d m_normal;
|
||||
friend class EigenMesh3D;
|
||||
|
||||
// A valid object of this class can only be obtained from
|
||||
// EigenMesh3D::query_ray_hit method.
|
||||
explicit inline hit_result(const EigenMesh3D& em): m_mesh(&em) {}
|
||||
public:
|
||||
// This denotes no hit on the mesh.
|
||||
static inline constexpr double infty() { return std::numeric_limits<double>::infinity(); }
|
||||
|
||||
explicit inline hit_result(double val = infty()) : m_t(val) {}
|
||||
|
||||
inline double distance() const { return m_t; }
|
||||
inline const Vec3d& direction() const { return m_dir; }
|
||||
inline const Vec3d& source() const { return m_source; }
|
||||
inline Vec3d position() const { return m_source + m_dir * m_t; }
|
||||
inline bool is_valid() const { return m_mesh != nullptr; }
|
||||
inline bool is_hit() const { return !std::isinf(m_t); }
|
||||
|
||||
// Hit_result can decay into a double as the hit distance.
|
||||
inline operator double() const { return distance(); }
|
||||
|
||||
inline const Vec3d& normal() const {
|
||||
assert(is_valid());
|
||||
return m_normal;
|
||||
}
|
||||
|
||||
inline bool is_inside() const {
|
||||
return is_hit() && normal().dot(m_dir) > 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Inform the object about location of holes
|
||||
// creates internal copy of the vector
|
||||
void load_holes(const std::vector<DrainHole>& holes) {
|
||||
m_holes = holes;
|
||||
}
|
||||
|
||||
// Casting a ray on the mesh, returns the distance where the hit occures.
|
||||
hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const;
|
||||
|
||||
// Casts a ray on the mesh and returns all hits
|
||||
std::vector<hit_result> query_ray_hits(const Vec3d &s, const Vec3d &dir) const;
|
||||
|
||||
// Iterates over hits and holes and returns the true hit, possibly
|
||||
// on the inside of a hole.
|
||||
hit_result filter_hits(const std::vector<EigenMesh3D::hit_result>& obj_hits) const;
|
||||
|
||||
class si_result {
|
||||
double m_value;
|
||||
int m_fidx;
|
||||
Vec3d m_p;
|
||||
si_result(double val, int i, const Vec3d& c):
|
||||
m_value(val), m_fidx(i), m_p(c) {}
|
||||
friend class EigenMesh3D;
|
||||
public:
|
||||
|
||||
si_result() = delete;
|
||||
|
||||
double value() const { return m_value; }
|
||||
operator double() const { return m_value; }
|
||||
const Vec3d& point_on_mesh() const { return m_p; }
|
||||
int F_idx() const { return m_fidx; }
|
||||
};
|
||||
|
||||
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
||||
// The signed distance from a point to the mesh. Outputs the distance,
|
||||
// the index of the triangle and the closest point in mesh coordinate space.
|
||||
si_result signed_distance(const Vec3d& p) const;
|
||||
|
||||
bool inside(const Vec3d& p) const;
|
||||
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
||||
|
||||
double squared_distance(const Vec3d& p, int& i, Vec3d& c) const;
|
||||
inline double squared_distance(const Vec3d &p) const
|
||||
{
|
||||
int i;
|
||||
Vec3d c;
|
||||
return squared_distance(p, i, c);
|
||||
}
|
||||
|
||||
Vec3d normal_by_face_id(int face_id) const {
|
||||
auto trindex = F().row(face_id);
|
||||
const Vec3d& p1 = V().row(trindex(0));
|
||||
const Vec3d& p2 = V().row(trindex(1));
|
||||
const Vec3d& p3 = V().row(trindex(2));
|
||||
Eigen::Vector3d U = p2 - p1;
|
||||
Eigen::Vector3d V = p3 - p1;
|
||||
return U.cross(V).normalized();
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate the normals for the selected points (from 'points' set) on the
|
||||
// mesh. This will call squared distance for each point.
|
||||
PointSet normals(const PointSet& points,
|
||||
const EigenMesh3D& convert_mesh,
|
||||
double eps = 0.05, // min distance from edges
|
||||
std::function<void()> throw_on_cancel = [](){},
|
||||
const std::vector<unsigned>& selected_points = {});
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // EIGENMESH3D_H
|
283
src/libslic3r/SLA/Hollowing.cpp
Normal file
283
src/libslic3r/SLA/Hollowing.cpp
Normal file
|
@ -0,0 +1,283 @@
|
|||
#include <functional>
|
||||
|
||||
#include <libslic3r/OpenVDBUtils.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
#include <libslic3r/SimplifyMesh.hpp>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <libslic3r/MTUtils.hpp>
|
||||
#include <libslic3r/I18N.hpp>
|
||||
|
||||
//! macro used to mark string used at localization,
|
||||
//! return same string
|
||||
#define L(s) Slic3r::I18N::translate(s)
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
template<class S, class = FloatingOnly<S>>
|
||||
inline void _scale(S s, TriangleMesh &m) { m.scale(float(s)); }
|
||||
|
||||
template<class S, class = FloatingOnly<S>>
|
||||
inline void _scale(S s, Contour3D &m) { for (auto &p : m.points) p *= s; }
|
||||
|
||||
static TriangleMesh _generate_interior(const TriangleMesh &mesh,
|
||||
const JobController &ctl,
|
||||
double min_thickness,
|
||||
double voxel_scale,
|
||||
double closing_dist)
|
||||
{
|
||||
TriangleMesh imesh{mesh};
|
||||
|
||||
_scale(voxel_scale, imesh);
|
||||
|
||||
double offset = voxel_scale * min_thickness;
|
||||
double D = voxel_scale * closing_dist;
|
||||
float out_range = 0.1f * float(offset);
|
||||
float in_range = 1.1f * float(offset + D);
|
||||
|
||||
if (ctl.stopcondition()) return {};
|
||||
else ctl.statuscb(0, L("Hollowing"));
|
||||
|
||||
auto gridptr = mesh_to_grid(imesh, {}, out_range, in_range);
|
||||
|
||||
assert(gridptr);
|
||||
|
||||
if (!gridptr) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Returned OpenVDB grid is NULL";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (ctl.stopcondition()) return {};
|
||||
else ctl.statuscb(30, L("Hollowing"));
|
||||
|
||||
if (closing_dist > .0) {
|
||||
gridptr = redistance_grid(*gridptr, -(offset + D), double(in_range));
|
||||
} else {
|
||||
D = -offset;
|
||||
}
|
||||
|
||||
if (ctl.stopcondition()) return {};
|
||||
else ctl.statuscb(70, L("Hollowing"));
|
||||
|
||||
double iso_surface = D;
|
||||
double adaptivity = 0.;
|
||||
auto omesh = grid_to_mesh(*gridptr, iso_surface, adaptivity);
|
||||
|
||||
_scale(1. / voxel_scale, omesh);
|
||||
|
||||
if (ctl.stopcondition()) return {};
|
||||
else ctl.statuscb(100, L("Hollowing"));
|
||||
|
||||
return omesh;
|
||||
}
|
||||
|
||||
std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh & mesh,
|
||||
const HollowingConfig &hc,
|
||||
const JobController & ctl)
|
||||
{
|
||||
static const double MIN_OVERSAMPL = 3.;
|
||||
static const double MAX_OVERSAMPL = 8.;
|
||||
|
||||
// I can't figure out how to increase the grid resolution through openvdb
|
||||
// API so the model will be scaled up before conversion and the result
|
||||
// scaled down. Voxels have a unit size. If I set voxelSize smaller, it
|
||||
// scales the whole geometry down, and doesn't increase the number of
|
||||
// voxels.
|
||||
//
|
||||
// max 8x upscale, min is native voxel size
|
||||
auto voxel_scale = MIN_OVERSAMPL + (MAX_OVERSAMPL - MIN_OVERSAMPL) * hc.quality;
|
||||
auto meshptr = std::make_unique<TriangleMesh>(
|
||||
_generate_interior(mesh, ctl, hc.min_thickness, voxel_scale,
|
||||
hc.closing_distance));
|
||||
|
||||
if (meshptr) {
|
||||
|
||||
// This flips the normals to be outward facing...
|
||||
meshptr->require_shared_vertices();
|
||||
indexed_triangle_set its = std::move(meshptr->its);
|
||||
|
||||
Slic3r::simplify_mesh(its);
|
||||
|
||||
// flip normals back...
|
||||
for (stl_triangle_vertex_indices &ind : its.indices)
|
||||
std::swap(ind(0), ind(2));
|
||||
|
||||
*meshptr = Slic3r::TriangleMesh{its};
|
||||
}
|
||||
|
||||
return meshptr;
|
||||
}
|
||||
|
||||
Contour3D DrainHole::to_mesh() const
|
||||
{
|
||||
auto r = double(radius);
|
||||
auto h = double(height);
|
||||
sla::Contour3D hole = sla::cylinder(r, h);
|
||||
Eigen::Quaterniond q;
|
||||
q.setFromTwoVectors(Vec3d{0., 0., 1.}, normal.cast<double>());
|
||||
for(auto& p : hole.points) p = q * p + pos.cast<double>();
|
||||
return hole;
|
||||
}
|
||||
|
||||
bool DrainHole::operator==(const DrainHole &sp) const
|
||||
{
|
||||
return (pos == sp.pos) && (normal == sp.normal) &&
|
||||
is_approx(radius, sp.radius) &&
|
||||
is_approx(height, sp.height);
|
||||
}
|
||||
|
||||
bool DrainHole::is_inside(const Vec3f& pt) const
|
||||
{
|
||||
Eigen::Hyperplane<float, 3> plane(normal, pos);
|
||||
float dist = plane.signedDistance(pt);
|
||||
if (dist < float(EPSILON) || dist > height)
|
||||
return false;
|
||||
|
||||
Eigen::ParametrizedLine<float, 3> axis(pos, normal);
|
||||
if ( axis.squaredDistance(pt) < pow(radius, 2.f))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Given a line s+dir*t, find parameter t of intersections with the hole
|
||||
// and the normal (points inside the hole). Outputs through out reference,
|
||||
// returns true if two intersections were found.
|
||||
bool DrainHole::get_intersections(const Vec3f& s, const Vec3f& dir,
|
||||
std::array<std::pair<float, Vec3d>, 2>& out)
|
||||
const
|
||||
{
|
||||
assert(is_approx(normal.norm(), 1.f));
|
||||
const Eigen::ParametrizedLine<float, 3> ray(s, dir.normalized());
|
||||
|
||||
for (size_t i=0; i<2; ++i)
|
||||
out[i] = std::make_pair(sla::EigenMesh3D::hit_result::infty(), Vec3d::Zero());
|
||||
|
||||
const float sqr_radius = pow(radius, 2.f);
|
||||
|
||||
// first check a bounding sphere of the hole:
|
||||
Vec3f center = pos+normal*height/2.f;
|
||||
float sqr_dist_limit = pow(height/2.f, 2.f) + sqr_radius ;
|
||||
if (ray.squaredDistance(center) > sqr_dist_limit)
|
||||
return false;
|
||||
|
||||
// The line intersects the bounding sphere, look for intersections with
|
||||
// bases of the cylinder.
|
||||
|
||||
size_t found = 0; // counts how many intersections were found
|
||||
Eigen::Hyperplane<float, 3> base;
|
||||
if (! is_approx(ray.direction().dot(normal), 0.f)) {
|
||||
for (size_t i=1; i<=1; --i) {
|
||||
Vec3f cylinder_center = pos+i*height*normal;
|
||||
if (i == 0) {
|
||||
// The hole base can be identical to mesh surface if it is flat
|
||||
// let's better move the base outward a bit
|
||||
cylinder_center -= EPSILON*normal;
|
||||
}
|
||||
base = Eigen::Hyperplane<float, 3>(normal, cylinder_center);
|
||||
Vec3f intersection = ray.intersectionPoint(base);
|
||||
// Only accept the point if it is inside the cylinder base.
|
||||
if ((cylinder_center-intersection).squaredNorm() < sqr_radius) {
|
||||
out[found].first = ray.intersectionParameter(base);
|
||||
out[found].second = (i==0 ? 1. : -1.) * normal.cast<double>();
|
||||
++found;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// In case the line was perpendicular to the cylinder axis, previous
|
||||
// block was skipped, but base will later be assumed to be valid.
|
||||
base = Eigen::Hyperplane<float, 3>(normal, pos-EPSILON*normal);
|
||||
}
|
||||
|
||||
// In case there is still an intersection to be found, check the wall
|
||||
if (found != 2 && ! is_approx(std::abs(ray.direction().dot(normal)), 1.f)) {
|
||||
// Project the ray onto the base plane
|
||||
Vec3f proj_origin = base.projection(ray.origin());
|
||||
Vec3f proj_dir = base.projection(ray.origin()+ray.direction())-proj_origin;
|
||||
// save how the parameter scales and normalize the projected direction
|
||||
float par_scale = proj_dir.norm();
|
||||
proj_dir = proj_dir/par_scale;
|
||||
Eigen::ParametrizedLine<float, 3> projected_ray(proj_origin, proj_dir);
|
||||
// Calculate point on the secant that's closest to the center
|
||||
// and its distance to the circle along the projected line
|
||||
Vec3f closest = projected_ray.projection(pos);
|
||||
float dist = sqrt((sqr_radius - (closest-pos).squaredNorm()));
|
||||
// Unproject both intersections on the original line and check
|
||||
// they are on the cylinder and not past it:
|
||||
for (int i=-1; i<=1 && found !=2; i+=2) {
|
||||
Vec3f isect = closest + i*dist * projected_ray.direction();
|
||||
Vec3f to_isect = isect-proj_origin;
|
||||
float par = to_isect.norm() / par_scale;
|
||||
if (to_isect.normalized().dot(proj_dir.normalized()) < 0.f)
|
||||
par *= -1.f;
|
||||
Vec3d hit_normal = (pos-isect).normalized().cast<double>();
|
||||
isect = ray.pointAt(par);
|
||||
// check that the intersection is between the base planes:
|
||||
float vert_dist = base.signedDistance(isect);
|
||||
if (vert_dist > 0.f && vert_dist < height) {
|
||||
out[found].first = par;
|
||||
out[found].second = hit_normal;
|
||||
++found;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If only one intersection was found, it is some corner case,
|
||||
// no intersection will be returned:
|
||||
if (found != 2)
|
||||
return false;
|
||||
|
||||
// Sort the intersections:
|
||||
if (out[0].first > out[1].first)
|
||||
std::swap(out[0], out[1]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void cut_drainholes(std::vector<ExPolygons> & obj_slices,
|
||||
const std::vector<float> &slicegrid,
|
||||
float closing_radius,
|
||||
const sla::DrainHoles & holes,
|
||||
std::function<void(void)> thr)
|
||||
{
|
||||
TriangleMesh mesh;
|
||||
for (const sla::DrainHole &holept : holes) {
|
||||
auto r = double(holept.radius);
|
||||
auto h = double(holept.height);
|
||||
sla::Contour3D hole = sla::cylinder(r, h);
|
||||
Eigen::Quaterniond q;
|
||||
q.setFromTwoVectors(Vec3d{0., 0., 1.}, holept.normal.cast<double>());
|
||||
for(auto& p : hole.points) p = q * p + holept.pos.cast<double>();
|
||||
mesh.merge(sla::to_triangle_mesh(hole));
|
||||
}
|
||||
|
||||
if (mesh.empty()) return;
|
||||
|
||||
mesh.require_shared_vertices();
|
||||
|
||||
TriangleMeshSlicer slicer(&mesh);
|
||||
|
||||
std::vector<ExPolygons> hole_slices;
|
||||
slicer.slice(slicegrid, closing_radius, &hole_slices, thr);
|
||||
|
||||
if (obj_slices.size() != hole_slices.size())
|
||||
BOOST_LOG_TRIVIAL(warning)
|
||||
<< "Sliced object and drain-holes layer count does not match!";
|
||||
|
||||
size_t until = std::min(obj_slices.size(), hole_slices.size());
|
||||
|
||||
for (size_t i = 0; i < until; ++i)
|
||||
obj_slices[i] = diff_ex(obj_slices[i], hole_slices[i]);
|
||||
}
|
||||
|
||||
}} // namespace Slic3r::sla
|
70
src/libslic3r/SLA/Hollowing.hpp
Normal file
70
src/libslic3r/SLA/Hollowing.hpp
Normal file
|
@ -0,0 +1,70 @@
|
|||
#ifndef SLA_HOLLOWING_HPP
|
||||
#define SLA_HOLLOWING_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <libslic3r/SLA/JobController.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh;
|
||||
|
||||
namespace sla {
|
||||
|
||||
struct HollowingConfig
|
||||
{
|
||||
double min_thickness = 2.;
|
||||
double quality = 0.5;
|
||||
double closing_distance = 0.5;
|
||||
bool enabled = true;
|
||||
};
|
||||
|
||||
struct DrainHole
|
||||
{
|
||||
Vec3f pos;
|
||||
Vec3f normal;
|
||||
float radius;
|
||||
float height;
|
||||
|
||||
DrainHole()
|
||||
: pos(Vec3f::Zero()), normal(Vec3f::UnitZ()), radius(5.f), height(10.f)
|
||||
{}
|
||||
|
||||
DrainHole(Vec3f p, Vec3f n, float r, float h)
|
||||
: pos(p), normal(n), radius(r), height(h)
|
||||
{}
|
||||
|
||||
bool operator==(const DrainHole &sp) const;
|
||||
|
||||
bool operator!=(const DrainHole &sp) const { return !(sp == (*this)); }
|
||||
|
||||
bool is_inside(const Vec3f& pt) const;
|
||||
|
||||
bool get_intersections(const Vec3f& s, const Vec3f& dir,
|
||||
std::array<std::pair<float, Vec3d>, 2>& out) const;
|
||||
|
||||
Contour3D to_mesh() const;
|
||||
|
||||
template<class Archive> inline void serialize(Archive &ar)
|
||||
{
|
||||
ar(pos, normal, radius, height);
|
||||
}
|
||||
};
|
||||
|
||||
using DrainHoles = std::vector<DrainHole>;
|
||||
|
||||
std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh &mesh,
|
||||
const HollowingConfig & = {},
|
||||
const JobController &ctl = {});
|
||||
|
||||
void cut_drainholes(std::vector<ExPolygons> & obj_slices,
|
||||
const std::vector<float> &slicegrid,
|
||||
float closing_radius,
|
||||
const sla::DrainHoles & holes,
|
||||
std::function<void(void)> thr);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // HOLLOWINGFILTER_H
|
31
src/libslic3r/SLA/JobController.hpp
Normal file
31
src/libslic3r/SLA/JobController.hpp
Normal file
|
@ -0,0 +1,31 @@
|
|||
#ifndef SLA_JOBCONTROLLER_HPP
|
||||
#define SLA_JOBCONTROLLER_HPP
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
/// A Control structure for the support calculation. Consists of the status
|
||||
/// indicator callback and the stop condition predicate.
|
||||
struct JobController
|
||||
{
|
||||
using StatusFn = std::function<void(unsigned, const std::string&)>;
|
||||
using StopCond = std::function<bool(void)>;
|
||||
using CancelFn = std::function<void(void)>;
|
||||
|
||||
// This will signal the status of the calculation to the front-end
|
||||
StatusFn statuscb = [](unsigned, const std::string&){};
|
||||
|
||||
// Returns true if the calculation should be aborted.
|
||||
StopCond stopcondition = [](){ return false; };
|
||||
|
||||
// Similar to cancel callback. This should check the stop condition and
|
||||
// if true, throw an appropriate exception. (TriangleMeshSlicer needs this)
|
||||
// consider it a hard abort. stopcondition is permits the algorithm to
|
||||
// terminate itself
|
||||
CancelFn cancelfn = [](){};
|
||||
};
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // JOBCONTROLLER_HPP
|
|
@ -1,10 +1,12 @@
|
|||
#include "SLAPad.hpp"
|
||||
#include "SLABoilerPlate.hpp"
|
||||
#include "SLASpatIndex.hpp"
|
||||
#include <libslic3r/SLA/Pad.hpp>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
#include <libslic3r/SLA/BoostAdapter.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
|
||||
#include "ConcaveHull.hpp"
|
||||
|
||||
#include "boost/log/trivial.hpp"
|
||||
#include "SLABoostAdapter.hpp"
|
||||
#include "ClipperUtils.hpp"
|
||||
#include "Tesselate.hpp"
|
||||
#include "MTUtils.hpp"
|
||||
|
@ -69,7 +71,7 @@ Contour3D walls(
|
|||
|
||||
// Shorthand for the vertex arrays
|
||||
auto& upts = upper.points, &lpts = lower.points;
|
||||
auto& rpts = ret.points; auto& ind = ret.indices;
|
||||
auto& rpts = ret.points; auto& ind = ret.faces3;
|
||||
|
||||
// If the Z levels are flipped, or the offset difference is negative, we
|
||||
// will interpret that as the triangles normals should be inverted.
|
||||
|
@ -676,7 +678,7 @@ void create_pad(const ExPolygons &sup_blueprint,
|
|||
ThrowOnCancel thr)
|
||||
{
|
||||
Contour3D t = create_pad_geometry(sup_blueprint, model_blueprint, cfg, thr);
|
||||
out.merge(mesh(std::move(t)));
|
||||
out.merge(to_triangle_mesh(std::move(t)));
|
||||
}
|
||||
|
||||
std::string PadConfig::validate() const
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef SLABASEPOOL_HPP
|
||||
#define SLABASEPOOL_HPP
|
||||
#ifndef SLA_PAD_HPP
|
||||
#define SLA_PAD_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#include <functional>
|
||||
|
||||
#include "SLARaster.hpp"
|
||||
#include <libslic3r/SLA/Raster.hpp>
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "libslic3r/MTUtils.hpp"
|
||||
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef SLARASTER_HPP
|
||||
#define SLARASTER_HPP
|
||||
#ifndef SLA_RASTER_HPP
|
||||
#define SLA_RASTER_HPP
|
||||
|
||||
#include <ostream>
|
||||
#include <memory>
|
|
@ -1,6 +1,8 @@
|
|||
#include "SLARasterWriter.hpp"
|
||||
#include "libslic3r/Zipper.hpp"
|
||||
#include "libslic3r/Time.hpp"
|
||||
#include <libslic3r/SLA/RasterWriter.hpp>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include <libslic3r/Zipper.hpp>
|
||||
#include <libslic3r/Time.hpp>
|
||||
|
||||
#include "ExPolygon.hpp"
|
||||
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef SLARASTERWRITER_HPP
|
||||
#define SLARASTERWRITER_HPP
|
||||
#ifndef SLA_RASTERWRITER_HPP
|
||||
#define SLA_RASTERWRITER_HPP
|
||||
|
||||
// For png export of the sliced model
|
||||
#include <fstream>
|
||||
|
@ -9,12 +9,14 @@
|
|||
#include <map>
|
||||
#include <array>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include <libslic3r/SLA/Raster.hpp>
|
||||
#include <libslic3r/Zipper.hpp>
|
||||
|
||||
#include "SLARaster.hpp"
|
||||
#include "libslic3r/Zipper.hpp"
|
||||
namespace Slic3r {
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
class DynamicPrintConfig;
|
||||
|
||||
namespace sla {
|
||||
|
||||
// API to write the zipped sla output layers and metadata.
|
||||
// Implementation uses PNG raster output.
|
||||
|
@ -116,7 +118,7 @@ public:
|
|||
void save(Zipper &zipper, const std::string &prjname = "");
|
||||
|
||||
void set_statistics(const PrintStatistics &statistics);
|
||||
|
||||
|
||||
void set_config(const DynamicPrintConfig &cfg);
|
||||
};
|
||||
|
|
@ -2,9 +2,9 @@
|
|||
#include <exception>
|
||||
|
||||
#include <libnest2d/optimizers/nlopt/genetic.hpp>
|
||||
#include "SLABoilerPlate.hpp"
|
||||
#include "SLARotfinder.hpp"
|
||||
#include "SLASupportTree.hpp"
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Rotfinder.hpp>
|
||||
#include <libslic3r/SLA/SupportTree.hpp>
|
||||
#include "Model.hpp"
|
||||
|
||||
namespace Slic3r {
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef SLAROTFINDER_HPP
|
||||
#define SLAROTFINDER_HPP
|
||||
#ifndef SLA_ROTFINDER_HPP
|
||||
#define SLA_ROTFINDER_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <array>
|
|
@ -1,103 +0,0 @@
|
|||
#ifndef SLABOILERPLATE_HPP
|
||||
#define SLABOILERPLATE_HPP
|
||||
|
||||
#include <iostream>
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
#include "SLACommon.hpp"
|
||||
#include "SLASpatIndex.hpp"
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
/// Intermediate struct for a 3D mesh
|
||||
struct Contour3D {
|
||||
Pointf3s points;
|
||||
std::vector<Vec3i> indices;
|
||||
|
||||
Contour3D& merge(const Contour3D& ctr)
|
||||
{
|
||||
auto s3 = coord_t(points.size());
|
||||
auto s = indices.size();
|
||||
|
||||
points.insert(points.end(), ctr.points.begin(), ctr.points.end());
|
||||
indices.insert(indices.end(), ctr.indices.begin(), ctr.indices.end());
|
||||
|
||||
for(size_t n = s; n < indices.size(); n++) {
|
||||
auto& idx = indices[n]; idx.x() += s3; idx.y() += s3; idx.z() += s3;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Contour3D& merge(const Pointf3s& triangles)
|
||||
{
|
||||
const size_t offs = points.size();
|
||||
points.insert(points.end(), triangles.begin(), triangles.end());
|
||||
indices.reserve(indices.size() + points.size() / 3);
|
||||
|
||||
for(int i = int(offs); i < int(points.size()); i += 3)
|
||||
indices.emplace_back(i, i + 1, i + 2);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Write the index triangle structure to OBJ file for debugging purposes.
|
||||
void to_obj(std::ostream& stream)
|
||||
{
|
||||
for(auto& p : points) {
|
||||
stream << "v " << p.transpose() << "\n";
|
||||
}
|
||||
|
||||
for(auto& f : indices) {
|
||||
stream << "f " << (f + Vec3i(1, 1, 1)).transpose() << "\n";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
using ClusterEl = std::vector<unsigned>;
|
||||
using ClusteredPoints = std::vector<ClusterEl>;
|
||||
|
||||
// Clustering a set of points by the given distance.
|
||||
ClusteredPoints cluster(const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
double dist,
|
||||
unsigned max_points);
|
||||
|
||||
ClusteredPoints cluster(const PointSet& points,
|
||||
double dist,
|
||||
unsigned max_points);
|
||||
|
||||
ClusteredPoints cluster(
|
||||
const std::vector<unsigned>& indices,
|
||||
std::function<Vec3d(unsigned)> pointfn,
|
||||
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
|
||||
unsigned max_points);
|
||||
|
||||
|
||||
// Calculate the normals for the selected points (from 'points' set) on the
|
||||
// mesh. This will call squared distance for each point.
|
||||
PointSet normals(const PointSet& points,
|
||||
const EigenMesh3D& mesh,
|
||||
double eps = 0.05, // min distance from edges
|
||||
std::function<void()> throw_on_cancel = [](){},
|
||||
const std::vector<unsigned>& selected_points = {});
|
||||
|
||||
/// Mesh from an existing contour.
|
||||
inline TriangleMesh mesh(const Contour3D& ctour) {
|
||||
return {ctour.points, ctour.indices};
|
||||
}
|
||||
|
||||
/// Mesh from an evaporating 3D contour
|
||||
inline TriangleMesh mesh(Contour3D&& ctour) {
|
||||
return {std::move(ctour.points), std::move(ctour.indices)};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // SLABOILERPLATE_HPP
|
|
@ -1,187 +0,0 @@
|
|||
#ifndef SLACOMMON_HPP
|
||||
#define SLACOMMON_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
// #define SLIC3R_SLA_NEEDS_WINDTREE
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Typedefs from Point.hpp
|
||||
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f;
|
||||
typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d;
|
||||
|
||||
class TriangleMesh;
|
||||
|
||||
namespace sla {
|
||||
|
||||
// An enum to keep track of where the current points on the ModelObject came from.
|
||||
enum class PointsStatus {
|
||||
NoPoints, // No points were generated so far.
|
||||
Generating, // The autogeneration algorithm triggered, but not yet finished.
|
||||
AutoGenerated, // Points were autogenerated (i.e. copied from the backend).
|
||||
UserModified // User has done some edits.
|
||||
};
|
||||
|
||||
struct SupportPoint
|
||||
{
|
||||
Vec3f pos;
|
||||
float head_front_radius;
|
||||
bool is_new_island;
|
||||
|
||||
SupportPoint()
|
||||
: pos(Vec3f::Zero()), head_front_radius(0.f), is_new_island(false)
|
||||
{}
|
||||
|
||||
SupportPoint(float pos_x,
|
||||
float pos_y,
|
||||
float pos_z,
|
||||
float head_radius,
|
||||
bool new_island)
|
||||
: pos(pos_x, pos_y, pos_z)
|
||||
, head_front_radius(head_radius)
|
||||
, is_new_island(new_island)
|
||||
{}
|
||||
|
||||
SupportPoint(Vec3f position, float head_radius, bool new_island)
|
||||
: pos(position)
|
||||
, head_front_radius(head_radius)
|
||||
, is_new_island(new_island)
|
||||
{}
|
||||
|
||||
SupportPoint(Eigen::Matrix<float, 5, 1, Eigen::DontAlign> data)
|
||||
: pos(data(0), data(1), data(2))
|
||||
, head_front_radius(data(3))
|
||||
, is_new_island(data(4) != 0.f)
|
||||
{}
|
||||
|
||||
bool operator==(const SupportPoint &sp) const
|
||||
{
|
||||
return (pos == sp.pos) && head_front_radius == sp.head_front_radius &&
|
||||
is_new_island == sp.is_new_island;
|
||||
}
|
||||
bool operator!=(const SupportPoint &sp) const { return !(sp == (*this)); }
|
||||
|
||||
template<class Archive> void serialize(Archive &ar)
|
||||
{
|
||||
ar(pos, head_front_radius, is_new_island);
|
||||
}
|
||||
};
|
||||
|
||||
using SupportPoints = std::vector<SupportPoint>;
|
||||
|
||||
/// An index-triangle structure for libIGL functions. Also serves as an
|
||||
/// alternative (raw) input format for the SLASupportTree
|
||||
class EigenMesh3D {
|
||||
class AABBImpl;
|
||||
|
||||
Eigen::MatrixXd m_V;
|
||||
Eigen::MatrixXi m_F;
|
||||
double m_ground_level = 0, m_gnd_offset = 0;
|
||||
|
||||
std::unique_ptr<AABBImpl> m_aabb;
|
||||
public:
|
||||
|
||||
EigenMesh3D(const TriangleMesh&);
|
||||
EigenMesh3D(const EigenMesh3D& other);
|
||||
EigenMesh3D& operator=(const EigenMesh3D&);
|
||||
|
||||
~EigenMesh3D();
|
||||
|
||||
inline double ground_level() const { return m_ground_level + m_gnd_offset; }
|
||||
inline void ground_level_offset(double o) { m_gnd_offset = o; }
|
||||
inline double ground_level_offset() const { return m_gnd_offset; }
|
||||
|
||||
inline const Eigen::MatrixXd& V() const { return m_V; }
|
||||
inline const Eigen::MatrixXi& F() const { return m_F; }
|
||||
|
||||
// Result of a raycast
|
||||
class hit_result {
|
||||
double m_t = std::nan("");
|
||||
int m_face_id = -1;
|
||||
const EigenMesh3D *m_mesh = nullptr;
|
||||
Vec3d m_dir;
|
||||
Vec3d m_source;
|
||||
friend class EigenMesh3D;
|
||||
|
||||
// A valid object of this class can only be obtained from
|
||||
// EigenMesh3D::query_ray_hit method.
|
||||
explicit inline hit_result(const EigenMesh3D& em): m_mesh(&em) {}
|
||||
public:
|
||||
|
||||
// This can create a placeholder object which is invalid (not created
|
||||
// by a query_ray_hit call) but the distance can be preset to
|
||||
// a specific value for distinguishing the placeholder.
|
||||
inline hit_result(double val = std::nan("")): m_t(val) {}
|
||||
|
||||
inline double distance() const { return m_t; }
|
||||
inline const Vec3d& direction() const { return m_dir; }
|
||||
inline Vec3d position() const { return m_source + m_dir * m_t; }
|
||||
inline int face() const { return m_face_id; }
|
||||
inline bool is_valid() const { return m_mesh != nullptr; }
|
||||
|
||||
// Hit_result can decay into a double as the hit distance.
|
||||
inline operator double() const { return distance(); }
|
||||
|
||||
inline Vec3d normal() const {
|
||||
if(m_face_id < 0 || !is_valid()) return {};
|
||||
auto trindex = m_mesh->m_F.row(m_face_id);
|
||||
const Vec3d& p1 = m_mesh->V().row(trindex(0));
|
||||
const Vec3d& p2 = m_mesh->V().row(trindex(1));
|
||||
const Vec3d& p3 = m_mesh->V().row(trindex(2));
|
||||
Eigen::Vector3d U = p2 - p1;
|
||||
Eigen::Vector3d V = p3 - p1;
|
||||
return U.cross(V).normalized();
|
||||
}
|
||||
|
||||
inline bool is_inside() {
|
||||
return m_face_id >= 0 && normal().dot(m_dir) > 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Casting a ray on the mesh, returns the distance where the hit occures.
|
||||
hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const;
|
||||
|
||||
class si_result {
|
||||
double m_value;
|
||||
int m_fidx;
|
||||
Vec3d m_p;
|
||||
si_result(double val, int i, const Vec3d& c):
|
||||
m_value(val), m_fidx(i), m_p(c) {}
|
||||
friend class EigenMesh3D;
|
||||
public:
|
||||
|
||||
si_result() = delete;
|
||||
|
||||
double value() const { return m_value; }
|
||||
operator double() const { return m_value; }
|
||||
const Vec3d& point_on_mesh() const { return m_p; }
|
||||
int F_idx() const { return m_fidx; }
|
||||
};
|
||||
|
||||
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
||||
// The signed distance from a point to the mesh. Outputs the distance,
|
||||
// the index of the triangle and the closest point in mesh coordinate space.
|
||||
si_result signed_distance(const Vec3d& p) const;
|
||||
|
||||
bool inside(const Vec3d& p) const;
|
||||
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
||||
|
||||
double squared_distance(const Vec3d& p, int& i, Vec3d& c) const;
|
||||
inline double squared_distance(const Vec3d &p) const
|
||||
{
|
||||
int i;
|
||||
Vec3d c;
|
||||
return squared_distance(p, i, c);
|
||||
}
|
||||
};
|
||||
|
||||
using PointSet = Eigen::MatrixXd;
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
|
||||
|
||||
#endif // SLASUPPORTTREE_HPP
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef SPATINDEX_HPP
|
||||
#define SPATINDEX_HPP
|
||||
#ifndef SLA_SPATINDEX_HPP
|
||||
#define SLA_SPATINDEX_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
69
src/libslic3r/SLA/SupportPoint.hpp
Normal file
69
src/libslic3r/SLA/SupportPoint.hpp
Normal file
|
@ -0,0 +1,69 @@
|
|||
#ifndef SLA_SUPPORTPOINT_HPP
|
||||
#define SLA_SUPPORTPOINT_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/ExPolygon.hpp>
|
||||
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
// An enum to keep track of where the current points on the ModelObject came from.
|
||||
enum class PointsStatus {
|
||||
NoPoints, // No points were generated so far.
|
||||
Generating, // The autogeneration algorithm triggered, but not yet finished.
|
||||
AutoGenerated, // Points were autogenerated (i.e. copied from the backend).
|
||||
UserModified // User has done some edits.
|
||||
};
|
||||
|
||||
struct SupportPoint
|
||||
{
|
||||
Vec3f pos;
|
||||
float head_front_radius;
|
||||
bool is_new_island;
|
||||
|
||||
SupportPoint()
|
||||
: pos(Vec3f::Zero()), head_front_radius(0.f), is_new_island(false)
|
||||
{}
|
||||
|
||||
SupportPoint(float pos_x,
|
||||
float pos_y,
|
||||
float pos_z,
|
||||
float head_radius,
|
||||
bool new_island)
|
||||
: pos(pos_x, pos_y, pos_z)
|
||||
, head_front_radius(head_radius)
|
||||
, is_new_island(new_island)
|
||||
{}
|
||||
|
||||
SupportPoint(Vec3f position, float head_radius, bool new_island)
|
||||
: pos(position)
|
||||
, head_front_radius(head_radius)
|
||||
, is_new_island(new_island)
|
||||
{}
|
||||
|
||||
SupportPoint(Eigen::Matrix<float, 5, 1, Eigen::DontAlign> data)
|
||||
: pos(data(0), data(1), data(2))
|
||||
, head_front_radius(data(3))
|
||||
, is_new_island(data(4) != 0.f)
|
||||
{}
|
||||
|
||||
bool operator==(const SupportPoint &sp) const
|
||||
{
|
||||
float rdiff = std::abs(head_front_radius - sp.head_front_radius);
|
||||
return (pos == sp.pos) && rdiff < float(EPSILON) &&
|
||||
is_new_island == sp.is_new_island;
|
||||
}
|
||||
|
||||
bool operator!=(const SupportPoint &sp) const { return !(sp == (*this)); }
|
||||
|
||||
template<class Archive> void serialize(Archive &ar)
|
||||
{
|
||||
ar(pos, head_front_radius, is_new_island);
|
||||
}
|
||||
};
|
||||
|
||||
using SupportPoints = std::vector<SupportPoint>;
|
||||
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
#endif // SUPPORTPOINT_HPP
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
#include "SLAAutoSupports.hpp"
|
||||
#include "SupportPointGenerator.hpp"
|
||||
#include "Model.hpp"
|
||||
#include "ExPolygon.hpp"
|
||||
#include "SVG.hpp"
|
||||
|
@ -18,7 +18,7 @@
|
|||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
/*float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
|
||||
/*float SupportPointGenerator::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
|
||||
{
|
||||
n1.normalize();
|
||||
n2.normalize();
|
||||
|
@ -36,7 +36,7 @@ namespace sla {
|
|||
}
|
||||
|
||||
|
||||
float SLAAutoSupports::get_required_density(float angle) const
|
||||
float SupportPointGenerator::get_required_density(float angle) const
|
||||
{
|
||||
// calculation would be density_0 * cos(angle). To provide one more degree of freedom, we will scale the angle
|
||||
// to get the user-set density for 45 deg. So it ends up as density_0 * cos(K * angle).
|
||||
|
@ -44,27 +44,45 @@ float SLAAutoSupports::get_required_density(float angle) const
|
|||
return std::max(0.f, float(m_config.density_at_horizontal * cos(K*angle)));
|
||||
}
|
||||
|
||||
float SLAAutoSupports::distance_limit(float angle) const
|
||||
float SupportPointGenerator::distance_limit(float angle) const
|
||||
{
|
||||
return 1./(2.4*get_required_density(angle));
|
||||
}*/
|
||||
|
||||
SLAAutoSupports::SLAAutoSupports(const sla::EigenMesh3D & emesh,
|
||||
const std::vector<ExPolygons> &slices,
|
||||
const std::vector<float> & heights,
|
||||
const Config & config,
|
||||
std::function<void(void)> throw_on_cancel,
|
||||
std::function<void(int)> statusfn)
|
||||
SupportPointGenerator::SupportPointGenerator(
|
||||
const sla::EigenMesh3D &emesh,
|
||||
const std::vector<ExPolygons> &slices,
|
||||
const std::vector<float> & heights,
|
||||
const Config & config,
|
||||
std::function<void(void)> throw_on_cancel,
|
||||
std::function<void(int)> statusfn)
|
||||
: SupportPointGenerator(emesh, config, throw_on_cancel, statusfn)
|
||||
{
|
||||
std::random_device rd;
|
||||
m_rng.seed(rd());
|
||||
execute(slices, heights);
|
||||
}
|
||||
|
||||
SupportPointGenerator::SupportPointGenerator(
|
||||
const EigenMesh3D &emesh,
|
||||
const SupportPointGenerator::Config &config,
|
||||
std::function<void ()> throw_on_cancel,
|
||||
std::function<void (int)> statusfn)
|
||||
: m_config(config)
|
||||
, m_emesh(emesh)
|
||||
, m_throw_on_cancel(throw_on_cancel)
|
||||
, m_statusfn(statusfn)
|
||||
{
|
||||
}
|
||||
|
||||
void SupportPointGenerator::execute(const std::vector<ExPolygons> &slices,
|
||||
const std::vector<float> & heights)
|
||||
{
|
||||
process(slices, heights);
|
||||
project_onto_mesh(m_output);
|
||||
}
|
||||
|
||||
void SLAAutoSupports::project_onto_mesh(std::vector<sla::SupportPoint>& points) const
|
||||
void SupportPointGenerator::project_onto_mesh(std::vector<sla::SupportPoint>& points) const
|
||||
{
|
||||
// The function makes sure that all the points are really exactly placed on the mesh.
|
||||
|
||||
|
@ -77,36 +95,29 @@ void SLAAutoSupports::project_onto_mesh(std::vector<sla::SupportPoint>& points)
|
|||
m_throw_on_cancel();
|
||||
Vec3f& p = points[point_id].pos;
|
||||
// Project the point upward and downward and choose the closer intersection with the mesh.
|
||||
//bool up = igl::ray_mesh_intersect(p.cast<float>(), Vec3f(0., 0., 1.), m_V, m_F, hit_up);
|
||||
//bool down = igl::ray_mesh_intersect(p.cast<float>(), Vec3f(0., 0., -1.), m_V, m_F, hit_down);
|
||||
|
||||
sla::EigenMesh3D::hit_result hit_up = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., 1.));
|
||||
sla::EigenMesh3D::hit_result hit_down = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., -1.));
|
||||
|
||||
bool up = hit_up.face() != -1;
|
||||
bool down = hit_down.face() != -1;
|
||||
bool up = hit_up.is_hit();
|
||||
bool down = hit_down.is_hit();
|
||||
|
||||
if (!up && !down)
|
||||
continue;
|
||||
|
||||
sla::EigenMesh3D::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down;
|
||||
//int fid = hit.face();
|
||||
//Vec3f bc(1-hit.u-hit.v, hit.u, hit.v);
|
||||
//p = (bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2))).cast<float>();
|
||||
|
||||
p = p + (hit.distance() * hit.direction()).cast<float>();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static std::vector<SLAAutoSupports::MyLayer> make_layers(
|
||||
static std::vector<SupportPointGenerator::MyLayer> make_layers(
|
||||
const std::vector<ExPolygons>& slices, const std::vector<float>& heights,
|
||||
std::function<void(void)> throw_on_cancel)
|
||||
{
|
||||
assert(slices.size() == heights.size());
|
||||
|
||||
// Allocate empty layers.
|
||||
std::vector<SLAAutoSupports::MyLayer> layers;
|
||||
std::vector<SupportPointGenerator::MyLayer> layers;
|
||||
layers.reserve(slices.size());
|
||||
for (size_t i = 0; i < slices.size(); ++ i)
|
||||
layers.emplace_back(i, heights[i]);
|
||||
|
@ -122,7 +133,7 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers(
|
|||
if ((layer_id % 8) == 0)
|
||||
// Don't call the following function too often as it flushes CPU write caches due to synchronization primitves.
|
||||
throw_on_cancel();
|
||||
SLAAutoSupports::MyLayer &layer = layers[layer_id];
|
||||
SupportPointGenerator::MyLayer &layer = layers[layer_id];
|
||||
const ExPolygons &islands = slices[layer_id];
|
||||
//FIXME WTF?
|
||||
const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0]));
|
||||
|
@ -143,8 +154,8 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers(
|
|||
if ((layer_id % 2) == 0)
|
||||
// Don't call the following function too often as it flushes CPU write caches due to synchronization primitves.
|
||||
throw_on_cancel();
|
||||
SLAAutoSupports::MyLayer &layer_above = layers[layer_id];
|
||||
SLAAutoSupports::MyLayer &layer_below = layers[layer_id - 1];
|
||||
SupportPointGenerator::MyLayer &layer_above = layers[layer_id];
|
||||
SupportPointGenerator::MyLayer &layer_below = layers[layer_id - 1];
|
||||
//FIXME WTF?
|
||||
const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]);
|
||||
const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports
|
||||
|
@ -152,8 +163,8 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers(
|
|||
const float slope_angle = 75.f * (float(M_PI)/180.f); // smaller number - less supports
|
||||
const float slope_offset = float(scale_(layer_height / std::tan(slope_angle)));
|
||||
//FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands.
|
||||
for (SLAAutoSupports::Structure &top : layer_above.islands) {
|
||||
for (SLAAutoSupports::Structure &bottom : layer_below.islands) {
|
||||
for (SupportPointGenerator::Structure &top : layer_above.islands) {
|
||||
for (SupportPointGenerator::Structure &bottom : layer_below.islands) {
|
||||
float overlap_area = top.overlap_area(bottom);
|
||||
if (overlap_area > 0) {
|
||||
top.islands_below.emplace_back(&bottom, overlap_area);
|
||||
|
@ -191,13 +202,13 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers(
|
|||
return layers;
|
||||
}
|
||||
|
||||
void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights)
|
||||
void SupportPointGenerator::process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights)
|
||||
{
|
||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
||||
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||
std::vector<std::pair<ExPolygon, coord_t>> islands;
|
||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
|
||||
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
|
||||
|
||||
std::vector<SLAAutoSupports::MyLayer> layers = make_layers(slices, heights, m_throw_on_cancel);
|
||||
std::vector<SupportPointGenerator::MyLayer> layers = make_layers(slices, heights, m_throw_on_cancel);
|
||||
|
||||
PointGrid3D point_grid;
|
||||
point_grid.cell_size = Vec3f(10.f, 10.f, 10.f);
|
||||
|
@ -206,8 +217,8 @@ void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::
|
|||
double status = 0;
|
||||
|
||||
for (unsigned int layer_id = 0; layer_id < layers.size(); ++ layer_id) {
|
||||
SLAAutoSupports::MyLayer *layer_top = &layers[layer_id];
|
||||
SLAAutoSupports::MyLayer *layer_bottom = (layer_id > 0) ? &layers[layer_id - 1] : nullptr;
|
||||
SupportPointGenerator::MyLayer *layer_top = &layers[layer_id];
|
||||
SupportPointGenerator::MyLayer *layer_bottom = (layer_id > 0) ? &layers[layer_id - 1] : nullptr;
|
||||
std::vector<float> support_force_bottom;
|
||||
if (layer_bottom != nullptr) {
|
||||
support_force_bottom.assign(layer_bottom->islands.size(), 0.f);
|
||||
|
@ -263,13 +274,13 @@ void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::
|
|||
status += increment;
|
||||
m_statusfn(int(std::round(status)));
|
||||
|
||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
||||
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||
/*std::string layer_num_str = std::string((i<10 ? "0" : "")) + std::string((i<100 ? "0" : "")) + std::to_string(i);
|
||||
output_expolygons(expolys_top, "top" + layer_num_str + ".svg");
|
||||
output_expolygons(diff, "diff" + layer_num_str + ".svg");
|
||||
if (!islands.empty())
|
||||
output_expolygons(islands, "islands" + layer_num_str + ".svg");*/
|
||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
|
||||
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -449,7 +460,7 @@ static inline std::vector<Vec2f> poisson_disk_from_samples(const std::vector<Vec
|
|||
return out;
|
||||
}
|
||||
|
||||
void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island, bool just_one)
|
||||
void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island, bool just_one)
|
||||
{
|
||||
//int num_of_points = std::max(1, (int)((island.area()*pow(SCALING_FACTOR, 2) * m_config.tear_pressure)/m_config.support_force));
|
||||
|
||||
|
@ -470,9 +481,8 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru
|
|||
float min_spacing = poisson_radius;
|
||||
|
||||
//FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon.
|
||||
std::random_device rd;
|
||||
std::mt19937 rng(rd());
|
||||
std::vector<Vec2f> raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, rng);
|
||||
|
||||
std::vector<Vec2f> raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, m_rng);
|
||||
std::vector<Vec2f> poisson_samples;
|
||||
for (size_t iter = 0; iter < 4; ++ iter) {
|
||||
poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius,
|
||||
|
@ -488,7 +498,7 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru
|
|||
min_spacing = std::max(m_config.minimal_distance, min_spacing * coeff);
|
||||
}
|
||||
|
||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
||||
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||
{
|
||||
static int irun = 0;
|
||||
Slic3r::SVG svg(debug_out_path("SLA_supports-uniformly_cover-%d.svg", irun ++), get_extents(islands));
|
||||
|
@ -503,7 +513,7 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru
|
|||
|
||||
// assert(! poisson_samples.empty());
|
||||
if (poisson_samples_target < poisson_samples.size()) {
|
||||
std::shuffle(poisson_samples.begin(), poisson_samples.end(), rng);
|
||||
std::shuffle(poisson_samples.begin(), poisson_samples.end(), m_rng);
|
||||
poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end());
|
||||
}
|
||||
for (const Vec2f &pt : poisson_samples) {
|
||||
|
@ -528,8 +538,8 @@ void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double
|
|||
pts.erase(endit, pts.end());
|
||||
}
|
||||
|
||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
||||
void SLAAutoSupports::output_structures(const std::vector<Structure>& structures)
|
||||
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||
void SupportPointGenerator::output_structures(const std::vector<Structure>& structures)
|
||||
{
|
||||
for (unsigned int i=0 ; i<structures.size(); ++i) {
|
||||
std::stringstream ss;
|
||||
|
@ -538,7 +548,7 @@ void SLAAutoSupports::output_structures(const std::vector<Structure>& structures
|
|||
}
|
||||
}
|
||||
|
||||
void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, const std::string &filename)
|
||||
void SupportPointGenerator::output_expolygons(const ExPolygons& expolys, const std::string &filename)
|
||||
{
|
||||
BoundingBox bb(Point(-30000000, -30000000), Point(30000000, 30000000));
|
||||
Slic3r::SVG svg_cummulative(filename, bb);
|
|
@ -1,43 +1,50 @@
|
|||
#ifndef SLAAUTOSUPPORTS_HPP_
|
||||
#define SLAAUTOSUPPORTS_HPP_
|
||||
#ifndef SLA_SUPPORTPOINTGENERATOR_HPP
|
||||
#define SLA_SUPPORTPOINTGENERATOR_HPP
|
||||
|
||||
#include <random>
|
||||
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/SupportPoint.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
#include <libslic3r/Point.hpp>
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
#include <libslic3r/SLA/SLACommon.hpp>
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
// #define SLA_AUTOSUPPORTS_DEBUG
|
||||
// #define SLA_SUPPORTPOINTGEN_DEBUG
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
namespace Slic3r { namespace sla {
|
||||
|
||||
class SLAAutoSupports {
|
||||
class SupportPointGenerator {
|
||||
public:
|
||||
struct Config {
|
||||
float density_relative {1.f};
|
||||
float minimal_distance {1.f};
|
||||
float head_diameter {0.4f};
|
||||
///////////////
|
||||
inline float support_force() const { return 7.7f / density_relative; } // a force one point can support (arbitrary force unit)
|
||||
inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2)
|
||||
};
|
||||
|
||||
SLAAutoSupports(const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
|
||||
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
||||
float density_relative {1.f};
|
||||
float minimal_distance {1.f};
|
||||
float head_diameter {0.4f};
|
||||
///////////////
|
||||
inline float support_force() const { return 7.7f / density_relative; } // a force one point can support (arbitrary force unit)
|
||||
inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2)
|
||||
};
|
||||
|
||||
SupportPointGenerator(const EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
|
||||
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
||||
|
||||
SupportPointGenerator(const EigenMesh3D& emesh, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
||||
|
||||
const std::vector<SupportPoint>& output() const { return m_output; }
|
||||
std::vector<SupportPoint>& output() { return m_output; }
|
||||
|
||||
struct MyLayer;
|
||||
|
||||
const std::vector<sla::SupportPoint>& output() { return m_output; }
|
||||
|
||||
struct MyLayer;
|
||||
|
||||
struct Structure {
|
||||
Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f ¢roid, float area, float h) :
|
||||
layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), height(h)
|
||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
||||
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||
, unique_id(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()))
|
||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
|
||||
{}
|
||||
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
|
||||
{}
|
||||
MyLayer *layer;
|
||||
const ExPolygon* polygon = nullptr;
|
||||
const BoundingBox bbox;
|
||||
|
@ -49,24 +56,24 @@ public:
|
|||
float supports_force_this_layer = 0.f;
|
||||
float supports_force_inherited = 0.f;
|
||||
float supports_force_total() const { return this->supports_force_this_layer + this->supports_force_inherited; }
|
||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
||||
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||
std::chrono::milliseconds unique_id;
|
||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
|
||||
|
||||
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
|
||||
|
||||
struct Link {
|
||||
Link(Structure *island, float overlap_area) : island(island), overlap_area(overlap_area) {}
|
||||
Link(Structure *island, float overlap_area) : island(island), overlap_area(overlap_area) {}
|
||||
Structure *island;
|
||||
float overlap_area;
|
||||
};
|
||||
|
||||
#ifdef NDEBUG
|
||||
// In release mode, use the optimized container.
|
||||
// In release mode, use the optimized container.
|
||||
boost::container::small_vector<Link, 4> islands_above;
|
||||
boost::container::small_vector<Link, 4> islands_below;
|
||||
#else
|
||||
// In debug mode, use the standard vector, which is well handled by debugger visualizer.
|
||||
std::vector<Link> islands_above;
|
||||
std::vector<Link> islands_below;
|
||||
// In debug mode, use the standard vector, which is well handled by debugger visualizer.
|
||||
std::vector<Link> islands_above;
|
||||
std::vector<Link> islands_below;
|
||||
#endif
|
||||
// Overhangs, that are dangling considerably.
|
||||
ExPolygons dangling_areas;
|
||||
|
@ -74,16 +81,16 @@ public:
|
|||
ExPolygons overhangs;
|
||||
// Overhangs, where the surface must slope.
|
||||
ExPolygons overhangs_slopes;
|
||||
float overhangs_area;
|
||||
|
||||
float overhangs_area = 0.f;
|
||||
|
||||
bool overlaps(const Structure &rhs) const {
|
||||
return this->bbox.overlap(rhs.bbox) && (this->polygon->overlaps(*rhs.polygon) || rhs.polygon->overlaps(*this->polygon));
|
||||
}
|
||||
float overlap_area(const Structure &rhs) const {
|
||||
double out = 0.;
|
||||
if (this->bbox.overlap(rhs.bbox)) {
|
||||
Polygons polys = intersection(to_polygons(*this->polygon), to_polygons(*rhs.polygon), false);
|
||||
for (const Polygon &poly : polys)
|
||||
Polygons polys = intersection(to_polygons(*this->polygon), to_polygons(*rhs.polygon), false);
|
||||
for (const Polygon &poly : polys)
|
||||
out += poly.area();
|
||||
}
|
||||
return float(out);
|
||||
|
@ -96,13 +103,13 @@ public:
|
|||
}
|
||||
Polygons polygons_below() const {
|
||||
size_t cnt = 0;
|
||||
for (const Link &below : this->islands_below)
|
||||
for (const Link &below : this->islands_below)
|
||||
cnt += 1 + below.island->polygon->holes.size();
|
||||
Polygons out;
|
||||
out.reserve(cnt);
|
||||
for (const Link &below : this->islands_below) {
|
||||
for (const Link &below : this->islands_below) {
|
||||
out.emplace_back(below.island->polygon->contour);
|
||||
append(out, below.island->polygon->holes);
|
||||
append(out, below.island->polygon->holes);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
@ -116,19 +123,19 @@ public:
|
|||
// Positive deficit of the supports. If negative, this area is well supported. If positive, more supports need to be added.
|
||||
float support_force_deficit(const float tear_pressure) const { return this->area * tear_pressure - this->supports_force_total(); }
|
||||
};
|
||||
|
||||
|
||||
struct MyLayer {
|
||||
MyLayer(const size_t layer_id, coordf_t print_z) : layer_id(layer_id), print_z(print_z) {}
|
||||
MyLayer(const size_t layer_id, coordf_t print_z) : layer_id(layer_id), print_z(print_z) {}
|
||||
size_t layer_id;
|
||||
coordf_t print_z;
|
||||
std::vector<Structure> islands;
|
||||
};
|
||||
|
||||
|
||||
struct RichSupportPoint {
|
||||
Vec3f position;
|
||||
Structure *island;
|
||||
};
|
||||
|
||||
|
||||
struct PointGrid3D {
|
||||
struct GridHash {
|
||||
std::size_t operator()(const Vec3i &cell_id) const {
|
||||
|
@ -136,23 +143,23 @@ public:
|
|||
}
|
||||
};
|
||||
typedef std::unordered_multimap<Vec3i, RichSupportPoint, GridHash> Grid;
|
||||
|
||||
|
||||
Vec3f cell_size;
|
||||
Grid grid;
|
||||
|
||||
|
||||
Vec3i cell_id(const Vec3f &pos) {
|
||||
return Vec3i(int(floor(pos.x() / cell_size.x())),
|
||||
int(floor(pos.y() / cell_size.y())),
|
||||
int(floor(pos.z() / cell_size.z())));
|
||||
}
|
||||
|
||||
|
||||
void insert(const Vec2f &pos, Structure *island) {
|
||||
RichSupportPoint pt;
|
||||
pt.position = Vec3f(pos.x(), pos.y(), float(island->layer->print_z));
|
||||
pt.position = Vec3f(pos.x(), pos.y(), float(island->layer->print_z));
|
||||
pt.island = island;
|
||||
grid.emplace(cell_id(pt.position), pt);
|
||||
}
|
||||
|
||||
|
||||
bool collides_with(const Vec2f &pos, Structure *island, float radius) {
|
||||
Vec3f pos3d(pos.x(), pos.y(), float(island->layer->print_z));
|
||||
Vec3i cell = cell_id(pos3d);
|
||||
|
@ -170,41 +177,45 @@ public:
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
bool collides_with(const Vec3f &pos, float radius, Grid::const_iterator it_begin, Grid::const_iterator it_end) {
|
||||
for (Grid::const_iterator it = it_begin; it != it_end; ++ it) {
|
||||
float dist2 = (it->second.position - pos).squaredNorm();
|
||||
float dist2 = (it->second.position - pos).squaredNorm();
|
||||
if (dist2 < radius * radius)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void execute(const std::vector<ExPolygons> &slices,
|
||||
const std::vector<float> & heights);
|
||||
|
||||
void seed(std::mt19937::result_type s) { m_rng.seed(s); }
|
||||
private:
|
||||
std::vector<sla::SupportPoint> m_output;
|
||||
|
||||
SLAAutoSupports::Config m_config;
|
||||
|
||||
std::vector<SupportPoint> m_output;
|
||||
|
||||
SupportPointGenerator::Config m_config;
|
||||
|
||||
void process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights);
|
||||
void uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island = false, bool just_one = false);
|
||||
void project_onto_mesh(std::vector<sla::SupportPoint>& points) const;
|
||||
void project_onto_mesh(std::vector<SupportPoint>& points) const;
|
||||
|
||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
||||
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||
static void output_expolygons(const ExPolygons& expolys, const std::string &filename);
|
||||
static void output_structures(const std::vector<Structure> &structures);
|
||||
#endif // SLA_AUTOSUPPORTS_DEBUG
|
||||
|
||||
const sla::EigenMesh3D& m_emesh;
|
||||
#endif // SLA_SUPPORTPOINTGEN_DEBUG
|
||||
|
||||
const EigenMesh3D& m_emesh;
|
||||
std::function<void(void)> m_throw_on_cancel;
|
||||
std::function<void(int)> m_statusfn;
|
||||
|
||||
std::mt19937 m_rng;
|
||||
};
|
||||
|
||||
void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance);
|
||||
|
||||
} // namespace sla
|
||||
} // namespace Slic3r
|
||||
}} // namespace Slic3r::sla
|
||||
|
||||
|
||||
#endif // SLAAUTOSUPPORTS_HPP_
|
||||
#endif // SUPPORTPOINTGENERATOR_HPP
|
|
@ -4,10 +4,10 @@
|
|||
*/
|
||||
|
||||
#include <numeric>
|
||||
#include "SLASupportTree.hpp"
|
||||
#include "SLABoilerPlate.hpp"
|
||||
#include "SLASpatIndex.hpp"
|
||||
#include "SLASupportTreeBuilder.hpp"
|
||||
#include <libslic3r/SLA/SupportTree.hpp>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
|
||||
#include <libslic3r/MTUtils.hpp>
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
|
@ -1,12 +1,15 @@
|
|||
#ifndef SLASUPPORTTREE_HPP
|
||||
#define SLASUPPORTTREE_HPP
|
||||
#ifndef SLA_SUPPORTTREE_HPP
|
||||
#define SLA_SUPPORTTREE_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <Eigen/Geometry>
|
||||
|
||||
#include "SLACommon.hpp"
|
||||
#include "SLAPad.hpp"
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/Pad.hpp>
|
||||
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||
#include <libslic3r/SLA/SupportPoint.hpp>
|
||||
#include <libslic3r/SLA/JobController.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
@ -105,27 +108,6 @@ struct SupportConfig
|
|||
|
||||
enum class MeshType { Support, Pad };
|
||||
|
||||
/// A Control structure for the support calculation. Consists of the status
|
||||
/// indicator callback and the stop condition predicate.
|
||||
struct JobController
|
||||
{
|
||||
using StatusFn = std::function<void(unsigned, const std::string&)>;
|
||||
using StopCond = std::function<bool(void)>;
|
||||
using CancelFn = std::function<void(void)>;
|
||||
|
||||
// This will signal the status of the calculation to the front-end
|
||||
StatusFn statuscb = [](unsigned, const std::string&){};
|
||||
|
||||
// Returns true if the calculation should be aborted.
|
||||
StopCond stopcondition = [](){ return false; };
|
||||
|
||||
// Similar to cancel callback. This should check the stop condition and
|
||||
// if true, throw an appropriate exception. (TriangleMeshSlicer needs this)
|
||||
// consider it a hard abort. stopcondition is permits the algorithm to
|
||||
// terminate itself
|
||||
CancelFn cancelfn = [](){};
|
||||
};
|
||||
|
||||
struct SupportableMesh
|
||||
{
|
||||
EigenMesh3D emesh;
|
|
@ -1,5 +1,6 @@
|
|||
#include "SLASupportTreeBuilder.hpp"
|
||||
#include "SLASupportTreeBuildsteps.hpp"
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
@ -12,7 +13,7 @@ Contour3D sphere(double rho, Portion portion, double fa) {
|
|||
if(rho <= 1e-6 && rho >= -1e-6) return ret;
|
||||
|
||||
auto& vertices = ret.points;
|
||||
auto& facets = ret.indices;
|
||||
auto& facets = ret.faces3;
|
||||
|
||||
// Algorithm:
|
||||
// Add points one-by-one to the sphere grid and form facets using relative
|
||||
|
@ -102,7 +103,7 @@ Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp)
|
|||
|
||||
auto steps = int(ssteps);
|
||||
auto& points = ret.points;
|
||||
auto& indices = ret.indices;
|
||||
auto& indices = ret.faces3;
|
||||
points.reserve(2*ssteps);
|
||||
double a = 2*PI/steps;
|
||||
|
||||
|
@ -211,8 +212,8 @@ Head::Head(double r_big_mm,
|
|||
coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2);
|
||||
coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1;
|
||||
|
||||
mesh.indices.emplace_back(i1s1, i2s1, i2s2);
|
||||
mesh.indices.emplace_back(i1s1, i2s2, i1s2);
|
||||
mesh.faces3.emplace_back(i1s1, i2s1, i2s2);
|
||||
mesh.faces3.emplace_back(i1s1, i2s2, i1s2);
|
||||
}
|
||||
|
||||
auto i1s1 = coord_t(s1.points.size()) - coord_t(steps);
|
||||
|
@ -220,8 +221,8 @@ Head::Head(double r_big_mm,
|
|||
auto i1s2 = coord_t(s1.points.size());
|
||||
auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1;
|
||||
|
||||
mesh.indices.emplace_back(i2s2, i2s1, i1s1);
|
||||
mesh.indices.emplace_back(i1s2, i2s2, i1s1);
|
||||
mesh.faces3.emplace_back(i2s2, i2s1, i1s1);
|
||||
mesh.faces3.emplace_back(i1s2, i2s2, i1s1);
|
||||
|
||||
// To simplify further processing, we translate the mesh so that the
|
||||
// last vertex of the pointing sphere (the pinpoint) will be at (0,0,0)
|
||||
|
@ -240,7 +241,7 @@ Pillar::Pillar(const Vec3d &jp, const Vec3d &endp, double radius, size_t st):
|
|||
// move the data.
|
||||
Contour3D body = cylinder(radius, height, st, endp);
|
||||
mesh.points.swap(body.points);
|
||||
mesh.indices.swap(body.indices);
|
||||
mesh.faces3.swap(body.faces3);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -275,7 +276,7 @@ Pillar &Pillar::add_base(double baseheight, double radius)
|
|||
base.points.emplace_back(endpt);
|
||||
base.points.emplace_back(ep);
|
||||
|
||||
auto& indices = base.indices;
|
||||
auto& indices = base.faces3;
|
||||
auto hcenter = int(base.points.size() - 1);
|
||||
auto lcenter = int(base.points.size() - 2);
|
||||
auto offs = int(steps);
|
||||
|
@ -466,7 +467,7 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh() const
|
|||
return m_meshcache;
|
||||
}
|
||||
|
||||
m_meshcache = mesh(merged);
|
||||
m_meshcache = to_triangle_mesh(merged);
|
||||
|
||||
// The mesh will be passed by const-pointer to TriangleMeshSlicer,
|
||||
// which will need this.
|
|
@ -1,10 +1,11 @@
|
|||
#ifndef SUPPORTTREEBUILDER_HPP
|
||||
#define SUPPORTTREEBUILDER_HPP
|
||||
#ifndef SLA_SUPPORTTREEBUILDER_HPP
|
||||
#define SLA_SUPPORTTREEBUILDER_HPP
|
||||
|
||||
#include "SLAConcurrency.hpp"
|
||||
#include "SLABoilerPlate.hpp"
|
||||
#include "SLASupportTree.hpp"
|
||||
#include "SLAPad.hpp"
|
||||
#include <libslic3r/SLA/Concurrency.hpp>
|
||||
#include <libslic3r/SLA/Common.hpp>
|
||||
#include <libslic3r/SLA/SupportTree.hpp>
|
||||
#include <libslic3r/SLA/Contour3D.hpp>
|
||||
#include <libslic3r/SLA/Pad.hpp>
|
||||
#include <libslic3r/MTUtils.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
@ -73,7 +74,7 @@ Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI),
|
|||
// h: Height
|
||||
// ssteps: how many edges will create the base circle
|
||||
// sp: starting point
|
||||
Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp = {0,0,0});
|
||||
Contour3D cylinder(double r, double h, size_t ssteps = 45, const Vec3d &sp = {0,0,0});
|
||||
|
||||
const constexpr long ID_UNSET = -1;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
#include "SLASupportTreeBuildsteps.hpp"
|
||||
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
|
||||
|
||||
#include <libnest2d/optimizers/nlopt/genetic.hpp>
|
||||
#include <libnest2d/optimizers/nlopt/subplex.hpp>
|
||||
|
@ -7,8 +7,16 @@
|
|||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
||||
static const Vec3d DOWN = {0.0, 0.0, -1.0};
|
||||
|
||||
using libnest2d::opt::initvals;
|
||||
using libnest2d::opt::bound;
|
||||
using libnest2d::opt::StopCriteria;
|
||||
using libnest2d::opt::GeneticOptimizer;
|
||||
using libnest2d::opt::SubplexOptimizer;
|
||||
|
||||
SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder,
|
||||
const SupportableMesh &sm)
|
||||
const SupportableMesh &sm)
|
||||
: m_cfg(sm.cfg)
|
||||
, m_mesh(sm.emesh)
|
||||
, m_support_pts(sm.pts)
|
||||
|
@ -30,7 +38,7 @@ SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder,
|
|||
}
|
||||
|
||||
bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder,
|
||||
const SupportableMesh &sm)
|
||||
const SupportableMesh &sm)
|
||||
{
|
||||
if(sm.pts.empty()) return false;
|
||||
|
||||
|
@ -560,9 +568,7 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
|||
double radius,
|
||||
long head_id)
|
||||
{
|
||||
// People were killed for this number (seriously)
|
||||
static const double SQR2 = std::sqrt(2.0);
|
||||
static const Vec3d DOWN = {0.0, 0.0, -1.0};
|
||||
const double SLOPE = 1. / std::cos(m_cfg.bridge_slope);
|
||||
|
||||
double gndlvl = m_builder.ground_level;
|
||||
Vec3d endp = {jp(X), jp(Y), gndlvl};
|
||||
|
@ -573,38 +579,47 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
|||
bool can_add_base = true;
|
||||
bool normal_mode = true;
|
||||
|
||||
// If in zero elevation mode and the pillar is too close to the model body,
|
||||
// the support pillar can not be placed in the gap between the model and
|
||||
// the pad, and the pillar bases must not touch the model body either.
|
||||
// To solve this, a corrector bridge is inserted between the starting point
|
||||
// (jp) and the new pillar.
|
||||
if (m_cfg.object_elevation_mm < EPSILON
|
||||
&& (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) {
|
||||
// Get the distance from the mesh. This can be later optimized
|
||||
// to get the distance in 2D plane because we are dealing with
|
||||
// the ground level only.
|
||||
|
||||
normal_mode = false;
|
||||
|
||||
// The min distance needed to move away from the model in XY plane.
|
||||
double current_d = min_dist - dist;
|
||||
double current_bride_d = SLOPE * current_d;
|
||||
|
||||
// get a suitable direction for the corrector bridge. It is the
|
||||
// original sourcedir's azimuth but the polar angle is saturated to the
|
||||
// configured bridge slope.
|
||||
auto [polar, azimuth] = dir_to_spheric(sourcedir);
|
||||
polar = PI - m_cfg.bridge_slope;
|
||||
auto dir = spheric_to_dir(polar, azimuth).normalized();
|
||||
|
||||
normal_mode = false;
|
||||
double mind = min_dist - dist;
|
||||
double azimuth = std::atan2(sourcedir(Y), sourcedir(X));
|
||||
double sinpolar = std::sin(PI - m_cfg.bridge_slope);
|
||||
double cospolar = std::cos(PI - m_cfg.bridge_slope);
|
||||
double cosazm = std::cos(azimuth);
|
||||
double sinazm = std::sin(azimuth);
|
||||
|
||||
auto dir = Vec3d(cosazm * sinpolar, sinazm * sinpolar, cospolar)
|
||||
.normalized();
|
||||
|
||||
using namespace libnest2d::opt;
|
||||
StopCriteria scr;
|
||||
scr.stop_score = min_dist;
|
||||
SubplexOptimizer solver(scr);
|
||||
|
||||
// Search for a distance along the corrector bridge to move the endpoint
|
||||
// sufficiently away form the model body. The first few optimization
|
||||
// cycles should succeed here.
|
||||
auto result = solver.optimize_max(
|
||||
[this, dir, jp, gndlvl](double mv) {
|
||||
Vec3d endpt = jp + SQR2 * mv * dir;
|
||||
Vec3d endpt = jp + mv * dir;
|
||||
endpt(Z) = gndlvl;
|
||||
return std::sqrt(m_mesh.squared_distance(endpt));
|
||||
},
|
||||
initvals(mind), bound(0.0, 2 * min_dist));
|
||||
initvals(current_bride_d),
|
||||
bound(0.0, m_cfg.max_bridge_length_mm - current_bride_d));
|
||||
|
||||
mind = std::get<0>(result.optimum);
|
||||
endp = jp + SQR2 * mind * dir;
|
||||
endp = jp + std::get<0>(result.optimum) * dir;
|
||||
Vec3d pgnd = {endp(X), endp(Y), gndlvl};
|
||||
can_add_base = result.score > min_dist;
|
||||
|
||||
|
@ -623,7 +638,7 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
|||
else {
|
||||
// If the new endpoint is below ground, do not make a pillar
|
||||
if (endp(Z) < gndlvl)
|
||||
endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off
|
||||
endp = endp - SLOPE * (gndlvl - endp(Z)) * dir; // back off
|
||||
else {
|
||||
|
||||
auto hit = bridge_mesh_intersect(endp, DOWN, radius);
|
||||
|
@ -685,11 +700,6 @@ void SupportTreeBuildsteps::filter()
|
|||
// not be enough space for the pinhead. Filtering is applied for
|
||||
// these reasons.
|
||||
|
||||
using libnest2d::opt::bound;
|
||||
using libnest2d::opt::initvals;
|
||||
using libnest2d::opt::GeneticOptimizer;
|
||||
using libnest2d::opt::StopCriteria;
|
||||
|
||||
ccr::SpinningMutex mutex;
|
||||
auto addfn = [&mutex](PtIndices &container, unsigned val) {
|
||||
std::lock_guard<ccr::SpinningMutex> lk(mutex);
|
||||
|
@ -708,10 +718,7 @@ void SupportTreeBuildsteps::filter()
|
|||
// (Quaternion::FromTwoVectors) and apply the rotation to the
|
||||
// arrow head.
|
||||
|
||||
double z = n(2);
|
||||
double r = 1.0; // for normalized vector
|
||||
double polar = std::acos(z / r);
|
||||
double azimuth = std::atan2(n(1), n(0));
|
||||
auto [polar, azimuth] = dir_to_spheric(n);
|
||||
|
||||
// skip if the tilt is not sane
|
||||
if(polar >= PI - m_cfg.normal_cutoff_angle) {
|
||||
|
@ -729,9 +736,7 @@ void SupportTreeBuildsteps::filter()
|
|||
double pin_r = double(m_support_pts[fidx].head_front_radius);
|
||||
|
||||
// Reassemble the now corrected normal
|
||||
auto nn = Vec3d(std::cos(azimuth) * std::sin(polar),
|
||||
std::sin(azimuth) * std::sin(polar),
|
||||
std::cos(polar)).normalized();
|
||||
auto nn = spheric_to_dir(polar, azimuth).normalized();
|
||||
|
||||
// check available distance
|
||||
EigenMesh3D::hit_result t
|
||||
|
@ -757,9 +762,7 @@ void SupportTreeBuildsteps::filter()
|
|||
auto oresult = solver.optimize_max(
|
||||
[this, pin_r, w, hp](double plr, double azm)
|
||||
{
|
||||
auto dir = Vec3d(std::cos(azm) * std::sin(plr),
|
||||
std::sin(azm) * std::sin(plr),
|
||||
std::cos(plr)).normalized();
|
||||
auto dir = spheric_to_dir(plr, azm).normalized();
|
||||
|
||||
double score = pinhead_mesh_intersect(
|
||||
hp, dir, pin_r, m_cfg.head_back_radius_mm, w);
|
||||
|
@ -767,18 +770,15 @@ void SupportTreeBuildsteps::filter()
|
|||
return score;
|
||||
},
|
||||
initvals(polar, azimuth), // start with what we have
|
||||
bound(3 * PI / 4,
|
||||
PI), // Must not exceed the tilt limit
|
||||
bound(3 * PI / 4, PI), // Must not exceed the tilt limit
|
||||
bound(-PI, PI) // azimuth can be a full search
|
||||
);
|
||||
|
||||
if(oresult.score > w) {
|
||||
polar = std::get<0>(oresult.optimum);
|
||||
azimuth = std::get<1>(oresult.optimum);
|
||||
nn = Vec3d(std::cos(azimuth) * std::sin(polar),
|
||||
std::sin(azimuth) * std::sin(polar),
|
||||
std::cos(polar)).normalized();
|
||||
t = oresult.score;
|
||||
nn = spheric_to_dir(polar, azimuth).normalized();
|
||||
t = EigenMesh3D::hit_result(oresult.score);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -837,16 +837,17 @@ void SupportTreeBuildsteps::classify()
|
|||
m_thr();
|
||||
|
||||
auto& head = m_builder.head(i);
|
||||
Vec3d n(0, 0, -1);
|
||||
double r = head.r_back_mm;
|
||||
Vec3d headjp = head.junction_point();
|
||||
|
||||
// collision check
|
||||
auto hit = bridge_mesh_intersect(headjp, n, r);
|
||||
auto hit = bridge_mesh_intersect(headjp, DOWN, r);
|
||||
|
||||
if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i);
|
||||
else if(m_cfg.ground_facing_only) head.invalidate();
|
||||
else m_iheads_onmodel.emplace_back(std::make_pair(i, hit));
|
||||
else m_iheads_onmodel.emplace_back(i);
|
||||
|
||||
m_head_to_ground_scans[i] = hit;
|
||||
}
|
||||
|
||||
// We want to search for clusters of points that are far enough
|
||||
|
@ -893,13 +894,14 @@ void SupportTreeBuildsteps::routing_to_ground()
|
|||
// get the current cluster centroid
|
||||
auto & thr = m_thr;
|
||||
const auto &points = m_points;
|
||||
long lcid = cluster_centroid(
|
||||
|
||||
long lcid = cluster_centroid(
|
||||
cl, [&points](size_t idx) { return points.row(long(idx)); },
|
||||
[thr](const Vec3d &p1, const Vec3d &p2) {
|
||||
thr();
|
||||
return distance(Vec2d(p1(X), p1(Y)), Vec2d(p2(X), p2(Y)));
|
||||
});
|
||||
|
||||
|
||||
assert(lcid >= 0);
|
||||
unsigned hid = cl[size_t(lcid)]; // Head ID
|
||||
|
||||
|
@ -944,192 +946,138 @@ void SupportTreeBuildsteps::routing_to_ground()
|
|||
}
|
||||
}
|
||||
|
||||
bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir)
|
||||
{
|
||||
auto hjp = head.junction_point();
|
||||
double r = head.r_back_mm;
|
||||
double t = bridge_mesh_intersect(hjp, dir, head.r_back_mm);
|
||||
double d = 0, tdown = 0;
|
||||
t = std::min(t, m_cfg.max_bridge_length_mm);
|
||||
|
||||
while (d < t && !std::isinf(tdown = bridge_mesh_intersect(hjp + d * dir, DOWN, r)))
|
||||
d += r;
|
||||
|
||||
if(!std::isinf(tdown)) return false;
|
||||
|
||||
Vec3d endp = hjp + d * dir;
|
||||
m_builder.add_bridge(head.id, endp);
|
||||
m_builder.add_junction(endp, head.r_back_mm);
|
||||
|
||||
this->create_ground_pillar(endp, dir, head.r_back_mm);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SupportTreeBuildsteps::connect_to_ground(Head &head)
|
||||
{
|
||||
if (connect_to_ground(head, head.dir)) return true;
|
||||
|
||||
// Optimize bridge direction:
|
||||
// Straight path failed so we will try to search for a suitable
|
||||
// direction out of the cavity.
|
||||
auto [polar, azimuth] = dir_to_spheric(head.dir);
|
||||
|
||||
StopCriteria stc;
|
||||
stc.max_iterations = m_cfg.optimizer_max_iterations;
|
||||
stc.relative_score_difference = m_cfg.optimizer_rel_score_diff;
|
||||
stc.stop_score = 1e6;
|
||||
GeneticOptimizer solver(stc);
|
||||
solver.seed(0); // we want deterministic behavior
|
||||
|
||||
double r_back = head.r_back_mm;
|
||||
Vec3d hjp = head.junction_point();
|
||||
auto oresult = solver.optimize_max(
|
||||
[this, hjp, r_back](double plr, double azm) {
|
||||
Vec3d n = spheric_to_dir(plr, azm).normalized();
|
||||
return bridge_mesh_intersect(hjp, n, r_back);
|
||||
},
|
||||
initvals(polar, azimuth), // let's start with what we have
|
||||
bound(3*PI/4, PI), // Must not exceed the slope limit
|
||||
bound(-PI, PI) // azimuth can be a full range search
|
||||
);
|
||||
|
||||
Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
|
||||
return connect_to_ground(head, bridgedir);
|
||||
}
|
||||
|
||||
bool SupportTreeBuildsteps::connect_to_model_body(Head &head)
|
||||
{
|
||||
if (head.id <= ID_UNSET) return false;
|
||||
|
||||
auto it = m_head_to_ground_scans.find(unsigned(head.id));
|
||||
if (it == m_head_to_ground_scans.end()) return false;
|
||||
|
||||
auto &hit = it->second;
|
||||
Vec3d hjp = head.junction_point();
|
||||
double zangle = std::asin(hit.direction()(Z));
|
||||
zangle = std::max(zangle, PI/4);
|
||||
double h = std::sin(zangle) * head.fullwidth();
|
||||
|
||||
// The width of the tail head that we would like to have...
|
||||
h = std::min(hit.distance() - head.r_back_mm, h);
|
||||
|
||||
if(h <= 0.) return false;
|
||||
|
||||
Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h};
|
||||
auto center_hit = m_mesh.query_ray_hit(hjp, DOWN);
|
||||
|
||||
double hitdiff = center_hit.distance() - hit.distance();
|
||||
Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm?
|
||||
center_hit.position() : hit.position();
|
||||
|
||||
head.transform();
|
||||
|
||||
long pillar_id = m_builder.add_pillar(head.id, endp, head.r_back_mm);
|
||||
Pillar &pill = m_builder.pillar(pillar_id);
|
||||
|
||||
Vec3d taildir = endp - hitp;
|
||||
double dist = distance(endp, hitp) + m_cfg.head_penetration_mm;
|
||||
double w = dist - 2 * head.r_pin_mm - head.r_back_mm;
|
||||
|
||||
if (w < 0.) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Pinhead width is negative!";
|
||||
w = 0.;
|
||||
}
|
||||
|
||||
Head tailhead(head.r_back_mm, head.r_pin_mm, w,
|
||||
m_cfg.head_penetration_mm, taildir, hitp);
|
||||
|
||||
tailhead.transform();
|
||||
pill.base = tailhead.mesh;
|
||||
|
||||
m_pillar_index.guarded_insert(pill.endpoint(), pill.id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SupportTreeBuildsteps::routing_to_model()
|
||||
{
|
||||
// We need to check if there is an easy way out to the bed surface.
|
||||
// If it can be routed there with a bridge shorter than
|
||||
// min_bridge_distance.
|
||||
|
||||
// First we want to index the available pillars. The best is to connect
|
||||
// these points to the available pillars
|
||||
|
||||
auto routedown = [this](Head& head, const Vec3d& dir, double dist)
|
||||
{
|
||||
head.transform();
|
||||
Vec3d endp = head.junction_point() + dist * dir;
|
||||
m_builder.add_bridge(head.id, endp);
|
||||
m_builder.add_junction(endp, head.r_back_mm);
|
||||
|
||||
this->create_ground_pillar(endp, dir, head.r_back_mm);
|
||||
};
|
||||
|
||||
std::vector<unsigned> modelpillars;
|
||||
ccr::SpinningMutex mutex;
|
||||
|
||||
auto onmodelfn =
|
||||
[this, routedown, &modelpillars, &mutex]
|
||||
(const std::pair<unsigned, EigenMesh3D::hit_result> &el, size_t)
|
||||
{
|
||||
ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(),
|
||||
[this] (const unsigned idx, size_t) {
|
||||
m_thr();
|
||||
unsigned idx = el.first;
|
||||
EigenMesh3D::hit_result hit = el.second;
|
||||
|
||||
auto& head = m_builder.head(idx);
|
||||
Vec3d hjp = head.junction_point();
|
||||
|
||||
// /////////////////////////////////////////////////////////////////
|
||||
// Search nearby pillar
|
||||
// /////////////////////////////////////////////////////////////////
|
||||
|
||||
if(search_pillar_and_connect(head)) { head.transform(); return; }
|
||||
|
||||
// /////////////////////////////////////////////////////////////////
|
||||
// Try straight path
|
||||
// /////////////////////////////////////////////////////////////////
|
||||
|
||||
// Cannot connect to nearby pillar. We will try to search for
|
||||
// a route to the ground.
|
||||
if(connect_to_ground(head)) { head.transform(); return; }
|
||||
|
||||
double t = bridge_mesh_intersect(hjp, head.dir, head.r_back_mm);
|
||||
double d = 0, tdown = 0;
|
||||
Vec3d dirdown(0.0, 0.0, -1.0);
|
||||
|
||||
t = std::min(t, m_cfg.max_bridge_length_mm);
|
||||
|
||||
while(d < t && !std::isinf(tdown = bridge_mesh_intersect(
|
||||
hjp + d*head.dir,
|
||||
dirdown, head.r_back_mm))) {
|
||||
d += head.r_back_mm;
|
||||
}
|
||||
|
||||
if(std::isinf(tdown)) { // we heave found a route to the ground
|
||||
routedown(head, head.dir, d); return;
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////
|
||||
// Optimize bridge direction
|
||||
// /////////////////////////////////////////////////////////////////
|
||||
|
||||
// Straight path failed so we will try to search for a suitable
|
||||
// direction out of the cavity.
|
||||
|
||||
// Get the spherical representation of the normal. its easier to
|
||||
// work with.
|
||||
double z = head.dir(Z);
|
||||
double r = 1.0; // for normalized vector
|
||||
double polar = std::acos(z / r);
|
||||
double azimuth = std::atan2(head.dir(Y), head.dir(X));
|
||||
|
||||
using libnest2d::opt::bound;
|
||||
using libnest2d::opt::initvals;
|
||||
using libnest2d::opt::GeneticOptimizer;
|
||||
using libnest2d::opt::StopCriteria;
|
||||
|
||||
StopCriteria stc;
|
||||
stc.max_iterations = m_cfg.optimizer_max_iterations;
|
||||
stc.relative_score_difference = m_cfg.optimizer_rel_score_diff;
|
||||
stc.stop_score = 1e6;
|
||||
GeneticOptimizer solver(stc);
|
||||
solver.seed(0); // we want deterministic behavior
|
||||
|
||||
double r_back = head.r_back_mm;
|
||||
|
||||
auto oresult = solver.optimize_max(
|
||||
[this, hjp, r_back](double plr, double azm)
|
||||
{
|
||||
Vec3d n = Vec3d(std::cos(azm) * std::sin(plr),
|
||||
std::sin(azm) * std::sin(plr),
|
||||
std::cos(plr)).normalized();
|
||||
return bridge_mesh_intersect(hjp, n, r_back);
|
||||
},
|
||||
initvals(polar, azimuth), // let's start with what we have
|
||||
bound(3*PI/4, PI), // Must not exceed the slope limit
|
||||
bound(-PI, PI) // azimuth can be a full range search
|
||||
);
|
||||
|
||||
d = 0; t = oresult.score;
|
||||
|
||||
polar = std::get<0>(oresult.optimum);
|
||||
azimuth = std::get<1>(oresult.optimum);
|
||||
Vec3d bridgedir = Vec3d(std::cos(azimuth) * std::sin(polar),
|
||||
std::sin(azimuth) * std::sin(polar),
|
||||
std::cos(polar)).normalized();
|
||||
|
||||
t = std::min(t, m_cfg.max_bridge_length_mm);
|
||||
|
||||
while(d < t && !std::isinf(tdown = bridge_mesh_intersect(
|
||||
hjp + d*bridgedir,
|
||||
dirdown,
|
||||
head.r_back_mm))) {
|
||||
d += head.r_back_mm;
|
||||
}
|
||||
|
||||
if(std::isinf(tdown)) { // we heave found a route to the ground
|
||||
routedown(head, bridgedir, d); return;
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////
|
||||
// Route to model body
|
||||
// /////////////////////////////////////////////////////////////////
|
||||
|
||||
double zangle = std::asin(hit.direction()(Z));
|
||||
zangle = std::max(zangle, PI/4);
|
||||
double h = std::sin(zangle) * head.fullwidth();
|
||||
|
||||
// The width of the tail head that we would like to have...
|
||||
h = std::min(hit.distance() - head.r_back_mm, h);
|
||||
|
||||
if(h > 0) {
|
||||
Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h};
|
||||
auto center_hit = m_mesh.query_ray_hit(hjp, dirdown);
|
||||
|
||||
double hitdiff = center_hit.distance() - hit.distance();
|
||||
Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm?
|
||||
center_hit.position() : hit.position();
|
||||
|
||||
head.transform();
|
||||
|
||||
long pillar_id = m_builder.add_pillar(head.id, endp, head.r_back_mm);
|
||||
Pillar &pill = m_builder.pillar(pillar_id);
|
||||
|
||||
Vec3d taildir = endp - hitp;
|
||||
double dist = distance(endp, hitp) + m_cfg.head_penetration_mm;
|
||||
double w = dist - 2 * head.r_pin_mm - head.r_back_mm;
|
||||
|
||||
if (w < 0.) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Pinhead width is negative!";
|
||||
w = 0.;
|
||||
}
|
||||
|
||||
Head tailhead(head.r_back_mm,
|
||||
head.r_pin_mm,
|
||||
w,
|
||||
m_cfg.head_penetration_mm,
|
||||
taildir,
|
||||
hitp);
|
||||
|
||||
tailhead.transform();
|
||||
pill.base = tailhead.mesh;
|
||||
|
||||
// Experimental: add the pillar to the index for cascading
|
||||
std::lock_guard<ccr::SpinningMutex> lk(mutex);
|
||||
modelpillars.emplace_back(unsigned(pill.id));
|
||||
return;
|
||||
}
|
||||
// No route to the ground, so connect to the model body as a last resort
|
||||
if (connect_to_model_body(head)) { return; }
|
||||
|
||||
// We have failed to route this head.
|
||||
BOOST_LOG_TRIVIAL(warning)
|
||||
<< "Failed to route model facing support point."
|
||||
<< " ID: " << idx;
|
||||
<< "Failed to route model facing support point. ID: " << idx;
|
||||
|
||||
head.invalidate();
|
||||
};
|
||||
|
||||
ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(), onmodelfn);
|
||||
|
||||
for(auto pillid : modelpillars) {
|
||||
auto& pillar = m_builder.pillar(pillid);
|
||||
m_pillar_index.insert(pillar.endpoint(), pillid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SupportTreeBuildsteps::interconnect_pillars()
|
||||
|
@ -1280,7 +1228,8 @@ void SupportTreeBuildsteps::interconnect_pillars()
|
|||
spts[n] = s;
|
||||
|
||||
// Check the path vertically down
|
||||
auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r);
|
||||
Vec3d check_from = s + Vec3d{0., 0., pillar().r};
|
||||
auto hr = bridge_mesh_intersect(check_from, DOWN, pillar().r);
|
||||
Vec3d gndsp{s(X), s(Y), gnd};
|
||||
|
||||
// If the path is clear, check for pillar base collisions
|
||||
|
@ -1360,12 +1309,11 @@ void SupportTreeBuildsteps::routing_headless()
|
|||
Vec3d n = m_support_nmls.row(i); // mesh outward normal
|
||||
Vec3d sp = sph - n * HWIDTH_MM; // stick head start point
|
||||
|
||||
Vec3d dir = {0, 0, -1};
|
||||
Vec3d sj = sp + R * n; // stick start point
|
||||
|
||||
// This is only for checking
|
||||
double idist = bridge_mesh_intersect(sph, dir, R, true);
|
||||
double realdist = ray_mesh_intersect(sj, dir);
|
||||
double idist = bridge_mesh_intersect(sph, DOWN, R, true);
|
||||
double realdist = ray_mesh_intersect(sj, DOWN);
|
||||
double dist = realdist;
|
||||
|
||||
if (std::isinf(dist)) dist = sph(Z) - m_builder.ground_level;
|
||||
|
@ -1378,7 +1326,7 @@ void SupportTreeBuildsteps::routing_headless()
|
|||
}
|
||||
|
||||
bool use_endball = !std::isinf(realdist);
|
||||
Vec3d ej = sj + (dist + HWIDTH_MM) * dir;
|
||||
Vec3d ej = sj + (dist + HWIDTH_MM) * DOWN ;
|
||||
m_builder.add_compact_bridge(sp, ej, n, R, use_endball);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,8 @@
|
|||
|
||||
#include <cstdint>
|
||||
|
||||
#include "SLASupportTreeBuilder.hpp"
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
#include <libslic3r/SLA/Clustering.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace sla {
|
||||
|
@ -19,6 +20,32 @@ inline Vec2d to_vec2(const Vec3d& v3) {
|
|||
return {v3(X), v3(Y)};
|
||||
}
|
||||
|
||||
inline std::pair<double, double> dir_to_spheric(const Vec3d &n, double norm = 1.)
|
||||
{
|
||||
double z = n.z();
|
||||
double r = norm;
|
||||
double polar = std::acos(z / r);
|
||||
double azimuth = std::atan2(n(1), n(0));
|
||||
return {polar, azimuth};
|
||||
}
|
||||
|
||||
inline Vec3d spheric_to_dir(double polar, double azimuth)
|
||||
{
|
||||
return {std::cos(azimuth) * std::sin(polar),
|
||||
std::sin(azimuth) * std::sin(polar), std::cos(polar)};
|
||||
}
|
||||
|
||||
inline Vec3d spheric_to_dir(const std::tuple<double, double> &v)
|
||||
{
|
||||
auto [plr, azm] = v;
|
||||
return spheric_to_dir(plr, azm);
|
||||
}
|
||||
|
||||
inline Vec3d spheric_to_dir(const std::pair<double, double> &v)
|
||||
{
|
||||
return spheric_to_dir(v.first, v.second);
|
||||
}
|
||||
|
||||
// This function returns the position of the centroid in the input 'clust'
|
||||
// vector of point indices.
|
||||
template<class DistFn>
|
||||
|
@ -150,10 +177,10 @@ class SupportTreeBuildsteps {
|
|||
using PtIndices = std::vector<unsigned>;
|
||||
|
||||
PtIndices m_iheads; // support points with pinhead
|
||||
PtIndices m_iheads_onmodel;
|
||||
PtIndices m_iheadless; // headless support points
|
||||
|
||||
// supp. pts. connecting to model: point index and the ray hit data
|
||||
std::vector<std::pair<unsigned, EigenMesh3D::hit_result>> m_iheads_onmodel;
|
||||
|
||||
std::map<unsigned, EigenMesh3D::hit_result> m_head_to_ground_scans;
|
||||
|
||||
// normals for support points from model faces.
|
||||
PointSet m_support_nmls;
|
||||
|
@ -222,15 +249,29 @@ class SupportTreeBuildsteps {
|
|||
|
||||
// For connecting a head to a nearby pillar.
|
||||
bool connect_to_nearpillar(const Head& head, long nearpillar_id);
|
||||
|
||||
|
||||
// Find route for a head to the ground. Inserts additional bridge from the
|
||||
// head to the pillar if cannot create pillar directly.
|
||||
// The optional dir parameter is the direction of the bridge which is the
|
||||
// direction of the pinhead if omitted.
|
||||
bool connect_to_ground(Head& head, const Vec3d &dir);
|
||||
inline bool connect_to_ground(Head& head);
|
||||
|
||||
bool connect_to_model_body(Head &head);
|
||||
|
||||
bool search_pillar_and_connect(const Head& head);
|
||||
|
||||
|
||||
// This is a proxy function for pillar creation which will mind the gap
|
||||
// between the pad and the model bottom in zero elevation mode.
|
||||
// jp is the starting junction point which needs to be routed down.
|
||||
// sourcedir is the allowed direction of an optional bridge between the
|
||||
// jp junction and the final pillar.
|
||||
void create_ground_pillar(const Vec3d &jp,
|
||||
const Vec3d &sourcedir,
|
||||
double radius,
|
||||
long head_id = ID_UNSET);
|
||||
|
||||
|
||||
public:
|
||||
SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#include <cmath>
|
||||
#include "SLA/SLASupportTree.hpp"
|
||||
#include "SLA/SLABoilerPlate.hpp"
|
||||
#include "SLA/SLACommon.hpp"
|
||||
#include "SLA/SLASpatIndex.hpp"
|
||||
|
||||
// Workaround: IGL signed_distance.h will define PI in the igl namespace.
|
||||
|
@ -228,6 +228,26 @@ EigenMesh3D::EigenMesh3D(const EigenMesh3D &other):
|
|||
m_V(other.m_V), m_F(other.m_F), m_ground_level(other.m_ground_level),
|
||||
m_aabb( new AABBImpl(*other.m_aabb) ) {}
|
||||
|
||||
EigenMesh3D::EigenMesh3D(const Contour3D &other)
|
||||
{
|
||||
m_V.resize(Eigen::Index(other.points.size()), 3);
|
||||
m_F.resize(Eigen::Index(other.faces3.size() + 2 * other.faces4.size()), 3);
|
||||
|
||||
for (Eigen::Index i = 0; i < Eigen::Index(other.points.size()); ++i)
|
||||
m_V.row(i) = other.points[size_t(i)];
|
||||
|
||||
for (Eigen::Index i = 0; i < Eigen::Index(other.faces3.size()); ++i)
|
||||
m_F.row(i) = other.faces3[size_t(i)];
|
||||
|
||||
size_t N = other.faces3.size() + 2 * other.faces4.size();
|
||||
for (size_t i = other.faces3.size(); i < N; i += 2) {
|
||||
size_t quad_idx = (i - other.faces3.size()) / 2;
|
||||
auto & quad = other.faces4[quad_idx];
|
||||
m_F.row(Eigen::Index(i)) = Vec3i{quad(0), quad(1), quad(2)};
|
||||
m_F.row(Eigen::Index(i + 1)) = Vec3i{quad(2), quad(3), quad(0)};
|
||||
}
|
||||
}
|
||||
|
||||
EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other)
|
||||
{
|
||||
m_V = other.m_V;
|
||||
|
@ -252,6 +272,31 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
|
|||
return ret;
|
||||
}
|
||||
|
||||
std::vector<EigenMesh3D::hit_result>
|
||||
EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
|
||||
{
|
||||
std::vector<EigenMesh3D::hit_result> outs;
|
||||
std::vector<igl::Hit> hits;
|
||||
m_aabb->intersect_ray(m_V, m_F, s, dir, hits);
|
||||
|
||||
// The sort is necessary, the hits are not always sorted.
|
||||
std::sort(hits.begin(), hits.end(),
|
||||
[](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
|
||||
|
||||
// Convert the igl::Hit into hit_result
|
||||
outs.reserve(hits.size());
|
||||
for (const igl::Hit& hit : hits) {
|
||||
outs.emplace_back(EigenMesh3D::hit_result(*this));
|
||||
outs.back().m_t = double(hit.t);
|
||||
outs.back().m_dir = dir;
|
||||
outs.back().m_source = s;
|
||||
if(!std::isinf(hit.t) && !std::isnan(hit.t))
|
||||
outs.back().m_face_id = hit.id;
|
||||
}
|
||||
|
||||
return outs;
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
||||
EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const {
|
||||
double sign = 0; double sqdst = 0; int i = 0; Vec3d c;
|
File diff suppressed because it is too large
Load diff
|
@ -3,8 +3,8 @@
|
|||
|
||||
#include <mutex>
|
||||
#include "PrintBase.hpp"
|
||||
//#include "PrintExport.hpp"
|
||||
#include "SLA/SLARasterWriter.hpp"
|
||||
#include "SLA/RasterWriter.hpp"
|
||||
#include "SLA/SupportTree.hpp"
|
||||
#include "Point.hpp"
|
||||
#include "MTUtils.hpp"
|
||||
#include "Zipper.hpp"
|
||||
|
@ -19,7 +19,9 @@ enum SLAPrintStep : unsigned int {
|
|||
};
|
||||
|
||||
enum SLAPrintObjectStep : unsigned int {
|
||||
slaposHollowing,
|
||||
slaposObjectSlice,
|
||||
slaposDrillHolesIfHollowed,
|
||||
slaposSupportPoints,
|
||||
slaposSupportTree,
|
||||
slaposPad,
|
||||
|
@ -73,13 +75,17 @@ public:
|
|||
// Support mesh is only valid if this->is_step_done(slaposSupportTree) is true.
|
||||
const TriangleMesh& support_mesh() const;
|
||||
// Get a pad mesh centered around origin in XY, and with zero rotation around Z applied.
|
||||
// Support mesh is only valid if this->is_step_done(slaposBasePool) is true.
|
||||
// Support mesh is only valid if this->is_step_done(slaposPad) is true.
|
||||
const TriangleMesh& pad_mesh() const;
|
||||
|
||||
// Ready after this->is_step_done(slaposHollowing) is true
|
||||
const TriangleMesh& hollowed_interior_mesh() const;
|
||||
|
||||
// This will return the transformed mesh which is cached
|
||||
const TriangleMesh& transformed_mesh() const;
|
||||
|
||||
std::vector<sla::SupportPoint> transformed_support_points() const;
|
||||
sla::SupportPoints transformed_support_points() const;
|
||||
sla::DrainHoles transformed_drainhole_points() const;
|
||||
|
||||
// Get the needed Z elevation for the model geometry if supports should be
|
||||
// displayed. This Z offset should also be applied to the support
|
||||
|
@ -287,9 +293,35 @@ private:
|
|||
|
||||
// Caching the transformed (m_trafo) raw mesh of the object
|
||||
mutable CachedObject<TriangleMesh> m_transformed_rmesh;
|
||||
|
||||
class SupportData;
|
||||
|
||||
class SupportData : public sla::SupportableMesh
|
||||
{
|
||||
public:
|
||||
sla::SupportTree::UPtr support_tree_ptr; // the supports
|
||||
std::vector<ExPolygons> support_slices; // sliced supports
|
||||
|
||||
inline SupportData(const TriangleMesh &t)
|
||||
: sla::SupportableMesh{t, {}, {}}
|
||||
{}
|
||||
|
||||
sla::SupportTree::UPtr &create_support_tree(const sla::JobController &ctl)
|
||||
{
|
||||
support_tree_ptr = sla::SupportTree::create(*this, ctl);
|
||||
return support_tree_ptr;
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<SupportData> m_supportdata;
|
||||
|
||||
class HollowingData
|
||||
{
|
||||
public:
|
||||
|
||||
TriangleMesh interior;
|
||||
// std::vector<drillpoints>
|
||||
};
|
||||
|
||||
std::unique_ptr<HollowingData> m_hollowing_data;
|
||||
};
|
||||
|
||||
using PrintObjects = std::vector<SLAPrintObject*>;
|
||||
|
@ -339,7 +371,9 @@ class SLAPrint : public PrintBaseWithState<SLAPrintStep, slapsCount>
|
|||
{
|
||||
private: // Prevents erroneous use by other classes.
|
||||
typedef PrintBaseWithState<SLAPrintStep, slapsCount> Inherited;
|
||||
|
||||
|
||||
class Steps; // See SLAPrintSteps.cpp
|
||||
|
||||
public:
|
||||
|
||||
SLAPrint(): m_stepmask(slapsCount, true) {}
|
||||
|
@ -401,8 +435,8 @@ public:
|
|||
template<class Container> void transformed_slices(Container&& c) {
|
||||
m_transformed_slices = std::forward<Container>(c);
|
||||
}
|
||||
|
||||
friend void SLAPrint::process();
|
||||
|
||||
friend class SLAPrint::Steps;
|
||||
|
||||
public:
|
||||
|
||||
|
@ -478,6 +512,19 @@ private:
|
|||
friend SLAPrintObject;
|
||||
};
|
||||
|
||||
// Helper functions:
|
||||
|
||||
bool is_zero_elevation(const SLAPrintObjectConfig &c);
|
||||
|
||||
sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c);
|
||||
|
||||
sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c);
|
||||
|
||||
sla::PadConfig make_pad_cfg(const SLAPrintObjectConfig& c);
|
||||
|
||||
bool validate_pad(const TriangleMesh &pad, const sla::PadConfig &pcfg);
|
||||
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif /* slic3r_SLAPrint_hpp_ */
|
||||
|
|
862
src/libslic3r/SLAPrintSteps.cpp
Normal file
862
src/libslic3r/SLAPrintSteps.cpp
Normal file
|
@ -0,0 +1,862 @@
|
|||
#include <libslic3r/SLAPrintSteps.hpp>
|
||||
|
||||
|
||||
// Need the cylinder method for the the drainholes in hollowing step
|
||||
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||
|
||||
#include <libslic3r/SLA/Concurrency.hpp>
|
||||
#include <libslic3r/SLA/Pad.hpp>
|
||||
#include <libslic3r/SLA/SupportPointGenerator.hpp>
|
||||
|
||||
#include <libslic3r/ClipperUtils.hpp>
|
||||
|
||||
// For geometry algorithms with native Clipper types (no copies and conversions)
|
||||
#include <libnest2d/backends/clipper/geometries.hpp>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "I18N.hpp"
|
||||
|
||||
//! macro used to mark string used at localization,
|
||||
//! return same string
|
||||
#define L(s) Slic3r::I18N::translate(s)
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
|
||||
const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS = {
|
||||
5, // slaposHollowing,
|
||||
20, // slaposObjectSlice,
|
||||
5, // slaposDrillHolesIfHollowed
|
||||
20, // slaposSupportPoints,
|
||||
10, // slaposSupportTree,
|
||||
10, // slaposPad,
|
||||
30, // slaposSliceSupports,
|
||||
};
|
||||
|
||||
std::string OBJ_STEP_LABELS(size_t idx)
|
||||
{
|
||||
switch (idx) {
|
||||
case slaposHollowing: return L("Hollowing out the model");
|
||||
case slaposObjectSlice: return L("Slicing model");
|
||||
case slaposDrillHolesIfHollowed: return L("Drilling holes into hollowed model.");
|
||||
case slaposSupportPoints: return L("Generating support points");
|
||||
case slaposSupportTree: return L("Generating support tree");
|
||||
case slaposPad: return L("Generating pad");
|
||||
case slaposSliceSupports: return L("Slicing supports");
|
||||
default:;
|
||||
}
|
||||
assert(false);
|
||||
return "Out of bounds!";
|
||||
};
|
||||
|
||||
const std::array<unsigned, slapsCount> PRINT_STEP_LEVELS = {
|
||||
10, // slapsMergeSlicesAndEval
|
||||
90, // slapsRasterize
|
||||
};
|
||||
|
||||
std::string PRINT_STEP_LABELS(size_t idx)
|
||||
{
|
||||
switch (idx) {
|
||||
case slapsMergeSlicesAndEval: return L("Merging slices and calculating statistics");
|
||||
case slapsRasterize: return L("Rasterizing layers");
|
||||
default:;
|
||||
}
|
||||
assert(false); return "Out of bounds!";
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
SLAPrint::Steps::Steps(SLAPrint *print)
|
||||
: m_print{print}
|
||||
, objcount{m_print->m_objects.size()}
|
||||
, ilhd{m_print->m_material_config.initial_layer_height.getFloat()}
|
||||
, ilh{float(ilhd)}
|
||||
, ilhs{scaled(ilhd)}
|
||||
, objectstep_scale{(max_objstatus - min_objstatus) / (objcount * 100.0)}
|
||||
{}
|
||||
|
||||
void SLAPrint::Steps::hollow_model(SLAPrintObject &po)
|
||||
{
|
||||
if (!po.m_config.hollowing_enable.getBool()) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!";
|
||||
po.m_hollowing_data.reset();
|
||||
return;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!";
|
||||
}
|
||||
|
||||
if (!po.m_hollowing_data)
|
||||
po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
|
||||
|
||||
double thickness = po.m_config.hollowing_min_thickness.getFloat();
|
||||
double quality = po.m_config.hollowing_quality.getFloat();
|
||||
double closing_d = po.m_config.hollowing_closing_distance.getFloat();
|
||||
sla::HollowingConfig hlwcfg{thickness, quality, closing_d};
|
||||
auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg);
|
||||
if (meshptr) po.m_hollowing_data->interior = *meshptr;
|
||||
|
||||
if (po.m_hollowing_data->interior.empty())
|
||||
BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!";
|
||||
}
|
||||
|
||||
// The slicing will be performed on an imaginary 1D grid which starts from
|
||||
// the bottom of the bounding box created around the supported model. So
|
||||
// the first layer which is usually thicker will be part of the supports
|
||||
// not the model geometry. Exception is when the model is not in the air
|
||||
// (elevation is zero) and no pad creation was requested. In this case the
|
||||
// model geometry starts on the ground level and the initial layer is part
|
||||
// of it. In any case, the model and the supports have to be sliced in the
|
||||
// same imaginary grid (the height vector argument to TriangleMeshSlicer).
|
||||
void SLAPrint::Steps::slice_model(SLAPrintObject &po)
|
||||
{
|
||||
TriangleMesh hollowed_mesh;
|
||||
|
||||
bool is_hollowing = po.m_config.hollowing_enable.getBool() && po.m_hollowing_data;
|
||||
|
||||
if (is_hollowing) {
|
||||
hollowed_mesh = po.transformed_mesh();
|
||||
hollowed_mesh.merge(po.m_hollowing_data->interior);
|
||||
hollowed_mesh.require_shared_vertices();
|
||||
}
|
||||
|
||||
const TriangleMesh &mesh = is_hollowing ? hollowed_mesh : po.transformed_mesh();
|
||||
|
||||
// We need to prepare the slice index...
|
||||
|
||||
double lhd = m_print->m_objects.front()->m_config.layer_height.getFloat();
|
||||
float lh = float(lhd);
|
||||
coord_t lhs = scaled(lhd);
|
||||
auto && bb3d = mesh.bounding_box();
|
||||
double minZ = bb3d.min(Z) - po.get_elevation();
|
||||
double maxZ = bb3d.max(Z);
|
||||
auto minZf = float(minZ);
|
||||
coord_t minZs = scaled(minZ);
|
||||
coord_t maxZs = scaled(maxZ);
|
||||
|
||||
po.m_slice_index.clear();
|
||||
|
||||
size_t cap = size_t(1 + (maxZs - minZs - ilhs) / lhs);
|
||||
po.m_slice_index.reserve(cap);
|
||||
|
||||
po.m_slice_index.emplace_back(minZs + ilhs, minZf + ilh / 2.f, ilh);
|
||||
|
||||
for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs)
|
||||
po.m_slice_index.emplace_back(h, unscaled<float>(h) - lh / 2.f, lh);
|
||||
|
||||
// Just get the first record that is from the model:
|
||||
auto slindex_it =
|
||||
po.closest_slice_record(po.m_slice_index, float(bb3d.min(Z)));
|
||||
|
||||
if(slindex_it == po.m_slice_index.end())
|
||||
//TRN To be shown at the status bar on SLA slicing error.
|
||||
throw std::runtime_error(
|
||||
L("Slicing had to be stopped due to an internal error: "
|
||||
"Inconsistent slice index."));
|
||||
|
||||
po.m_model_height_levels.clear();
|
||||
po.m_model_height_levels.reserve(po.m_slice_index.size());
|
||||
for(auto it = slindex_it; it != po.m_slice_index.end(); ++it)
|
||||
po.m_model_height_levels.emplace_back(it->slice_level());
|
||||
|
||||
TriangleMeshSlicer slicer(&mesh);
|
||||
|
||||
po.m_model_slices.clear();
|
||||
float closing_r = float(po.config().slice_closing_radius.value);
|
||||
auto thr = [this]() { m_print->throw_if_canceled(); };
|
||||
auto &slice_grid = po.m_model_height_levels;
|
||||
slicer.slice(slice_grid, closing_r, &po.m_model_slices, thr);
|
||||
|
||||
sla::DrainHoles drainholes = po.transformed_drainhole_points();
|
||||
cut_drainholes(po.m_model_slices, slice_grid, closing_r, drainholes, thr);
|
||||
|
||||
auto mit = slindex_it;
|
||||
double doffs = m_print->m_printer_config.absolute_correction.getFloat();
|
||||
coord_t clpr_offs = scaled(doffs);
|
||||
for(size_t id = 0;
|
||||
id < po.m_model_slices.size() && mit != po.m_slice_index.end();
|
||||
id++)
|
||||
{
|
||||
// We apply the printer correction offset here.
|
||||
if(clpr_offs != 0)
|
||||
po.m_model_slices[id] =
|
||||
offset_ex(po.m_model_slices[id], float(clpr_offs));
|
||||
|
||||
mit->set_model_slice_idx(po, id); ++mit;
|
||||
}
|
||||
|
||||
if(po.m_config.supports_enable.getBool() || po.m_config.pad_enable.getBool())
|
||||
{
|
||||
po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh));
|
||||
}
|
||||
}
|
||||
|
||||
// In this step we check the slices, identify island and cover them with
|
||||
// support points. Then we sprinkle the rest of the mesh.
|
||||
void SLAPrint::Steps::support_points(SLAPrintObject &po)
|
||||
{
|
||||
// If supports are disabled, we can skip the model scan.
|
||||
if(!po.m_config.supports_enable.getBool()) return;
|
||||
|
||||
bool is_hollowing = po.m_config.hollowing_enable.getBool() && po.m_hollowing_data;
|
||||
|
||||
TriangleMesh hollowed_mesh;
|
||||
if (is_hollowing) {
|
||||
hollowed_mesh = po.transformed_mesh();
|
||||
hollowed_mesh.merge(po.m_hollowing_data->interior);
|
||||
hollowed_mesh.require_shared_vertices();
|
||||
}
|
||||
|
||||
const TriangleMesh &mesh = is_hollowing ? hollowed_mesh : po.transformed_mesh();
|
||||
|
||||
if (!po.m_supportdata)
|
||||
po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh));
|
||||
|
||||
const ModelObject& mo = *po.m_model_object;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Support point count "
|
||||
<< mo.sla_support_points.size();
|
||||
|
||||
// Unless the user modified the points or we already did the calculation,
|
||||
// we will do the autoplacement. Otherwise we will just blindly copy the
|
||||
// frontend data into the backend cache.
|
||||
if (mo.sla_points_status != sla::PointsStatus::UserModified) {
|
||||
|
||||
// calculate heights of slices (slices are calculated already)
|
||||
const std::vector<float>& heights = po.m_model_height_levels;
|
||||
|
||||
// Tell the mesh where drain holes are. Although the points are
|
||||
// calculated on slices, the algorithm then raycasts the points
|
||||
// so they actually lie on the mesh.
|
||||
po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
|
||||
|
||||
throw_if_canceled();
|
||||
sla::SupportPointGenerator::Config config;
|
||||
const SLAPrintObjectConfig& cfg = po.config();
|
||||
|
||||
// the density config value is in percents:
|
||||
config.density_relative = float(cfg.support_points_density_relative / 100.f);
|
||||
config.minimal_distance = float(cfg.support_points_minimal_distance);
|
||||
config.head_diameter = float(cfg.support_head_front_diameter);
|
||||
|
||||
// scaling for the sub operations
|
||||
double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0;
|
||||
double init = current_status();
|
||||
|
||||
auto statuscb = [this, d, init](unsigned st)
|
||||
{
|
||||
double current = init + st * d;
|
||||
if(std::round(current_status()) < std::round(current))
|
||||
report_status(current, OBJ_STEP_LABELS(slaposSupportPoints));
|
||||
};
|
||||
|
||||
// Construction of this object does the calculation.
|
||||
throw_if_canceled();
|
||||
sla::SupportPointGenerator auto_supports(
|
||||
po.m_supportdata->emesh, po.get_model_slices(), heights, config,
|
||||
[this]() { throw_if_canceled(); }, statuscb);
|
||||
|
||||
// Now let's extract the result.
|
||||
const std::vector<sla::SupportPoint>& points = auto_supports.output();
|
||||
throw_if_canceled();
|
||||
po.m_supportdata->pts = points;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Automatic support points: "
|
||||
<< po.m_supportdata->pts.size();
|
||||
|
||||
// Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
|
||||
// the update status to GLGizmoSlaSupports
|
||||
report_status(-1, L("Generating support points"),
|
||||
SlicingStatus::RELOAD_SLA_SUPPORT_POINTS);
|
||||
} else {
|
||||
// There are either some points on the front-end, or the user
|
||||
// removed them on purpose. No calculation will be done.
|
||||
po.m_supportdata->pts = po.transformed_support_points();
|
||||
}
|
||||
|
||||
// If the zero elevation mode is engaged, we have to filter out all the
|
||||
// points that are on the bottom of the object
|
||||
if (is_zero_elevation(po.config())) {
|
||||
double tolerance = po.config().pad_enable.getBool() ?
|
||||
po.m_config.pad_wall_thickness.getFloat() :
|
||||
po.m_config.support_base_height.getFloat();
|
||||
|
||||
remove_bottom_points(po.m_supportdata->pts,
|
||||
po.m_supportdata->emesh.ground_level(),
|
||||
tolerance);
|
||||
}
|
||||
}
|
||||
|
||||
void SLAPrint::Steps::support_tree(SLAPrintObject &po)
|
||||
{
|
||||
if(!po.m_supportdata) return;
|
||||
|
||||
sla::PadConfig pcfg = make_pad_cfg(po.m_config);
|
||||
|
||||
if (pcfg.embed_object)
|
||||
po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm);
|
||||
|
||||
po.m_supportdata->cfg = make_support_cfg(po.m_config);
|
||||
po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
|
||||
|
||||
// scaling for the sub operations
|
||||
double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0;
|
||||
double init = current_status();
|
||||
sla::JobController ctl;
|
||||
|
||||
ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) {
|
||||
double current = init + st * d;
|
||||
if (std::round(current_status()) < std::round(current))
|
||||
report_status(current, OBJ_STEP_LABELS(slaposSupportTree),
|
||||
SlicingStatus::DEFAULT, logmsg);
|
||||
};
|
||||
ctl.stopcondition = [this]() { return canceled(); };
|
||||
ctl.cancelfn = [this]() { throw_if_canceled(); };
|
||||
|
||||
po.m_supportdata->create_support_tree(ctl);
|
||||
|
||||
if (!po.m_config.supports_enable.getBool()) return;
|
||||
|
||||
throw_if_canceled();
|
||||
|
||||
// Create the unified mesh
|
||||
auto rc = SlicingStatus::RELOAD_SCENE;
|
||||
|
||||
// This is to prevent "Done." being displayed during merged_mesh()
|
||||
report_status(-1, L("Visualizing supports"));
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Processed support point count "
|
||||
<< po.m_supportdata->pts.size();
|
||||
|
||||
// Check the mesh for later troubleshooting.
|
||||
if(po.support_mesh().empty())
|
||||
BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty";
|
||||
|
||||
report_status(-1, L("Visualizing supports"), rc);
|
||||
}
|
||||
|
||||
void SLAPrint::Steps::generate_pad(SLAPrintObject &po) {
|
||||
// this step can only go after the support tree has been created
|
||||
// and before the supports had been sliced. (or the slicing has to be
|
||||
// repeated)
|
||||
|
||||
if(po.m_config.pad_enable.getBool()) {
|
||||
// Get the distilled pad configuration from the config
|
||||
sla::PadConfig pcfg = make_pad_cfg(po.m_config);
|
||||
|
||||
ExPolygons bp; // This will store the base plate of the pad.
|
||||
double pad_h = pcfg.full_height();
|
||||
const TriangleMesh &trmesh = po.transformed_mesh();
|
||||
|
||||
if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) {
|
||||
// No support (thus no elevation) or zero elevation mode
|
||||
// we sometimes call it "builtin pad" is enabled so we will
|
||||
// get a sample from the bottom of the mesh and use it for pad
|
||||
// creation.
|
||||
sla::pad_blueprint(trmesh, bp, float(pad_h),
|
||||
float(po.m_config.layer_height.getFloat()),
|
||||
[this](){ throw_if_canceled(); });
|
||||
}
|
||||
|
||||
po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg);
|
||||
auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad);
|
||||
|
||||
if (!validate_pad(pad_mesh, pcfg))
|
||||
throw std::runtime_error(
|
||||
L("No pad can be generated for this model with the "
|
||||
"current configuration"));
|
||||
|
||||
} else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) {
|
||||
po.m_supportdata->support_tree_ptr->remove_pad();
|
||||
}
|
||||
|
||||
throw_if_canceled();
|
||||
report_status(-1, L("Visualizing supports"), SlicingStatus::RELOAD_SCENE);
|
||||
}
|
||||
|
||||
// Slicing the support geometries similarly to the model slicing procedure.
|
||||
// If the pad had been added previously (see step "base_pool" than it will
|
||||
// be part of the slices)
|
||||
void SLAPrint::Steps::slice_supports(SLAPrintObject &po) {
|
||||
auto& sd = po.m_supportdata;
|
||||
|
||||
if(sd) sd->support_slices.clear();
|
||||
|
||||
// Don't bother if no supports and no pad is present.
|
||||
if (!po.m_config.supports_enable.getBool() && !po.m_config.pad_enable.getBool())
|
||||
return;
|
||||
|
||||
if(sd && sd->support_tree_ptr) {
|
||||
auto heights = reserve_vector<float>(po.m_slice_index.size());
|
||||
|
||||
for(auto& rec : po.m_slice_index) heights.emplace_back(rec.slice_level());
|
||||
|
||||
sd->support_slices = sd->support_tree_ptr->slice(
|
||||
heights, float(po.config().slice_closing_radius.value));
|
||||
}
|
||||
|
||||
double doffs = m_print->m_printer_config.absolute_correction.getFloat();
|
||||
coord_t clpr_offs = scaled(doffs);
|
||||
|
||||
for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i) {
|
||||
// We apply the printer correction offset here.
|
||||
if (clpr_offs != 0)
|
||||
sd->support_slices[i] = offset_ex(sd->support_slices[i], float(clpr_offs));
|
||||
|
||||
po.m_slice_index[i].set_support_slice_idx(po, i);
|
||||
}
|
||||
|
||||
// Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update
|
||||
// status to the 3D preview to load the SLA slices.
|
||||
report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
|
||||
}
|
||||
|
||||
using ClipperPoint = ClipperLib::IntPoint;
|
||||
using ClipperPolygon = ClipperLib::Polygon; // see clipper_polygon.hpp in libnest2d
|
||||
using ClipperPolygons = std::vector<ClipperPolygon>;
|
||||
|
||||
static ClipperPolygons polyunion(const ClipperPolygons &subjects)
|
||||
{
|
||||
ClipperLib::Clipper clipper;
|
||||
|
||||
bool closed = true;
|
||||
|
||||
for(auto& path : subjects) {
|
||||
clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
|
||||
clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed);
|
||||
}
|
||||
|
||||
auto mode = ClipperLib::pftPositive;
|
||||
|
||||
return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode);
|
||||
}
|
||||
|
||||
static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPolygons& clips)
|
||||
{
|
||||
ClipperLib::Clipper clipper;
|
||||
|
||||
bool closed = true;
|
||||
|
||||
for(auto& path : subjects) {
|
||||
clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
|
||||
clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed);
|
||||
}
|
||||
|
||||
for(auto& path : clips) {
|
||||
clipper.AddPath(path.Contour, ClipperLib::ptClip, closed);
|
||||
clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed);
|
||||
}
|
||||
|
||||
auto mode = ClipperLib::pftPositive;
|
||||
|
||||
return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode);
|
||||
}
|
||||
|
||||
// get polygons for all instances in the object
|
||||
static ClipperPolygons get_all_polygons(
|
||||
const ExPolygons & input_polygons,
|
||||
const std::vector<SLAPrintObject::Instance> &instances,
|
||||
bool is_lefthanded)
|
||||
{
|
||||
namespace sl = libnest2d::sl;
|
||||
|
||||
ClipperPolygons polygons;
|
||||
polygons.reserve(input_polygons.size() * instances.size());
|
||||
|
||||
for (const ExPolygon& polygon : input_polygons) {
|
||||
if(polygon.contour.empty()) continue;
|
||||
|
||||
for (size_t i = 0; i < instances.size(); ++i)
|
||||
{
|
||||
ClipperPolygon poly;
|
||||
|
||||
// We need to reverse if is_lefthanded is true but
|
||||
bool needreverse = is_lefthanded;
|
||||
|
||||
// should be a move
|
||||
poly.Contour.reserve(polygon.contour.size() + 1);
|
||||
|
||||
auto& cntr = polygon.contour.points;
|
||||
if(needreverse)
|
||||
for(auto it = cntr.rbegin(); it != cntr.rend(); ++it)
|
||||
poly.Contour.emplace_back(it->x(), it->y());
|
||||
else
|
||||
for(auto& p : cntr)
|
||||
poly.Contour.emplace_back(p.x(), p.y());
|
||||
|
||||
for(auto& h : polygon.holes) {
|
||||
poly.Holes.emplace_back();
|
||||
auto& hole = poly.Holes.back();
|
||||
hole.reserve(h.points.size() + 1);
|
||||
|
||||
if(needreverse)
|
||||
for(auto it = h.points.rbegin(); it != h.points.rend(); ++it)
|
||||
hole.emplace_back(it->x(), it->y());
|
||||
else
|
||||
for(auto& p : h.points)
|
||||
hole.emplace_back(p.x(), p.y());
|
||||
}
|
||||
|
||||
if(is_lefthanded) {
|
||||
for(auto& p : poly.Contour) p.X = -p.X;
|
||||
for(auto& h : poly.Holes) for(auto& p : h) p.X = -p.X;
|
||||
}
|
||||
|
||||
sl::rotate(poly, double(instances[i].rotation));
|
||||
sl::translate(poly, ClipperPoint{instances[i].shift(X),
|
||||
instances[i].shift(Y)});
|
||||
|
||||
polygons.emplace_back(std::move(poly));
|
||||
}
|
||||
}
|
||||
|
||||
return polygons;
|
||||
}
|
||||
|
||||
void SLAPrint::Steps::initialize_printer_input()
|
||||
{
|
||||
auto &printer_input = m_print->m_printer_input;
|
||||
|
||||
// clear the rasterizer input
|
||||
printer_input.clear();
|
||||
|
||||
size_t mx = 0;
|
||||
for(SLAPrintObject * o : m_print->m_objects) {
|
||||
if(auto m = o->get_slice_index().size() > mx) mx = m;
|
||||
}
|
||||
|
||||
printer_input.reserve(mx);
|
||||
|
||||
auto eps = coord_t(SCALED_EPSILON);
|
||||
|
||||
for(SLAPrintObject * o : m_print->m_objects) {
|
||||
coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs;
|
||||
|
||||
for(const SliceRecord& slicerecord : o->get_slice_index()) {
|
||||
coord_t lvlid = slicerecord.print_level() - gndlvl;
|
||||
|
||||
// Neat trick to round the layer levels to the grid.
|
||||
lvlid = eps * (lvlid / eps);
|
||||
|
||||
auto it = std::lower_bound(printer_input.begin(),
|
||||
printer_input.end(),
|
||||
PrintLayer(lvlid));
|
||||
|
||||
if(it == printer_input.end() || it->level() != lvlid)
|
||||
it = printer_input.insert(it, PrintLayer(lvlid));
|
||||
|
||||
|
||||
it->add(slicerecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merging the slices from all the print objects into one slice grid and
|
||||
// calculating print statistics from the merge result.
|
||||
void SLAPrint::Steps::merge_slices_and_eval_stats() {
|
||||
|
||||
initialize_printer_input();
|
||||
|
||||
auto &print_statistics = m_print->m_print_statistics;
|
||||
auto &printer_config = m_print->m_printer_config;
|
||||
auto &material_config = m_print->m_material_config;
|
||||
auto &printer_input = m_print->m_printer_input;
|
||||
|
||||
print_statistics.clear();
|
||||
|
||||
// libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise
|
||||
auto areafn = [](const ClipperPolygon& poly) { return - libnest2d::sl::area(poly); };
|
||||
|
||||
const double area_fill = printer_config.area_fill.getFloat()*0.01;// 0.5 (50%);
|
||||
const double fast_tilt = printer_config.fast_tilt_time.getFloat();// 5.0;
|
||||
const double slow_tilt = printer_config.slow_tilt_time.getFloat();// 8.0;
|
||||
|
||||
const double init_exp_time = material_config.initial_exposure_time.getFloat();
|
||||
const double exp_time = material_config.exposure_time.getFloat();
|
||||
|
||||
const int fade_layers_cnt = m_print->m_default_object_config.faded_layers.getInt();// 10 // [3;20]
|
||||
|
||||
const auto width = scaled<double>(printer_config.display_width.getFloat());
|
||||
const auto height = scaled<double>(printer_config.display_height.getFloat());
|
||||
const double display_area = width*height;
|
||||
|
||||
double supports_volume(0.0);
|
||||
double models_volume(0.0);
|
||||
|
||||
double estim_time(0.0);
|
||||
|
||||
size_t slow_layers = 0;
|
||||
size_t fast_layers = 0;
|
||||
|
||||
const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1);
|
||||
double fade_layer_time = init_exp_time;
|
||||
|
||||
sla::ccr::SpinningMutex mutex;
|
||||
using Lock = std::lock_guard<sla::ccr::SpinningMutex>;
|
||||
|
||||
// Going to parallel:
|
||||
auto printlayerfn = [
|
||||
// functions and read only vars
|
||||
areafn, area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time,
|
||||
|
||||
// write vars
|
||||
&mutex, &models_volume, &supports_volume, &estim_time, &slow_layers,
|
||||
&fast_layers, &fade_layer_time](PrintLayer& layer, size_t sliced_layer_cnt)
|
||||
{
|
||||
// vector of slice record references
|
||||
auto& slicerecord_references = layer.slices();
|
||||
|
||||
if(slicerecord_references.empty()) return;
|
||||
|
||||
// Layer height should match for all object slices for a given level.
|
||||
const auto l_height = double(slicerecord_references.front().get().layer_height());
|
||||
|
||||
// Calculation of the consumed material
|
||||
|
||||
ClipperPolygons model_polygons;
|
||||
ClipperPolygons supports_polygons;
|
||||
|
||||
size_t c = std::accumulate(layer.slices().begin(),
|
||||
layer.slices().end(),
|
||||
size_t(0),
|
||||
[](size_t a, const SliceRecord &sr) {
|
||||
return a + sr.get_slice(soModel).size();
|
||||
});
|
||||
|
||||
model_polygons.reserve(c);
|
||||
|
||||
c = std::accumulate(layer.slices().begin(),
|
||||
layer.slices().end(),
|
||||
size_t(0),
|
||||
[](size_t a, const SliceRecord &sr) {
|
||||
return a + sr.get_slice(soModel).size();
|
||||
});
|
||||
|
||||
supports_polygons.reserve(c);
|
||||
|
||||
for(const SliceRecord& record : layer.slices()) {
|
||||
const SLAPrintObject *po = record.print_obj();
|
||||
|
||||
const ExPolygons &modelslices = record.get_slice(soModel);
|
||||
|
||||
bool is_lefth = record.print_obj()->is_left_handed();
|
||||
if (!modelslices.empty()) {
|
||||
ClipperPolygons v = get_all_polygons(modelslices, po->instances(), is_lefth);
|
||||
for(ClipperPolygon& p_tmp : v) model_polygons.emplace_back(std::move(p_tmp));
|
||||
}
|
||||
|
||||
const ExPolygons &supportslices = record.get_slice(soSupport);
|
||||
|
||||
if (!supportslices.empty()) {
|
||||
ClipperPolygons v = get_all_polygons(supportslices, po->instances(), is_lefth);
|
||||
for(ClipperPolygon& p_tmp : v) supports_polygons.emplace_back(std::move(p_tmp));
|
||||
}
|
||||
}
|
||||
|
||||
model_polygons = polyunion(model_polygons);
|
||||
double layer_model_area = 0;
|
||||
for (const ClipperPolygon& polygon : model_polygons)
|
||||
layer_model_area += areafn(polygon);
|
||||
|
||||
if (layer_model_area < 0 || layer_model_area > 0) {
|
||||
Lock lck(mutex); models_volume += layer_model_area * l_height;
|
||||
}
|
||||
|
||||
if(!supports_polygons.empty()) {
|
||||
if(model_polygons.empty()) supports_polygons = polyunion(supports_polygons);
|
||||
else supports_polygons = polydiff(supports_polygons, model_polygons);
|
||||
// allegedly, union of subject is done withing the diff according to the pftPositive polyFillType
|
||||
}
|
||||
|
||||
double layer_support_area = 0;
|
||||
for (const ClipperPolygon& polygon : supports_polygons)
|
||||
layer_support_area += areafn(polygon);
|
||||
|
||||
if (layer_support_area < 0 || layer_support_area > 0) {
|
||||
Lock lck(mutex); supports_volume += layer_support_area * l_height;
|
||||
}
|
||||
|
||||
// Here we can save the expensively calculated polygons for printing
|
||||
ClipperPolygons trslices;
|
||||
trslices.reserve(model_polygons.size() + supports_polygons.size());
|
||||
for(ClipperPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly));
|
||||
for(ClipperPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly));
|
||||
|
||||
layer.transformed_slices(polyunion(trslices));
|
||||
|
||||
// Calculation of the slow and fast layers to the future controlling those values on FW
|
||||
|
||||
const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill;
|
||||
const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt;
|
||||
|
||||
{ Lock lck(mutex);
|
||||
if (is_fast_layer)
|
||||
fast_layers++;
|
||||
else
|
||||
slow_layers++;
|
||||
|
||||
|
||||
// Calculation of the printing time
|
||||
|
||||
if (sliced_layer_cnt < 3)
|
||||
estim_time += init_exp_time;
|
||||
else if (fade_layer_time > exp_time)
|
||||
{
|
||||
fade_layer_time -= delta_fade_time;
|
||||
estim_time += fade_layer_time;
|
||||
}
|
||||
else
|
||||
estim_time += exp_time;
|
||||
|
||||
estim_time += tilt_time;
|
||||
}
|
||||
};
|
||||
|
||||
// sequential version for debugging:
|
||||
// for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i);
|
||||
sla::ccr::enumerate(printer_input.begin(), printer_input.end(), printlayerfn);
|
||||
|
||||
auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR;
|
||||
print_statistics.support_used_material = supports_volume * SCALING2;
|
||||
print_statistics.objects_used_material = models_volume * SCALING2;
|
||||
|
||||
// Estimated printing time
|
||||
// A layers count o the highest object
|
||||
if (printer_input.size() == 0)
|
||||
print_statistics.estimated_print_time = std::nan("");
|
||||
else
|
||||
print_statistics.estimated_print_time = estim_time;
|
||||
|
||||
print_statistics.fast_layers_count = fast_layers;
|
||||
print_statistics.slow_layers_count = slow_layers;
|
||||
|
||||
report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
|
||||
}
|
||||
|
||||
// Rasterizing the model objects, and their supports
|
||||
void SLAPrint::Steps::rasterize()
|
||||
{
|
||||
if(canceled()) return;
|
||||
|
||||
auto &print_statistics = m_print->m_print_statistics;
|
||||
auto &printer_input = m_print->m_printer_input;
|
||||
|
||||
// Set up the printer, allocate space for all the layers
|
||||
sla::RasterWriter &printer = m_print->init_printer();
|
||||
|
||||
auto lvlcnt = unsigned(printer_input.size());
|
||||
printer.layers(lvlcnt);
|
||||
|
||||
// coefficient to map the rasterization state (0-99) to the allocated
|
||||
// portion (slot) of the process state
|
||||
double sd = (100 - max_objstatus) / 100.0;
|
||||
|
||||
// slot is the portion of 100% that is realted to rasterization
|
||||
unsigned slot = PRINT_STEP_LEVELS[slapsRasterize];
|
||||
|
||||
// pst: previous state
|
||||
double pst = current_status();
|
||||
|
||||
double increment = (slot * sd) / printer_input.size();
|
||||
double dstatus = current_status();
|
||||
|
||||
sla::ccr::SpinningMutex slck;
|
||||
using Lock = std::lock_guard<sla::ccr::SpinningMutex>;
|
||||
|
||||
// procedure to process one height level. This will run in parallel
|
||||
auto lvlfn =
|
||||
[this, &slck, &printer, increment, &dstatus, &pst]
|
||||
(PrintLayer& printlayer, size_t idx)
|
||||
{
|
||||
if(canceled()) return;
|
||||
auto level_id = unsigned(idx);
|
||||
|
||||
// Switch to the appropriate layer in the printer
|
||||
printer.begin_layer(level_id);
|
||||
|
||||
for(const ClipperLib::Polygon& poly : printlayer.transformed_slices())
|
||||
printer.draw_polygon(poly, level_id);
|
||||
|
||||
// Finish the layer for later saving it.
|
||||
printer.finish_layer(level_id);
|
||||
|
||||
// Status indication guarded with the spinlock
|
||||
{
|
||||
Lock lck(slck);
|
||||
dstatus += increment;
|
||||
double st = std::round(dstatus);
|
||||
if(st > pst) {
|
||||
report_status(st, PRINT_STEP_LABELS(slapsRasterize));
|
||||
pst = st;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// last minute escape
|
||||
if(canceled()) return;
|
||||
|
||||
// Sequential version (for testing)
|
||||
// for(unsigned l = 0; l < lvlcnt; ++l) lvlfn(l);
|
||||
|
||||
// Print all the layers in parallel
|
||||
sla::ccr::enumerate(printer_input.begin(), printer_input.end(), lvlfn);
|
||||
|
||||
// Set statistics values to the printer
|
||||
sla::RasterWriter::PrintStatistics stats;
|
||||
stats.used_material = (print_statistics.objects_used_material +
|
||||
print_statistics.support_used_material) / 1000;
|
||||
|
||||
int num_fade = m_print->m_default_object_config.faded_layers.getInt();
|
||||
stats.num_fade = num_fade >= 0 ? size_t(num_fade) : size_t(0);
|
||||
stats.num_fast = print_statistics.fast_layers_count;
|
||||
stats.num_slow = print_statistics.slow_layers_count;
|
||||
stats.estimated_print_time_s = print_statistics.estimated_print_time;
|
||||
|
||||
printer.set_statistics(stats);
|
||||
}
|
||||
|
||||
std::string SLAPrint::Steps::label(SLAPrintObjectStep step)
|
||||
{
|
||||
return OBJ_STEP_LABELS(step);
|
||||
}
|
||||
|
||||
std::string SLAPrint::Steps::label(SLAPrintStep step)
|
||||
{
|
||||
return PRINT_STEP_LABELS(step);
|
||||
}
|
||||
|
||||
double SLAPrint::Steps::progressrange(SLAPrintObjectStep step) const
|
||||
{
|
||||
return OBJ_STEP_LEVELS[step] * objectstep_scale;
|
||||
}
|
||||
|
||||
double SLAPrint::Steps::progressrange(SLAPrintStep step) const
|
||||
{
|
||||
return PRINT_STEP_LEVELS[step] * (100 - max_objstatus) / 100.0;
|
||||
}
|
||||
|
||||
void SLAPrint::Steps::execute(SLAPrintObjectStep step, SLAPrintObject &obj)
|
||||
{
|
||||
switch(step) {
|
||||
case slaposHollowing: hollow_model(obj); break;
|
||||
case slaposObjectSlice: slice_model(obj); break;
|
||||
case slaposDrillHolesIfHollowed: break;
|
||||
case slaposSupportPoints: support_points(obj); break;
|
||||
case slaposSupportTree: support_tree(obj); break;
|
||||
case slaposPad: generate_pad(obj); break;
|
||||
case slaposSliceSupports: slice_supports(obj); break;
|
||||
case slaposCount: assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void SLAPrint::Steps::execute(SLAPrintStep step)
|
||||
{
|
||||
switch (step) {
|
||||
case slapsMergeSlicesAndEval: merge_slices_and_eval_stats(); break;
|
||||
case slapsRasterize: rasterize(); break;
|
||||
case slapsCount: assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
68
src/libslic3r/SLAPrintSteps.hpp
Normal file
68
src/libslic3r/SLAPrintSteps.hpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
#ifndef SLAPRINTSTEPS_HPP
|
||||
#define SLAPRINTSTEPS_HPP
|
||||
|
||||
#include <libslic3r/SLAPrint.hpp>
|
||||
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
#include <libslic3r/SLA/SupportTree.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class SLAPrint::Steps
|
||||
{
|
||||
private:
|
||||
SLAPrint *m_print = nullptr;
|
||||
|
||||
public:
|
||||
// where the per object operations start and end
|
||||
static const constexpr unsigned min_objstatus = 0;
|
||||
static const constexpr unsigned max_objstatus = 50;
|
||||
|
||||
private:
|
||||
const size_t objcount;
|
||||
|
||||
// shortcut to initial layer height
|
||||
const double ilhd;
|
||||
const float ilh;
|
||||
const coord_t ilhs;
|
||||
|
||||
// the coefficient that multiplies the per object status values which
|
||||
// are set up for <0, 100>. They need to be scaled into the whole process
|
||||
const double objectstep_scale;
|
||||
|
||||
template<class...Args> void report_status(Args&&...args)
|
||||
{
|
||||
m_print->m_report_status(*m_print, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
double current_status() const { return m_print->m_report_status.status(); }
|
||||
void throw_if_canceled() const { m_print->throw_if_canceled(); }
|
||||
bool canceled() const { return m_print->canceled(); }
|
||||
void initialize_printer_input();
|
||||
|
||||
public:
|
||||
Steps(SLAPrint *print);
|
||||
|
||||
void hollow_model(SLAPrintObject &po);
|
||||
void slice_model(SLAPrintObject& po);
|
||||
void support_points(SLAPrintObject& po);
|
||||
void support_tree(SLAPrintObject& po);
|
||||
void generate_pad(SLAPrintObject& po);
|
||||
void slice_supports(SLAPrintObject& po);
|
||||
|
||||
void merge_slices_and_eval_stats();
|
||||
void rasterize();
|
||||
|
||||
void execute(SLAPrintObjectStep step, SLAPrintObject &obj);
|
||||
void execute(SLAPrintStep step);
|
||||
|
||||
static std::string label(SLAPrintObjectStep step);
|
||||
static std::string label(SLAPrintStep step);
|
||||
|
||||
double progressrange(SLAPrintObjectStep step) const;
|
||||
double progressrange(SLAPrintStep step) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // SLAPRINTSTEPS_HPP
|
66
src/libslic3r/SimplifyMesh.cpp
Normal file
66
src/libslic3r/SimplifyMesh.cpp
Normal file
|
@ -0,0 +1,66 @@
|
|||
#include "SimplifyMesh.hpp"
|
||||
#include "SimplifyMeshImpl.hpp"
|
||||
|
||||
namespace SimplifyMesh {
|
||||
|
||||
template<> struct vertex_traits<stl_vertex> {
|
||||
using coord_type = float;
|
||||
using compute_type = double;
|
||||
|
||||
static inline float x(const stl_vertex &v) { return v.x(); }
|
||||
static inline float& x(stl_vertex &v) { return v.x(); }
|
||||
|
||||
static inline float y(const stl_vertex &v) { return v.y(); }
|
||||
static inline float& y(stl_vertex &v) { return v.y(); }
|
||||
|
||||
static inline float z(const stl_vertex &v) { return v.z(); }
|
||||
static inline float& z(stl_vertex &v) { return v.z(); }
|
||||
};
|
||||
|
||||
template<> struct mesh_traits<indexed_triangle_set> {
|
||||
using vertex_t = stl_vertex;
|
||||
static size_t face_count(const indexed_triangle_set &m)
|
||||
{
|
||||
return m.indices.size();
|
||||
}
|
||||
static size_t vertex_count(const indexed_triangle_set &m)
|
||||
{
|
||||
return m.vertices.size();
|
||||
}
|
||||
static vertex_t vertex(const indexed_triangle_set &m, size_t idx)
|
||||
{
|
||||
return m.vertices[idx];
|
||||
}
|
||||
static void vertex(indexed_triangle_set &m, size_t idx, const vertex_t &v)
|
||||
{
|
||||
m.vertices[idx] = v;
|
||||
}
|
||||
static Index3 triangle(const indexed_triangle_set &m, size_t idx)
|
||||
{
|
||||
std::array<size_t, 3> t;
|
||||
for (size_t i = 0; i < 3; ++i) t[i] = size_t(m.indices[idx](int(i)));
|
||||
return t;
|
||||
}
|
||||
static void triangle(indexed_triangle_set &m, size_t fidx, const Index3 &t)
|
||||
{
|
||||
auto &face = m.indices[fidx];
|
||||
face(0) = int(t[0]); face(1) = int(t[1]); face(2) = int(t[2]);
|
||||
}
|
||||
static void update(indexed_triangle_set &m, size_t vc, size_t fc)
|
||||
{
|
||||
m.vertices.resize(vc);
|
||||
m.indices.resize(fc);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace SimplifyMesh
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void simplify_mesh(indexed_triangle_set &m)
|
||||
{
|
||||
SimplifyMesh::implementation::SimplifiableMesh sm{&m};
|
||||
sm.simplify_mesh_lossless();
|
||||
}
|
||||
|
||||
}
|
25
src/libslic3r/SimplifyMesh.hpp
Normal file
25
src/libslic3r/SimplifyMesh.hpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#ifndef MESHSIMPLIFY_HPP
|
||||
#define MESHSIMPLIFY_HPP
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <libslic3r/TriangleMesh.hpp>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
void simplify_mesh(indexed_triangle_set &);
|
||||
|
||||
// TODO: (but this can be done with IGL as well)
|
||||
// void simplify_mesh(indexed_triangle_set &, int face_count, float agressiveness = 0.5f);
|
||||
|
||||
template<class...Args> void simplify_mesh(TriangleMesh &m, Args &&...a)
|
||||
{
|
||||
m.require_shared_vertices();
|
||||
simplify_mesh(m.its, std::forward<Args>(a)...);
|
||||
m = TriangleMesh{m.its};
|
||||
m.require_shared_vertices();
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // MESHSIMPLIFY_H
|
699
src/libslic3r/SimplifyMeshImpl.hpp
Normal file
699
src/libslic3r/SimplifyMeshImpl.hpp
Normal file
|
@ -0,0 +1,699 @@
|
|||
// ///////////////////////////////////////////
|
||||
//
|
||||
// Mesh Simplification Tutorial
|
||||
//
|
||||
// (C) by Sven Forstmann in 2014
|
||||
//
|
||||
// License : MIT
|
||||
// http://opensource.org/licenses/MIT
|
||||
//
|
||||
// https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification
|
||||
//
|
||||
// 5/2016: Chris Rorden created minimal version for OSX/Linux/Windows compile
|
||||
// https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification/
|
||||
//
|
||||
// libslic3r refactor by tamasmeszaros
|
||||
|
||||
#ifndef SIMPLIFYMESHIMPL_HPP
|
||||
#define SIMPLIFYMESHIMPL_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <array>
|
||||
#include <type_traits>
|
||||
#include <algorithm>
|
||||
|
||||
#ifndef NDEBUG
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
namespace SimplifyMesh {
|
||||
|
||||
using Bary = std::array<double, 3>;
|
||||
using Index3 = std::array<size_t, 3>;
|
||||
|
||||
template<class Vertex> struct vertex_traits {
|
||||
using coord_type = typename Vertex::coord_type;
|
||||
using compute_type = coord_type;
|
||||
|
||||
static coord_type x(const Vertex &v);
|
||||
static coord_type& x(Vertex &v);
|
||||
|
||||
static coord_type y(const Vertex &v);
|
||||
static coord_type& y(Vertex &v);
|
||||
|
||||
static coord_type z(const Vertex &v);
|
||||
static coord_type& z(Vertex &v);
|
||||
};
|
||||
|
||||
template<class Mesh> struct mesh_traits {
|
||||
using vertex_t = typename Mesh::vertex_t;
|
||||
|
||||
static size_t face_count(const Mesh &m);
|
||||
static size_t vertex_count(const Mesh &m);
|
||||
static vertex_t vertex(const Mesh &m, size_t vertex_idx);
|
||||
static void vertex(Mesh &m, size_t vertex_idx, const vertex_t &v);
|
||||
static Index3 triangle(const Mesh &m, size_t face_idx);
|
||||
static void triangle(Mesh &m, size_t face_idx, const Index3 &t);
|
||||
static void update(Mesh &m, size_t vertex_count, size_t face_count);
|
||||
};
|
||||
|
||||
namespace implementation {
|
||||
|
||||
// A shorter C++14 style form of the enable_if metafunction
|
||||
template<bool B, class T>
|
||||
using enable_if_t = typename std::enable_if<B, T>::type;
|
||||
|
||||
// Meta predicates for floating, 'scaled coord' and generic arithmetic types
|
||||
template<class T, class O = T>
|
||||
using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, O>;
|
||||
|
||||
template<class T, class O = T>
|
||||
using IntegerOnly = enable_if_t<std::is_integral<T>::value, O>;
|
||||
|
||||
template<class T, class O = T>
|
||||
using ArithmeticOnly = enable_if_t<std::is_arithmetic<T>::value, O>;
|
||||
|
||||
template< class T >
|
||||
struct remove_cvref {
|
||||
using type = typename std::remove_cv<
|
||||
typename std::remove_reference<T>::type>::type;
|
||||
};
|
||||
|
||||
template< class T >
|
||||
using remove_cvref_t = typename remove_cvref<T>::type;
|
||||
|
||||
struct DOut {
|
||||
#ifndef NDEBUG
|
||||
std::ostream& out = std::cout;
|
||||
#endif
|
||||
};
|
||||
|
||||
template<class T>
|
||||
inline DOut&& operator<<( DOut&& out, T&& d) {
|
||||
#ifndef NDEBUG
|
||||
out.out << d;
|
||||
#endif
|
||||
return std::move(out);
|
||||
}
|
||||
|
||||
inline DOut dout() { return DOut(); }
|
||||
|
||||
template<class T> FloatingOnly<T, bool> is_approx(T val, T ref) { return std::abs(val - ref) < 1e-8; }
|
||||
template<class T> IntegerOnly <T, bool> is_approx(T val, T ref) { val == ref; }
|
||||
|
||||
template<class T, size_t N = 10> class SymetricMatrix {
|
||||
public:
|
||||
|
||||
explicit SymetricMatrix(ArithmeticOnly<T> c = T()) { std::fill(m, m + N, c); }
|
||||
|
||||
SymetricMatrix(T m11, T m12, T m13, T m14,
|
||||
T m22, T m23, T m24,
|
||||
T m33, T m34,
|
||||
T m44)
|
||||
{
|
||||
m[0] = m11; m[1] = m12; m[2] = m13; m[3] = m14;
|
||||
m[4] = m22; m[5] = m23; m[6] = m24;
|
||||
m[7] = m33; m[8] = m34;
|
||||
m[9] = m44;
|
||||
}
|
||||
|
||||
// Make plane
|
||||
SymetricMatrix(T a, T b, T c, T d)
|
||||
{
|
||||
m[0] = a * a; m[1] = a * b; m[2] = a * c; m[3] = a * d;
|
||||
m[4] = b * b; m[5] = b * c; m[6] = b * d;
|
||||
m[7] = c * c; m[8] = c * d;
|
||||
m[9] = d * d;
|
||||
}
|
||||
|
||||
T operator[](int c) const { return m[c]; }
|
||||
|
||||
// Determinant
|
||||
T det(int a11, int a12, int a13,
|
||||
int a21, int a22, int a23,
|
||||
int a31, int a32, int a33)
|
||||
{
|
||||
T det = m[a11] * m[a22] * m[a33] + m[a13] * m[a21] * m[a32] +
|
||||
m[a12] * m[a23] * m[a31] - m[a13] * m[a22] * m[a31] -
|
||||
m[a11] * m[a23] * m[a32] - m[a12] * m[a21] * m[a33];
|
||||
|
||||
return det;
|
||||
}
|
||||
|
||||
const SymetricMatrix operator+(const SymetricMatrix& n) const
|
||||
{
|
||||
return SymetricMatrix(m[0] + n[0], m[1] + n[1], m[2] + n[2], m[3]+n[3],
|
||||
m[4] + n[4], m[5] + n[5], m[6] + n[6],
|
||||
m[7] + n[7], m[8] + n[8],
|
||||
m[9] + n[9]);
|
||||
}
|
||||
|
||||
SymetricMatrix& operator+=(const SymetricMatrix& n)
|
||||
{
|
||||
m[0]+=n[0]; m[1]+=n[1]; m[2]+=n[2]; m[3]+=n[3];
|
||||
m[4]+=n[4]; m[5]+=n[5]; m[6]+=n[6]; m[7]+=n[7];
|
||||
m[8]+=n[8]; m[9]+=n[9];
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
T m[N];
|
||||
};
|
||||
|
||||
template<class V> using TCoord = typename vertex_traits<remove_cvref_t<V>>::coord_type;
|
||||
template<class V> using TCompute = typename vertex_traits<remove_cvref_t<V>>::compute_type;
|
||||
template<class V> inline TCoord<V> x(const V &v) { return vertex_traits<remove_cvref_t<V>>::x(v); }
|
||||
template<class V> inline TCoord<V> y(const V &v) { return vertex_traits<remove_cvref_t<V>>::y(v); }
|
||||
template<class V> inline TCoord<V> z(const V &v) { return vertex_traits<remove_cvref_t<V>>::z(v); }
|
||||
template<class V> inline TCoord<V>& x(V &v) { return vertex_traits<remove_cvref_t<V>>::x(v); }
|
||||
template<class V> inline TCoord<V>& y(V &v) { return vertex_traits<remove_cvref_t<V>>::y(v); }
|
||||
template<class V> inline TCoord<V>& z(V &v) { return vertex_traits<remove_cvref_t<V>>::z(v); }
|
||||
template<class M> using TVertex = typename mesh_traits<remove_cvref_t<M>>::vertex_t;
|
||||
template<class Mesh> using TMeshCoord = TCoord<TVertex<Mesh>>;
|
||||
|
||||
template<class Vertex> TCompute<Vertex> dot(const Vertex &v1, const Vertex &v2)
|
||||
{
|
||||
return TCompute<Vertex>(x(v1)) * x(v2) +
|
||||
TCompute<Vertex>(y(v1)) * y(v2) +
|
||||
TCompute<Vertex>(z(v1)) * z(v2);
|
||||
}
|
||||
|
||||
template<class Vertex> Vertex cross(const Vertex &a, const Vertex &b)
|
||||
{
|
||||
return Vertex{y(a) * z(b) - z(a) * y(b),
|
||||
z(a) * x(b) - x(a) * z(b),
|
||||
x(a) * y(b) - y(a) * x(b)};
|
||||
}
|
||||
|
||||
template<class Vertex> TCompute<Vertex> lengthsq(const Vertex &v)
|
||||
{
|
||||
return TCompute<Vertex>(x(v)) * x(v) + TCompute<Vertex>(y(v)) * y(v) +
|
||||
TCompute<Vertex>(z(v)) * z(v);
|
||||
}
|
||||
|
||||
template<class Vertex> void normalize(Vertex &v)
|
||||
{
|
||||
double square = std::sqrt(lengthsq(v));
|
||||
x(v) /= square; y(v) /= square; z(v) /= square;
|
||||
}
|
||||
|
||||
using Bary = std::array<double, 3>;
|
||||
|
||||
template<class Vertex>
|
||||
Bary barycentric(const Vertex &p, const Vertex &a, const Vertex &b, const Vertex &c)
|
||||
{
|
||||
Vertex v0 = (b - a);
|
||||
Vertex v1 = (c - a);
|
||||
Vertex v2 = (p - a);
|
||||
|
||||
double d00 = dot(v0, v0);
|
||||
double d01 = dot(v0, v1);
|
||||
double d11 = dot(v1, v1);
|
||||
double d20 = dot(v2, v0);
|
||||
double d21 = dot(v2, v1);
|
||||
double denom = d00 * d11 - d01 * d01;
|
||||
double v = (d11 * d20 - d01 * d21) / denom;
|
||||
double w = (d00 * d21 - d01 * d20) / denom;
|
||||
double u = 1.0 - v - w;
|
||||
|
||||
return {u, v, w};
|
||||
}
|
||||
|
||||
template<class Mesh> class SimplifiableMesh {
|
||||
Mesh *m_mesh;
|
||||
|
||||
using Vertex = TVertex<Mesh>;
|
||||
using Coord = TMeshCoord<Mesh>;
|
||||
using HiPrecison = TCompute<TVertex<Mesh>>;
|
||||
using SymMat = SymetricMatrix<HiPrecison>;
|
||||
|
||||
struct FaceInfo {
|
||||
size_t idx;
|
||||
double err[4] = {0.};
|
||||
bool deleted = false, dirty = false;
|
||||
Vertex n;
|
||||
explicit FaceInfo(size_t id): idx(id) {}
|
||||
};
|
||||
|
||||
struct VertexInfo {
|
||||
size_t idx;
|
||||
size_t tstart = 0, tcount = 0;
|
||||
bool border = false;
|
||||
SymMat q;
|
||||
explicit VertexInfo(size_t id): idx(id) {}
|
||||
};
|
||||
|
||||
struct Ref { size_t face; size_t vertex; };
|
||||
|
||||
std::vector<Ref> m_refs;
|
||||
std::vector<FaceInfo> m_faceinfo;
|
||||
std::vector<VertexInfo> m_vertexinfo;
|
||||
|
||||
void compact_faces();
|
||||
void compact();
|
||||
|
||||
size_t mesh_vcount() const { return mesh_traits<Mesh>::vertex_count(*m_mesh); }
|
||||
size_t mesh_facecount() const { return mesh_traits<Mesh>::face_count(*m_mesh); }
|
||||
|
||||
size_t vcount() const { return m_vertexinfo.size(); }
|
||||
|
||||
inline Vertex read_vertex(size_t vi) const
|
||||
{
|
||||
return mesh_traits<Mesh>::vertex(*m_mesh, vi);
|
||||
}
|
||||
|
||||
inline Vertex read_vertex(const VertexInfo &vinf) const
|
||||
{
|
||||
return read_vertex(vinf.idx);
|
||||
}
|
||||
|
||||
inline void write_vertex(size_t idx, const Vertex &v) const
|
||||
{
|
||||
mesh_traits<Mesh>::vertex(*m_mesh, idx, v);
|
||||
}
|
||||
|
||||
inline void write_vertex(const VertexInfo &vinf, const Vertex &v) const
|
||||
{
|
||||
write_vertex(vinf.idx, v);
|
||||
}
|
||||
|
||||
inline Index3 read_triangle(size_t fi) const
|
||||
{
|
||||
return mesh_traits<Mesh>::triangle(*m_mesh, fi);
|
||||
}
|
||||
|
||||
inline Index3 read_triangle(const FaceInfo &finf) const
|
||||
{
|
||||
return read_triangle(finf.idx);
|
||||
}
|
||||
|
||||
inline void write_triangle(size_t idx, const Index3 &t)
|
||||
{
|
||||
return mesh_traits<Mesh>::triangle(*m_mesh, idx, t);
|
||||
}
|
||||
|
||||
inline void write_triangle(const FaceInfo &finf, const Index3 &t)
|
||||
{
|
||||
return write_triangle(finf.idx, t);
|
||||
}
|
||||
|
||||
inline std::array<Vertex, 3> triangle_vertices(const Index3 &f) const
|
||||
{
|
||||
std::array<Vertex, 3> p;
|
||||
for (size_t i = 0; i < 3; ++i) p[i] = read_vertex(f[i]);
|
||||
return p;
|
||||
}
|
||||
|
||||
// Error between vertex and Quadric
|
||||
static double vertex_error(const SymMat &q, const Vertex &v)
|
||||
{
|
||||
Coord _x = x(v) , _y = y(v), _z = z(v);
|
||||
return q[0] * _x * _x + 2 * q[1] * _x * _y + 2 * q[2] * _x * _z +
|
||||
2 * q[3] * _x + q[4] * _y * _y + 2 * q[5] * _y * _z +
|
||||
2 * q[6] * _y + q[7] * _z * _z + 2 * q[8] * _z + q[9];
|
||||
}
|
||||
|
||||
// Error for one edge
|
||||
double calculate_error(size_t id_v1, size_t id_v2, Vertex &p_result);
|
||||
|
||||
void calculate_error(FaceInfo &fi)
|
||||
{
|
||||
Vertex p;
|
||||
Index3 t = read_triangle(fi);
|
||||
for (size_t j = 0; j < 3; ++j)
|
||||
fi.err[j] = calculate_error(t[j], t[(j + 1) % 3], p);
|
||||
|
||||
fi.err[3] = std::min(fi.err[0], std::min(fi.err[1], fi.err[2]));
|
||||
}
|
||||
|
||||
void update_mesh(int iteration);
|
||||
|
||||
// Update triangle connections and edge error after a edge is collapsed
|
||||
void update_triangles(size_t i, VertexInfo &vi, std::vector<bool> &deleted, int &deleted_triangles);
|
||||
|
||||
// Check if a triangle flips when this edge is removed
|
||||
bool flipped(const Vertex &p, size_t i0, size_t i1, VertexInfo &v0, VertexInfo &v1, std::vector<bool> &deleted);
|
||||
|
||||
public:
|
||||
|
||||
explicit SimplifiableMesh(Mesh *m) : m_mesh{m}
|
||||
{
|
||||
static_assert(
|
||||
std::is_arithmetic<Coord>::value,
|
||||
"Coordinate type of mesh has to be an arithmetic type!");
|
||||
|
||||
m_faceinfo.reserve(mesh_traits<Mesh>::face_count(*m));
|
||||
m_vertexinfo.reserve(mesh_traits<Mesh>::vertex_count(*m));
|
||||
for (size_t i = 0; i < mesh_facecount(); ++i) m_faceinfo.emplace_back(i);
|
||||
for (size_t i = 0; i < mesh_vcount(); ++i) m_vertexinfo.emplace_back(i);
|
||||
|
||||
}
|
||||
|
||||
void simplify_mesh_lossless();
|
||||
};
|
||||
|
||||
|
||||
template<class Mesh> void SimplifiableMesh<Mesh>::compact_faces()
|
||||
{
|
||||
auto it = std::remove_if(m_faceinfo.begin(), m_faceinfo.end(),
|
||||
[](const FaceInfo &inf) { return inf.deleted; });
|
||||
|
||||
m_faceinfo.erase(it, m_faceinfo.end());
|
||||
}
|
||||
|
||||
template<class M> void SimplifiableMesh<M>::compact()
|
||||
{
|
||||
for (auto &vi : m_vertexinfo) vi.tcount = 0;
|
||||
|
||||
compact_faces();
|
||||
|
||||
for (FaceInfo &fi : m_faceinfo)
|
||||
for (size_t vidx : read_triangle(fi)) m_vertexinfo[vidx].tcount = 1;
|
||||
|
||||
size_t dst = 0;
|
||||
for (VertexInfo &vi : m_vertexinfo) {
|
||||
if (vi.tcount) {
|
||||
vi.tstart = dst;
|
||||
write_vertex(dst++, read_vertex(vi));
|
||||
}
|
||||
}
|
||||
|
||||
size_t vertex_count = dst;
|
||||
|
||||
dst = 0;
|
||||
for (const FaceInfo &fi : m_faceinfo) {
|
||||
Index3 t = read_triangle(fi);
|
||||
for (size_t &idx : t) idx = m_vertexinfo[idx].tstart;
|
||||
write_triangle(dst++, t);
|
||||
}
|
||||
|
||||
mesh_traits<M>::update(*m_mesh, vertex_count, m_faceinfo.size());
|
||||
}
|
||||
|
||||
template<class Mesh>
|
||||
double SimplifiableMesh<Mesh>::calculate_error(size_t id_v1, size_t id_v2, Vertex &p_result)
|
||||
{
|
||||
// compute interpolated vertex
|
||||
|
||||
SymMat q = m_vertexinfo[id_v1].q + m_vertexinfo[id_v2].q;
|
||||
|
||||
bool border = m_vertexinfo[id_v1].border & m_vertexinfo[id_v2].border;
|
||||
double error = 0;
|
||||
HiPrecison det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7);
|
||||
|
||||
if (!is_approx(det, HiPrecison(0)) && !border)
|
||||
{
|
||||
// q_delta is invertible
|
||||
x(p_result) = Coord(-1) / det * q.det(1, 2, 3, 4, 5, 6, 5, 7, 8); // vx = A41/det(q_delta)
|
||||
y(p_result) = Coord( 1) / det * q.det(0, 2, 3, 1, 5, 6, 2, 7, 8); // vy = A42/det(q_delta)
|
||||
z(p_result) = Coord(-1) / det * q.det(0, 1, 3, 1, 4, 6, 2, 5, 8); // vz = A43/det(q_delta)
|
||||
|
||||
error = vertex_error(q, p_result);
|
||||
} else {
|
||||
// det = 0 -> try to find best result
|
||||
Vertex p1 = read_vertex(id_v1);
|
||||
Vertex p2 = read_vertex(id_v2);
|
||||
Vertex p3 = (p1 + p2) / 2;
|
||||
double error1 = vertex_error(q, p1);
|
||||
double error2 = vertex_error(q, p2);
|
||||
double error3 = vertex_error(q, p3);
|
||||
error = std::min(error1, std::min(error2, error3));
|
||||
|
||||
if (is_approx(error1, error)) p_result = p1;
|
||||
if (is_approx(error2, error)) p_result = p2;
|
||||
if (is_approx(error3, error)) p_result = p3;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
template<class Mesh> void SimplifiableMesh<Mesh>::update_mesh(int iteration)
|
||||
{
|
||||
if (iteration > 0) compact_faces();
|
||||
|
||||
assert(mesh_vcount() == m_vertexinfo.size());
|
||||
|
||||
//
|
||||
// Init Quadrics by Plane & Edge Errors
|
||||
//
|
||||
// required at the beginning ( iteration == 0 )
|
||||
// recomputing during the simplification is not required,
|
||||
// but mostly improves the result for closed meshes
|
||||
//
|
||||
if (iteration == 0) {
|
||||
|
||||
for (VertexInfo &vinf : m_vertexinfo) vinf.q = SymMat{};
|
||||
for (FaceInfo &finf : m_faceinfo) {
|
||||
Index3 t = read_triangle(finf);
|
||||
std::array<Vertex, 3> p = triangle_vertices(t);
|
||||
Vertex n = cross(Vertex(p[1] - p[0]), Vertex(p[2] - p[0]));
|
||||
normalize(n);
|
||||
finf.n = n;
|
||||
|
||||
for (size_t fi : t)
|
||||
m_vertexinfo[fi].q += SymMat(x(n), y(n), z(n), -dot(n, p[0]));
|
||||
|
||||
calculate_error(finf);
|
||||
}
|
||||
}
|
||||
|
||||
// Init Reference ID list
|
||||
for (VertexInfo &vi : m_vertexinfo) { vi.tstart = 0; vi.tcount = 0; }
|
||||
|
||||
for (FaceInfo &fi : m_faceinfo)
|
||||
for (size_t vidx : read_triangle(fi))
|
||||
m_vertexinfo[vidx].tcount++;
|
||||
|
||||
size_t tstart = 0;
|
||||
for (VertexInfo &vi : m_vertexinfo) {
|
||||
vi.tstart = tstart;
|
||||
tstart += vi.tcount;
|
||||
vi.tcount = 0;
|
||||
}
|
||||
|
||||
// Write References
|
||||
m_refs.resize(m_faceinfo.size() * 3);
|
||||
for (size_t i = 0; i < m_faceinfo.size(); ++i) {
|
||||
const FaceInfo &fi = m_faceinfo[i];
|
||||
Index3 t = read_triangle(fi);
|
||||
for (size_t j = 0; j < 3; ++j) {
|
||||
VertexInfo &vi = m_vertexinfo[t[j]];
|
||||
|
||||
assert(vi.tstart + vi.tcount < m_refs.size());
|
||||
|
||||
Ref &ref = m_refs[vi.tstart + vi.tcount];
|
||||
ref.face = i;
|
||||
ref.vertex = j;
|
||||
vi.tcount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Identify boundary : vertices[].border=0,1
|
||||
if (iteration == 0) {
|
||||
for (VertexInfo &vi: m_vertexinfo) vi.border = false;
|
||||
|
||||
std::vector<size_t> vcount, vids;
|
||||
|
||||
for (VertexInfo &vi: m_vertexinfo) {
|
||||
vcount.clear();
|
||||
vids.clear();
|
||||
|
||||
for(size_t j = 0; j < vi.tcount; ++j) {
|
||||
assert(vi.tstart + j < m_refs.size());
|
||||
FaceInfo &fi = m_faceinfo[m_refs[vi.tstart + j].face];
|
||||
Index3 t = read_triangle(fi);
|
||||
|
||||
for (size_t fid : t) {
|
||||
size_t ofs=0;
|
||||
while (ofs < vcount.size())
|
||||
{
|
||||
if (vids[ofs] == fid) break;
|
||||
ofs++;
|
||||
}
|
||||
if (ofs == vcount.size())
|
||||
{
|
||||
vcount.emplace_back(1);
|
||||
vids.emplace_back(fid);
|
||||
}
|
||||
else
|
||||
vcount[ofs]++;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < vcount.size(); ++j)
|
||||
if(vcount[j] == 1) m_vertexinfo[vids[j]].border = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<class Mesh>
|
||||
void SimplifiableMesh<Mesh>::update_triangles(size_t i0,
|
||||
VertexInfo & vi,
|
||||
std::vector<bool> &deleted,
|
||||
int &deleted_triangles)
|
||||
{
|
||||
Vertex p;
|
||||
for (size_t k = 0; k < vi.tcount; ++k) {
|
||||
assert(vi.tstart + k < m_refs.size());
|
||||
|
||||
Ref &r = m_refs[vi.tstart + k];
|
||||
FaceInfo &fi = m_faceinfo[r.face];
|
||||
|
||||
if (fi.deleted) continue;
|
||||
|
||||
if (deleted[k]) {
|
||||
fi.deleted = true;
|
||||
deleted_triangles++;
|
||||
continue;
|
||||
}
|
||||
|
||||
Index3 t = read_triangle(fi);
|
||||
t[r.vertex] = i0;
|
||||
write_triangle(fi, t);
|
||||
|
||||
fi.dirty = true;
|
||||
fi.err[0] = calculate_error(t[0], t[1], p);
|
||||
fi.err[1] = calculate_error(t[1], t[2], p);
|
||||
fi.err[2] = calculate_error(t[2], t[0], p);
|
||||
fi.err[3] = std::min(fi.err[0], std::min(fi.err[1], fi.err[2]));
|
||||
m_refs.emplace_back(r);
|
||||
}
|
||||
}
|
||||
|
||||
template<class Mesh>
|
||||
bool SimplifiableMesh<Mesh>::flipped(const Vertex & p,
|
||||
size_t /*i0*/,
|
||||
size_t i1,
|
||||
VertexInfo & v0,
|
||||
VertexInfo & /*v1*/,
|
||||
std::vector<bool> &deleted)
|
||||
{
|
||||
for (size_t k = 0; k < v0.tcount; ++k) {
|
||||
size_t ridx = v0.tstart + k;
|
||||
assert(ridx < m_refs.size());
|
||||
|
||||
FaceInfo &fi = m_faceinfo[m_refs[ridx].face];
|
||||
if (fi.deleted) continue;
|
||||
|
||||
Index3 t = read_triangle(fi);
|
||||
int s = m_refs[ridx].vertex;
|
||||
size_t id1 = t[(s+1) % 3];
|
||||
size_t id2 = t[(s+2) % 3];
|
||||
|
||||
if(id1 == i1 || id2 == i1) // delete ?
|
||||
{
|
||||
deleted[k] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
Vertex d1 = read_vertex(id1) - p;
|
||||
normalize(d1);
|
||||
Vertex d2 = read_vertex(id2) - p;
|
||||
normalize(d2);
|
||||
|
||||
if (std::abs(dot(d1, d2)) > 0.999) return true;
|
||||
|
||||
Vertex n = cross(d1, d2);
|
||||
normalize(n);
|
||||
|
||||
deleted[k] = false;
|
||||
if (dot(n, fi.n) < 0.2) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class Mesh>
|
||||
void SimplifiableMesh<Mesh>::simplify_mesh_lossless()
|
||||
{
|
||||
// init
|
||||
for (FaceInfo &fi : m_faceinfo) fi.deleted = false;
|
||||
|
||||
// main iteration loop
|
||||
int deleted_triangles=0;
|
||||
std::vector<bool> deleted0, deleted1;
|
||||
|
||||
for (int iteration = 0; iteration < 9999; iteration ++) {
|
||||
// update mesh constantly
|
||||
update_mesh(iteration);
|
||||
|
||||
// clear dirty flag
|
||||
for (FaceInfo &fi : m_faceinfo) fi.dirty = false;
|
||||
|
||||
//
|
||||
// All triangles with edges below the threshold will be removed
|
||||
//
|
||||
// The following numbers works well for most models.
|
||||
// If it does not, try to adjust the 3 parameters
|
||||
//
|
||||
double threshold = std::numeric_limits<double>::epsilon(); //1.0E-3 EPS; // Really? (tm)
|
||||
|
||||
dout() << "lossless iteration " << iteration << "\n";
|
||||
|
||||
for (FaceInfo &fi : m_faceinfo) {
|
||||
if (fi.err[3] > threshold || fi.deleted || fi.dirty) continue;
|
||||
|
||||
for (size_t j = 0; j < 3; ++j) {
|
||||
if (fi.err[j] > threshold) continue;
|
||||
|
||||
Index3 t = read_triangle(fi);
|
||||
size_t i0 = t[j];
|
||||
VertexInfo &v0 = m_vertexinfo[i0];
|
||||
|
||||
size_t i1 = t[(j + 1) % 3];
|
||||
VertexInfo &v1 = m_vertexinfo[i1];
|
||||
|
||||
// Border check
|
||||
if(v0.border != v1.border) continue;
|
||||
|
||||
// Compute vertex to collapse to
|
||||
Vertex p;
|
||||
calculate_error(i0, i1, p);
|
||||
|
||||
deleted0.resize(v0.tcount); // normals temporarily
|
||||
deleted1.resize(v1.tcount); // normals temporarily
|
||||
|
||||
// don't remove if flipped
|
||||
if (flipped(p, i0, i1, v0, v1, deleted0)) continue;
|
||||
if (flipped(p, i1, i0, v1, v0, deleted1)) continue;
|
||||
|
||||
// not flipped, so remove edge
|
||||
write_vertex(v0, p);
|
||||
v0.q = v1.q + v0.q;
|
||||
size_t tstart = m_refs.size();
|
||||
|
||||
update_triangles(i0, v0, deleted0, deleted_triangles);
|
||||
update_triangles(i0, v1, deleted1, deleted_triangles);
|
||||
|
||||
assert(m_refs.size() >= tstart);
|
||||
|
||||
size_t tcount = m_refs.size() - tstart;
|
||||
|
||||
if(tcount <= v0.tcount)
|
||||
{
|
||||
// save ram
|
||||
if (tcount) {
|
||||
auto from = m_refs.begin() + tstart, to = from + tcount;
|
||||
std::copy(from, to, m_refs.begin() + v0.tstart);
|
||||
}
|
||||
}
|
||||
else
|
||||
// append
|
||||
v0.tstart = tstart;
|
||||
|
||||
v0.tcount = tcount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (deleted_triangles <= 0) break;
|
||||
deleted_triangles = 0;
|
||||
}
|
||||
|
||||
compact();
|
||||
}
|
||||
|
||||
} // namespace implementation
|
||||
} // namespace SimplifyMesh
|
||||
|
||||
#endif // SIMPLIFYMESHIMPL_HPP
|
|
@ -70,6 +70,34 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd>& f
|
|||
stl_get_size(&stl);
|
||||
}
|
||||
|
||||
TriangleMesh::TriangleMesh(const indexed_triangle_set &M)
|
||||
{
|
||||
stl.stats.type = inmemory;
|
||||
|
||||
// count facets and allocate memory
|
||||
stl.stats.number_of_facets = uint32_t(M.indices.size());
|
||||
stl.stats.original_num_facets = int(stl.stats.number_of_facets);
|
||||
stl_allocate(&stl);
|
||||
|
||||
for (uint32_t i = 0; i < stl.stats.number_of_facets; ++ i) {
|
||||
stl_facet facet;
|
||||
facet.vertex[0] = M.vertices[size_t(M.indices[i](0))];
|
||||
facet.vertex[1] = M.vertices[size_t(M.indices[i](1))];
|
||||
facet.vertex[2] = M.vertices[size_t(M.indices[i](2))];
|
||||
facet.extra[0] = 0;
|
||||
facet.extra[1] = 0;
|
||||
|
||||
stl_normal normal;
|
||||
stl_calculate_normal(normal, &facet);
|
||||
stl_normalize_vector(normal);
|
||||
facet.normal = normal;
|
||||
|
||||
stl.facet_start[i] = facet;
|
||||
}
|
||||
|
||||
stl_get_size(&stl);
|
||||
}
|
||||
|
||||
// #define SLIC3R_TRACE_REPAIR
|
||||
|
||||
void TriangleMesh::repair(bool update_shared_vertices)
|
||||
|
|
|
@ -23,6 +23,7 @@ class TriangleMesh
|
|||
public:
|
||||
TriangleMesh() : repaired(false) {}
|
||||
TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd> &facets);
|
||||
explicit TriangleMesh(const indexed_triangle_set &M);
|
||||
void clear() { this->stl.clear(); this->its.clear(); this->repaired = false; }
|
||||
bool ReadSTLFile(const char* input_file) { return stl_open(&stl, input_file); }
|
||||
bool write_ascii(const char* output_file) { return stl_write_ascii(&this->stl, output_file, ""); }
|
||||
|
|
|
@ -230,6 +230,22 @@ static inline bool is_approx(Number value, Number test_value)
|
|||
return std::fabs(double(value) - double(test_value)) < double(EPSILON);
|
||||
}
|
||||
|
||||
template<class...Args>
|
||||
std::string string_printf(const char *const fmt, Args &&...args)
|
||||
{
|
||||
static const size_t INITIAL_LEN = 1024;
|
||||
std::vector<char> buffer(INITIAL_LEN, '\0');
|
||||
|
||||
int bufflen = snprintf(buffer.data(), INITIAL_LEN - 1, fmt, std::forward<Args>(args)...);
|
||||
|
||||
if (bufflen >= int(INITIAL_LEN)) {
|
||||
buffer.resize(size_t(bufflen) + 1);
|
||||
snprintf(buffer.data(), buffer.size(), fmt, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
return std::string(buffer.begin(), buffer.begin() + bufflen);
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif
|
||||
|
|
|
@ -47,6 +47,8 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/Gizmos/GLGizmoFlatten.hpp
|
||||
GUI/Gizmos/GLGizmoCut.cpp
|
||||
GUI/Gizmos/GLGizmoCut.hpp
|
||||
GUI/Gizmos/GLGizmoHollow.cpp
|
||||
GUI/Gizmos/GLGizmoHollow.hpp
|
||||
GUI/GLSelectionRectangle.cpp
|
||||
GUI/GLSelectionRectangle.hpp
|
||||
GUI/GLTexture.hpp
|
||||
|
@ -140,6 +142,7 @@ set(SLIC3R_GUI_SOURCES
|
|||
GUI/ProgressStatusBar.cpp
|
||||
GUI/PrintHostDialogs.cpp
|
||||
GUI/PrintHostDialogs.hpp
|
||||
GUI/Job.hpp
|
||||
GUI/Mouse3DController.cpp
|
||||
GUI/Mouse3DController.hpp
|
||||
GUI/DoubleSlider.cpp
|
||||
|
|
|
@ -1438,14 +1438,14 @@ int GLCanvas3D::check_volumes_outside_state() const
|
|||
|
||||
void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo, int instance_idx)
|
||||
{
|
||||
m_render_sla_auxiliaries = visible;
|
||||
|
||||
for (GLVolume* vol : m_volumes.volumes) {
|
||||
if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
|
||||
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)
|
||||
&& vol->composite_id.volume_id < 0)
|
||||
vol->is_active = visible;
|
||||
}
|
||||
|
||||
m_render_sla_auxiliaries = visible;
|
||||
}
|
||||
|
||||
void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx)
|
||||
|
@ -6061,6 +6061,8 @@ void GLCanvas3D::_load_sla_shells()
|
|||
unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size();
|
||||
for (const SLAPrintObject::Instance& instance : obj->instances()) {
|
||||
add_volume(*obj, 0, instance, obj->transformed_mesh(), GLVolume::MODEL_COLOR[0], true);
|
||||
if (! obj->hollowed_interior_mesh().empty())
|
||||
add_volume(*obj, -int(slaposHollowing), instance, obj->hollowed_interior_mesh(), GLVolume::MODEL_COLOR[0], false);
|
||||
// Set the extruder_id and volume_id to achieve the same color as in the 3D scene when
|
||||
// through the update_volumes_colors_by_extruder() call.
|
||||
m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id();
|
||||
|
|
|
@ -499,6 +499,7 @@ public:
|
|||
void set_color_by(const std::string& value);
|
||||
|
||||
const Camera& get_camera() const { return m_camera; }
|
||||
const Shader& get_shader() const { return m_shader; }
|
||||
Camera& get_camera() { return m_camera; }
|
||||
|
||||
BoundingBoxf3 volumes_bounding_box() const;
|
||||
|
|
|
@ -103,6 +103,7 @@ ObjectList::ObjectList(wxWindow* parent) :
|
|||
// ptSLA
|
||||
CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(nullptr, "support"/*"sla_supports"*/);
|
||||
CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(nullptr, "pad");
|
||||
CATEGORY_ICON[L("Hollowing")] = create_scaled_bitmap(nullptr, "hollowing");
|
||||
}
|
||||
|
||||
// create control
|
||||
|
|
|
@ -135,7 +135,7 @@ void GLGizmoBase::Grabber::render_face(float half_size) const
|
|||
}
|
||||
|
||||
|
||||
GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
|
||||
GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, CommonGizmosData* common_data_ptr)
|
||||
: m_parent(parent)
|
||||
, m_group_id(-1)
|
||||
, m_state(Off)
|
||||
|
@ -146,6 +146,7 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, u
|
|||
, m_dragging(false)
|
||||
, m_imgui(wxGetApp().imgui())
|
||||
, m_first_input_window_render(true)
|
||||
, m_c(common_data_ptr)
|
||||
{
|
||||
::memcpy((void*)m_base_color, (const void*)DEFAULT_BASE_COLOR, 4 * sizeof(float));
|
||||
::memcpy((void*)m_drag_color, (const void*)DEFAULT_DRAG_COLOR, 4 * sizeof(float));
|
||||
|
|
|
@ -30,7 +30,7 @@ static const float CONSTRAINED_COLOR[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
|
|||
|
||||
|
||||
class ImGuiWrapper;
|
||||
|
||||
class CommonGizmosData;
|
||||
|
||||
class GLGizmoBase
|
||||
{
|
||||
|
@ -99,9 +99,13 @@ protected:
|
|||
mutable std::vector<Grabber> m_grabbers;
|
||||
ImGuiWrapper* m_imgui;
|
||||
bool m_first_input_window_render;
|
||||
CommonGizmosData* m_c = nullptr;
|
||||
|
||||
public:
|
||||
GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
GLGizmoBase(GLCanvas3D& parent,
|
||||
const std::string& icon_filename,
|
||||
unsigned int sprite_id,
|
||||
CommonGizmosData* common_data = nullptr);
|
||||
virtual ~GLGizmoBase() {}
|
||||
|
||||
bool init() { return on_init(); }
|
||||
|
@ -179,6 +183,34 @@ protected:
|
|||
// were not interpolated by alpha blending or multi sampling.
|
||||
extern unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue);
|
||||
|
||||
class MeshRaycaster;
|
||||
class MeshClipper;
|
||||
|
||||
class CommonGizmosData {
|
||||
public:
|
||||
const TriangleMesh* mesh() const {
|
||||
return (! m_mesh ? nullptr : (m_cavity_mesh ? m_cavity_mesh.get() : m_mesh));
|
||||
}
|
||||
|
||||
|
||||
|
||||
ModelObject* m_model_object = nullptr;
|
||||
const TriangleMesh* m_mesh;
|
||||
std::unique_ptr<MeshRaycaster> m_mesh_raycaster;
|
||||
std::unique_ptr<MeshClipper> m_object_clipper;
|
||||
std::unique_ptr<MeshClipper> m_supports_clipper;
|
||||
|
||||
std::unique_ptr<TriangleMesh> m_cavity_mesh;
|
||||
std::unique_ptr<GLVolume> m_volume_with_cavity;
|
||||
|
||||
int m_active_instance = -1;
|
||||
float m_active_instance_bb_radius = 0;
|
||||
ObjectID m_model_object_id = 0;
|
||||
int m_print_object_idx = -1;
|
||||
int m_print_objects_count = -1;
|
||||
int m_old_timestamp = -1;
|
||||
};
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
|
|
1140
src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp
Normal file
1140
src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp
Normal file
File diff suppressed because it is too large
Load diff
131
src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp
Normal file
131
src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp
Normal file
|
@ -0,0 +1,131 @@
|
|||
#ifndef slic3r_GLGizmoHollow_hpp_
|
||||
#define slic3r_GLGizmoHollow_hpp_
|
||||
|
||||
#include "GLGizmoBase.hpp"
|
||||
#include "slic3r/GUI/GLSelectionRectangle.hpp"
|
||||
|
||||
#include <libslic3r/SLA/Hollowing.hpp>
|
||||
#include <wx/dialog.h>
|
||||
|
||||
#include <cereal/types/vector.hpp>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class ClippingPlane;
|
||||
class MeshClipper;
|
||||
class MeshRaycaster;
|
||||
enum class SLAGizmoEventType : unsigned char;
|
||||
|
||||
class GLGizmoHollow : public GLGizmoBase
|
||||
{
|
||||
private:
|
||||
mutable double m_z_shift = 0.;
|
||||
bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal);
|
||||
|
||||
const float HoleStickOutLength = 1.f;
|
||||
|
||||
GLUquadricObj* m_quadric;
|
||||
|
||||
|
||||
public:
|
||||
GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, CommonGizmosData* cd);
|
||||
~GLGizmoHollow() override;
|
||||
void set_sla_support_data(ModelObject* model_object, const Selection& selection);
|
||||
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||
void delete_selected_points();
|
||||
ClippingPlane get_sla_clipping_plane() const;
|
||||
|
||||
|
||||
std::pair<const TriangleMesh *, sla::HollowingConfig> get_hollowing_parameters() const;
|
||||
void update_mesh_raycaster(std::unique_ptr<MeshRaycaster> &&rc);
|
||||
void update_hollowed_mesh(std::unique_ptr<TriangleMesh> &&mesh);
|
||||
|
||||
bool is_selection_rectangle_dragging() const { return m_selection_rectangle.is_dragging(); }
|
||||
|
||||
private:
|
||||
bool on_init() override;
|
||||
void on_update(const UpdateData& data) override;
|
||||
void on_render() const override;
|
||||
void on_render_for_picking() const override;
|
||||
|
||||
void render_points(const Selection& selection, bool picking = false) const;
|
||||
void render_clipping_plane(const Selection& selection) const;
|
||||
void render_hollowed_mesh() const;
|
||||
bool is_mesh_update_necessary() const;
|
||||
void update_mesh();
|
||||
void hollow_mesh();
|
||||
bool unsaved_changes() const;
|
||||
|
||||
bool m_show_supports = true;
|
||||
float m_new_hole_radius = 2.f; // Size of a new hole.
|
||||
float m_new_hole_height = 5.f;
|
||||
mutable std::vector<bool> m_selected; // which holes are currently selected
|
||||
|
||||
bool m_enable_hollowing = true;
|
||||
|
||||
// Stashes to keep data for undo redo. Is taken after the editing
|
||||
// is done, the data are updated continuously.
|
||||
float m_offset_stash = 3.0f;
|
||||
float m_quality_stash = 0.5f;
|
||||
float m_closing_d_stash = 2.f;
|
||||
Vec3f m_hole_before_drag = Vec3f::Zero();
|
||||
|
||||
|
||||
sla::DrainHoles m_holes_stash;
|
||||
|
||||
|
||||
|
||||
float m_clipping_plane_distance = 0.f;
|
||||
std::unique_ptr<ClippingPlane> m_clipping_plane;
|
||||
|
||||
// This map holds all translated description texts, so they can be easily referenced during layout calculations
|
||||
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
|
||||
std::map<std::string, wxString> m_desc;
|
||||
|
||||
GLSelectionRectangle m_selection_rectangle;
|
||||
|
||||
bool m_wait_for_up_event = false;
|
||||
bool m_selection_empty = true;
|
||||
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
|
||||
|
||||
std::vector<std::pair<const ConfigOption*, const ConfigOptionDef*>> get_config_options(const std::vector<std::string>& keys) const;
|
||||
bool is_mesh_point_clipped(const Vec3d& point) const;
|
||||
|
||||
// Methods that do the model_object and editing cache synchronization,
|
||||
// editing mode selection, etc:
|
||||
enum {
|
||||
AllPoints = -2,
|
||||
NoPoints,
|
||||
};
|
||||
void select_point(int i);
|
||||
void unselect_point(int i);
|
||||
void reload_cache();
|
||||
void update_clipping_plane(bool keep_normal = false) const;
|
||||
|
||||
protected:
|
||||
void on_set_state() override;
|
||||
void on_set_hover_id() override
|
||||
|
||||
{
|
||||
if (int(m_c->m_model_object->sla_drain_holes.size()) <= m_hover_id)
|
||||
m_hover_id = -1;
|
||||
}
|
||||
void on_start_dragging() override;
|
||||
void on_stop_dragging() override;
|
||||
void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||
|
||||
std::string on_get_name() const override;
|
||||
bool on_is_activable() const override;
|
||||
bool on_is_selectable() const override;
|
||||
void on_load(cereal::BinaryInputArchive& ar) override;
|
||||
void on_save(cereal::BinaryOutputArchive& ar) const override;
|
||||
};
|
||||
|
||||
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
#endif // slic3r_GLGizmoHollow_hpp_
|
|
@ -22,8 +22,8 @@
|
|||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
|
||||
: GLGizmoBase(parent, icon_filename, sprite_id)
|
||||
GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, CommonGizmosData* cd)
|
||||
: GLGizmoBase(parent, icon_filename, sprite_id, cd)
|
||||
, m_quadric(nullptr)
|
||||
, m_its(nullptr)
|
||||
{
|
||||
|
@ -64,23 +64,35 @@ bool GLGizmoSlaSupports::on_init()
|
|||
void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const Selection& selection)
|
||||
{
|
||||
if (! model_object || selection.is_empty()) {
|
||||
m_model_object = nullptr;
|
||||
m_c->m_model_object = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_model_object != model_object || m_model_object_id != model_object->id()) {
|
||||
m_model_object = model_object;
|
||||
m_print_object_idx = -1;
|
||||
}
|
||||
bool something_changed = false;
|
||||
|
||||
m_active_instance = selection.get_instance_idx();
|
||||
if (m_c->m_model_object != model_object
|
||||
|| m_c->m_model_object_id != model_object->id()
|
||||
|| m_c->m_active_instance != selection.get_instance_idx()) {
|
||||
m_c->m_model_object = model_object;
|
||||
m_c->m_print_object_idx = -1;
|
||||
m_c->m_active_instance = selection.get_instance_idx();
|
||||
something_changed = true;
|
||||
}
|
||||
|
||||
if (model_object && selection.is_from_single_instance())
|
||||
{
|
||||
// Cache the bb - it's needed for dealing with the clipping plane quite often
|
||||
// It could be done inside update_mesh but one has to account for scaling of the instance.
|
||||
//FIXME calling ModelObject::instance_bounding_box() is expensive!
|
||||
m_active_instance_bb_radius = m_model_object->instance_bounding_box(m_active_instance).radius();
|
||||
if (something_changed) {
|
||||
m_c->m_active_instance_bb_radius = m_c->m_model_object->instance_bounding_box(m_c->m_active_instance).radius();
|
||||
if (m_state == On) {
|
||||
m_parent.toggle_model_objects_visibility(false);
|
||||
m_parent.toggle_model_objects_visibility(! m_c->m_cavity_mesh, m_c->m_model_object, m_c->m_active_instance);
|
||||
m_parent.toggle_sla_auxiliaries_visibility(! m_editing_mode, m_c->m_model_object, m_c->m_active_instance);
|
||||
}
|
||||
else
|
||||
m_parent.toggle_model_objects_visibility(true, nullptr, -1);
|
||||
}
|
||||
|
||||
if (is_mesh_update_necessary()) {
|
||||
update_mesh();
|
||||
|
@ -88,15 +100,8 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S
|
|||
}
|
||||
|
||||
// If we triggered autogeneration before, check backend and fetch results if they are there
|
||||
if (m_model_object->sla_points_status == sla::PointsStatus::Generating)
|
||||
if (m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating)
|
||||
get_data_from_backend();
|
||||
|
||||
if (m_state == On) {
|
||||
m_parent.toggle_model_objects_visibility(false);
|
||||
m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance);
|
||||
}
|
||||
else
|
||||
m_parent.toggle_model_objects_visibility(true, nullptr, -1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,16 +111,16 @@ void GLGizmoSlaSupports::on_render() const
|
|||
{
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
|
||||
// If current m_model_object does not match selection, ask GLCanvas3D to turn us off
|
||||
// If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off
|
||||
if (m_state == On
|
||||
&& (m_model_object != selection.get_model()->objects[selection.get_object_idx()]
|
||||
|| m_active_instance != selection.get_instance_idx()
|
||||
|| m_model_object_id != m_model_object->id())) {
|
||||
&& (m_c->m_model_object != selection.get_model()->objects[selection.get_object_idx()]
|
||||
|| m_c->m_active_instance != selection.get_instance_idx()
|
||||
|| m_c->m_model_object_id != m_c->m_model_object->id())) {
|
||||
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS));
|
||||
return;
|
||||
}
|
||||
|
||||
if (! m_its || ! m_mesh)
|
||||
if (! m_its || ! m_c->m_mesh)
|
||||
const_cast<GLGizmoSlaSupports*>(this)->update_mesh();
|
||||
|
||||
glsafe(::glEnable(GL_BLEND));
|
||||
|
@ -123,6 +128,8 @@ void GLGizmoSlaSupports::on_render() const
|
|||
|
||||
m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z();
|
||||
|
||||
render_hollowed_mesh();
|
||||
|
||||
if (m_quadric != nullptr && selection.is_from_single_instance())
|
||||
render_points(selection, false);
|
||||
|
||||
|
@ -134,9 +141,32 @@ void GLGizmoSlaSupports::on_render() const
|
|||
|
||||
|
||||
|
||||
void GLGizmoSlaSupports::render_hollowed_mesh() const
|
||||
{
|
||||
if (m_c->m_volume_with_cavity) {
|
||||
m_c->m_volume_with_cavity->set_sla_shift_z(m_z_shift);
|
||||
m_parent.get_shader().start_using();
|
||||
|
||||
GLint current_program_id;
|
||||
glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id));
|
||||
GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1;
|
||||
GLint print_box_detection_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.volume_detection") : -1;
|
||||
GLint print_box_worldmatrix_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.volume_world_matrix") : -1;
|
||||
glcheck();
|
||||
m_c->m_volume_with_cavity->set_render_color();
|
||||
const Geometry::Transformation& volume_trafo = m_c->m_model_object->volumes.front()->get_transformation();
|
||||
m_c->m_volume_with_cavity->set_volume_transformation(volume_trafo);
|
||||
m_c->m_volume_with_cavity->set_instance_transformation(m_c->m_model_object->instances[size_t(m_c->m_active_instance)]->get_transformation());
|
||||
m_c->m_volume_with_cavity->render(color_id, print_box_detection_id, print_box_worldmatrix_id);
|
||||
m_parent.get_shader().stop_using();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const
|
||||
{
|
||||
if (m_clipping_plane_distance == 0.f)
|
||||
if (m_clipping_plane_distance == 0.f || m_c->m_mesh->empty())
|
||||
return;
|
||||
|
||||
// Get transformation of the instance
|
||||
|
@ -154,66 +184,66 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const
|
|||
1.));
|
||||
|
||||
// Now initialize the TMS for the object, perform the cut and save the result.
|
||||
if (! m_object_clipper) {
|
||||
m_object_clipper.reset(new MeshClipper);
|
||||
m_object_clipper->set_mesh(*m_mesh);
|
||||
if (! m_c->m_object_clipper) {
|
||||
m_c->m_object_clipper.reset(new MeshClipper);
|
||||
m_c->m_object_clipper->set_mesh(*m_c->mesh());
|
||||
}
|
||||
m_object_clipper->set_plane(*m_clipping_plane);
|
||||
m_object_clipper->set_transformation(trafo);
|
||||
m_c->m_object_clipper->set_plane(*m_clipping_plane);
|
||||
m_c->m_object_clipper->set_transformation(trafo);
|
||||
|
||||
|
||||
// Next, ask the backend if supports are already calculated. If so, we are gonna cut them too.
|
||||
// First we need a pointer to the respective SLAPrintObject. The index into objects vector is
|
||||
// cached so we don't have todo it on each render. We only search for the po if needed:
|
||||
if (m_print_object_idx < 0 || (int)m_parent.sla_print()->objects().size() != m_print_objects_count) {
|
||||
m_print_objects_count = m_parent.sla_print()->objects().size();
|
||||
m_print_object_idx = -1;
|
||||
if (m_c->m_print_object_idx < 0 || (int)m_parent.sla_print()->objects().size() != m_c->m_print_objects_count) {
|
||||
m_c->m_print_objects_count = m_parent.sla_print()->objects().size();
|
||||
m_c->m_print_object_idx = -1;
|
||||
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
|
||||
++m_print_object_idx;
|
||||
if (po->model_object()->id() == m_model_object->id())
|
||||
++m_c->m_print_object_idx;
|
||||
if (po->model_object()->id() == m_c->m_model_object->id())
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m_print_object_idx >= 0) {
|
||||
const SLAPrintObject* print_object = m_parent.sla_print()->objects()[m_print_object_idx];
|
||||
if (m_c->m_print_object_idx >= 0) {
|
||||
const SLAPrintObject* print_object = m_parent.sla_print()->objects()[m_c->m_print_object_idx];
|
||||
|
||||
if (print_object->is_step_done(slaposSupportTree)) {
|
||||
if (print_object->is_step_done(slaposSupportTree) && !print_object->get_mesh(slaposSupportTree).empty()) {
|
||||
// If the supports are already calculated, save the timestamp of the respective step
|
||||
// so we can later tell they were recalculated.
|
||||
size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp;
|
||||
|
||||
if (! m_supports_clipper || (int)timestamp != m_old_timestamp) {
|
||||
if (! m_c->m_supports_clipper || (int)timestamp != m_c->m_old_timestamp) {
|
||||
// The timestamp has changed.
|
||||
m_supports_clipper.reset(new MeshClipper);
|
||||
m_c->m_supports_clipper.reset(new MeshClipper);
|
||||
// The mesh should already have the shared vertices calculated.
|
||||
m_supports_clipper->set_mesh(print_object->support_mesh());
|
||||
m_old_timestamp = timestamp;
|
||||
m_c->m_supports_clipper->set_mesh(print_object->support_mesh());
|
||||
m_c->m_old_timestamp = timestamp;
|
||||
}
|
||||
m_supports_clipper->set_plane(*m_clipping_plane);
|
||||
m_supports_clipper->set_transformation(supports_trafo);
|
||||
m_c->m_supports_clipper->set_plane(*m_clipping_plane);
|
||||
m_c->m_supports_clipper->set_transformation(supports_trafo);
|
||||
}
|
||||
else
|
||||
// The supports are not valid. We better dump the cached data.
|
||||
m_supports_clipper.reset();
|
||||
m_c->m_supports_clipper.reset();
|
||||
}
|
||||
|
||||
// At this point we have the triangulated cuts for both the object and supports - let's render.
|
||||
if (! m_object_clipper->get_triangles().empty()) {
|
||||
if (! m_c->m_object_clipper->get_triangles().empty()) {
|
||||
::glPushMatrix();
|
||||
::glColor3f(1.0f, 0.37f, 0.0f);
|
||||
::glBegin(GL_TRIANGLES);
|
||||
for (const Vec3f& point : m_object_clipper->get_triangles())
|
||||
for (const Vec3f& point : m_c->m_object_clipper->get_triangles())
|
||||
::glVertex3f(point(0), point(1), point(2));
|
||||
::glEnd();
|
||||
::glPopMatrix();
|
||||
}
|
||||
|
||||
if (m_supports_clipper && ! m_supports_clipper->get_triangles().empty() && !m_editing_mode) {
|
||||
if (m_c->m_supports_clipper && ! m_c->m_supports_clipper->get_triangles().empty() && !m_editing_mode) {
|
||||
// The supports are hidden in the editing mode, so it makes no sense to render the cuts.
|
||||
::glPushMatrix();
|
||||
::glColor3f(1.0f, 0.f, 0.37f);
|
||||
::glBegin(GL_TRIANGLES);
|
||||
for (const Vec3f& point : m_supports_clipper->get_triangles())
|
||||
for (const Vec3f& point : m_c->m_supports_clipper->get_triangles())
|
||||
::glVertex3f(point(0), point(1), point(2));
|
||||
::glEnd();
|
||||
::glPopMatrix();
|
||||
|
@ -230,6 +260,7 @@ void GLGizmoSlaSupports::on_render_for_picking() const
|
|||
|
||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
render_points(selection, true);
|
||||
render_hollowed_mesh();
|
||||
}
|
||||
|
||||
void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const
|
||||
|
@ -298,7 +329,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
|
|||
if (m_editing_mode) {
|
||||
// in case the normal is not yet cached, find and cache it
|
||||
if (m_editing_cache[i].normal == Vec3f::Zero())
|
||||
m_mesh_raycaster->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal);
|
||||
m_c->m_mesh_raycaster->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal);
|
||||
|
||||
Eigen::Quaterniond q;
|
||||
q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast<double>());
|
||||
|
@ -327,6 +358,44 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
|
|||
glsafe(::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive));
|
||||
}
|
||||
|
||||
// Now render the drain holes:
|
||||
if (! m_c->m_cavity_mesh) {
|
||||
render_color[0] = 0.7f;
|
||||
render_color[1] = 0.7f;
|
||||
render_color[2] = 0.7f;
|
||||
render_color[3] = 0.7f;
|
||||
glsafe(::glColor4fv(render_color));
|
||||
for (const sla::DrainHole& drain_hole : m_c->m_model_object->sla_drain_holes) {
|
||||
// Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
|
||||
glsafe(::glPushMatrix());
|
||||
glsafe(::glTranslatef(drain_hole.pos(0), drain_hole.pos(1), drain_hole.pos(2)));
|
||||
glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data()));
|
||||
|
||||
if (vol->is_left_handed())
|
||||
glFrontFace(GL_CW);
|
||||
|
||||
// Matrices set, we can render the point mark now.
|
||||
|
||||
Eigen::Quaterniond q;
|
||||
q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * (-drain_hole.normal).cast<double>());
|
||||
Eigen::AngleAxisd aa(q);
|
||||
glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2)));
|
||||
glsafe(::glPushMatrix());
|
||||
glsafe(::glTranslated(0., 0., -drain_hole.height));
|
||||
::gluCylinder(m_quadric, drain_hole.radius, drain_hole.radius, drain_hole.height, 24, 1);
|
||||
glsafe(::glTranslated(0., 0., drain_hole.height));
|
||||
::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1);
|
||||
glsafe(::glTranslated(0., 0., -drain_hole.height));
|
||||
glsafe(::glRotatef(180.f, 1.f, 0.f, 0.f));
|
||||
::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1);
|
||||
glsafe(::glPopMatrix());
|
||||
|
||||
if (vol->is_left_handed())
|
||||
glFrontFace(GL_CCW);
|
||||
glsafe(::glPopMatrix());
|
||||
}
|
||||
}
|
||||
|
||||
if (!picking)
|
||||
glsafe(::glDisable(GL_LIGHTING));
|
||||
|
||||
|
@ -340,7 +409,7 @@ bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const
|
|||
if (m_clipping_plane_distance == 0.f)
|
||||
return false;
|
||||
|
||||
Vec3d transformed_point = m_model_object->instances.front()->get_transformation().get_matrix() * point;
|
||||
Vec3d transformed_point = m_c->m_model_object->instances[m_c->m_active_instance]->get_transformation().get_matrix() * point;
|
||||
transformed_point(2) += m_z_shift;
|
||||
return m_clipping_plane->is_point_clipped(transformed_point);
|
||||
}
|
||||
|
@ -349,39 +418,38 @@ bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const
|
|||
|
||||
bool GLGizmoSlaSupports::is_mesh_update_necessary() const
|
||||
{
|
||||
return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty())
|
||||
&& ((m_model_object->id() != m_model_object_id) || m_its == nullptr);
|
||||
return ((m_state == On) && (m_c->m_model_object != nullptr) && !m_c->m_model_object->instances.empty())
|
||||
&& ((m_c->m_model_object->id() != m_c->m_model_object_id) || m_its == nullptr);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GLGizmoSlaSupports::update_mesh()
|
||||
{
|
||||
if (! m_model_object)
|
||||
if (! m_c->m_model_object)
|
||||
return;
|
||||
|
||||
wxBusyCursor wait;
|
||||
// this way we can use that mesh directly.
|
||||
// This mesh does not account for the possible Z up SLA offset.
|
||||
m_mesh = &m_model_object->volumes.front()->mesh();
|
||||
m_its = &m_mesh->its;
|
||||
m_c->m_mesh = &m_c->m_model_object->volumes.front()->mesh();
|
||||
m_its = &m_c->m_mesh->its;
|
||||
|
||||
// If this is different mesh than last time or if the AABB tree is uninitialized, recalculate it.
|
||||
if (m_model_object_id != m_model_object->id() || ! m_mesh_raycaster)
|
||||
m_mesh_raycaster.reset(new MeshRaycaster(*m_mesh));
|
||||
if (m_c->m_model_object_id != m_c->m_model_object->id() || ! m_c->m_mesh_raycaster)
|
||||
m_c->m_mesh_raycaster.reset(new MeshRaycaster(*m_c->mesh()));
|
||||
|
||||
m_model_object_id = m_model_object->id();
|
||||
m_c->m_model_object_id = m_c->m_model_object->id();
|
||||
disable_editing_mode();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal
|
||||
// Return false if no intersection was found, true otherwise.
|
||||
bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal)
|
||||
{
|
||||
// if the gizmo doesn't have the V, F structures for igl, calculate them first:
|
||||
if (! m_mesh_raycaster)
|
||||
if (! m_c->m_mesh_raycaster)
|
||||
update_mesh();
|
||||
|
||||
const Camera& camera = m_parent.get_camera();
|
||||
|
@ -393,13 +461,28 @@ bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec
|
|||
// The raycaster query
|
||||
Vec3f hit;
|
||||
Vec3f normal;
|
||||
if (m_mesh_raycaster->unproject_on_mesh(mouse_pos, trafo.get_matrix(), camera, hit, normal, m_clipping_plane.get())) {
|
||||
// Return both the point and the facet normal.
|
||||
pos_and_normal = std::make_pair(hit, normal);
|
||||
return true;
|
||||
if (m_c->m_mesh_raycaster->unproject_on_mesh(mouse_pos, trafo.get_matrix(), camera, hit, normal, m_clipping_plane.get())) {
|
||||
// Check whether the hit is in a hole
|
||||
bool in_hole = false;
|
||||
// In case the hollowed and drilled mesh is available, we can allow
|
||||
// placing points in holes, because they should never end up
|
||||
// on surface that's been drilled away.
|
||||
if (! m_c->m_cavity_mesh) {
|
||||
for (const sla::DrainHole& hole : m_c->m_model_object->sla_drain_holes) {
|
||||
if (hole.is_inside(hit)) {
|
||||
in_hole = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (! in_hole) {
|
||||
// Return both the point and the facet normal.
|
||||
pos_and_normal = std::make_pair(hit, normal);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
|
||||
|
@ -459,7 +542,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state();
|
||||
|
||||
// First collect positions of all the points in world coordinates.
|
||||
Geometry::Transformation trafo = m_model_object->instances[m_active_instance]->get_transformation();
|
||||
Geometry::Transformation trafo = m_c->m_model_object->instances[m_c->m_active_instance]->get_transformation();
|
||||
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
|
||||
std::vector<Vec3d> points;
|
||||
for (unsigned int i=0; i<m_editing_cache.size(); ++i)
|
||||
|
@ -472,7 +555,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
points_inside.push_back(points[idx].cast<float>());
|
||||
|
||||
// Only select/deselect points that are actually visible
|
||||
for (size_t idx : m_mesh_raycaster->get_unobscured_idxs(trafo, m_parent.get_camera(), points_inside, m_clipping_plane.get()))
|
||||
for (size_t idx : m_c->m_mesh_raycaster->get_unobscured_idxs(trafo, m_parent.get_camera(), points_inside, m_clipping_plane.get()))
|
||||
{
|
||||
if (rectangle_status == GLSelectionRectangle::Deselect)
|
||||
unselect_point(points_idxs[idx]);
|
||||
|
@ -610,10 +693,10 @@ std::vector<const ConfigOption*> GLGizmoSlaSupports::get_config_options(const st
|
|||
{
|
||||
std::vector<const ConfigOption*> out;
|
||||
|
||||
if (!m_model_object)
|
||||
if (!m_c->m_model_object)
|
||||
return out;
|
||||
|
||||
const DynamicPrintConfig& object_cfg = m_model_object->config;
|
||||
const DynamicPrintConfig& object_cfg = m_c->m_model_object->config;
|
||||
const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
|
||||
std::unique_ptr<DynamicPrintConfig> default_cfg = nullptr;
|
||||
|
||||
|
@ -636,7 +719,7 @@ std::vector<const ConfigOption*> GLGizmoSlaSupports::get_config_options(const st
|
|||
|
||||
ClippingPlane GLGizmoSlaSupports::get_sla_clipping_plane() const
|
||||
{
|
||||
if (!m_model_object || m_state == Off || m_clipping_plane_distance == 0.f)
|
||||
if (!m_c->m_model_object || m_state == Off || m_clipping_plane_distance == 0.f)
|
||||
return ClippingPlane::ClipsNothing();
|
||||
else
|
||||
return ClippingPlane(-m_clipping_plane->get_normal(), m_clipping_plane->get_data()[3]);
|
||||
|
@ -665,7 +748,7 @@ void GLGizmoSlaSupports::find_intersecting_facets(const igl::AABB<Eigen::MatrixX
|
|||
|
||||
void GLGizmoSlaSupports::make_line_segments() const
|
||||
{
|
||||
TriangleMeshSlicer tms(&m_model_object->volumes.front()->mesh);
|
||||
TriangleMeshSlicer tms(&m_c->m_model_object->volumes.front()->mesh);
|
||||
Vec3f normal(0.f, 1.f, 1.f);
|
||||
double d = 0.;
|
||||
|
||||
|
@ -689,7 +772,7 @@ void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_l
|
|||
static float last_y = 0.0f;
|
||||
static float last_h = 0.0f;
|
||||
|
||||
if (!m_model_object)
|
||||
if (! m_c->m_model_object)
|
||||
return;
|
||||
|
||||
bool first_run = true; // This is a hack to redraw the button when all points are removed,
|
||||
|
@ -699,7 +782,7 @@ RENDER_AGAIN:
|
|||
//const ImVec2 window_size(m_imgui->scaled(18.f, 16.f));
|
||||
//ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) ));
|
||||
//ImGui::SetNextWindowSize(ImVec2(window_size));
|
||||
|
||||
|
||||
m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
// adjust window position to avoid overlap the view toolbar
|
||||
|
@ -728,7 +811,6 @@ RENDER_AGAIN:
|
|||
float window_width = minimal_slider_width + std::max(std::max(settings_sliders_left, clipping_slider_left), diameter_slider_left);
|
||||
window_width = std::max(std::max(window_width, buttons_width_approx), lock_supports_width_approx);
|
||||
|
||||
|
||||
bool force_refresh = false;
|
||||
bool remove_selected = false;
|
||||
bool remove_all = false;
|
||||
|
@ -827,15 +909,15 @@ RENDER_AGAIN:
|
|||
m_density_stash = density;
|
||||
}
|
||||
if (slider_edited) {
|
||||
m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance;
|
||||
m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density;
|
||||
m_c->m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance;
|
||||
m_c->m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density;
|
||||
}
|
||||
if (slider_released) {
|
||||
m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = m_minimal_point_distance_stash;
|
||||
m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)m_density_stash;
|
||||
m_c->m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = m_minimal_point_distance_stash;
|
||||
m_c->m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)m_density_stash;
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Support parameter change")));
|
||||
m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance;
|
||||
m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density;
|
||||
m_c->m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance;
|
||||
m_c->m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density;
|
||||
wxGetApp().obj_list()->update_and_show_object_settings_item();
|
||||
}
|
||||
|
||||
|
@ -853,10 +935,10 @@ RENDER_AGAIN:
|
|||
m_imgui->disabled_end();
|
||||
|
||||
// m_imgui->text("");
|
||||
// m_imgui->text(m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) :
|
||||
// (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? _(L("Autogenerated points (no modifications)")) :
|
||||
// (m_model_object->sla_points_status == sla::PointsStatus::UserModified ? _(L("User-modified points")) :
|
||||
// (m_model_object->sla_points_status == sla::PointsStatus::Generating ? _(L("Generation in progress...")) : "UNKNOWN STATUS"))));
|
||||
// m_imgui->text(m_c->m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) :
|
||||
// (m_c->m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? _(L("Autogenerated points (no modifications)")) :
|
||||
// (m_c->m_model_object->sla_points_status == sla::PointsStatus::UserModified ? _(L("User-modified points")) :
|
||||
// (m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating ? _(L("Generation in progress...")) : "UNKNOWN STATUS"))));
|
||||
}
|
||||
|
||||
|
||||
|
@ -890,12 +972,6 @@ RENDER_AGAIN:
|
|||
|
||||
m_imgui->end();
|
||||
|
||||
if (m_editing_mode != m_old_editing_state) { // user toggled between editing/non-editing mode
|
||||
m_parent.toggle_sla_auxiliaries_visibility(!m_editing_mode, m_model_object, m_active_instance);
|
||||
force_refresh = true;
|
||||
}
|
||||
m_old_editing_state = m_editing_mode;
|
||||
|
||||
if (remove_selected || remove_all) {
|
||||
force_refresh = false;
|
||||
m_parent.set_as_dirty();
|
||||
|
@ -952,12 +1028,12 @@ std::string GLGizmoSlaSupports::on_get_name() const
|
|||
|
||||
void GLGizmoSlaSupports::on_set_state()
|
||||
{
|
||||
// m_model_object pointer can be invalid (for instance because of undo/redo action),
|
||||
// m_c->m_model_object pointer can be invalid (for instance because of undo/redo action),
|
||||
// we should recover it from the object id
|
||||
m_model_object = nullptr;
|
||||
m_c->m_model_object = nullptr;
|
||||
for (const auto mo : wxGetApp().model().objects) {
|
||||
if (mo->id() == m_model_object_id) {
|
||||
m_model_object = mo;
|
||||
if (mo->id() == m_c->m_model_object_id) {
|
||||
m_c->m_model_object = mo;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -971,19 +1047,20 @@ void GLGizmoSlaSupports::on_set_state()
|
|||
update_mesh();
|
||||
|
||||
// we'll now reload support points:
|
||||
if (m_model_object)
|
||||
if (m_c->m_model_object)
|
||||
reload_cache();
|
||||
|
||||
m_parent.toggle_model_objects_visibility(false);
|
||||
if (m_model_object)
|
||||
m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance);
|
||||
if (m_c->m_model_object && ! m_c->m_cavity_mesh)
|
||||
m_parent.toggle_model_objects_visibility(true, m_c->m_model_object, m_c->m_active_instance);
|
||||
m_parent.toggle_sla_auxiliaries_visibility(! m_editing_mode, m_c->m_model_object, m_c->m_active_instance);
|
||||
|
||||
// Set default head diameter from config.
|
||||
const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
|
||||
m_new_point_head_diameter = static_cast<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value;
|
||||
}
|
||||
if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
|
||||
bool will_ask = m_model_object && m_editing_mode && unsaved_changes();
|
||||
bool will_ask = m_c->m_model_object && m_editing_mode && unsaved_changes();
|
||||
if (will_ask) {
|
||||
wxGetApp().CallAfter([this]() {
|
||||
// Following is called through CallAfter, because otherwise there was a problem
|
||||
|
@ -1005,11 +1082,12 @@ void GLGizmoSlaSupports::on_set_state()
|
|||
m_parent.toggle_model_objects_visibility(true);
|
||||
m_normal_cache.clear();
|
||||
m_clipping_plane_distance = 0.f;
|
||||
update_clipping_plane();
|
||||
// Release clippers and the AABB raycaster.
|
||||
m_its = nullptr;
|
||||
m_object_clipper.reset();
|
||||
m_supports_clipper.reset();
|
||||
m_mesh_raycaster.reset();
|
||||
m_c->m_object_clipper.reset();
|
||||
m_c->m_supports_clipper.reset();
|
||||
m_c->m_mesh_raycaster.reset();
|
||||
}
|
||||
}
|
||||
m_old_state = m_state;
|
||||
|
@ -1051,7 +1129,7 @@ void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar)
|
|||
{
|
||||
ar(m_clipping_plane_distance,
|
||||
*m_clipping_plane,
|
||||
m_model_object_id,
|
||||
m_c->m_model_object_id,
|
||||
m_new_point_head_diameter,
|
||||
m_normal_cache,
|
||||
m_editing_cache,
|
||||
|
@ -1065,7 +1143,7 @@ void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const
|
|||
{
|
||||
ar(m_clipping_plane_distance,
|
||||
*m_clipping_plane,
|
||||
m_model_object_id,
|
||||
m_c->m_model_object_id,
|
||||
m_new_point_head_diameter,
|
||||
m_normal_cache,
|
||||
m_editing_cache,
|
||||
|
@ -1143,9 +1221,9 @@ void GLGizmoSlaSupports::editing_mode_apply_changes()
|
|||
for (const CacheEntry& ce : m_editing_cache)
|
||||
m_normal_cache.push_back(ce.support_point);
|
||||
|
||||
m_model_object->sla_points_status = sla::PointsStatus::UserModified;
|
||||
m_model_object->sla_support_points.clear();
|
||||
m_model_object->sla_support_points = m_normal_cache;
|
||||
m_c->m_model_object->sla_points_status = sla::PointsStatus::UserModified;
|
||||
m_c->m_model_object->sla_support_points.clear();
|
||||
m_c->m_model_object->sla_support_points = m_normal_cache;
|
||||
|
||||
reslice_SLA_supports();
|
||||
}
|
||||
|
@ -1156,10 +1234,10 @@ void GLGizmoSlaSupports::editing_mode_apply_changes()
|
|||
void GLGizmoSlaSupports::reload_cache()
|
||||
{
|
||||
m_normal_cache.clear();
|
||||
if (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated || m_model_object->sla_points_status == sla::PointsStatus::Generating)
|
||||
if (m_c->m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated || m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating)
|
||||
get_data_from_backend();
|
||||
else
|
||||
for (const sla::SupportPoint& point : m_model_object->sla_support_points)
|
||||
for (const sla::SupportPoint& point : m_c->m_model_object->sla_support_points)
|
||||
m_normal_cache.emplace_back(point);
|
||||
}
|
||||
|
||||
|
@ -1168,7 +1246,7 @@ bool GLGizmoSlaSupports::has_backend_supports() const
|
|||
{
|
||||
// find SlaPrintObject with this ID
|
||||
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
|
||||
if (po->model_object()->id() == m_model_object->id())
|
||||
if (po->model_object()->id() == m_c->m_model_object->id())
|
||||
return po->is_step_done(slaposSupportPoints);
|
||||
}
|
||||
return false;
|
||||
|
@ -1176,7 +1254,7 @@ bool GLGizmoSlaSupports::has_backend_supports() const
|
|||
|
||||
void GLGizmoSlaSupports::reslice_SLA_supports(bool postpone_error_messages) const
|
||||
{
|
||||
wxGetApp().CallAfter([this, postpone_error_messages]() { wxGetApp().plater()->reslice_SLA_supports(*m_model_object, postpone_error_messages); });
|
||||
wxGetApp().CallAfter([this, postpone_error_messages]() { wxGetApp().plater()->reslice_SLA_supports(*m_c->m_model_object, postpone_error_messages); });
|
||||
}
|
||||
|
||||
void GLGizmoSlaSupports::get_data_from_backend()
|
||||
|
@ -1186,14 +1264,14 @@ void GLGizmoSlaSupports::get_data_from_backend()
|
|||
|
||||
// find the respective SLAPrintObject, we need a pointer to it
|
||||
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
|
||||
if (po->model_object()->id() == m_model_object->id()) {
|
||||
if (po->model_object()->id() == m_c->m_model_object->id()) {
|
||||
m_normal_cache.clear();
|
||||
const std::vector<sla::SupportPoint>& points = po->get_support_points();
|
||||
auto mat = po->trafo().inverse().cast<float>();
|
||||
for (unsigned int i=0; i<points.size();++i)
|
||||
m_normal_cache.emplace_back(sla::SupportPoint(mat * points[i].pos, points[i].head_front_radius, points[i].is_new_island));
|
||||
|
||||
m_model_object->sla_points_status = sla::PointsStatus::AutoGenerated;
|
||||
m_c->m_model_object->sla_points_status = sla::PointsStatus::AutoGenerated;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1210,10 +1288,10 @@ void GLGizmoSlaSupports::auto_generate()
|
|||
_(L("Are you sure you want to do it?")) + "\n",
|
||||
_(L("Warning")), wxICON_WARNING | wxYES | wxNO);
|
||||
|
||||
if (m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) {
|
||||
if (m_c->m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) {
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Autogenerate support points")));
|
||||
wxGetApp().CallAfter([this]() { reslice_SLA_supports(); });
|
||||
m_model_object->sla_points_status = sla::PointsStatus::Generating;
|
||||
m_c->m_model_object->sla_points_status = sla::PointsStatus::Generating;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1227,6 +1305,9 @@ void GLGizmoSlaSupports::switch_to_editing_mode()
|
|||
for (const sla::SupportPoint& sp : m_normal_cache)
|
||||
m_editing_cache.emplace_back(sp);
|
||||
select_point(NoPoints);
|
||||
|
||||
m_parent.toggle_sla_auxiliaries_visibility(false, m_c->m_model_object, m_c->m_active_instance);
|
||||
m_parent.set_as_dirty();
|
||||
}
|
||||
|
||||
|
||||
|
@ -1235,6 +1316,8 @@ void GLGizmoSlaSupports::disable_editing_mode()
|
|||
if (m_editing_mode) {
|
||||
m_editing_mode = false;
|
||||
wxGetApp().plater()->leave_gizmos_stack();
|
||||
m_parent.toggle_sla_auxiliaries_visibility(true, m_c->m_model_object, m_c->m_active_instance);
|
||||
m_parent.set_as_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1258,9 +1341,9 @@ void GLGizmoSlaSupports::update_clipping_plane(bool keep_normal) const
|
|||
Vec3d normal = (keep_normal && m_clipping_plane->get_normal() != Vec3d::Zero() ?
|
||||
m_clipping_plane->get_normal() : -m_parent.get_camera().get_dir_forward());
|
||||
|
||||
const Vec3d& center = m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift);
|
||||
const Vec3d& center = m_c->m_model_object->instances[m_c->m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift);
|
||||
float dist = normal.dot(center);
|
||||
*m_clipping_plane = ClippingPlane(normal, (dist - (-m_active_instance_bb_radius) - m_clipping_plane_distance * 2*m_active_instance_bb_radius));
|
||||
*m_clipping_plane = ClippingPlane(normal, (dist - (-m_c->m_active_instance_bb_radius) - m_clipping_plane_distance * 2*m_c->m_active_instance_bb_radius));
|
||||
m_parent.set_as_dirty();
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
#include "GLGizmoBase.hpp"
|
||||
#include "slic3r/GUI/GLSelectionRectangle.hpp"
|
||||
|
||||
#include "libslic3r/SLA/SLACommon.hpp"
|
||||
#include "libslic3r/SLA/Common.hpp"
|
||||
#include <wx/dialog.h>
|
||||
|
||||
#include <cereal/types/vector.hpp>
|
||||
|
@ -21,10 +21,10 @@ enum class SLAGizmoEventType : unsigned char;
|
|||
class GLGizmoSlaSupports : public GLGizmoBase
|
||||
{
|
||||
private:
|
||||
ModelObject* m_model_object = nullptr;
|
||||
ObjectID m_model_object_id = 0;
|
||||
int m_active_instance = -1;
|
||||
float m_active_instance_bb_radius; // to cache the bb
|
||||
//ModelObject* m_model_object = nullptr;
|
||||
//ObjectID m_model_object_id = 0;
|
||||
//int m_active_instance = -1;
|
||||
//float m_active_instance_bb_radius; // to cache the bb
|
||||
mutable double m_z_shift = 0.f;
|
||||
bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal);
|
||||
|
||||
|
@ -34,15 +34,12 @@ private:
|
|||
typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
|
||||
typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
|
||||
|
||||
std::unique_ptr<MeshRaycaster> m_mesh_raycaster;
|
||||
const TriangleMesh* m_mesh;
|
||||
//std::unique_ptr<MeshRaycaster> m_mesh_raycaster;
|
||||
//const TriangleMesh* m_mesh;
|
||||
const indexed_triangle_set* m_its;
|
||||
mutable const TriangleMesh* m_supports_mesh;
|
||||
mutable std::vector<Vec2f> m_triangles;
|
||||
mutable std::vector<Vec2f> m_supports_triangles;
|
||||
mutable int m_old_timestamp = -1;
|
||||
mutable int m_print_object_idx = -1;
|
||||
mutable int m_print_objects_count = -1;
|
||||
//mutable int m_old_timestamp = -1;
|
||||
//mutable int m_print_object_idx = -1;
|
||||
//mutable int m_print_objects_count = -1;
|
||||
|
||||
class CacheEntry {
|
||||
public:
|
||||
|
@ -72,7 +69,7 @@ private:
|
|||
};
|
||||
|
||||
public:
|
||||
GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
||||
GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, CommonGizmosData* cd);
|
||||
~GLGizmoSlaSupports() override;
|
||||
void set_sla_support_data(ModelObject* model_object, const Selection& selection);
|
||||
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||
|
@ -93,13 +90,13 @@ private:
|
|||
//void render_selection_rectangle() const;
|
||||
void render_points(const Selection& selection, bool picking = false) const;
|
||||
void render_clipping_plane(const Selection& selection) const;
|
||||
void render_hollowed_mesh() const;
|
||||
bool is_mesh_update_necessary() const;
|
||||
void update_mesh();
|
||||
bool unsaved_changes() const;
|
||||
|
||||
bool m_lock_unique_islands = false;
|
||||
bool m_editing_mode = false; // Is editing mode active?
|
||||
bool m_old_editing_state = false; // To keep track of whether the user toggled between the modes (needed for imgui refreshes).
|
||||
float m_new_point_head_diameter; // Size of a new point.
|
||||
CacheEntry m_point_before_drag; // undo/redo - so we know what state was edited
|
||||
float m_old_point_head_diameter = 0.; // the same
|
||||
|
@ -121,11 +118,12 @@ private:
|
|||
bool m_selection_empty = true;
|
||||
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
|
||||
|
||||
mutable std::unique_ptr<MeshClipper> m_object_clipper;
|
||||
mutable std::unique_ptr<MeshClipper> m_supports_clipper;
|
||||
//mutable std::unique_ptr<MeshClipper> m_object_clipper;
|
||||
//mutable std::unique_ptr<MeshClipper> m_supports_clipper;
|
||||
|
||||
std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const;
|
||||
bool is_mesh_point_clipped(const Vec3d& point) const;
|
||||
bool is_point_in_hole(const Vec3f& pt) const;
|
||||
//void find_intersecting_facets(const igl::AABB<Eigen::MatrixXf, 3>* aabb, const Vec3f& normal, double offset, std::vector<unsigned int>& out) const;
|
||||
|
||||
// Methods that do the model_object and editing cache synchronization,
|
||||
|
|
|
@ -32,5 +32,6 @@ enum class SLAGizmoEventType : unsigned char {
|
|||
#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
|
||||
|
||||
#endif //slic3r_GLGizmos_hpp_
|
||||
|
|
|
@ -83,12 +83,16 @@ bool GLGizmosManager::init()
|
|||
return false;
|
||||
}
|
||||
|
||||
m_common_gizmos_data.reset(new CommonGizmosData());
|
||||
|
||||
// Order of gizmos in the vector must match order in EType!
|
||||
m_gizmos.emplace_back(new GLGizmoMove3D(m_parent, "move.svg", 0));
|
||||
m_gizmos.emplace_back(new GLGizmoScale3D(m_parent, "scale.svg", 1));
|
||||
m_gizmos.emplace_back(new GLGizmoRotate3D(m_parent, "rotate.svg", 2));
|
||||
m_gizmos.emplace_back(new GLGizmoFlatten(m_parent, "place.svg", 3));
|
||||
m_gizmos.emplace_back(new GLGizmoCut(m_parent, "cut.svg", 4));
|
||||
m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 5));
|
||||
m_gizmos.emplace_back(new GLGizmoHollow(m_parent, "hollow.svg", 5, m_common_gizmos_data.get()));
|
||||
m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 6, m_common_gizmos_data.get()));
|
||||
|
||||
for (auto& gizmo : m_gizmos) {
|
||||
if (! gizmo->init()) {
|
||||
|
@ -345,6 +349,7 @@ void GLGizmosManager::set_sla_support_data(ModelObject* model_object)
|
|||
return;
|
||||
|
||||
dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->set_sla_support_data(model_object, m_parent.get_selection());
|
||||
dynamic_cast<GLGizmoHollow*>(m_gizmos[Hollow].get())->set_sla_support_data(model_object, m_parent.get_selection());
|
||||
}
|
||||
|
||||
// Returns true if the gizmo used the event to do something, false otherwise.
|
||||
|
@ -353,15 +358,22 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
|
|||
if (!m_enabled || m_gizmos.empty())
|
||||
return false;
|
||||
|
||||
return dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
if (m_current == SlaSupports)
|
||||
return dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
if (m_current == Hollow)
|
||||
return dynamic_cast<GLGizmoHollow*>(m_gizmos[Hollow].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||
return false;
|
||||
}
|
||||
|
||||
ClippingPlane GLGizmosManager::get_sla_clipping_plane() const
|
||||
{
|
||||
if (!m_enabled || m_current != SlaSupports || m_gizmos.empty())
|
||||
if (!m_enabled || (m_current != SlaSupports && m_current != Hollow) || m_gizmos.empty())
|
||||
return ClippingPlane::ClipsNothing();
|
||||
|
||||
return dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->get_sla_clipping_plane();
|
||||
if (m_current == SlaSupports)
|
||||
return dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->get_sla_clipping_plane();
|
||||
else
|
||||
return dynamic_cast<GLGizmoHollow*>(m_gizmos[Hollow].get())->get_sla_clipping_plane();
|
||||
}
|
||||
|
||||
bool GLGizmosManager::wants_reslice_supports_on_undo() const
|
||||
|
@ -401,7 +413,7 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt)
|
|||
{
|
||||
bool processed = false;
|
||||
|
||||
if (m_current == SlaSupports) {
|
||||
if (m_current == SlaSupports || m_current == Hollow) {
|
||||
float rot = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta();
|
||||
if (gizmo_event((rot > 0.f ? SLAGizmoEventType::MouseWheelUp : SLAGizmoEventType::MouseWheelDown), Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
|
||||
processed = true;
|
||||
|
@ -459,7 +471,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
|
|||
|
||||
if (evt.LeftDown())
|
||||
{
|
||||
if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
|
||||
if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
|
||||
// the gizmo got the event and took some action, there is no need to do anything more
|
||||
processed = true;
|
||||
else if (!selection.is_empty() && grabber_contains_mouse()) {
|
||||
|
@ -477,17 +489,17 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
|
|||
processed = true;
|
||||
}
|
||||
}
|
||||
else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::RightDown))
|
||||
else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::RightDown))
|
||||
{
|
||||
// we need to set the following right up as processed to avoid showing the context menu if the user release the mouse over the object
|
||||
pending_right_up = true;
|
||||
// event was taken care of by the SlaSupports gizmo
|
||||
processed = true;
|
||||
}
|
||||
else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports))
|
||||
else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports || m_current == Hollow))
|
||||
// don't allow dragging objects with the Sla gizmo on
|
||||
processed = true;
|
||||
else if (evt.Dragging() && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
|
||||
else if (evt.Dragging() && (m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
|
||||
{
|
||||
// the gizmo got the event and took some action, no need to do anything more here
|
||||
m_parent.set_as_dirty();
|
||||
|
@ -513,10 +525,10 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
|
|||
case Scale:
|
||||
{
|
||||
// Apply new temporary scale factors
|
||||
TransformationType transformation_type(TransformationType::Local_Absolute_Joint);
|
||||
if (evt.AltDown())
|
||||
transformation_type.set_independent();
|
||||
selection.scale(get_scale(), transformation_type);
|
||||
TransformationType transformation_type(TransformationType::Local_Absolute_Joint);
|
||||
if (evt.AltDown())
|
||||
transformation_type.set_independent();
|
||||
selection.scale(get_scale(), transformation_type);
|
||||
if (evt.ControlDown())
|
||||
selection.translate(get_scale_offset(), true);
|
||||
wxGetApp().obj_manipul()->set_dirty();
|
||||
|
@ -563,7 +575,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
|
|||
|
||||
processed = true;
|
||||
}
|
||||
else if (evt.LeftUp() && (m_current == SlaSupports) && !m_parent.is_mouse_dragging())
|
||||
else if (evt.LeftUp() && (m_current == SlaSupports || m_current == Hollow) && !m_parent.is_mouse_dragging())
|
||||
{
|
||||
// in case SLA gizmo is selected, we just pass the LeftUp event and stop processing - neither
|
||||
// object moving or selecting is suppressed in that case
|
||||
|
@ -628,7 +640,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
|
|||
#endif /* __APPLE__ */
|
||||
{
|
||||
// Sla gizmo selects all support points
|
||||
if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::SelectAll))
|
||||
if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::SelectAll))
|
||||
processed = true;
|
||||
|
||||
break;
|
||||
|
@ -662,7 +674,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
|
|||
case 'r' :
|
||||
case 'R' :
|
||||
{
|
||||
if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::ResetClippingPlane))
|
||||
if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::ResetClippingPlane))
|
||||
processed = true;
|
||||
|
||||
break;
|
||||
|
@ -674,7 +686,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
|
|||
case WXK_DELETE:
|
||||
#endif /* __APPLE__ */
|
||||
{
|
||||
if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::Delete))
|
||||
if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::Delete))
|
||||
processed = true;
|
||||
|
||||
break;
|
||||
|
@ -695,7 +707,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
|
|||
{
|
||||
if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::ManualEditing))
|
||||
processed = true;
|
||||
|
||||
|
||||
break;
|
||||
}
|
||||
case 'F':
|
||||
|
@ -736,20 +748,31 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
|
|||
|
||||
if (evt.GetEventType() == wxEVT_KEY_UP)
|
||||
{
|
||||
if (m_current == SlaSupports)
|
||||
if (m_current == SlaSupports || m_current == Hollow)
|
||||
{
|
||||
GLGizmoSlaSupports* gizmo = dynamic_cast<GLGizmoSlaSupports*>(get_current());
|
||||
bool is_editing = true;
|
||||
bool is_rectangle_dragging = false;
|
||||
|
||||
if (m_current == SlaSupports) {
|
||||
GLGizmoSlaSupports* gizmo = dynamic_cast<GLGizmoSlaSupports*>(get_current());
|
||||
is_editing = gizmo->is_in_editing_mode();
|
||||
is_rectangle_dragging = gizmo->is_selection_rectangle_dragging();
|
||||
}
|
||||
else {
|
||||
GLGizmoHollow* gizmo = dynamic_cast<GLGizmoHollow*>(get_current());
|
||||
is_rectangle_dragging = gizmo->is_selection_rectangle_dragging();
|
||||
}
|
||||
|
||||
if (keyCode == WXK_SHIFT)
|
||||
{
|
||||
// shift has been just released - SLA gizmo might want to close rectangular selection.
|
||||
if (gizmo_event(SLAGizmoEventType::ShiftUp) || (gizmo->is_in_editing_mode() && gizmo->is_selection_rectangle_dragging()))
|
||||
if (gizmo_event(SLAGizmoEventType::ShiftUp) || (is_editing && is_rectangle_dragging))
|
||||
processed = true;
|
||||
}
|
||||
else if (keyCode == WXK_ALT)
|
||||
{
|
||||
// alt has been just released - SLA gizmo might want to close rectangular selection.
|
||||
if (gizmo_event(SLAGizmoEventType::AltUp) || (gizmo->is_in_editing_mode() && gizmo->is_selection_rectangle_dragging()))
|
||||
if (gizmo_event(SLAGizmoEventType::AltUp) || (is_editing && is_rectangle_dragging))
|
||||
processed = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,11 +54,13 @@ public:
|
|||
|
||||
enum EType : unsigned char
|
||||
{
|
||||
// Order must match index in m_gizmos!
|
||||
Move,
|
||||
Scale,
|
||||
Rotate,
|
||||
Flatten,
|
||||
Cut,
|
||||
Hollow,
|
||||
SlaSupports,
|
||||
Undefined
|
||||
};
|
||||
|
@ -111,6 +113,7 @@ private:
|
|||
MouseCapture m_mouse_capture;
|
||||
std::string m_tooltip;
|
||||
bool m_serializing;
|
||||
std::unique_ptr<CommonGizmosData> m_common_gizmos_data;
|
||||
|
||||
public:
|
||||
explicit GLGizmosManager(GLCanvas3D& parent);
|
||||
|
@ -168,6 +171,7 @@ public:
|
|||
void update_data();
|
||||
|
||||
EType get_current_type() const { return m_current; }
|
||||
GLGizmoBase* get_current() const;
|
||||
|
||||
bool is_running() const;
|
||||
bool handle_shortcut(int key);
|
||||
|
@ -216,8 +220,6 @@ private:
|
|||
float get_scaled_total_height() const;
|
||||
float get_scaled_total_width() const;
|
||||
|
||||
GLGizmoBase* get_current() const;
|
||||
|
||||
bool generate_icons_texture() const;
|
||||
|
||||
void update_on_off_state(const Vec2d& mouse_pos);
|
||||
|
|
155
src/slic3r/GUI/Job.hpp
Normal file
155
src/slic3r/GUI/Job.hpp
Normal file
|
@ -0,0 +1,155 @@
|
|||
#ifndef JOB_HPP
|
||||
#define JOB_HPP
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <slic3r/Utils/Thread.hpp>
|
||||
#include <slic3r/GUI/I18N.hpp>
|
||||
#include <slic3r/GUI/ProgressIndicator.hpp>
|
||||
|
||||
#include <wx/event.h>
|
||||
|
||||
#include <boost/thread.hpp>
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// A class to handle UI jobs like arranging and optimizing rotation.
|
||||
// These are not instant jobs, the user has to be informed about their
|
||||
// state in the status progress indicator. On the other hand they are
|
||||
// separated from the background slicing process. Ideally, these jobs should
|
||||
// run when the background process is not running.
|
||||
//
|
||||
// TODO: A mechanism would be useful for blocking the plater interactions:
|
||||
// objects would be frozen for the user. In case of arrange, an animation
|
||||
// could be shown, or with the optimize orientations, partial results
|
||||
// could be displayed.
|
||||
class Job : public wxEvtHandler
|
||||
{
|
||||
int m_range = 100;
|
||||
boost::thread m_thread;
|
||||
std::atomic<bool> m_running{false}, m_canceled{false};
|
||||
bool m_finalized = false;
|
||||
std::shared_ptr<ProgressIndicator> m_progress;
|
||||
|
||||
void run()
|
||||
{
|
||||
m_running.store(true);
|
||||
process();
|
||||
m_running.store(false);
|
||||
|
||||
// ensure to call the last status to finalize the job
|
||||
update_status(status_range(), "");
|
||||
}
|
||||
|
||||
protected:
|
||||
// status range for a particular job
|
||||
virtual int status_range() const { return 100; }
|
||||
|
||||
// status update, to be used from the work thread (process() method)
|
||||
void update_status(int st, const wxString &msg = "")
|
||||
{
|
||||
auto evt = new wxThreadEvent();
|
||||
evt->SetInt(st);
|
||||
evt->SetString(msg);
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
bool was_canceled() const { return m_canceled.load(); }
|
||||
|
||||
// Launched just before start(), a job can use it to prepare internals
|
||||
virtual void prepare() {}
|
||||
|
||||
// Launched when the job is finished. It refreshes the 3Dscene by def.
|
||||
virtual void finalize() { m_finalized = true; }
|
||||
|
||||
|
||||
public:
|
||||
Job(std::shared_ptr<ProgressIndicator> pri) : m_progress(pri)
|
||||
{
|
||||
Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) {
|
||||
auto msg = evt.GetString();
|
||||
if (!msg.empty())
|
||||
m_progress->set_status_text(msg.ToUTF8().data());
|
||||
|
||||
if (m_finalized) return;
|
||||
|
||||
m_progress->set_progress(evt.GetInt());
|
||||
if (evt.GetInt() == status_range()) {
|
||||
// set back the original range and cancel callback
|
||||
m_progress->set_range(m_range);
|
||||
m_progress->set_cancel_callback();
|
||||
wxEndBusyCursor();
|
||||
|
||||
finalize();
|
||||
|
||||
// dont do finalization again for the same process
|
||||
m_finalized = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool is_finalized() const { return m_finalized; }
|
||||
|
||||
Job(const Job &) = delete;
|
||||
Job(Job &&) = delete;
|
||||
Job &operator=(const Job &) = delete;
|
||||
Job &operator=(Job &&) = delete;
|
||||
|
||||
virtual void process() = 0;
|
||||
|
||||
void start()
|
||||
{ // Start the job. No effect if the job is already running
|
||||
if (!m_running.load()) {
|
||||
prepare();
|
||||
|
||||
// Save the current status indicatior range and push the new one
|
||||
m_range = m_progress->get_range();
|
||||
m_progress->set_range(status_range());
|
||||
|
||||
// init cancellation flag and set the cancel callback
|
||||
m_canceled.store(false);
|
||||
m_progress->set_cancel_callback(
|
||||
[this]() { m_canceled.store(true); });
|
||||
|
||||
m_finalized = false;
|
||||
|
||||
// Changing cursor to busy
|
||||
wxBeginBusyCursor();
|
||||
|
||||
try { // Execute the job
|
||||
m_thread = create_thread([this] { this->run(); });
|
||||
} catch (std::exception &) {
|
||||
update_status(status_range(),
|
||||
_(L("ERROR: not enough resources to "
|
||||
"execute a new job.")));
|
||||
}
|
||||
|
||||
// The state changes will be undone when the process hits the
|
||||
// last status value, in the status update handler (see ctor)
|
||||
}
|
||||
}
|
||||
|
||||
// To wait for the running job and join the threads. False is
|
||||
// returned if the timeout has been reached and the job is still
|
||||
// running. Call cancel() before this fn if you want to explicitly
|
||||
// end the job.
|
||||
bool join(int timeout_ms = 0)
|
||||
{
|
||||
if (!m_thread.joinable()) return true;
|
||||
|
||||
if (timeout_ms <= 0)
|
||||
m_thread.join();
|
||||
else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_running() const { return m_running.load(); }
|
||||
void cancel() { m_canceled.store(true); }
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif // JOB_HPP
|
|
@ -58,7 +58,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
|
|||
#endif // _WIN32
|
||||
|
||||
// initialize status bar
|
||||
m_statusbar.reset(new ProgressStatusBar(this));
|
||||
m_statusbar = std::make_shared<ProgressStatusBar>(this);
|
||||
m_statusbar->set_font(GUI::wxGetApp().normal_font());
|
||||
m_statusbar->embed(this);
|
||||
m_statusbar->set_status_text(_(L("Version")) + " " +
|
||||
SLIC3R_VERSION +
|
||||
|
|
|
@ -135,7 +135,7 @@ public:
|
|||
Plater* m_plater { nullptr };
|
||||
wxNotebook* m_tabpanel { nullptr };
|
||||
wxProgressDialog* m_progress_dialog { nullptr };
|
||||
std::unique_ptr<ProgressStatusBar> m_statusbar;
|
||||
std::shared_ptr<ProgressStatusBar> m_statusbar;
|
||||
};
|
||||
|
||||
} // GUI
|
||||
|
|
|
@ -5,10 +5,6 @@
|
|||
|
||||
#include "slic3r/GUI/Camera.hpp"
|
||||
|
||||
// There is an L function in igl that would be overridden by our localization macro.
|
||||
#undef L
|
||||
#include <igl/AABB.h>
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
|
||||
|
@ -99,57 +95,6 @@ void MeshClipper::recalculate_triangles()
|
|||
}
|
||||
|
||||
|
||||
class MeshRaycaster::AABBWrapper {
|
||||
public:
|
||||
AABBWrapper(const TriangleMesh* mesh);
|
||||
~AABBWrapper() { m_AABB.deinit(); }
|
||||
|
||||
typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
|
||||
typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
|
||||
igl::AABB<MapMatrixXfUnaligned, 3> m_AABB;
|
||||
|
||||
Vec3f get_hit_pos(const igl::Hit& hit) const;
|
||||
Vec3f get_hit_normal(const igl::Hit& hit) const;
|
||||
|
||||
private:
|
||||
const TriangleMesh* m_mesh;
|
||||
};
|
||||
|
||||
MeshRaycaster::AABBWrapper::AABBWrapper(const TriangleMesh* mesh)
|
||||
: m_mesh(mesh)
|
||||
{
|
||||
m_AABB.init(
|
||||
MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3),
|
||||
MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3));
|
||||
}
|
||||
|
||||
|
||||
MeshRaycaster::MeshRaycaster(const TriangleMesh& mesh)
|
||||
: m_AABB_wrapper(new AABBWrapper(&mesh)), m_mesh(&mesh)
|
||||
{
|
||||
}
|
||||
|
||||
// Define the default destructor here. This is needed for the PIMPL with
|
||||
// unique_ptr to work, the AABBWrapper is complete here.
|
||||
MeshRaycaster::~MeshRaycaster() = default;
|
||||
|
||||
Vec3f MeshRaycaster::AABBWrapper::get_hit_pos(const igl::Hit& hit) const
|
||||
{
|
||||
const stl_triangle_vertex_indices& indices = m_mesh->its.indices[hit.id];
|
||||
return Vec3f((1-hit.u-hit.v) * m_mesh->its.vertices[indices(0)]
|
||||
+ hit.u * m_mesh->its.vertices[indices(1)]
|
||||
+ hit.v * m_mesh->its.vertices[indices(2)]);
|
||||
}
|
||||
|
||||
|
||||
Vec3f MeshRaycaster::AABBWrapper::get_hit_normal(const igl::Hit& hit) const
|
||||
{
|
||||
const stl_triangle_vertex_indices& indices = m_mesh->its.indices[hit.id];
|
||||
Vec3f a(m_mesh->its.vertices[indices(1)] - m_mesh->its.vertices[indices(0)]);
|
||||
Vec3f b(m_mesh->its.vertices[indices(2)] - m_mesh->its.vertices[indices(0)]);
|
||||
return Vec3f(a.cross(b));
|
||||
}
|
||||
|
||||
|
||||
bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
|
||||
Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane) const
|
||||
|
@ -163,27 +108,20 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
|
|||
::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 0., model_mat.data(), proj_mat.data(), viewport.data(), &pt1(0), &pt1(1), &pt1(2));
|
||||
::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 1., model_mat.data(), proj_mat.data(), viewport.data(), &pt2(0), &pt2(1), &pt2(2));
|
||||
|
||||
std::vector<igl::Hit> hits;
|
||||
|
||||
Transform3d inv = trafo.inverse();
|
||||
|
||||
pt1 = inv * pt1;
|
||||
pt2 = inv * pt2;
|
||||
|
||||
if (! m_AABB_wrapper->m_AABB.intersect_ray(
|
||||
AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3),
|
||||
AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3),
|
||||
pt1.cast<float>(), (pt2-pt1).cast<float>(), hits))
|
||||
std::vector<sla::EigenMesh3D::hit_result> hits = m_emesh.query_ray_hits(pt1, pt2-pt1);
|
||||
if (hits.empty())
|
||||
return false; // no intersection found
|
||||
|
||||
std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
|
||||
|
||||
unsigned i = 0;
|
||||
|
||||
// Remove points that are obscured or cut by the clipping plane
|
||||
if (clipping_plane) {
|
||||
for (i=0; i<hits.size(); ++i)
|
||||
if (! clipping_plane->is_point_clipped(trafo * m_AABB_wrapper->get_hit_pos(hits[i]).cast<double>()))
|
||||
if (! clipping_plane->is_point_clipped(trafo * hits[i].position()))
|
||||
break;
|
||||
|
||||
if (i==hits.size() || (hits.size()-i) % 2 != 0) {
|
||||
|
@ -194,8 +132,8 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
|
|||
}
|
||||
|
||||
// Now stuff the points in the provided vector and calculate normals if asked about them:
|
||||
position = m_AABB_wrapper->get_hit_pos(hits[i]);
|
||||
normal = m_AABB_wrapper->get_hit_normal(hits[i]);
|
||||
position = hits[i].position().cast<float>();
|
||||
normal = hits[i].normal().cast<float>();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -219,24 +157,21 @@ std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo
|
|||
|
||||
bool is_obscured = false;
|
||||
// Cast a ray in the direction of the camera and look for intersection with the mesh:
|
||||
std::vector<igl::Hit> hits;
|
||||
std::vector<sla::EigenMesh3D::hit_result> hits;
|
||||
// Offset the start of the ray by EPSILON to account for numerical inaccuracies.
|
||||
if (m_AABB_wrapper->m_AABB.intersect_ray(
|
||||
AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3),
|
||||
AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3),
|
||||
inverse_trafo * pt + direction_to_camera_mesh * EPSILON, direction_to_camera_mesh, hits)) {
|
||||
hits = m_emesh.query_ray_hits((inverse_trafo * pt + direction_to_camera_mesh * EPSILON).cast<double>(),
|
||||
direction_to_camera.cast<double>());
|
||||
|
||||
std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; });
|
||||
|
||||
if (! hits.empty()) {
|
||||
// If the closest hit facet normal points in the same direction as the ray,
|
||||
// we are looking through the mesh and should therefore discard the point:
|
||||
if (m_AABB_wrapper->get_hit_normal(hits.front()).dot(direction_to_camera_mesh) > 0.f)
|
||||
if (hits.front().normal().dot(direction_to_camera_mesh.cast<double>()) > 0)
|
||||
is_obscured = true;
|
||||
|
||||
// Eradicate all hits that the caller wants to ignore
|
||||
for (unsigned j=0; j<hits.size(); ++j) {
|
||||
const igl::Hit& hit = hits[j];
|
||||
if (clipping_plane && clipping_plane->is_point_clipped(trafo.get_matrix() * m_AABB_wrapper->get_hit_pos(hit).cast<double>())) {
|
||||
if (clipping_plane && clipping_plane->is_point_clipped(trafo.get_matrix() * hits[j].position())) {
|
||||
hits.erase(hits.begin()+j);
|
||||
--j;
|
||||
}
|
||||
|
@ -257,17 +192,15 @@ std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo
|
|||
Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const
|
||||
{
|
||||
int idx = 0;
|
||||
Eigen::Matrix<float, 1, 3> closest_point;
|
||||
m_AABB_wrapper->m_AABB.squared_distance(
|
||||
AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3),
|
||||
AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3),
|
||||
point, idx, closest_point);
|
||||
Vec3d closest_point;
|
||||
m_emesh.squared_distance(point.cast<double>(), idx, closest_point);
|
||||
if (normal) {
|
||||
igl::Hit imag_hit;
|
||||
imag_hit.id = idx;
|
||||
*normal = m_AABB_wrapper->get_hit_normal(imag_hit);
|
||||
auto indices = m_emesh.F().row(idx);
|
||||
Vec3d a(m_emesh.V().row(indices(1)) - m_emesh.V().row(indices(0)));
|
||||
Vec3d b(m_emesh.V().row(indices(2)) - m_emesh.V().row(indices(0)));
|
||||
*normal = Vec3f(a.cross(b).cast<float>());
|
||||
}
|
||||
return closest_point;
|
||||
return closest_point.cast<float>();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
#include "libslic3r/SLA/EigenMesh3D.hpp"
|
||||
|
||||
|
||||
#include <cfloat>
|
||||
|
@ -46,7 +47,7 @@ public:
|
|||
bool operator!=(const ClippingPlane& cp) const { return ! (*this==cp); }
|
||||
|
||||
double distance(const Vec3d& pt) const {
|
||||
assert(is_approx(get_normal().norm(), 1.));
|
||||
// FIXME: this fails: assert(is_approx(get_normal().norm(), 1.));
|
||||
return (-get_normal().dot(pt) + m_data[3]);
|
||||
}
|
||||
|
||||
|
@ -104,11 +105,11 @@ private:
|
|||
// whether certain points are visible or obscured by the mesh etc.
|
||||
class MeshRaycaster {
|
||||
public:
|
||||
// The class saves a const* to the mesh, called is responsible
|
||||
// for making sure it does not get invalid.
|
||||
MeshRaycaster(const TriangleMesh& mesh);
|
||||
|
||||
~MeshRaycaster();
|
||||
// The class makes a copy of the mesh as EigenMesh3D.
|
||||
// The pointer can be invalidated after constructor returns.
|
||||
MeshRaycaster(const TriangleMesh& mesh)
|
||||
: m_emesh(mesh)
|
||||
{}
|
||||
|
||||
// Given a mouse position, this returns true in case it is on the mesh.
|
||||
bool unproject_on_mesh(
|
||||
|
@ -136,10 +137,7 @@ public:
|
|||
Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const;
|
||||
|
||||
private:
|
||||
// PIMPL wrapper around igl::AABB so I don't have to include the header-only IGL here
|
||||
class AABBWrapper;
|
||||
std::unique_ptr<AABBWrapper> m_AABB_wrapper;
|
||||
const TriangleMesh* m_mesh = nullptr;
|
||||
sla::EigenMesh3D m_emesh;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -39,11 +39,13 @@
|
|||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/SLA/Hollowing.hpp"
|
||||
#include "libslic3r/SLA/Rotfinder.hpp"
|
||||
#include "libslic3r/SLA/SupportPoint.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/Print.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/SLAPrint.hpp"
|
||||
#include "libslic3r/SLA/SLARotfinder.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
//#include "libslic3r/ClipperUtils.hpp"
|
||||
|
@ -70,6 +72,7 @@
|
|||
#include "Camera.hpp"
|
||||
#include "Mouse3DController.hpp"
|
||||
#include "Tab.hpp"
|
||||
#include "Job.hpp"
|
||||
#include "PresetBundle.hpp"
|
||||
#include "BackgroundSlicingProcess.hpp"
|
||||
#include "ProgressStatusBar.hpp"
|
||||
|
@ -1503,144 +1506,39 @@ struct Plater::priv
|
|||
// objects would be frozen for the user. In case of arrange, an animation
|
||||
// could be shown, or with the optimize orientations, partial results
|
||||
// could be displayed.
|
||||
class Job : public wxEvtHandler
|
||||
class PlaterJob: public Job
|
||||
{
|
||||
int m_range = 100;
|
||||
boost::thread m_thread;
|
||||
priv * m_plater = nullptr;
|
||||
std::atomic<bool> m_running{false}, m_canceled{false};
|
||||
bool m_finalized = false;
|
||||
|
||||
void run()
|
||||
{
|
||||
m_running.store(true);
|
||||
process();
|
||||
m_running.store(false);
|
||||
|
||||
// ensure to call the last status to finalize the job
|
||||
update_status(status_range(), "");
|
||||
}
|
||||
|
||||
priv *m_plater;
|
||||
protected:
|
||||
// status range for a particular job
|
||||
virtual int status_range() const { return 100; }
|
||||
|
||||
// status update, to be used from the work thread (process() method)
|
||||
void update_status(int st, const wxString &msg = "")
|
||||
{
|
||||
auto evt = new wxThreadEvent();
|
||||
evt->SetInt(st);
|
||||
evt->SetString(msg);
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
priv & plater() { return *m_plater; }
|
||||
const priv &plater() const { return *m_plater; }
|
||||
bool was_canceled() const { return m_canceled.load(); }
|
||||
|
||||
// Launched just before start(), a job can use it to prepare internals
|
||||
virtual void prepare() {}
|
||||
|
||||
// Launched when the job is finished. It refreshes the 3Dscene by def.
|
||||
virtual void finalize()
|
||||
void finalize() override
|
||||
{
|
||||
// Do a full refresh of scene tree, including regenerating
|
||||
// all the GLVolumes. FIXME The update function shall just
|
||||
// reload the modified matrices.
|
||||
if (!was_canceled())
|
||||
if (!Job::was_canceled())
|
||||
plater().update(unsigned(UpdateParams::FORCE_FULL_SCREEN_REFRESH));
|
||||
|
||||
Job::finalize();
|
||||
}
|
||||
|
||||
public:
|
||||
Job(priv *_plater) : m_plater(_plater)
|
||||
{
|
||||
Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) {
|
||||
auto msg = evt.GetString();
|
||||
if (!msg.empty())
|
||||
plater().statusbar()->set_status_text(msg);
|
||||
|
||||
if (m_finalized) return;
|
||||
|
||||
plater().statusbar()->set_progress(evt.GetInt());
|
||||
if (evt.GetInt() == status_range()) {
|
||||
// set back the original range and cancel callback
|
||||
plater().statusbar()->set_range(m_range);
|
||||
plater().statusbar()->set_cancel_callback();
|
||||
wxEndBusyCursor();
|
||||
|
||||
finalize();
|
||||
|
||||
// dont do finalization again for the same process
|
||||
m_finalized = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Job(const Job &) = delete;
|
||||
Job(Job &&) = delete;
|
||||
Job &operator=(const Job &) = delete;
|
||||
Job &operator=(Job &&) = delete;
|
||||
|
||||
virtual void process() = 0;
|
||||
|
||||
void start()
|
||||
{ // Start the job. No effect if the job is already running
|
||||
if (!m_running.load()) {
|
||||
prepare();
|
||||
|
||||
// Save the current status indicatior range and push the new one
|
||||
m_range = plater().statusbar()->get_range();
|
||||
plater().statusbar()->set_range(status_range());
|
||||
|
||||
// init cancellation flag and set the cancel callback
|
||||
m_canceled.store(false);
|
||||
plater().statusbar()->set_cancel_callback(
|
||||
[this]() { m_canceled.store(true); });
|
||||
|
||||
m_finalized = false;
|
||||
|
||||
// Changing cursor to busy
|
||||
wxBeginBusyCursor();
|
||||
|
||||
try { // Execute the job
|
||||
m_thread = create_thread([this] { this->run(); });
|
||||
} catch (std::exception &) {
|
||||
update_status(status_range(),
|
||||
_(L("ERROR: not enough resources to "
|
||||
"execute a new job.")));
|
||||
}
|
||||
|
||||
// The state changes will be undone when the process hits the
|
||||
// last status value, in the status update handler (see ctor)
|
||||
}
|
||||
}
|
||||
|
||||
// To wait for the running job and join the threads. False is
|
||||
// returned if the timeout has been reached and the job is still
|
||||
// running. Call cancel() before this fn if you want to explicitly
|
||||
// end the job.
|
||||
bool join(int timeout_ms = 0)
|
||||
{
|
||||
if (!m_thread.joinable()) return true;
|
||||
|
||||
if (timeout_ms <= 0)
|
||||
m_thread.join();
|
||||
else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_running() const { return m_running.load(); }
|
||||
void cancel() { m_canceled.store(true); }
|
||||
PlaterJob(priv *_plater)
|
||||
: Job(_plater->statusbar()), m_plater(_plater)
|
||||
{}
|
||||
};
|
||||
|
||||
enum class Jobs : size_t {
|
||||
Arrange,
|
||||
Rotoptimize
|
||||
Rotoptimize,
|
||||
Hollow
|
||||
};
|
||||
|
||||
class ArrangeJob : public Job
|
||||
class ArrangeJob : public PlaterJob
|
||||
{
|
||||
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||
using ArrangePolygons = arrangement::ArrangePolygons;
|
||||
|
@ -1757,7 +1655,7 @@ struct Plater::priv
|
|||
}
|
||||
|
||||
public:
|
||||
using Job::Job;
|
||||
using PlaterJob::PlaterJob;
|
||||
|
||||
int status_range() const override { return int(m_selected.size()); }
|
||||
|
||||
|
@ -1774,13 +1672,30 @@ struct Plater::priv
|
|||
}
|
||||
};
|
||||
|
||||
class RotoptimizeJob : public Job
|
||||
class RotoptimizeJob : public PlaterJob
|
||||
{
|
||||
public:
|
||||
using Job::Job;
|
||||
using PlaterJob::PlaterJob;
|
||||
void process() override;
|
||||
};
|
||||
|
||||
class HollowJob : public PlaterJob
|
||||
{
|
||||
public:
|
||||
using PlaterJob::PlaterJob;
|
||||
void prepare() override;
|
||||
void process() override;
|
||||
void finalize() override;
|
||||
private:
|
||||
GLGizmoHollow * get_gizmo();
|
||||
const GLGizmoHollow * get_gizmo() const;
|
||||
|
||||
std::unique_ptr<TriangleMesh> m_output_mesh;
|
||||
std::unique_ptr<MeshRaycaster> m_output_raycaster;
|
||||
const TriangleMesh* m_object_mesh = nullptr;
|
||||
sla::HollowingConfig m_cfg;
|
||||
};
|
||||
|
||||
// Jobs defined inside the group class will be managed so that only one can
|
||||
// run at a time. Also, the background process will be stopped if a job is
|
||||
// started.
|
||||
|
@ -1792,6 +1707,7 @@ struct Plater::priv
|
|||
|
||||
ArrangeJob arrange_job{m_plater};
|
||||
RotoptimizeJob rotoptimize_job{m_plater};
|
||||
HollowJob hollow_job{m_plater};
|
||||
|
||||
// To create a new job, just define a new subclass of Job, implement
|
||||
// the process and the optional prepare() and finalize() methods
|
||||
|
@ -1799,7 +1715,8 @@ struct Plater::priv
|
|||
// if it cannot run concurrently with other jobs in this group
|
||||
|
||||
std::vector<std::reference_wrapper<Job>> m_jobs{arrange_job,
|
||||
rotoptimize_job};
|
||||
rotoptimize_job,
|
||||
hollow_job};
|
||||
|
||||
public:
|
||||
ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {}
|
||||
|
@ -1876,7 +1793,7 @@ struct Plater::priv
|
|||
|
||||
void reset_all_gizmos();
|
||||
void update_ui_from_settings();
|
||||
ProgressStatusBar* statusbar();
|
||||
std::shared_ptr<ProgressStatusBar> statusbar();
|
||||
std::string get_config(const std::string &key) const;
|
||||
BoundingBoxf bed_shape_bb() const;
|
||||
BoundingBox scaled_bed_shape_bb() const;
|
||||
|
@ -1901,6 +1818,7 @@ struct Plater::priv
|
|||
void reset();
|
||||
void mirror(Axis axis);
|
||||
void arrange();
|
||||
void hollow();
|
||||
void sla_optimize_rotation();
|
||||
void split_object();
|
||||
void split_volume();
|
||||
|
@ -2291,9 +2209,9 @@ void Plater::priv::update_ui_from_settings()
|
|||
preview->get_canvas3d()->update_ui_from_settings();
|
||||
}
|
||||
|
||||
ProgressStatusBar* Plater::priv::statusbar()
|
||||
std::shared_ptr<ProgressStatusBar> Plater::priv::statusbar()
|
||||
{
|
||||
return main_frame->m_statusbar.get();
|
||||
return main_frame->m_statusbar;
|
||||
}
|
||||
|
||||
std::string Plater::priv::get_config(const std::string &key) const
|
||||
|
@ -2815,6 +2733,12 @@ void Plater::priv::arrange()
|
|||
m_ui_jobs.start(Jobs::Arrange);
|
||||
}
|
||||
|
||||
void Plater::priv::hollow()
|
||||
{
|
||||
this->take_snapshot(_(L("Hollow")));
|
||||
m_ui_jobs.start(Jobs::Hollow);
|
||||
}
|
||||
|
||||
// This method will find an optimal orientation for the currently selected item
|
||||
// Very similar in nature to the arrange method above...
|
||||
void Plater::priv::sla_optimize_rotation() {
|
||||
|
@ -2946,12 +2870,74 @@ void Plater::priv::RotoptimizeJob::process()
|
|||
: _(L("Orientation found.")));
|
||||
}
|
||||
|
||||
void Plater::priv::HollowJob::prepare()
|
||||
{
|
||||
const GLGizmosManager& gizmo_manager = plater().q->canvas3D()->get_gizmos_manager();
|
||||
const GLGizmoHollow* gizmo_hollow = dynamic_cast<const GLGizmoHollow*>(gizmo_manager.get_current());
|
||||
assert(gizmo_hollow);
|
||||
auto hlw_data = gizmo_hollow->get_hollowing_parameters();
|
||||
m_object_mesh = hlw_data.first;
|
||||
m_cfg = hlw_data.second;
|
||||
m_output_mesh.reset();
|
||||
}
|
||||
|
||||
void Plater::priv::HollowJob::process()
|
||||
{
|
||||
sla::JobController ctl;
|
||||
ctl.stopcondition = [this]{ return was_canceled(); };
|
||||
ctl.statuscb = [this](unsigned st, const std::string &s) {
|
||||
if (st < 100) update_status(int(st), s);
|
||||
};
|
||||
|
||||
std::unique_ptr<TriangleMesh> omesh =
|
||||
sla::generate_interior(*m_object_mesh, m_cfg, ctl);
|
||||
|
||||
if (omesh && !omesh->empty()) {
|
||||
m_output_mesh.reset(new TriangleMesh{*m_object_mesh});
|
||||
m_output_mesh->merge(*omesh);
|
||||
m_output_mesh->require_shared_vertices();
|
||||
|
||||
update_status(90, _(L("Indexing hollowed object")));
|
||||
|
||||
m_output_raycaster.reset(new MeshRaycaster(*m_output_mesh));
|
||||
|
||||
update_status(100, was_canceled() ? _(L("Hollowing cancelled.")) :
|
||||
_(L("Hollowing done.")));
|
||||
} else {
|
||||
update_status(100, _(L("Hollowing failed.")));
|
||||
}
|
||||
}
|
||||
|
||||
void Plater::priv::HollowJob::finalize()
|
||||
{
|
||||
if (auto gizmo = get_gizmo()) {
|
||||
gizmo->update_mesh_raycaster(std::move(m_output_raycaster));
|
||||
gizmo->update_hollowed_mesh(std::move(m_output_mesh));
|
||||
}
|
||||
}
|
||||
|
||||
GLGizmoHollow *Plater::priv::HollowJob::get_gizmo()
|
||||
{
|
||||
const GLGizmosManager& gizmo_manager = plater().q->canvas3D()->get_gizmos_manager();
|
||||
auto ret = dynamic_cast<GLGizmoHollow*>(gizmo_manager.get_current());
|
||||
assert(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
const GLGizmoHollow *Plater::priv::HollowJob::get_gizmo() const
|
||||
{
|
||||
const GLGizmosManager& gizmo_manager = plater().q->canvas3D()->get_gizmos_manager();
|
||||
auto ret = dynamic_cast<const GLGizmoHollow*>(gizmo_manager.get_current());
|
||||
assert(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Plater::priv::split_object()
|
||||
{
|
||||
int obj_idx = get_selected_object_idx();
|
||||
if (obj_idx == -1)
|
||||
return;
|
||||
|
||||
|
||||
// we clone model object because split_object() adds the split volumes
|
||||
// into the same model object, thus causing duplicates when we call load_model_objects()
|
||||
Model new_model = model;
|
||||
|
@ -5066,6 +5052,11 @@ void Plater::export_toolpaths_to_obj() const
|
|||
p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str());
|
||||
}
|
||||
|
||||
void Plater::hollow()
|
||||
{
|
||||
p->hollow();
|
||||
}
|
||||
|
||||
void Plater::reslice()
|
||||
{
|
||||
// Stop arrange and (or) optimize rotation tasks.
|
||||
|
|
|
@ -194,6 +194,7 @@ public:
|
|||
void reload_from_disk();
|
||||
bool has_toolpaths_to_export() const;
|
||||
void export_toolpaths_to_obj() const;
|
||||
void hollow();
|
||||
void reslice();
|
||||
void reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages = false);
|
||||
void changed_object(int obj_idx);
|
||||
|
|
|
@ -505,6 +505,10 @@ const std::vector<std::string>& Preset::sla_print_options()
|
|||
"pad_object_connector_stride",
|
||||
"pad_object_connector_width",
|
||||
"pad_object_connector_penetration",
|
||||
"hollowing_enable",
|
||||
"hollowing_min_thickness",
|
||||
"hollowing_quality",
|
||||
"hollowing_closing_distance",
|
||||
"output_filename_format",
|
||||
"default_sla_print_profile",
|
||||
"compatible_printers",
|
||||
|
|
|
@ -11,58 +11,17 @@ namespace Slic3r {
|
|||
*/
|
||||
class ProgressIndicator {
|
||||
public:
|
||||
using CancelFn = std::function<void(void)>; // Cancel function signature.
|
||||
|
||||
private:
|
||||
float m_state = .0f, m_max = 1.f, m_step;
|
||||
CancelFn m_cancelfunc = [](){};
|
||||
|
||||
public:
|
||||
|
||||
inline virtual ~ProgressIndicator() {}
|
||||
|
||||
/// Get the maximum of the progress range.
|
||||
float max() const { return m_max; }
|
||||
|
||||
/// Get the current progress state
|
||||
float state() const { return m_state; }
|
||||
|
||||
/// Set the maximum of the progress range
|
||||
virtual void max(float maxval) { m_max = maxval; }
|
||||
|
||||
/// Set the current state of the progress.
|
||||
virtual void state(float val) { m_state = val; }
|
||||
|
||||
/**
|
||||
* @brief Number of states int the progress. Can be used instead of giving a
|
||||
* maximum value.
|
||||
*/
|
||||
virtual void states(unsigned statenum) {
|
||||
m_step = m_max / statenum;
|
||||
}
|
||||
|
||||
/// Message shown on the next status update.
|
||||
virtual void message(const std::string&) = 0;
|
||||
|
||||
/// Title of the operation.
|
||||
virtual void title(const std::string&) = 0;
|
||||
|
||||
/// Formatted message for the next status update. Works just like sprintf.
|
||||
virtual void message_fmt(const std::string& fmt, ...);
|
||||
|
||||
/// Set up a cancel callback for the operation if feasible.
|
||||
virtual void on_cancel(CancelFn func = CancelFn()) { m_cancelfunc = func; }
|
||||
|
||||
/**
|
||||
* Explicitly shut down the progress indicator and call the associated
|
||||
* callback.
|
||||
*/
|
||||
virtual void cancel() { m_cancelfunc(); }
|
||||
|
||||
/// Convenience function to call message and status update in one function.
|
||||
void update(float st, const std::string& msg) {
|
||||
message(msg); state(st);
|
||||
}
|
||||
|
||||
/// Cancel callback function type
|
||||
using CancelFn = std::function<void()>;
|
||||
|
||||
virtual ~ProgressIndicator() = default;
|
||||
|
||||
virtual void set_range(int range) = 0;
|
||||
virtual void set_cancel_callback(CancelFn = CancelFn()) = 0;
|
||||
virtual void set_progress(int pr) = 0;
|
||||
virtual void set_status_text(const char *) = 0; // utf8 char array
|
||||
virtual int get_range() const = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
namespace Slic3r {
|
||||
|
||||
ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id)
|
||||
: self{new wxStatusBar(parent ? parent : GUI::wxGetApp().mainframe,
|
||||
id == -1 ? wxID_ANY : id)}
|
||||
: self{new wxStatusBar(parent, id == -1 ? wxID_ANY : id)}
|
||||
, m_prog{new wxGauge(self,
|
||||
wxGA_HORIZONTAL,
|
||||
100,
|
||||
|
@ -32,7 +31,6 @@ ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id)
|
|||
m_prog->Hide();
|
||||
m_cancelbutton->Hide();
|
||||
|
||||
self->SetFont(GUI::wxGetApp().normal_font());
|
||||
self->SetFieldsCount(3);
|
||||
int w[] = {-1, 150, 155};
|
||||
self->SetStatusWidths(3, w);
|
||||
|
@ -149,8 +147,7 @@ void ProgressStatusBar::run(int rate)
|
|||
|
||||
void ProgressStatusBar::embed(wxFrame *frame)
|
||||
{
|
||||
wxFrame* mf = frame ? frame : GUI::wxGetApp().mainframe;
|
||||
if(mf) mf->SetStatusBar(self);
|
||||
if(frame) frame->SetStatusBar(self);
|
||||
}
|
||||
|
||||
void ProgressStatusBar::set_status_text(const wxString& txt)
|
||||
|
@ -173,6 +170,11 @@ wxString ProgressStatusBar::get_status_text() const
|
|||
return self->GetStatusText();
|
||||
}
|
||||
|
||||
void ProgressStatusBar::set_font(const wxFont &font)
|
||||
{
|
||||
self->SetFont(font);
|
||||
}
|
||||
|
||||
void ProgressStatusBar::show_cancel_button()
|
||||
{
|
||||
if(m_cancelbutton) m_cancelbutton->Show();
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue