Merge branch 'lm_tm_hollowing'

This commit is contained in:
tamasmeszaros 2020-01-24 09:51:39 +01:00
commit ef30270048
115 changed files with 17393 additions and 2919 deletions

View file

@ -104,7 +104,7 @@ list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules/)
enable_testing () enable_testing ()
# Enable C++11 language standard. # Enable C++17 language standard.
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
@ -426,6 +426,12 @@ if(SLIC3R_STATIC)
set(USE_BLOSC TRUE) set(USE_BLOSC TRUE)
endif() 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}) set(TOP_LEVEL_PROJECT_DIR ${PROJECT_SOURCE_DIR})
function(prusaslicer_copy_dlls target) function(prusaslicer_copy_dlls target)
if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
@ -449,8 +455,6 @@ function(prusaslicer_copy_dlls target)
endfunction() endfunction()
#find_package(OpenVDB 5.0 COMPONENTS openvdb)
#slic3r_remap_configs(IlmBase::Half RelWithDebInfo Release)
# libslic3r, PrusaSlicer GUI and the PrusaSlicer executable. # libslic3r, PrusaSlicer GUI and the PrusaSlicer executable.
add_subdirectory(src) add_subdirectory(src)

View 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()

View file

@ -108,6 +108,18 @@ if(POLICY CMP0074)
cmake_policy(SET CMP0074 NEW) cmake_policy(SET CMP0074 NEW)
endif() 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 utility functions for version information
include(${CMAKE_CURRENT_LIST_DIR}/OpenVDBUtils.cmake) include(${CMAKE_CURRENT_LIST_DIR}/OpenVDBUtils.cmake)
@ -146,7 +158,7 @@ set(_OPENVDB_ROOT_SEARCH_DIR "")
# Additionally try and use pkconfig to find OpenVDB # Additionally try and use pkconfig to find OpenVDB
find_package(PkgConfig) find_package(PkgConfig ${_quiet} )
pkg_check_modules(PC_OpenVDB QUIET OpenVDB) pkg_check_modules(PC_OpenVDB QUIET OpenVDB)
# ------------------------------------------------------------------------ # ------------------------------------------------------------------------
@ -230,12 +242,14 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
else() else()
set(OpenVDB_${COMPONENT}_FOUND FALSE) set(OpenVDB_${COMPONENT}_FOUND FALSE)
endif() endif()
set(OpenVDB_${COMPONENT}_LIBRARY ${OpenVDB_${COMPONENT}_LIBRARY_RELEASE})
else () else ()
string(TOUPPER "${CMAKE_BUILD_TYPE}" _BUILD_TYPE) string(TOUPPER "${CMAKE_BUILD_TYPE}" _BUILD_TYPE)
set(OpenVDB_${COMPONENT}_LIBRARY ${OpenVDB_${COMPONENT}_LIBRARY_${_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}) set(OpenVDB_${COMPONENT}_LIBRARY ${OpenVDB_${COMPONENT}_LIBRARY_RELEASE})
endif () endif ()
@ -247,6 +261,7 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
set(OpenVDB_${COMPONENT}_FOUND FALSE) set(OpenVDB_${COMPONENT}_FOUND FALSE)
endif() endif()
endif () endif ()
endforeach() endforeach()
if(UNIX AND OPENVDB_USE_STATIC_LIBS) if(UNIX AND OPENVDB_USE_STATIC_LIBS)
@ -280,7 +295,7 @@ OPENVDB_ABI_VERSION_FROM_PRINT(
ABI OpenVDB_ABI ABI OpenVDB_ABI
) )
if(NOT OpenVDB_FIND_QUIET) if(NOT OpenVDB_FIND_QUIETLY)
if(NOT OpenVDB_ABI) if(NOT OpenVDB_ABI)
message(WARNING "Unable to determine OpenVDB ABI version from OpenVDB " message(WARNING "Unable to determine OpenVDB ABI version from OpenVDB "
"installation. The library major version \"${OpenVDB_MAJOR_VERSION}\" " "installation. The library major version \"${OpenVDB_MAJOR_VERSION}\" "
@ -298,7 +313,17 @@ endif()
# Add standard dependencies # 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) if(NOT IlmBase_FOUND)
pkg_check_modules(IlmBase QUIET IlmBase) pkg_check_modules(IlmBase QUIET IlmBase)
endif() endif()
@ -306,20 +331,20 @@ if (IlmBase_FOUND AND NOT TARGET IlmBase::Half)
message(STATUS "Falling back to IlmBase found by pkg-config...") message(STATUS "Falling back to IlmBase found by pkg-config...")
find_library(IlmHalf_LIBRARY NAMES Half) find_library(IlmHalf_LIBRARY NAMES Half)
if(IlmHalf_LIBRARY-NOTFOUND) if(IlmHalf_LIBRARY-NOTFOUND OR NOT IlmBase_INCLUDE_DIRS)
message(FATAL_ERROR "IlmBase::Half can not be found!") just_fail("IlmBase::Half can not be found!")
endif() endif()
add_library(IlmBase::Half UNKNOWN IMPORTED) add_library(IlmBase::Half UNKNOWN IMPORTED)
set_target_properties(IlmBase::Half PROPERTIES set_target_properties(IlmBase::Half PROPERTIES
IMPORTED_LOCATION "${IlmHalf_LIBRARY}" IMPORTED_LOCATION "${IlmHalf_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES ${IlmBase_INCLUDE_DIRS}) INTERFACE_INCLUDE_DIRECTORIES "${IlmBase_INCLUDE_DIRS}")
elseif(NOT IlmBase_FOUND) elseif(NOT IlmBase_FOUND)
message(FATAL_ERROR "IlmBase::Half can not be found!") just_fail("IlmBase::Half can not be found!")
endif() endif()
find_package(TBB REQUIRED COMPONENTS tbb) find_package(TBB ${_quiet} ${_required} COMPONENTS tbb)
find_package(ZLIB REQUIRED) find_package(ZLIB ${_quiet} ${_required})
find_package(Boost REQUIRED COMPONENTS iostreams system) find_package(Boost ${_quiet} ${_required} COMPONENTS iostreams system )
# Use GetPrerequisites to see which libraries this OpenVDB lib has linked to # 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 # which we can query for optional deps. This basically runs ldd/otoll/objdump
@ -380,7 +405,7 @@ unset(_OPENVDB_PREREQUISITE_LIST)
unset(_HAS_DEP) unset(_HAS_DEP)
if(OpenVDB_USES_BLOSC) if(OpenVDB_USES_BLOSC)
find_package(Blosc ) find_package(Blosc QUIET)
if(NOT Blosc_FOUND OR NOT TARGET Blosc::blosc) if(NOT Blosc_FOUND OR NOT TARGET Blosc::blosc)
message(STATUS "find_package could not find Blosc. Using fallback blosc search...") message(STATUS "find_package could not find Blosc. Using fallback blosc search...")
find_path(Blosc_INCLUDE_DIR blosc.h) find_path(Blosc_INCLUDE_DIR blosc.h)
@ -392,25 +417,25 @@ if(OpenVDB_USES_BLOSC)
IMPORTED_LOCATION "${Blosc_LIBRARY}" IMPORTED_LOCATION "${Blosc_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES ${Blosc_INCLUDE_DIR}) INTERFACE_INCLUDE_DIRECTORIES ${Blosc_INCLUDE_DIR})
elseif() elseif()
message(FATAL_ERROR "Blosc library can not be found!") just_fail("Blosc library can not be found!")
endif() endif()
endif() endif()
endif() endif()
if(OpenVDB_USES_LOG4CPLUS) if(OpenVDB_USES_LOG4CPLUS)
find_package(Log4cplus REQUIRED) find_package(Log4cplus ${_quiet} ${_required})
endif() endif()
if(OpenVDB_USES_ILM) if(OpenVDB_USES_ILM)
find_package(IlmBase REQUIRED) find_package(IlmBase ${_quiet} ${_required})
endif() endif()
if(OpenVDB_USES_EXR) if(OpenVDB_USES_EXR)
find_package(OpenEXR REQUIRED) find_package(OpenEXR ${_quiet} ${_required})
endif() endif()
if(UNIX) if(UNIX)
find_package(Threads REQUIRED) find_package(Threads ${_quiet} ${_required})
endif() endif()
# Set deps. Note that the order here is important. If we're building against # Set deps. Note that the order here is important. If we're building against
@ -500,6 +525,7 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
IMPORTED_LINK_DEPENDENT_LIBRARIES "${_OPENVDB_HIDDEN_DEPENDENCIES}" # non visible deps IMPORTED_LINK_DEPENDENT_LIBRARIES "${_OPENVDB_HIDDEN_DEPENDENCIES}" # non visible deps
INTERFACE_LINK_LIBRARIES "${_OPENVDB_VISIBLE_DEPENDENCIES}" # visible deps (headers) INTERFACE_LINK_LIBRARIES "${_OPENVDB_VISIBLE_DEPENDENCIES}" # visible deps (headers)
INTERFACE_COMPILE_FEATURES cxx_std_11 INTERFACE_COMPILE_FEATURES cxx_std_11
IMPORTED_LOCATION "${OpenVDB_${COMPONENT}_LIBRARY}"
) )
if (_is_multi) if (_is_multi)
@ -507,10 +533,6 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
IMPORTED_LOCATION_RELEASE "${OpenVDB_${COMPONENT}_LIBRARY_RELEASE}" IMPORTED_LOCATION_RELEASE "${OpenVDB_${COMPONENT}_LIBRARY_RELEASE}"
IMPORTED_LOCATION_DEBUG "${OpenVDB_${COMPONENT}_LIBRARY_DEBUG}" IMPORTED_LOCATION_DEBUG "${OpenVDB_${COMPONENT}_LIBRARY_DEBUG}"
) )
else ()
set_target_properties(OpenVDB::${COMPONENT} PROPERTIES
IMPORTED_LOCATION "${OpenVDB_${COMPONENT}_LIBRARY}"
)
endif () endif ()
if (OPENVDB_USE_STATIC_LIBS) if (OPENVDB_USE_STATIC_LIBS)
@ -521,7 +543,7 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
endif() endif()
endforeach() 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}") message(STATUS "OpenVDB libraries: ${OpenVDB_LIBRARIES}")
endif() endif()

View file

@ -125,7 +125,9 @@ function(OPENVDB_ABI_VERSION_FROM_PRINT OPENVDB_PRINT)
cmake_parse_arguments(_VDB "QUIET" "ABI" "" ${ARGN}) cmake_parse_arguments(_VDB "QUIET" "ABI" "" ${ARGN})
if(NOT EXISTS ${OPENVDB_PRINT}) if(NOT EXISTS ${OPENVDB_PRINT})
if(NOT OpenVDB_FIND_QUIETLY)
message(WARNING "vdb_print not found! ${OPENVDB_PRINT}") message(WARNING "vdb_print not found! ${OPENVDB_PRINT}")
endif()
return() return()
endif() endif()
@ -148,7 +150,9 @@ function(OPENVDB_ABI_VERSION_FROM_PRINT OPENVDB_PRINT)
endif() endif()
if(${_VDB_PRINT_RETURN_STATUS}) if(${_VDB_PRINT_RETURN_STATUS})
if(NOT OpenVDB_FIND_QUIETLY)
message(WARNING "vdb_print returned with status ${_VDB_PRINT_RETURN_STATUS}") message(WARNING "vdb_print returned with status ${_VDB_PRINT_RETURN_STATUS}")
endif()
return() return()
endif() endif()

3
deps/GMP/GMP.cmake vendored
View file

@ -18,7 +18,8 @@ if (MSVC)
else () else ()
ExternalProject_Add(dep_GMP 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 BUILD_IN_SOURCE ON
CONFIGURE_COMMAND ./configure --enable-shared=no --enable-cxx=yes --enable-static=yes "--prefix=${DESTDIR}/usr/local" --with-pic CONFIGURE_COMMAND ./configure --enable-shared=no --enable-cxx=yes --enable-static=yes "--prefix=${DESTDIR}/usr/local" --with-pic
BUILD_COMMAND make -j BUILD_COMMAND make -j

View file

@ -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> From: tamasmeszaros <meszaros.q@gmail.com>
Date: Wed, 16 Oct 2019 17:42:50 +0200 Date: Thu, 23 Jan 2020 17:17:36 +0100
Subject: [PATCH] Build fixes for PrusaSlicer integration Subject: [PATCH] openvdb-mods
Signed-off-by: tamasmeszaros <meszaros.q@gmail.com>
--- ---
CMakeLists.txt | 3 - CMakeLists.txt | 3 -
cmake/FindBlosc.cmake | 218 --------------- cmake/CheckAtomic.cmake | 106 ++++++
cmake/FindBlosc.cmake | 218 ------------
cmake/FindCppUnit.cmake | 4 +- cmake/FindCppUnit.cmake | 4 +-
cmake/FindIlmBase.cmake | 337 ---------------------- cmake/FindIlmBase.cmake | 337 ------------------
cmake/FindOpenEXR.cmake | 329 ---------------------- cmake/FindOpenEXR.cmake | 329 ------------------
cmake/FindOpenVDB.cmake | 19 +- cmake/FindOpenVDB.cmake | 19 +-
cmake/FindTBB.cmake | 605 ++++++++++++++++++++-------------------- cmake/FindTBB.cmake | 599 ++++++++++++++++----------------
openvdb/CMakeLists.txt | 13 +- openvdb/CMakeLists.txt | 16 +-
openvdb/Grid.cc | 3 + openvdb/Grid.cc | 3 +
openvdb/PlatformConfig.h | 9 +- openvdb/PlatformConfig.h | 9 +-
openvdb/cmd/CMakeLists.txt | 4 +- openvdb/cmd/CMakeLists.txt | 4 +-
openvdb/unittest/CMakeLists.txt | 3 +- openvdb/unittest/CMakeLists.txt | 3 +-
openvdb/unittest/TestFile.cc | 2 +- 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/FindBlosc.cmake
delete mode 100644 cmake/FindIlmBase.cmake delete mode 100644 cmake/FindIlmBase.cmake
delete mode 100644 cmake/FindOpenEXR.cmake delete mode 100644 cmake/FindOpenEXR.cmake
@ -40,6 +41,119 @@ index 580b353..6d364c1 100644
cmake/FindOpenVDB.cmake cmake/FindOpenVDB.cmake
cmake/FindTBB.cmake cmake/FindTBB.cmake
cmake/OpenVDBGLFW3Setup.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 diff --git a/cmake/FindBlosc.cmake b/cmake/FindBlosc.cmake
deleted file mode 100644 deleted file mode 100644
index 5aacfdd..0000000 index 5aacfdd..0000000
@ -965,7 +1079,7 @@ index 339c1a2..0000000
- message(FATAL_ERROR "Unable to find OpenEXR") - message(FATAL_ERROR "Unable to find OpenEXR")
-endif() -endif()
diff --git a/cmake/FindOpenVDB.cmake b/cmake/FindOpenVDB.cmake diff --git a/cmake/FindOpenVDB.cmake b/cmake/FindOpenVDB.cmake
index 63a2eda..6211071 100644 index 63a2eda..d9f6d07 100644
--- a/cmake/FindOpenVDB.cmake --- a/cmake/FindOpenVDB.cmake
+++ b/cmake/FindOpenVDB.cmake +++ b/cmake/FindOpenVDB.cmake
@@ -244,7 +244,7 @@ set(OpenVDB_LIB_COMPONENTS "") @@ -244,7 +244,7 @@ set(OpenVDB_LIB_COMPONENTS "")
@ -1012,7 +1126,7 @@ index 63a2eda..6211071 100644
endforeach() endforeach()
diff --git a/cmake/FindTBB.cmake b/cmake/FindTBB.cmake diff --git a/cmake/FindTBB.cmake b/cmake/FindTBB.cmake
index bdf9c81..c6bdec9 100644 index bdf9c81..06093a4 100644
--- a/cmake/FindTBB.cmake --- a/cmake/FindTBB.cmake
+++ b/cmake/FindTBB.cmake +++ b/cmake/FindTBB.cmake
@@ -1,333 +1,332 @@ @@ -1,333 +1,332 @@
@ -1022,35 +1136,21 @@ index bdf9c81..c6bdec9 100644
-# All rights reserved. This software is distributed under the -# All rights reserved. This software is distributed under the
-# Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ ) -# Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ )
+# Copyright (c) 2015 Justus Calvin +# 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 +# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal +# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights +# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is +# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions: +# 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 -# * Neither the name of DreamWorks Animation nor the names of
-# its contributors may be used to endorse or promote products derived -# its contributors may be used to endorse or promote products derived
-# from this software without specific prior written permission. -# 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 -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# "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. -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-# IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE -# IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE
-# LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00. -# 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: -#[=======================================================================[.rst:
- -
@ -1142,6 +1249,13 @@ index bdf9c81..c6bdec9 100644
-if(POLICY CMP0057) -if(POLICY CMP0057)
- cmake_policy(SET CMP0057 NEW) - cmake_policy(SET CMP0057 NEW)
-endif() -endif()
+# FindTBB
+# -------
+#
+# Find TBB include directories and libraries.
+#
+# Usage:
+#
+# find_package(TBB [major[.minor]] [EXACT] +# find_package(TBB [major[.minor]] [EXACT]
+# [QUIET] [REQUIRED] +# [QUIET] [REQUIRED]
+# [[COMPONENTS] [components...]] +# [[COMPONENTS] [components...]]
@ -1244,12 +1358,10 @@ index bdf9c81..c6bdec9 100644
- set(TBB_FIND_COMPONENTS ${_TBB_COMPONENT_LIST}) - set(TBB_FIND_COMPONENTS ${_TBB_COMPONENT_LIST})
-endif() -endif()
+include(FindPackageHandleStandardArgs) +include(FindPackageHandleStandardArgs)
+
+find_package(Threads QUIET REQUIRED)
-# Append TBB_ROOT or $ENV{TBB_ROOT} if set (prioritize the direct cmake var) -# Append TBB_ROOT or $ENV{TBB_ROOT} if set (prioritize the direct cmake var)
-set(_TBB_ROOT_SEARCH_DIR "") -set(_TBB_ROOT_SEARCH_DIR "")
+if(NOT TBB_FOUND) +find_package(Threads QUIET REQUIRED)
-if(TBB_ROOT) -if(TBB_ROOT)
- list(APPEND _TBB_ROOT_SEARCH_DIR ${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}) - set(_ENV_TBB_ROOT $ENV{TBB_ROOT})
- if(_ENV_TBB_ROOT) - if(_ENV_TBB_ROOT)
- list(APPEND _TBB_ROOT_SEARCH_DIR ${_ENV_TBB_ROOT}) - list(APPEND _TBB_ROOT_SEARCH_DIR ${_ENV_TBB_ROOT})
+ ################################## - 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()
-endif() -endif()
+ +if(NOT TBB_FOUND)
+ ##################################
+ # 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()
-# Additionally try and use pkconfig to find Tbb -# 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}) - set(Tbb_VERSION ${Tbb_VERSION_MAJOR}.${Tbb_VERSION_MINOR})
-endif() -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 + # Set the TBB search library path search suffix based on the version of VC
+ if(WINDOWS_STORE) + if(WINDOWS_STORE)
+ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11_ui") + 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") + set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc10")
+ endif() + endif()
-# ------------------------------------------------------------------------ -# platform branching
-# Search for TBB lib DIR
-# ------------------------------------------------------------------------
+ # Add the library path search suffix for the VC independent version of TBB + # Add the library path search suffix for the VC independent version of TBB
+ list(APPEND TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc_mt") + 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) -if(UNIX)
- list(INSERT TBB_PATH_SUFFIXES 0 lib/x86_64-linux-gnu) - list(INSERT TBB_PATH_SUFFIXES 0 lib/x86_64-linux-gnu)
-endif() -endif()
+ # Search for the libraries + elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+ find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp}${TBB_STATIC_SUFFIX} + # OS X
+ HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR} + set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb")
+ PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
+ PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
-if(APPLE) -if(APPLE)
- if(TBB_FOR_CLANG) - 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 0 GCC_MAJOR)
- list(GET GCC_VERSION_COMPONENTS 1 GCC_MINOR) - list(GET GCC_VERSION_COMPONENTS 1 GCC_MINOR)
- list(INSERT TBB_PATH_SUFFIXES 0 lib/intel64/gcc${GCC_MAJOR}.${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) - list(INSERT TBB_PATH_SUFFIXES 0 lib/intel64/gcc4.4)
- endif() + set(TBB_LIB_PATH_SUFFIX "lib")
- endif() + 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() -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) -if(UNIX AND TBB_USE_STATIC_LIBS)
- set(_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES}) - set(_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
- set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") - set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
-endif() -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 "") -set(Tbb_LIB_COMPONENTS "")
- -
@ -1516,39 +1563,44 @@ index bdf9c81..c6bdec9 100644
- # Extract the directory and apply the matched text (in brackets) - # Extract the directory and apply the matched text (in brackets)
- get_filename_component(Tbb_${COMPONENT}_DIR "${Tbb_${COMPONENT}_LIBRARY}" DIRECTORY) - get_filename_component(Tbb_${COMPONENT}_DIR "${Tbb_${COMPONENT}_LIBRARY}" DIRECTORY)
- set(Tbb_${COMPONENT}_LIBRARY "${Tbb_${COMPONENT}_DIR}/${CMAKE_MATCH_1}") - 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()
+
+ # 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)
+
endif()
- endif() - endif()
+ endforeach() - 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()
- list(APPEND Tbb_LIB_COMPONENTS ${Tbb_${COMPONENT}_LIBRARY}) - list(APPEND Tbb_LIB_COMPONENTS ${Tbb_${COMPONENT}_LIBRARY})
+ ################################## + ##################################
+ # Set compile flags and libraries + # Find TBB components
+ ################################## + ##################################
- if(Tbb_${COMPONENT}_LIBRARY) - if(Tbb_${COMPONENT}_LIBRARY)
- set(TBB_${COMPONENT}_FOUND TRUE) - 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_${COMPONENT}_FOUND FALSE)
+ set(TBB_DEFINITIONS_RELEASE "") + set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc_proxy tbbmalloc tbb)
+ 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() endif()
-endforeach() -endforeach()
@ -1556,61 +1608,51 @@ index bdf9c81..c6bdec9 100644
- set(CMAKE_FIND_LIBRARY_SUFFIXES ${_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES}) - set(CMAKE_FIND_LIBRARY_SUFFIXES ${_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES})
- unset(_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES) - unset(_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES)
-endif() -endif()
+ set(TBB_DEFINITIONS "") + if(TBB_STATIC)
+ if (MSVC AND TBB_STATIC) + set(TBB_STATIC_SUFFIX "_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() + endif()
-# ------------------------------------------------------------------------ -# ------------------------------------------------------------------------
-# Cache and set TBB_FOUND -# 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_${_comp}_LIBRARY_DEBUG CACHE)
+ unset(TBB_BUILD_TYPE) + unset(TBB_${_comp}_LIBRARY_RELEASE CACHE)
+ unset(TBB_LIB_PATH_SUFFIX)
+ unset(TBB_DEFAULT_SEARCH_DIR)
+ +
+ if(TBB_DEBUG) + # Search for the libraries
+ message(STATUS " TBB_FOUND = ${TBB_FOUND}") + find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp}${TBB_STATIC_SUFFIX}
+ message(STATUS " TBB_INCLUDE_DIRS = ${TBB_INCLUDE_DIRS}") + HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
+ message(STATUS " TBB_DEFINITIONS = ${TBB_DEFINITIONS}") + PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
+ message(STATUS " TBB_LIBRARIES = ${TBB_LIBRARIES}") + PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
+ message(STATUS " TBB_DEFINITIONS_DEBUG = ${TBB_DEFINITIONS_DEBUG}") +
+ message(STATUS " TBB_LIBRARIES_DEBUG = ${TBB_LIBRARIES_DEBUG}") + find_library(TBB_${_comp}_LIBRARY_DEBUG ${_comp}${TBB_STATIC_SUFFIX}_debug
+ message(STATUS " TBB_DEFINITIONS_RELEASE = ${TBB_DEFINITIONS_RELEASE}") + HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
+ message(STATUS " TBB_LIBRARIES_RELEASE = ${TBB_LIBRARIES_RELEASE}") + 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() + 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) -include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(TBB -find_package_handle_standard_args(TBB
@ -1646,13 +1688,82 @@ index bdf9c81..c6bdec9 100644
- INTERFACE_COMPILE_OPTIONS "${Tbb_DEFINITIONS}" - INTERFACE_COMPILE_OPTIONS "${Tbb_DEFINITIONS}"
- INTERFACE_INCLUDE_DIRECTORIES "${Tbb_INCLUDE_DIR}" - INTERFACE_INCLUDE_DIRECTORIES "${Tbb_INCLUDE_DIR}"
- ) - )
- endif() endif()
- endforeach() endforeach()
-elseif(TBB_FIND_REQUIRED) -elseif(TBB_FIND_REQUIRED)
- message(FATAL_ERROR "Unable to find TBB") - 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() endif()
diff --git a/openvdb/CMakeLists.txt b/openvdb/CMakeLists.txt diff --git a/openvdb/CMakeLists.txt b/openvdb/CMakeLists.txt
index 89301bd..df27aae 100644 index 89301bd..6a3c90c 100644
--- a/openvdb/CMakeLists.txt --- a/openvdb/CMakeLists.txt
+++ b/openvdb/CMakeLists.txt +++ b/openvdb/CMakeLists.txt
@@ -78,7 +78,7 @@ else() @@ -78,7 +78,7 @@ else()
@ -1664,7 +1775,21 @@ index 89301bd..df27aae 100644
message(DEPRECATION "Support for TBB versions < ${FUTURE_MINIMUM_TBB_VERSION} " message(DEPRECATION "Support for TBB versions < ${FUTURE_MINIMUM_TBB_VERSION} "
"is deprecated and will be removed.") "is deprecated and will be removed.")
endif() 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()
endif() endif()
@ -1676,7 +1801,7 @@ index 89301bd..df27aae 100644
##### Core library configuration ##### Core library configuration
set(OPENVDB_LIBRARY_SOURCE_FILES 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) if(OPENVDB_CORE_SHARED)
add_library(openvdb_shared SHARED ${OPENVDB_LIBRARY_SOURCE_FILES}) add_library(openvdb_shared SHARED ${OPENVDB_LIBRARY_SOURCE_FILES})
@ -1779,5 +1904,5 @@ index df51830..0ab0c12 100644
/// @todo This changes the compressor setting globally. /// @todo This changes the compressor setting globally.
if (blosc_set_compressor(compname) < 0) continue; if (blosc_set_compressor(compname) < 0) continue;
-- --
2.16.2.windows.1 2.17.1

View 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

View 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

View 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

View file

@ -1,3 +1,4 @@
#add_subdirectory(slasupporttree) #add_subdirectory(slasupporttree)
#add_subdirectory(openvdb) #add_subdirectory(openvdb)
add_subdirectory(meshboolean) add_subdirectory(meshboolean)
add_subdirectory(opencsg)

View file

@ -1,12 +1,6 @@
if (SLIC3R_STATIC)
set(CGAL_Boost_USE_STATIC_LIBS ON)
endif ()
find_package(CGAL REQUIRED)
add_executable(meshboolean MeshBoolean.cpp) add_executable(meshboolean MeshBoolean.cpp)
target_link_libraries(meshboolean libslic3r CGAL::CGAL) target_link_libraries(meshboolean libslic3r)
if (WIN32) if (WIN32)
prusaslicer_copy_dlls(meshboolean) prusaslicer_copy_dlls(meshboolean)

View file

@ -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 <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> #include <boost/log/trivial.hpp>
namespace Slic3r { 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 } // namespace Slic3r
int main(const int argc, const char * argv[]) 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; return 0;
} }

View 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
)

View 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

View 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

View 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

View 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
View 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() {}

View file

@ -1,2 +1,7 @@
add_executable(openvdb_example openvdb_example.cpp) if(TARGET OpenVDB::openvdb)
target_link_libraries(openvdb_example libslic3r) add_executable(openvdb_example openvdb_example.cpp)
target_link_libraries(openvdb_example libslic3r)
target_link_libraries(openvdb_example OpenVDB::openvdb)
endif()

View file

@ -9,6 +9,11 @@ if (MINGW)
add_compile_options(-Wa,-mbig-obj) add_compile_options(-Wa,-mbig-obj)
endif () endif ()
set(OpenVDBUtils_SOURCES "")
if (TARGET OpenVDB::openvdb)
set(OpenVDBUtils_SOURCES OpenVDBUtils.cpp OpenVDBUtils.hpp)
endif()
add_library(libslic3r STATIC add_library(libslic3r STATIC
pchheader.cpp pchheader.cpp
pchheader.hpp pchheader.hpp
@ -149,9 +154,9 @@ add_library(libslic3r STATIC
ShortestPath.cpp ShortestPath.cpp
ShortestPath.hpp ShortestPath.hpp
SLAPrint.cpp SLAPrint.cpp
SLAPrintSteps.cpp
SLAPrintSteps.hpp
SLAPrint.hpp SLAPrint.hpp
SLA/SLAAutoSupports.hpp
SLA/SLAAutoSupports.cpp
Slicing.cpp Slicing.cpp
Slicing.hpp Slicing.hpp
SlicingAdaptive.cpp SlicingAdaptive.cpp
@ -180,35 +185,65 @@ add_library(libslic3r STATIC
MinAreaBoundingBox.cpp MinAreaBoundingBox.cpp
miniz_extension.hpp miniz_extension.hpp
miniz_extension.cpp miniz_extension.cpp
SLA/SLACommon.hpp SimplifyMesh.hpp
SLA/SLABoilerPlate.hpp SimplifyMeshImpl.hpp
SLA/SLAPad.hpp SimplifyMesh.cpp
SLA/SLAPad.cpp ${OpenVDBUtils_SOURCES}
SLA/SLASupportTreeBuilder.hpp SLA/Common.hpp
SLA/SLASupportTreeBuildsteps.hpp SLA/Common.cpp
SLA/SLASupportTreeBuildsteps.cpp SLA/Pad.hpp
SLA/SLASupportTreeBuilder.cpp SLA/Pad.cpp
SLA/SLAConcurrency.hpp SLA/SupportTreeBuilder.hpp
SLA/SLASupportTree.hpp SLA/SupportTreeBuildsteps.hpp
SLA/SLASupportTree.cpp SLA/SupportTreeBuildsteps.cpp
SLA/SLASupportTreeIGL.cpp SLA/SupportTreeBuilder.cpp
SLA/SLARotfinder.hpp SLA/Concurrency.hpp
SLA/SLARotfinder.cpp SLA/SupportTree.hpp
SLA/SLABoostAdapter.hpp SLA/SupportTree.cpp
SLA/SLASpatIndex.hpp # SLA/SupportTreeIGL.cpp
SLA/SLARaster.hpp SLA/Rotfinder.hpp
SLA/SLARaster.cpp SLA/Rotfinder.cpp
SLA/SLARasterWriter.hpp SLA/BoostAdapter.hpp
SLA/SLARasterWriter.cpp SLA/SpatIndex.hpp
SLA/Raster.hpp
SLA/Raster.cpp
SLA/RasterWriter.hpp
SLA/RasterWriter.cpp
SLA/ConcaveHull.hpp SLA/ConcaveHull.hpp
SLA/ConcaveHull.cpp 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_STATIC)
set(CGAL_Boost_USE_STATIC_LIBS ON CACHE BOOL "" FORCE)
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE)
endif () 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_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}) 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 qhull
semver semver
TBB::tbb TBB::tbb
# OpenVDB::openvdb $<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_LINK_LIBRARIES>
${CMAKE_DL_LIBS} ${CMAKE_DL_LIBS}
) )
if (TARGET OpenVDB::openvdb)
target_link_libraries(libslic3r OpenVDB::openvdb)
endif()
if(WIN32) if(WIN32)
target_link_libraries(libslic3r Psapi.lib) target_link_libraries(libslic3r Psapi.lib)
endif() endif()
@ -239,3 +278,9 @@ endif()
if(SLIC3R_PROFILE) if(SLIC3R_PROFILE)
target_link_libraries(slic3r Shiny) target_link_libraries(slic3r Shiny)
endif() endif()
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE)
endif ()
target_sources(libslic3r PRIVATE $<TARGET_OBJECTS:libslic3r_cgal>)

View file

@ -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_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 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_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 std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml";
const char* MODEL_TAG = "model"; const char* MODEL_TAG = "model";
@ -385,6 +386,7 @@ namespace Slic3r {
typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap; typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap; 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::SupportPoint>> IdToSlaSupportPointsMap;
typedef std::map<int, std::vector<sla::DrainHole>> IdToSlaDrainHolesMap;
// Version of the 3mf file // Version of the 3mf file
unsigned int m_version; unsigned int m_version;
@ -403,6 +405,7 @@ namespace Slic3r {
IdToLayerHeightsProfileMap m_layer_heights_profiles; IdToLayerHeightsProfileMap m_layer_heights_profiles;
IdToLayerConfigRangesMap m_layer_config_ranges; IdToLayerConfigRangesMap m_layer_config_ranges;
IdToSlaSupportPointsMap m_sla_support_points; IdToSlaSupportPointsMap m_sla_support_points;
IdToSlaDrainHolesMap m_sla_drain_holes;
std::string m_curr_metadata_name; std::string m_curr_metadata_name;
std::string m_curr_characters; std::string m_curr_characters;
std::string m_name; 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_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_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_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); 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 file
_extract_sla_support_points_from_archive(archive, stat); _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)) else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE))
{ {
// extract slic3r print config file // extract slic3r print config file
@ -684,6 +693,11 @@ namespace Slic3r {
model_object->sla_points_status = sla::PointsStatus::UserModified; 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); IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first);
if (obj_metadata != m_objects_metadata.end()) if (obj_metadata != m_objects_metadata.end())
{ {
@ -955,8 +969,9 @@ namespace Slic3r {
// Info on format versioning - see 3mf.hpp // Info on format versioning - see 3mf.hpp
int version = 0; int version = 0;
if (!objects.empty() && objects[0].find("support_points_format_version=") != std::string::npos) { std::string key("support_points_format_version=");
objects[0].erase(objects[0].begin(), objects[0].begin() + 30); // removes the string 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]); version = std::stoi(objects[0]);
objects.erase(objects.begin()); // pop the header objects.erase(objects.begin()); // pop the header
} }
@ -1023,6 +1038,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) 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_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_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_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_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_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); bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model);
@ -2026,6 +2126,14 @@ namespace Slic3r {
return false; 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"). // 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 // All custom gcode per height of whole Model are stored here
if (!_add_custom_gcode_per_print_z_file_to_archive(archive, model)) if (!_add_custom_gcode_per_print_z_file_to_archive(archive, model))
@ -2483,6 +2591,50 @@ namespace Slic3r {
return true; 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) bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config)
{ {
char buffer[1024]; char buffer[1024];

View file

@ -20,6 +20,10 @@ namespace Slic3r {
support_points_format_version = 1 support_points_format_version = 1
}; };
enum {
drain_holes_format_version = 1
};
class Model; class Model;
class DynamicPrintConfig; class DynamicPrintConfig;
#if ENABLE_THUMBNAIL_GENERATOR #if ENABLE_THUMBNAIL_GENERATOR

View file

@ -355,6 +355,35 @@ bool objparse(const char *path, ObjData &data)
return true; 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> template<typename T>
bool savevector(FILE *pFile, const std::vector<T> &v) bool savevector(FILE *pFile, const std::vector<T> &v)
{ {

View file

@ -3,6 +3,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <istream>
namespace ObjParser { namespace ObjParser {
@ -97,6 +98,7 @@ struct ObjData {
}; };
extern bool objparse(const char *path, ObjData &data); 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); extern bool objbinsave(const char *path, const ObjData &data);

View 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

View 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_

View file

@ -620,6 +620,7 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs)
assert(this->config.id() == rhs.config.id()); assert(this->config.id() == rhs.config.id());
this->sla_support_points = rhs.sla_support_points; this->sla_support_points = rhs.sla_support_points;
this->sla_points_status = rhs.sla_points_status; 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_config_ranges = rhs.layer_config_ranges; // #ys_FIXME_experiment
this->layer_height_profile = rhs.layer_height_profile; this->layer_height_profile = rhs.layer_height_profile;
this->printable = rhs.printable; this->printable = rhs.printable;
@ -660,6 +661,7 @@ ModelObject& ModelObject::assign_copy(ModelObject &&rhs)
assert(this->config.id() == rhs.config.id()); assert(this->config.id() == rhs.config.id());
this->sla_support_points = std::move(rhs.sla_support_points); this->sla_support_points = std::move(rhs.sla_support_points);
this->sla_points_status = std::move(rhs.sla_points_status); 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_config_ranges = std::move(rhs.layer_config_ranges); // #ys_FIXME_experiment
this->layer_height_profile = std::move(rhs.layer_height_profile); this->layer_height_profile = std::move(rhs.layer_height_profile);
this->origin_translation = std::move(rhs.origin_translation); 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) { if (keep_upper) {
upper->set_model(nullptr); upper->set_model(nullptr);
upper->sla_support_points.clear(); upper->sla_support_points.clear();
lower->sla_drain_holes.clear();
upper->sla_points_status = sla::PointsStatus::NoPoints; upper->sla_points_status = sla::PointsStatus::NoPoints;
upper->clear_volumes(); upper->clear_volumes();
upper->input_file = ""; upper->input_file = "";
@ -1121,6 +1124,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
if (keep_lower) { if (keep_lower) {
lower->set_model(nullptr); lower->set_model(nullptr);
lower->sla_support_points.clear(); lower->sla_support_points.clear();
lower->sla_drain_holes.clear();
lower->sla_points_status = sla::PointsStatus::NoPoints; lower->sla_points_status = sla::PointsStatus::NoPoints;
lower->clear_volumes(); lower->clear_volumes();
lower->input_file = ""; 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_upper) { upper->add_volume(*volume); }
if (keep_lower) { lower->add_volume(*volume); } if (keep_lower) { lower->add_volume(*volume); }
} }
else { else if (! volume->mesh().empty()) {
TriangleMesh upper_mesh, lower_mesh; TriangleMesh upper_mesh, lower_mesh;
// Transform the mesh by the combined transformation matrix. // Transform the mesh by the combined transformation matrix.
@ -1165,6 +1170,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
mesh.transform(instance_matrix * volume_matrix, true); mesh.transform(instance_matrix * volume_matrix, true);
volume->reset_mesh(); volume->reset_mesh();
mesh.require_shared_vertices();
// Perform cut // Perform cut
TriangleMeshSlicer tms(&mesh); TriangleMeshSlicer tms(&mesh);
tms.cut(float(z), &upper_mesh, &lower_mesh); tms.cut(float(z), &upper_mesh, &lower_mesh);

View file

@ -8,7 +8,8 @@
#include "Point.hpp" #include "Point.hpp"
#include "PrintConfig.hpp" #include "PrintConfig.hpp"
#include "Slicing.hpp" #include "Slicing.hpp"
#include "SLA/SLACommon.hpp" #include "SLA/SupportPoint.hpp"
#include "SLA/Hollowing.hpp"
#include "TriangleMesh.hpp" #include "TriangleMesh.hpp"
#include "Arrange.hpp" #include "Arrange.hpp"
#include "CustomGCode.hpp" #include "CustomGCode.hpp"
@ -199,11 +200,14 @@ public:
// This vector holds position of selected support points for SLA. The data are // This vector holds position of selected support points for SLA. The data are
// saved in mesh coordinates to allow using them for several instances. // saved in mesh coordinates to allow using them for several instances.
// The format is (x, y, z, point_size, supports_island) // 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 // To keep track of where the points came from (used for synchronization between
// the SLA gizmo and the backend). // 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 /* This vector accumulates the total translation applied to the object by the
center_around_origin() method. Callers might want to apply the same translation center_around_origin() method. Callers might want to apply the same translation
to new volumes before adding them to this object in order to preserve alignment to new volumes before adding them to this object in order to preserve alignment
@ -373,7 +377,7 @@ private:
template<class Archive> void serialize(Archive &ar) { template<class Archive> void serialize(Archive &ar) {
ar(cereal::base_class<ObjectBase>(this)); ar(cereal::base_class<ObjectBase>(this));
Internal::StaticSerializationWrapper<ModelConfig> config_wrapper(config); 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); 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);
} }
}; };

View 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

View 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

View file

@ -2873,6 +2873,41 @@ void PrintConfigDef::init_sla_params()
def->min = 0; def->min = 0;
def->mode = comExpert; def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0.3)); 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) void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value)

View file

@ -1017,7 +1017,7 @@ public:
ConfigOptionFloat support_base_height /*= 1.0*/; ConfigOptionFloat support_base_height /*= 1.0*/;
// The minimum distance of the pillar base from the model in mm. // 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. // The default angle for connecting support sticks and junctions.
ConfigOptionFloat support_critical_angle /*= 45*/; ConfigOptionFloat support_critical_angle /*= 45*/;
@ -1062,7 +1062,7 @@ public:
// ///////////////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////////////
// Zero elevation mode parameters: // 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 // - There will be a gap between the object pad and the generated pad
// according to the support_base_safety_distance parameter. // according to the support_base_safety_distance parameter.
// - The two pads will be connected with tiny connector sticks // - The two pads will be connected with tiny connector sticks
@ -1085,6 +1085,28 @@ public:
// How much should the tiny connectors penetrate into the model body // How much should the tiny connectors penetrate into the model body
ConfigOptionFloat pad_object_connector_penetration; 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: protected:
void initialize(StaticCacheBase &cache, const char *base_ptr) void initialize(StaticCacheBase &cache, const char *base_ptr)
{ {
@ -1121,6 +1143,10 @@ protected:
OPT_PTR(pad_object_connector_stride); OPT_PTR(pad_object_connector_stride);
OPT_PTR(pad_object_connector_width); OPT_PTR(pad_object_connector_width);
OPT_PTR(pad_object_connector_penetration); OPT_PTR(pad_object_connector_penetration);
OPT_PTR(hollowing_enable);
OPT_PTR(hollowing_min_thickness);
OPT_PTR(hollowing_quality);
OPT_PTR(hollowing_closing_distance);
} }
}; };

View file

@ -1,7 +1,7 @@
#ifndef SLABOOSTADAPTER_HPP #ifndef SLA_BOOSTADAPTER_HPP
#define SLABOOSTADAPTER_HPP #define SLA_BOOSTADAPTER_HPP
#include "SLA/SLABoilerPlate.hpp" #include <libslic3r/SLA/Common.hpp>
#include <boost/geometry.hpp> #include <boost/geometry.hpp>
namespace boost { namespace boost {

View 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

View 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

View 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

View file

@ -1,7 +1,9 @@
#include "ConcaveHull.hpp" #include <libslic3r/SLA/ConcaveHull.hpp>
#include <libslic3r/SLA/SpatIndex.hpp>
#include <libslic3r/MTUtils.hpp> #include <libslic3r/MTUtils.hpp>
#include <libslic3r/ClipperUtils.hpp> #include <libslic3r/ClipperUtils.hpp>
#include "SLASpatIndex.hpp"
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
namespace Slic3r { namespace Slic3r {
@ -40,7 +42,7 @@ Point ConcaveHull::centroid(const Points &pp)
// As it shows, the current offset_ex in ClipperUtils hangs if used in jtRound // As it shows, the current offset_ex in ClipperUtils hangs if used in jtRound
// mode // mode
ClipperLib::Paths fast_offset(const ClipperLib::Paths &paths, static ClipperLib::Paths fast_offset(const ClipperLib::Paths &paths,
coord_t delta, coord_t delta,
ClipperLib::JoinType jointype) ClipperLib::JoinType jointype)
{ {
@ -73,7 +75,7 @@ Points ConcaveHull::calculate_centroids() const
Points centroids = reserve_vector<Point>(m_polys.size()); Points centroids = reserve_vector<Point>(m_polys.size());
std::transform(m_polys.begin(), m_polys.end(), std::transform(m_polys.begin(), m_polys.end(),
std::back_inserter(centroids), std::back_inserter(centroids),
[this](const Polygon &poly) { return centroid(poly); }); [](const Polygon &poly) { return centroid(poly); });
return centroids; return centroids;
} }

View file

@ -1,5 +1,5 @@
#ifndef CONCAVEHULL_HPP #ifndef SLA_CONCAVEHULL_HPP
#define CONCAVEHULL_HPP #define SLA_CONCAVEHULL_HPP
#include <libslic3r/ExPolygon.hpp> #include <libslic3r/ExPolygon.hpp>

View file

@ -1,5 +1,5 @@
#ifndef SLACONCURRENCY_H #ifndef SLA_CONCURRENCY_H
#define SLACONCURRENCY_H #define SLA_CONCURRENCY_H
#include <tbb/spin_mutex.h> #include <tbb/spin_mutex.h>
#include <tbb/mutex.h> #include <tbb/mutex.h>

View 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

View 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

View 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

View 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

View 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

View 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

View file

@ -1,10 +1,12 @@
#include "SLAPad.hpp" #include <libslic3r/SLA/Pad.hpp>
#include "SLABoilerPlate.hpp" #include <libslic3r/SLA/Common.hpp>
#include "SLASpatIndex.hpp" #include <libslic3r/SLA/SpatIndex.hpp>
#include <libslic3r/SLA/BoostAdapter.hpp>
#include <libslic3r/SLA/Contour3D.hpp>
#include "ConcaveHull.hpp" #include "ConcaveHull.hpp"
#include "boost/log/trivial.hpp" #include "boost/log/trivial.hpp"
#include "SLABoostAdapter.hpp"
#include "ClipperUtils.hpp" #include "ClipperUtils.hpp"
#include "Tesselate.hpp" #include "Tesselate.hpp"
#include "MTUtils.hpp" #include "MTUtils.hpp"
@ -69,7 +71,7 @@ Contour3D walls(
// Shorthand for the vertex arrays // Shorthand for the vertex arrays
auto& upts = upper.points, &lpts = lower.points; 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 // If the Z levels are flipped, or the offset difference is negative, we
// will interpret that as the triangles normals should be inverted. // will interpret that as the triangles normals should be inverted.
@ -676,7 +678,7 @@ void create_pad(const ExPolygons &sup_blueprint,
ThrowOnCancel thr) ThrowOnCancel thr)
{ {
Contour3D t = create_pad_geometry(sup_blueprint, model_blueprint, cfg, 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 std::string PadConfig::validate() const

View file

@ -1,5 +1,5 @@
#ifndef SLABASEPOOL_HPP #ifndef SLA_PAD_HPP
#define SLABASEPOOL_HPP #define SLA_PAD_HPP
#include <vector> #include <vector>
#include <functional> #include <functional>

View file

@ -3,7 +3,7 @@
#include <functional> #include <functional>
#include "SLARaster.hpp" #include <libslic3r/SLA/Raster.hpp>
#include "libslic3r/ExPolygon.hpp" #include "libslic3r/ExPolygon.hpp"
#include "libslic3r/MTUtils.hpp" #include "libslic3r/MTUtils.hpp"
#include <libnest2d/backends/clipper/clipper_polygon.hpp> #include <libnest2d/backends/clipper/clipper_polygon.hpp>

View file

@ -1,5 +1,5 @@
#ifndef SLARASTER_HPP #ifndef SLA_RASTER_HPP
#define SLARASTER_HPP #define SLA_RASTER_HPP
#include <ostream> #include <ostream>
#include <memory> #include <memory>

View file

@ -1,6 +1,8 @@
#include "SLARasterWriter.hpp" #include <libslic3r/SLA/RasterWriter.hpp>
#include "libslic3r/Zipper.hpp"
#include "libslic3r/Time.hpp" #include "libslic3r/PrintConfig.hpp"
#include <libslic3r/Zipper.hpp>
#include <libslic3r/Time.hpp>
#include "ExPolygon.hpp" #include "ExPolygon.hpp"
#include <libnest2d/backends/clipper/clipper_polygon.hpp> #include <libnest2d/backends/clipper/clipper_polygon.hpp>

View file

@ -1,5 +1,5 @@
#ifndef SLARASTERWRITER_HPP #ifndef SLA_RASTERWRITER_HPP
#define SLARASTERWRITER_HPP #define SLA_RASTERWRITER_HPP
// For png export of the sliced model // For png export of the sliced model
#include <fstream> #include <fstream>
@ -9,12 +9,14 @@
#include <map> #include <map>
#include <array> #include <array>
#include "libslic3r/PrintConfig.hpp" #include <libslic3r/SLA/Raster.hpp>
#include <libslic3r/Zipper.hpp>
#include "SLARaster.hpp" namespace Slic3r {
#include "libslic3r/Zipper.hpp"
namespace Slic3r { namespace sla { class DynamicPrintConfig;
namespace sla {
// API to write the zipped sla output layers and metadata. // API to write the zipped sla output layers and metadata.
// Implementation uses PNG raster output. // Implementation uses PNG raster output.

View file

@ -2,9 +2,9 @@
#include <exception> #include <exception>
#include <libnest2d/optimizers/nlopt/genetic.hpp> #include <libnest2d/optimizers/nlopt/genetic.hpp>
#include "SLABoilerPlate.hpp" #include <libslic3r/SLA/Common.hpp>
#include "SLARotfinder.hpp" #include <libslic3r/SLA/Rotfinder.hpp>
#include "SLASupportTree.hpp" #include <libslic3r/SLA/SupportTree.hpp>
#include "Model.hpp" #include "Model.hpp"
namespace Slic3r { namespace Slic3r {

View file

@ -1,5 +1,5 @@
#ifndef SLAROTFINDER_HPP #ifndef SLA_ROTFINDER_HPP
#define SLAROTFINDER_HPP #define SLA_ROTFINDER_HPP
#include <functional> #include <functional>
#include <array> #include <array>

View file

@ -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

View file

@ -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

View file

@ -1,5 +1,5 @@
#ifndef SPATINDEX_HPP #ifndef SLA_SPATINDEX_HPP
#define SPATINDEX_HPP #define SLA_SPATINDEX_HPP
#include <memory> #include <memory>
#include <utility> #include <utility>

View 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

View file

@ -3,7 +3,7 @@
#include <tbb/parallel_for.h> #include <tbb/parallel_for.h>
#include "SLAAutoSupports.hpp" #include "SupportPointGenerator.hpp"
#include "Model.hpp" #include "Model.hpp"
#include "ExPolygon.hpp" #include "ExPolygon.hpp"
#include "SVG.hpp" #include "SVG.hpp"
@ -18,7 +18,7 @@
namespace Slic3r { namespace Slic3r {
namespace sla { 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(); n1.normalize();
n2.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 // 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). // 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))); 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)); return 1./(2.4*get_required_density(angle));
}*/ }*/
SLAAutoSupports::SLAAutoSupports(const sla::EigenMesh3D & emesh, SupportPointGenerator::SupportPointGenerator(
const sla::EigenMesh3D &emesh,
const std::vector<ExPolygons> &slices, const std::vector<ExPolygons> &slices,
const std::vector<float> & heights, const std::vector<float> & heights,
const Config & config, const Config & config,
std::function<void(void)> throw_on_cancel, std::function<void(void)> throw_on_cancel,
std::function<void(int)> statusfn) 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_config(config)
, m_emesh(emesh) , m_emesh(emesh)
, m_throw_on_cancel(throw_on_cancel) , m_throw_on_cancel(throw_on_cancel)
, m_statusfn(statusfn) , m_statusfn(statusfn)
{
}
void SupportPointGenerator::execute(const std::vector<ExPolygons> &slices,
const std::vector<float> & heights)
{ {
process(slices, heights); process(slices, heights);
project_onto_mesh(m_output); 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. // 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(); m_throw_on_cancel();
Vec3f& p = points[point_id].pos; Vec3f& p = points[point_id].pos;
// Project the point upward and downward and choose the closer intersection with the mesh. // 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_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.)); 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 up = hit_up.is_hit();
bool down = hit_down.face() != -1; bool down = hit_down.is_hit();
if (!up && !down) if (!up && !down)
continue; continue;
sla::EigenMesh3D::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down; 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>(); 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, const std::vector<ExPolygons>& slices, const std::vector<float>& heights,
std::function<void(void)> throw_on_cancel) std::function<void(void)> throw_on_cancel)
{ {
assert(slices.size() == heights.size()); assert(slices.size() == heights.size());
// Allocate empty layers. // Allocate empty layers.
std::vector<SLAAutoSupports::MyLayer> layers; std::vector<SupportPointGenerator::MyLayer> layers;
layers.reserve(slices.size()); layers.reserve(slices.size());
for (size_t i = 0; i < slices.size(); ++ i) for (size_t i = 0; i < slices.size(); ++ i)
layers.emplace_back(i, heights[i]); layers.emplace_back(i, heights[i]);
@ -122,7 +133,7 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers(
if ((layer_id % 8) == 0) if ((layer_id % 8) == 0)
// Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves.
throw_on_cancel(); throw_on_cancel();
SLAAutoSupports::MyLayer &layer = layers[layer_id]; SupportPointGenerator::MyLayer &layer = layers[layer_id];
const ExPolygons &islands = slices[layer_id]; const ExPolygons &islands = slices[layer_id];
//FIXME WTF? //FIXME WTF?
const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0])); 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) if ((layer_id % 2) == 0)
// Don't call the following function too often as it flushes CPU write caches due to synchronization primitves. // Don't call the following function too often as it flushes CPU write caches due to synchronization primitves.
throw_on_cancel(); throw_on_cancel();
SLAAutoSupports::MyLayer &layer_above = layers[layer_id]; SupportPointGenerator::MyLayer &layer_above = layers[layer_id];
SLAAutoSupports::MyLayer &layer_below = layers[layer_id - 1]; SupportPointGenerator::MyLayer &layer_below = layers[layer_id - 1];
//FIXME WTF? //FIXME WTF?
const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]); 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 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_angle = 75.f * (float(M_PI)/180.f); // smaller number - less supports
const float slope_offset = float(scale_(layer_height / std::tan(slope_angle))); 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. //FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands.
for (SLAAutoSupports::Structure &top : layer_above.islands) { for (SupportPointGenerator::Structure &top : layer_above.islands) {
for (SLAAutoSupports::Structure &bottom : layer_below.islands) { for (SupportPointGenerator::Structure &bottom : layer_below.islands) {
float overlap_area = top.overlap_area(bottom); float overlap_area = top.overlap_area(bottom);
if (overlap_area > 0) { if (overlap_area > 0) {
top.islands_below.emplace_back(&bottom, overlap_area); top.islands_below.emplace_back(&bottom, overlap_area);
@ -191,13 +202,13 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers(
return 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; 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; PointGrid3D point_grid;
point_grid.cell_size = Vec3f(10.f, 10.f, 10.f); 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; double status = 0;
for (unsigned int layer_id = 0; layer_id < layers.size(); ++ layer_id) { for (unsigned int layer_id = 0; layer_id < layers.size(); ++ layer_id) {
SLAAutoSupports::MyLayer *layer_top = &layers[layer_id]; SupportPointGenerator::MyLayer *layer_top = &layers[layer_id];
SLAAutoSupports::MyLayer *layer_bottom = (layer_id > 0) ? &layers[layer_id - 1] : nullptr; SupportPointGenerator::MyLayer *layer_bottom = (layer_id > 0) ? &layers[layer_id - 1] : nullptr;
std::vector<float> support_force_bottom; std::vector<float> support_force_bottom;
if (layer_bottom != nullptr) { if (layer_bottom != nullptr) {
support_force_bottom.assign(layer_bottom->islands.size(), 0.f); 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; status += increment;
m_statusfn(int(std::round(status))); 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); /*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(expolys_top, "top" + layer_num_str + ".svg");
output_expolygons(diff, "diff" + layer_num_str + ".svg"); output_expolygons(diff, "diff" + layer_num_str + ".svg");
if (!islands.empty()) if (!islands.empty())
output_expolygons(islands, "islands" + layer_num_str + ".svg");*/ 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; 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)); //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; 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. //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, m_rng);
std::vector<Vec2f> raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, rng);
std::vector<Vec2f> poisson_samples; std::vector<Vec2f> poisson_samples;
for (size_t iter = 0; iter < 4; ++ iter) { for (size_t iter = 0; iter < 4; ++ iter) {
poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius, 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); min_spacing = std::max(m_config.minimal_distance, min_spacing * coeff);
} }
#ifdef SLA_AUTOSUPPORTS_DEBUG #ifdef SLA_SUPPORTPOINTGEN_DEBUG
{ {
static int irun = 0; static int irun = 0;
Slic3r::SVG svg(debug_out_path("SLA_supports-uniformly_cover-%d.svg", irun ++), get_extents(islands)); 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()); // assert(! poisson_samples.empty());
if (poisson_samples_target < poisson_samples.size()) { 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()); poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end());
} }
for (const Vec2f &pt : poisson_samples) { 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()); pts.erase(endit, pts.end());
} }
#ifdef SLA_AUTOSUPPORTS_DEBUG #ifdef SLA_SUPPORTPOINTGEN_DEBUG
void SLAAutoSupports::output_structures(const std::vector<Structure>& structures) void SupportPointGenerator::output_structures(const std::vector<Structure>& structures)
{ {
for (unsigned int i=0 ; i<structures.size(); ++i) { for (unsigned int i=0 ; i<structures.size(); ++i) {
std::stringstream ss; 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)); BoundingBox bb(Point(-30000000, -30000000), Point(30000000, 30000000));
Slic3r::SVG svg_cummulative(filename, bb); Slic3r::SVG svg_cummulative(filename, bb);

View file

@ -1,19 +1,23 @@
#ifndef SLAAUTOSUPPORTS_HPP_ #ifndef SLA_SUPPORTPOINTGENERATOR_HPP
#define SLAAUTOSUPPORTS_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/ClipperUtils.hpp>
#include <libslic3r/Point.hpp> #include <libslic3r/Point.hpp>
#include <libslic3r/TriangleMesh.hpp> #include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/SLA/SLACommon.hpp>
#include <boost/container/small_vector.hpp> #include <boost/container/small_vector.hpp>
// #define SLA_AUTOSUPPORTS_DEBUG // #define SLA_SUPPORTPOINTGEN_DEBUG
namespace Slic3r { namespace Slic3r { namespace sla {
namespace sla {
class SLAAutoSupports { class SupportPointGenerator {
public: public:
struct Config { struct Config {
float density_relative {1.f}; float density_relative {1.f};
@ -24,19 +28,22 @@ public:
inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2) 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, 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); const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
const std::vector<sla::SupportPoint>& output() { return m_output; } 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; struct MyLayer;
struct Structure { struct Structure {
Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f &centroid, float area, float h) : Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f &centroid, float area, float h) :
layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), height(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())) , 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; MyLayer *layer;
const ExPolygon* polygon = nullptr; const ExPolygon* polygon = nullptr;
@ -49,9 +56,9 @@ public:
float supports_force_this_layer = 0.f; float supports_force_this_layer = 0.f;
float supports_force_inherited = 0.f; float supports_force_inherited = 0.f;
float supports_force_total() const { return this->supports_force_this_layer + this->supports_force_inherited; } 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; std::chrono::milliseconds unique_id;
#endif /* SLA_AUTOSUPPORTS_DEBUG */ #endif /* SLA_SUPPORTPOINTGEN_DEBUG */
struct Link { 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) {}
@ -74,7 +81,7 @@ public:
ExPolygons overhangs; ExPolygons overhangs;
// Overhangs, where the surface must slope. // Overhangs, where the surface must slope.
ExPolygons overhangs_slopes; ExPolygons overhangs_slopes;
float overhangs_area; float overhangs_area = 0.f;
bool overlaps(const Structure &rhs) const { bool overlaps(const Structure &rhs) const {
return this->bbox.overlap(rhs.bbox) && (this->polygon->overlaps(*rhs.polygon) || rhs.polygon->overlaps(*this->polygon)); return this->bbox.overlap(rhs.bbox) && (this->polygon->overlaps(*rhs.polygon) || rhs.polygon->overlaps(*this->polygon));
@ -182,29 +189,33 @@ public:
} }
}; };
private: void execute(const std::vector<ExPolygons> &slices,
std::vector<sla::SupportPoint> m_output; const std::vector<float> & heights);
SLAAutoSupports::Config m_config; void seed(std::mt19937::result_type s) { m_rng.seed(s); }
private:
std::vector<SupportPoint> m_output;
SupportPointGenerator::Config m_config;
void process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights); 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 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_expolygons(const ExPolygons& expolys, const std::string &filename);
static void output_structures(const std::vector<Structure> &structures); static void output_structures(const std::vector<Structure> &structures);
#endif // SLA_AUTOSUPPORTS_DEBUG #endif // SLA_SUPPORTPOINTGEN_DEBUG
const sla::EigenMesh3D& m_emesh; const EigenMesh3D& m_emesh;
std::function<void(void)> m_throw_on_cancel; std::function<void(void)> m_throw_on_cancel;
std::function<void(int)> m_statusfn; std::function<void(int)> m_statusfn;
std::mt19937 m_rng;
}; };
void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance); void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance);
} // namespace sla }} // namespace Slic3r::sla
} // namespace Slic3r
#endif // SUPPORTPOINTGENERATOR_HPP
#endif // SLAAUTOSUPPORTS_HPP_

View file

@ -4,10 +4,10 @@
*/ */
#include <numeric> #include <numeric>
#include "SLASupportTree.hpp" #include <libslic3r/SLA/SupportTree.hpp>
#include "SLABoilerPlate.hpp" #include <libslic3r/SLA/Common.hpp>
#include "SLASpatIndex.hpp" #include <libslic3r/SLA/SpatIndex.hpp>
#include "SLASupportTreeBuilder.hpp" #include <libslic3r/SLA/SupportTreeBuilder.hpp>
#include <libslic3r/MTUtils.hpp> #include <libslic3r/MTUtils.hpp>
#include <libslic3r/ClipperUtils.hpp> #include <libslic3r/ClipperUtils.hpp>

View file

@ -1,12 +1,15 @@
#ifndef SLASUPPORTTREE_HPP #ifndef SLA_SUPPORTTREE_HPP
#define SLASUPPORTTREE_HPP #define SLA_SUPPORTTREE_HPP
#include <vector> #include <vector>
#include <memory> #include <memory>
#include <Eigen/Geometry> #include <Eigen/Geometry>
#include "SLACommon.hpp" #include <libslic3r/SLA/Common.hpp>
#include "SLAPad.hpp" #include <libslic3r/SLA/Pad.hpp>
#include <libslic3r/SLA/EigenMesh3D.hpp>
#include <libslic3r/SLA/SupportPoint.hpp>
#include <libslic3r/SLA/JobController.hpp>
namespace Slic3r { namespace Slic3r {
@ -105,27 +108,6 @@ struct SupportConfig
enum class MeshType { Support, Pad }; 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 struct SupportableMesh
{ {
EigenMesh3D emesh; EigenMesh3D emesh;

View file

@ -1,5 +1,6 @@
#include "SLASupportTreeBuilder.hpp" #include <libslic3r/SLA/SupportTreeBuilder.hpp>
#include "SLASupportTreeBuildsteps.hpp" #include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
#include <libslic3r/SLA/Contour3D.hpp>
namespace Slic3r { namespace Slic3r {
namespace sla { namespace sla {
@ -12,7 +13,7 @@ Contour3D sphere(double rho, Portion portion, double fa) {
if(rho <= 1e-6 && rho >= -1e-6) return ret; if(rho <= 1e-6 && rho >= -1e-6) return ret;
auto& vertices = ret.points; auto& vertices = ret.points;
auto& facets = ret.indices; auto& facets = ret.faces3;
// Algorithm: // Algorithm:
// Add points one-by-one to the sphere grid and form facets using relative // 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 steps = int(ssteps);
auto& points = ret.points; auto& points = ret.points;
auto& indices = ret.indices; auto& indices = ret.faces3;
points.reserve(2*ssteps); points.reserve(2*ssteps);
double a = 2*PI/steps; 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 i1s1 = coord_t(idx1), i1s2 = coord_t(idx2);
coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1;
mesh.indices.emplace_back(i1s1, i2s1, i2s2); mesh.faces3.emplace_back(i1s1, i2s1, i2s2);
mesh.indices.emplace_back(i1s1, i2s2, i1s2); mesh.faces3.emplace_back(i1s1, i2s2, i1s2);
} }
auto i1s1 = coord_t(s1.points.size()) - coord_t(steps); 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 i1s2 = coord_t(s1.points.size());
auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1; auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1;
mesh.indices.emplace_back(i2s2, i2s1, i1s1); mesh.faces3.emplace_back(i2s2, i2s1, i1s1);
mesh.indices.emplace_back(i1s2, i2s2, i1s1); mesh.faces3.emplace_back(i1s2, i2s2, i1s1);
// To simplify further processing, we translate the mesh so that the // 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) // 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. // move the data.
Contour3D body = cylinder(radius, height, st, endp); Contour3D body = cylinder(radius, height, st, endp);
mesh.points.swap(body.points); 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(endpt);
base.points.emplace_back(ep); base.points.emplace_back(ep);
auto& indices = base.indices; auto& indices = base.faces3;
auto hcenter = int(base.points.size() - 1); auto hcenter = int(base.points.size() - 1);
auto lcenter = int(base.points.size() - 2); auto lcenter = int(base.points.size() - 2);
auto offs = int(steps); auto offs = int(steps);
@ -466,7 +467,7 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh() const
return m_meshcache; return m_meshcache;
} }
m_meshcache = mesh(merged); m_meshcache = to_triangle_mesh(merged);
// The mesh will be passed by const-pointer to TriangleMeshSlicer, // The mesh will be passed by const-pointer to TriangleMeshSlicer,
// which will need this. // which will need this.

View file

@ -1,10 +1,11 @@
#ifndef SUPPORTTREEBUILDER_HPP #ifndef SLA_SUPPORTTREEBUILDER_HPP
#define SUPPORTTREEBUILDER_HPP #define SLA_SUPPORTTREEBUILDER_HPP
#include "SLAConcurrency.hpp" #include <libslic3r/SLA/Concurrency.hpp>
#include "SLABoilerPlate.hpp" #include <libslic3r/SLA/Common.hpp>
#include "SLASupportTree.hpp" #include <libslic3r/SLA/SupportTree.hpp>
#include "SLAPad.hpp" #include <libslic3r/SLA/Contour3D.hpp>
#include <libslic3r/SLA/Pad.hpp>
#include <libslic3r/MTUtils.hpp> #include <libslic3r/MTUtils.hpp>
namespace Slic3r { namespace Slic3r {
@ -73,7 +74,7 @@ Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI),
// h: Height // h: Height
// ssteps: how many edges will create the base circle // ssteps: how many edges will create the base circle
// sp: starting point // 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; const constexpr long ID_UNSET = -1;

View file

@ -1,4 +1,4 @@
#include "SLASupportTreeBuildsteps.hpp" #include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
#include <libnest2d/optimizers/nlopt/genetic.hpp> #include <libnest2d/optimizers/nlopt/genetic.hpp>
#include <libnest2d/optimizers/nlopt/subplex.hpp> #include <libnest2d/optimizers/nlopt/subplex.hpp>
@ -7,6 +7,14 @@
namespace Slic3r { namespace Slic3r {
namespace sla { 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, SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder,
const SupportableMesh &sm) const SupportableMesh &sm)
: m_cfg(sm.cfg) : m_cfg(sm.cfg)
@ -560,9 +568,7 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
double radius, double radius,
long head_id) long head_id)
{ {
// People were killed for this number (seriously) const double SLOPE = 1. / std::cos(m_cfg.bridge_slope);
static const double SQR2 = std::sqrt(2.0);
static const Vec3d DOWN = {0.0, 0.0, -1.0};
double gndlvl = m_builder.ground_level; double gndlvl = m_builder.ground_level;
Vec3d endp = {jp(X), jp(Y), gndlvl}; Vec3d endp = {jp(X), jp(Y), gndlvl};
@ -573,6 +579,11 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
bool can_add_base = true; bool can_add_base = true;
bool normal_mode = 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 if (m_cfg.object_elevation_mm < EPSILON
&& (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) { && (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) {
// Get the distance from the mesh. This can be later optimized // Get the distance from the mesh. This can be later optimized
@ -580,31 +591,35 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
// the ground level only. // the ground level only.
normal_mode = false; 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) // The min distance needed to move away from the model in XY plane.
.normalized(); 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();
using namespace libnest2d::opt;
StopCriteria scr; StopCriteria scr;
scr.stop_score = min_dist; scr.stop_score = min_dist;
SubplexOptimizer solver(scr); 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( auto result = solver.optimize_max(
[this, dir, jp, gndlvl](double mv) { [this, dir, jp, gndlvl](double mv) {
Vec3d endpt = jp + SQR2 * mv * dir; Vec3d endpt = jp + mv * dir;
endpt(Z) = gndlvl; endpt(Z) = gndlvl;
return std::sqrt(m_mesh.squared_distance(endpt)); 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 + std::get<0>(result.optimum) * dir;
endp = jp + SQR2 * mind * dir;
Vec3d pgnd = {endp(X), endp(Y), gndlvl}; Vec3d pgnd = {endp(X), endp(Y), gndlvl};
can_add_base = result.score > min_dist; can_add_base = result.score > min_dist;
@ -623,7 +638,7 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
else { else {
// If the new endpoint is below ground, do not make a pillar // If the new endpoint is below ground, do not make a pillar
if (endp(Z) < gndlvl) if (endp(Z) < gndlvl)
endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off endp = endp - SLOPE * (gndlvl - endp(Z)) * dir; // back off
else { else {
auto hit = bridge_mesh_intersect(endp, DOWN, radius); 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 // not be enough space for the pinhead. Filtering is applied for
// these reasons. // these reasons.
using libnest2d::opt::bound;
using libnest2d::opt::initvals;
using libnest2d::opt::GeneticOptimizer;
using libnest2d::opt::StopCriteria;
ccr::SpinningMutex mutex; ccr::SpinningMutex mutex;
auto addfn = [&mutex](PtIndices &container, unsigned val) { auto addfn = [&mutex](PtIndices &container, unsigned val) {
std::lock_guard<ccr::SpinningMutex> lk(mutex); std::lock_guard<ccr::SpinningMutex> lk(mutex);
@ -708,10 +718,7 @@ void SupportTreeBuildsteps::filter()
// (Quaternion::FromTwoVectors) and apply the rotation to the // (Quaternion::FromTwoVectors) and apply the rotation to the
// arrow head. // arrow head.
double z = n(2); auto [polar, azimuth] = dir_to_spheric(n);
double r = 1.0; // for normalized vector
double polar = std::acos(z / r);
double azimuth = std::atan2(n(1), n(0));
// skip if the tilt is not sane // skip if the tilt is not sane
if(polar >= PI - m_cfg.normal_cutoff_angle) { 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); double pin_r = double(m_support_pts[fidx].head_front_radius);
// Reassemble the now corrected normal // Reassemble the now corrected normal
auto nn = Vec3d(std::cos(azimuth) * std::sin(polar), auto nn = spheric_to_dir(polar, azimuth).normalized();
std::sin(azimuth) * std::sin(polar),
std::cos(polar)).normalized();
// check available distance // check available distance
EigenMesh3D::hit_result t EigenMesh3D::hit_result t
@ -757,9 +762,7 @@ void SupportTreeBuildsteps::filter()
auto oresult = solver.optimize_max( auto oresult = solver.optimize_max(
[this, pin_r, w, hp](double plr, double azm) [this, pin_r, w, hp](double plr, double azm)
{ {
auto dir = Vec3d(std::cos(azm) * std::sin(plr), auto dir = spheric_to_dir(plr, azm).normalized();
std::sin(azm) * std::sin(plr),
std::cos(plr)).normalized();
double score = pinhead_mesh_intersect( double score = pinhead_mesh_intersect(
hp, dir, pin_r, m_cfg.head_back_radius_mm, w); hp, dir, pin_r, m_cfg.head_back_radius_mm, w);
@ -767,18 +770,15 @@ void SupportTreeBuildsteps::filter()
return score; return score;
}, },
initvals(polar, azimuth), // start with what we have initvals(polar, azimuth), // start with what we have
bound(3 * PI / 4, bound(3 * PI / 4, PI), // Must not exceed the tilt limit
PI), // Must not exceed the tilt limit
bound(-PI, PI) // azimuth can be a full search bound(-PI, PI) // azimuth can be a full search
); );
if(oresult.score > w) { if(oresult.score > w) {
polar = std::get<0>(oresult.optimum); polar = std::get<0>(oresult.optimum);
azimuth = std::get<1>(oresult.optimum); azimuth = std::get<1>(oresult.optimum);
nn = Vec3d(std::cos(azimuth) * std::sin(polar), nn = spheric_to_dir(polar, azimuth).normalized();
std::sin(azimuth) * std::sin(polar), t = EigenMesh3D::hit_result(oresult.score);
std::cos(polar)).normalized();
t = oresult.score;
} }
} }
@ -837,16 +837,17 @@ void SupportTreeBuildsteps::classify()
m_thr(); m_thr();
auto& head = m_builder.head(i); auto& head = m_builder.head(i);
Vec3d n(0, 0, -1);
double r = head.r_back_mm; double r = head.r_back_mm;
Vec3d headjp = head.junction_point(); Vec3d headjp = head.junction_point();
// collision check // 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); if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i);
else if(m_cfg.ground_facing_only) head.invalidate(); 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 // We want to search for clusters of points that are far enough
@ -893,6 +894,7 @@ void SupportTreeBuildsteps::routing_to_ground()
// get the current cluster centroid // get the current cluster centroid
auto & thr = m_thr; auto & thr = m_thr;
const auto &points = m_points; const auto &points = m_points;
long lcid = cluster_centroid( long lcid = cluster_centroid(
cl, [&points](size_t idx) { return points.row(long(idx)); }, cl, [&points](size_t idx) { return points.row(long(idx)); },
[thr](const Vec3d &p1, const Vec3d &p2) { [thr](const Vec3d &p1, const Vec3d &p2) {
@ -944,86 +946,36 @@ void SupportTreeBuildsteps::routing_to_ground()
} }
} }
void SupportTreeBuildsteps::routing_to_model() bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir)
{ {
// We need to check if there is an easy way out to the bed surface. auto hjp = head.junction_point();
// If it can be routed there with a bridge shorter than double r = head.r_back_mm;
// min_bridge_distance. 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);
// First we want to index the available pillars. The best is to connect while (d < t && !std::isinf(tdown = bridge_mesh_intersect(hjp + d * dir, DOWN, r)))
// these points to the available pillars d += r;
auto routedown = [this](Head& head, const Vec3d& dir, double dist) if(!std::isinf(tdown)) return false;
{
head.transform(); Vec3d endp = hjp + d * dir;
Vec3d endp = head.junction_point() + dist * dir;
m_builder.add_bridge(head.id, endp); m_builder.add_bridge(head.id, endp);
m_builder.add_junction(endp, head.r_back_mm); m_builder.add_junction(endp, head.r_back_mm);
this->create_ground_pillar(endp, dir, head.r_back_mm); this->create_ground_pillar(endp, dir, head.r_back_mm);
};
std::vector<unsigned> modelpillars; return true;
ccr::SpinningMutex mutex; }
auto onmodelfn = bool SupportTreeBuildsteps::connect_to_ground(Head &head)
[this, routedown, &modelpillars, &mutex] {
(const std::pair<unsigned, EigenMesh3D::hit_result> &el, size_t) if (connect_to_ground(head, head.dir)) return true;
{
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.
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
// /////////////////////////////////////////////////////////////////
// Optimize bridge direction:
// Straight path failed so we will try to search for a suitable // Straight path failed so we will try to search for a suitable
// direction out of the cavity. // direction out of the cavity.
auto [polar, azimuth] = dir_to_spheric(head.dir);
// 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; StopCriteria stc;
stc.max_iterations = m_cfg.optimizer_max_iterations; stc.max_iterations = m_cfg.optimizer_max_iterations;
@ -1033,13 +985,10 @@ void SupportTreeBuildsteps::routing_to_model()
solver.seed(0); // we want deterministic behavior solver.seed(0); // we want deterministic behavior
double r_back = head.r_back_mm; double r_back = head.r_back_mm;
Vec3d hjp = head.junction_point();
auto oresult = solver.optimize_max( auto oresult = solver.optimize_max(
[this, hjp, r_back](double plr, double azm) [this, hjp, r_back](double plr, double azm) {
{ Vec3d n = spheric_to_dir(plr, azm).normalized();
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); return bridge_mesh_intersect(hjp, n, r_back);
}, },
initvals(polar, azimuth), // let's start with what we have initvals(polar, azimuth), // let's start with what we have
@ -1047,31 +996,19 @@ void SupportTreeBuildsteps::routing_to_model()
bound(-PI, PI) // azimuth can be a full range search bound(-PI, PI) // azimuth can be a full range search
); );
d = 0; t = oresult.score; Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
return connect_to_ground(head, bridgedir);
}
polar = std::get<0>(oresult.optimum); bool SupportTreeBuildsteps::connect_to_model_body(Head &head)
azimuth = std::get<1>(oresult.optimum); {
Vec3d bridgedir = Vec3d(std::cos(azimuth) * std::sin(polar), if (head.id <= ID_UNSET) return false;
std::sin(azimuth) * std::sin(polar),
std::cos(polar)).normalized();
t = std::min(t, m_cfg.max_bridge_length_mm); auto it = m_head_to_ground_scans.find(unsigned(head.id));
if (it == m_head_to_ground_scans.end()) return false;
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
// /////////////////////////////////////////////////////////////////
auto &hit = it->second;
Vec3d hjp = head.junction_point();
double zangle = std::asin(hit.direction()(Z)); double zangle = std::asin(hit.direction()(Z));
zangle = std::max(zangle, PI/4); zangle = std::max(zangle, PI/4);
double h = std::sin(zangle) * head.fullwidth(); double h = std::sin(zangle) * head.fullwidth();
@ -1079,9 +1016,10 @@ void SupportTreeBuildsteps::routing_to_model()
// The width of the tail head that we would like to have... // The width of the tail head that we would like to have...
h = std::min(hit.distance() - head.r_back_mm, h); h = std::min(hit.distance() - head.r_back_mm, h);
if(h > 0) { if(h <= 0.) return false;
Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h}; Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h};
auto center_hit = m_mesh.query_ray_hit(hjp, dirdown); auto center_hit = m_mesh.query_ray_hit(hjp, DOWN);
double hitdiff = center_hit.distance() - hit.distance(); double hitdiff = center_hit.distance() - hit.distance();
Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm? Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm?
@ -1101,35 +1039,45 @@ void SupportTreeBuildsteps::routing_to_model()
w = 0.; w = 0.;
} }
Head tailhead(head.r_back_mm, Head tailhead(head.r_back_mm, head.r_pin_mm, w,
head.r_pin_mm, m_cfg.head_penetration_mm, taildir, hitp);
w,
m_cfg.head_penetration_mm,
taildir,
hitp);
tailhead.transform(); tailhead.transform();
pill.base = tailhead.mesh; pill.base = tailhead.mesh;
// Experimental: add the pillar to the index for cascading m_pillar_index.guarded_insert(pill.endpoint(), pill.id);
std::lock_guard<ccr::SpinningMutex> lk(mutex);
modelpillars.emplace_back(unsigned(pill.id)); return true;
return; }
}
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.
ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(),
[this] (const unsigned idx, size_t) {
m_thr();
auto& head = m_builder.head(idx);
// Search nearby pillar
if(search_pillar_and_connect(head)) { head.transform(); return; }
// Cannot connect to nearby pillar. We will try to search for
// a route to the ground.
if(connect_to_ground(head)) { head.transform(); 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. // We have failed to route this head.
BOOST_LOG_TRIVIAL(warning) BOOST_LOG_TRIVIAL(warning)
<< "Failed to route model facing support point." << "Failed to route model facing support point. ID: " << idx;
<< " ID: " << idx;
head.invalidate(); 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() void SupportTreeBuildsteps::interconnect_pillars()
@ -1280,7 +1228,8 @@ void SupportTreeBuildsteps::interconnect_pillars()
spts[n] = s; spts[n] = s;
// Check the path vertically down // 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}; Vec3d gndsp{s(X), s(Y), gnd};
// If the path is clear, check for pillar base collisions // 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 n = m_support_nmls.row(i); // mesh outward normal
Vec3d sp = sph - n * HWIDTH_MM; // stick head start point Vec3d sp = sph - n * HWIDTH_MM; // stick head start point
Vec3d dir = {0, 0, -1};
Vec3d sj = sp + R * n; // stick start point Vec3d sj = sp + R * n; // stick start point
// This is only for checking // This is only for checking
double idist = bridge_mesh_intersect(sph, dir, R, true); double idist = bridge_mesh_intersect(sph, DOWN, R, true);
double realdist = ray_mesh_intersect(sj, dir); double realdist = ray_mesh_intersect(sj, DOWN);
double dist = realdist; double dist = realdist;
if (std::isinf(dist)) dist = sph(Z) - m_builder.ground_level; 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); 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); m_builder.add_compact_bridge(sp, ej, n, R, use_endball);
} }
} }

View file

@ -3,7 +3,8 @@
#include <cstdint> #include <cstdint>
#include "SLASupportTreeBuilder.hpp" #include <libslic3r/SLA/SupportTreeBuilder.hpp>
#include <libslic3r/SLA/Clustering.hpp>
namespace Slic3r { namespace Slic3r {
namespace sla { namespace sla {
@ -19,6 +20,32 @@ inline Vec2d to_vec2(const Vec3d& v3) {
return {v3(X), v3(Y)}; 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' // This function returns the position of the centroid in the input 'clust'
// vector of point indices. // vector of point indices.
template<class DistFn> template<class DistFn>
@ -150,10 +177,10 @@ class SupportTreeBuildsteps {
using PtIndices = std::vector<unsigned>; using PtIndices = std::vector<unsigned>;
PtIndices m_iheads; // support points with pinhead PtIndices m_iheads; // support points with pinhead
PtIndices m_iheads_onmodel;
PtIndices m_iheadless; // headless support points PtIndices m_iheadless; // headless support points
// supp. pts. connecting to model: point index and the ray hit data std::map<unsigned, EigenMesh3D::hit_result> m_head_to_ground_scans;
std::vector<std::pair<unsigned, EigenMesh3D::hit_result>> m_iheads_onmodel;
// normals for support points from model faces. // normals for support points from model faces.
PointSet m_support_nmls; PointSet m_support_nmls;
@ -223,14 +250,28 @@ class SupportTreeBuildsteps {
// For connecting a head to a nearby pillar. // For connecting a head to a nearby pillar.
bool connect_to_nearpillar(const Head& head, long nearpillar_id); 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); bool search_pillar_and_connect(const Head& head);
// This is a proxy function for pillar creation which will mind the gap // This is a proxy function for pillar creation which will mind the gap
// between the pad and the model bottom in zero elevation mode. // 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, void create_ground_pillar(const Vec3d &jp,
const Vec3d &sourcedir, const Vec3d &sourcedir,
double radius, double radius,
long head_id = ID_UNSET); long head_id = ID_UNSET);
public: public:
SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm); SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm);

View file

@ -1,6 +1,6 @@
#include <cmath> #include <cmath>
#include "SLA/SLASupportTree.hpp" #include "SLA/SLASupportTree.hpp"
#include "SLA/SLABoilerPlate.hpp" #include "SLA/SLACommon.hpp"
#include "SLA/SLASpatIndex.hpp" #include "SLA/SLASpatIndex.hpp"
// Workaround: IGL signed_distance.h will define PI in the igl namespace. // 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_V(other.m_V), m_F(other.m_F), m_ground_level(other.m_ground_level),
m_aabb( new AABBImpl(*other.m_aabb) ) {} 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) EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other)
{ {
m_V = other.m_V; m_V = other.m_V;
@ -252,6 +272,31 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
return ret; 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 #ifdef SLIC3R_SLA_NEEDS_WINDTREE
EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const { EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const {
double sign = 0; double sqdst = 0; int i = 0; Vec3d c; double sign = 0; double sqdst = 0; int i = 0; Vec3d c;

File diff suppressed because it is too large Load diff

View file

@ -3,8 +3,8 @@
#include <mutex> #include <mutex>
#include "PrintBase.hpp" #include "PrintBase.hpp"
//#include "PrintExport.hpp" #include "SLA/RasterWriter.hpp"
#include "SLA/SLARasterWriter.hpp" #include "SLA/SupportTree.hpp"
#include "Point.hpp" #include "Point.hpp"
#include "MTUtils.hpp" #include "MTUtils.hpp"
#include "Zipper.hpp" #include "Zipper.hpp"
@ -19,7 +19,9 @@ enum SLAPrintStep : unsigned int {
}; };
enum SLAPrintObjectStep : unsigned int { enum SLAPrintObjectStep : unsigned int {
slaposHollowing,
slaposObjectSlice, slaposObjectSlice,
slaposDrillHolesIfHollowed,
slaposSupportPoints, slaposSupportPoints,
slaposSupportTree, slaposSupportTree,
slaposPad, slaposPad,
@ -73,13 +75,17 @@ public:
// Support mesh is only valid if this->is_step_done(slaposSupportTree) is true. // Support mesh is only valid if this->is_step_done(slaposSupportTree) is true.
const TriangleMesh& support_mesh() const; const TriangleMesh& support_mesh() const;
// Get a pad mesh centered around origin in XY, and with zero rotation around Z applied. // 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; 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 // This will return the transformed mesh which is cached
const TriangleMesh& transformed_mesh() const; 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 // Get the needed Z elevation for the model geometry if supports should be
// displayed. This Z offset should also be applied to the support // displayed. This Z offset should also be applied to the support
@ -288,8 +294,34 @@ private:
// Caching the transformed (m_trafo) raw mesh of the object // Caching the transformed (m_trafo) raw mesh of the object
mutable CachedObject<TriangleMesh> m_transformed_rmesh; 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; 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*>; using PrintObjects = std::vector<SLAPrintObject*>;
@ -340,6 +372,8 @@ class SLAPrint : public PrintBaseWithState<SLAPrintStep, slapsCount>
private: // Prevents erroneous use by other classes. private: // Prevents erroneous use by other classes.
typedef PrintBaseWithState<SLAPrintStep, slapsCount> Inherited; typedef PrintBaseWithState<SLAPrintStep, slapsCount> Inherited;
class Steps; // See SLAPrintSteps.cpp
public: public:
SLAPrint(): m_stepmask(slapsCount, true) {} SLAPrint(): m_stepmask(slapsCount, true) {}
@ -402,7 +436,7 @@ public:
m_transformed_slices = std::forward<Container>(c); m_transformed_slices = std::forward<Container>(c);
} }
friend void SLAPrint::process(); friend class SLAPrint::Steps;
public: public:
@ -478,6 +512,19 @@ private:
friend SLAPrintObject; 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 } // namespace Slic3r
#endif /* slic3r_SLAPrint_hpp_ */ #endif /* slic3r_SLAPrint_hpp_ */

View 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);
}
}
}

View 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

View 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();
}
}

View 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

View 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

View file

@ -70,6 +70,34 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd>& f
stl_get_size(&stl); 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 // #define SLIC3R_TRACE_REPAIR
void TriangleMesh::repair(bool update_shared_vertices) void TriangleMesh::repair(bool update_shared_vertices)

View file

@ -23,6 +23,7 @@ class TriangleMesh
public: public:
TriangleMesh() : repaired(false) {} TriangleMesh() : repaired(false) {}
TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd> &facets); 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; } void clear() { this->stl.clear(); this->its.clear(); this->repaired = false; }
bool ReadSTLFile(const char* input_file) { return stl_open(&stl, input_file); } 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, ""); } bool write_ascii(const char* output_file) { return stl_write_ascii(&this->stl, output_file, ""); }

View 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); 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 } // namespace Slic3r
#endif #endif

View file

@ -47,6 +47,8 @@ set(SLIC3R_GUI_SOURCES
GUI/Gizmos/GLGizmoFlatten.hpp GUI/Gizmos/GLGizmoFlatten.hpp
GUI/Gizmos/GLGizmoCut.cpp GUI/Gizmos/GLGizmoCut.cpp
GUI/Gizmos/GLGizmoCut.hpp GUI/Gizmos/GLGizmoCut.hpp
GUI/Gizmos/GLGizmoHollow.cpp
GUI/Gizmos/GLGizmoHollow.hpp
GUI/GLSelectionRectangle.cpp GUI/GLSelectionRectangle.cpp
GUI/GLSelectionRectangle.hpp GUI/GLSelectionRectangle.hpp
GUI/GLTexture.hpp GUI/GLTexture.hpp
@ -140,6 +142,7 @@ set(SLIC3R_GUI_SOURCES
GUI/ProgressStatusBar.cpp GUI/ProgressStatusBar.cpp
GUI/PrintHostDialogs.cpp GUI/PrintHostDialogs.cpp
GUI/PrintHostDialogs.hpp GUI/PrintHostDialogs.hpp
GUI/Job.hpp
GUI/Mouse3DController.cpp GUI/Mouse3DController.cpp
GUI/Mouse3DController.hpp GUI/Mouse3DController.hpp
GUI/DoubleSlider.cpp GUI/DoubleSlider.cpp

View file

@ -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) 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) { for (GLVolume* vol : m_volumes.volumes) {
if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo) if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx) && (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)
&& vol->composite_id.volume_id < 0) && vol->composite_id.volume_id < 0)
vol->is_active = visible; vol->is_active = visible;
} }
m_render_sla_auxiliaries = visible;
} }
void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx) 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(); unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size();
for (const SLAPrintObject::Instance& instance : obj->instances()) { for (const SLAPrintObject::Instance& instance : obj->instances()) {
add_volume(*obj, 0, instance, obj->transformed_mesh(), GLVolume::MODEL_COLOR[0], true); 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 // 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. // through the update_volumes_colors_by_extruder() call.
m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id(); m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id();

View file

@ -499,6 +499,7 @@ public:
void set_color_by(const std::string& value); void set_color_by(const std::string& value);
const Camera& get_camera() const { return m_camera; } const Camera& get_camera() const { return m_camera; }
const Shader& get_shader() const { return m_shader; }
Camera& get_camera() { return m_camera; } Camera& get_camera() { return m_camera; }
BoundingBoxf3 volumes_bounding_box() const; BoundingBoxf3 volumes_bounding_box() const;

View file

@ -103,6 +103,7 @@ ObjectList::ObjectList(wxWindow* parent) :
// ptSLA // ptSLA
CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(nullptr, "support"/*"sla_supports"*/); CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(nullptr, "support"/*"sla_supports"*/);
CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(nullptr, "pad"); CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(nullptr, "pad");
CATEGORY_ICON[L("Hollowing")] = create_scaled_bitmap(nullptr, "hollowing");
} }
// create control // create control

View file

@ -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_parent(parent)
, m_group_id(-1) , m_group_id(-1)
, m_state(Off) , m_state(Off)
@ -146,6 +146,7 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, u
, m_dragging(false) , m_dragging(false)
, m_imgui(wxGetApp().imgui()) , m_imgui(wxGetApp().imgui())
, m_first_input_window_render(true) , 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_base_color, (const void*)DEFAULT_BASE_COLOR, 4 * sizeof(float));
::memcpy((void*)m_drag_color, (const void*)DEFAULT_DRAG_COLOR, 4 * sizeof(float)); ::memcpy((void*)m_drag_color, (const void*)DEFAULT_DRAG_COLOR, 4 * sizeof(float));

View file

@ -30,7 +30,7 @@ static const float CONSTRAINED_COLOR[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
class ImGuiWrapper; class ImGuiWrapper;
class CommonGizmosData;
class GLGizmoBase class GLGizmoBase
{ {
@ -99,9 +99,13 @@ protected:
mutable std::vector<Grabber> m_grabbers; mutable std::vector<Grabber> m_grabbers;
ImGuiWrapper* m_imgui; ImGuiWrapper* m_imgui;
bool m_first_input_window_render; bool m_first_input_window_render;
CommonGizmosData* m_c = nullptr;
public: 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() {} virtual ~GLGizmoBase() {}
bool init() { return on_init(); } bool init() { return on_init(); }
@ -179,6 +183,34 @@ protected:
// were not interpolated by alpha blending or multi sampling. // 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); 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 GUI
} // namespace Slic3r } // namespace Slic3r

File diff suppressed because it is too large Load diff

View 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_

View file

@ -22,8 +22,8 @@
namespace Slic3r { namespace Slic3r {
namespace GUI { namespace GUI {
GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id) GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, CommonGizmosData* cd)
: GLGizmoBase(parent, icon_filename, sprite_id) : GLGizmoBase(parent, icon_filename, sprite_id, cd)
, m_quadric(nullptr) , m_quadric(nullptr)
, m_its(nullptr) , m_its(nullptr)
{ {
@ -64,23 +64,35 @@ bool GLGizmoSlaSupports::on_init()
void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const Selection& selection) void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const Selection& selection)
{ {
if (! model_object || selection.is_empty()) { if (! model_object || selection.is_empty()) {
m_model_object = nullptr; m_c->m_model_object = nullptr;
return; return;
} }
if (m_model_object != model_object || m_model_object_id != model_object->id()) { bool something_changed = false;
m_model_object = model_object;
m_print_object_idx = -1;
}
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()) if (model_object && selection.is_from_single_instance())
{ {
// Cache the bb - it's needed for dealing with the clipping plane quite often // 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. // 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! if (something_changed) {
m_active_instance_bb_radius = m_model_object->instance_bounding_box(m_active_instance).radius(); 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()) { if (is_mesh_update_necessary()) {
update_mesh(); 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 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(); 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(); 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 if (m_state == On
&& (m_model_object != selection.get_model()->objects[selection.get_object_idx()] && (m_c->m_model_object != selection.get_model()->objects[selection.get_object_idx()]
|| m_active_instance != selection.get_instance_idx() || m_c->m_active_instance != selection.get_instance_idx()
|| m_model_object_id != m_model_object->id())) { || m_c->m_model_object_id != m_c->m_model_object->id())) {
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS)); m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS));
return; return;
} }
if (! m_its || ! m_mesh) if (! m_its || ! m_c->m_mesh)
const_cast<GLGizmoSlaSupports*>(this)->update_mesh(); const_cast<GLGizmoSlaSupports*>(this)->update_mesh();
glsafe(::glEnable(GL_BLEND)); 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(); 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()) if (m_quadric != nullptr && selection.is_from_single_instance())
render_points(selection, false); 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, &current_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 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; return;
// Get transformation of the instance // Get transformation of the instance
@ -154,66 +184,66 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const
1.)); 1.));
// Now initialize the TMS for the object, perform the cut and save the result. // Now initialize the TMS for the object, perform the cut and save the result.
if (! m_object_clipper) { if (! m_c->m_object_clipper) {
m_object_clipper.reset(new MeshClipper); m_c->m_object_clipper.reset(new MeshClipper);
m_object_clipper->set_mesh(*m_mesh); m_c->m_object_clipper->set_mesh(*m_c->mesh());
} }
m_object_clipper->set_plane(*m_clipping_plane); m_c->m_object_clipper->set_plane(*m_clipping_plane);
m_object_clipper->set_transformation(trafo); 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. // 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 // 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: // 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) { if (m_c->m_print_object_idx < 0 || (int)m_parent.sla_print()->objects().size() != m_c->m_print_objects_count) {
m_print_objects_count = m_parent.sla_print()->objects().size(); m_c->m_print_objects_count = m_parent.sla_print()->objects().size();
m_print_object_idx = -1; m_c->m_print_object_idx = -1;
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
++m_print_object_idx; ++m_c->m_print_object_idx;
if (po->model_object()->id() == m_model_object->id()) if (po->model_object()->id() == m_c->m_model_object->id())
break; break;
} }
} }
if (m_print_object_idx >= 0) { if (m_c->m_print_object_idx >= 0) {
const SLAPrintObject* print_object = m_parent.sla_print()->objects()[m_print_object_idx]; 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 // If the supports are already calculated, save the timestamp of the respective step
// so we can later tell they were recalculated. // so we can later tell they were recalculated.
size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp; 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. // 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. // The mesh should already have the shared vertices calculated.
m_supports_clipper->set_mesh(print_object->support_mesh()); m_c->m_supports_clipper->set_mesh(print_object->support_mesh());
m_old_timestamp = timestamp; m_c->m_old_timestamp = timestamp;
} }
m_supports_clipper->set_plane(*m_clipping_plane); m_c->m_supports_clipper->set_plane(*m_clipping_plane);
m_supports_clipper->set_transformation(supports_trafo); m_c->m_supports_clipper->set_transformation(supports_trafo);
} }
else else
// The supports are not valid. We better dump the cached data. // 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. // 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(); ::glPushMatrix();
::glColor3f(1.0f, 0.37f, 0.0f); ::glColor3f(1.0f, 0.37f, 0.0f);
::glBegin(GL_TRIANGLES); ::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)); ::glVertex3f(point(0), point(1), point(2));
::glEnd(); ::glEnd();
::glPopMatrix(); ::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. // The supports are hidden in the editing mode, so it makes no sense to render the cuts.
::glPushMatrix(); ::glPushMatrix();
::glColor3f(1.0f, 0.f, 0.37f); ::glColor3f(1.0f, 0.f, 0.37f);
::glBegin(GL_TRIANGLES); ::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)); ::glVertex3f(point(0), point(1), point(2));
::glEnd(); ::glEnd();
::glPopMatrix(); ::glPopMatrix();
@ -230,6 +260,7 @@ void GLGizmoSlaSupports::on_render_for_picking() const
glsafe(::glEnable(GL_DEPTH_TEST)); glsafe(::glEnable(GL_DEPTH_TEST));
render_points(selection, true); render_points(selection, true);
render_hollowed_mesh();
} }
void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const 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) { if (m_editing_mode) {
// in case the normal is not yet cached, find and cache it // in case the normal is not yet cached, find and cache it
if (m_editing_cache[i].normal == Vec3f::Zero()) 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; Eigen::Quaterniond q;
q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast<double>()); 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)); 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) if (!picking)
glsafe(::glDisable(GL_LIGHTING)); 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) if (m_clipping_plane_distance == 0.f)
return false; 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; transformed_point(2) += m_z_shift;
return m_clipping_plane->is_point_clipped(transformed_point); 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 bool GLGizmoSlaSupports::is_mesh_update_necessary() const
{ {
return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty()) return ((m_state == On) && (m_c->m_model_object != nullptr) && !m_c->m_model_object->instances.empty())
&& ((m_model_object->id() != m_model_object_id) || m_its == nullptr); && ((m_c->m_model_object->id() != m_c->m_model_object_id) || m_its == nullptr);
} }
void GLGizmoSlaSupports::update_mesh() void GLGizmoSlaSupports::update_mesh()
{ {
if (! m_model_object) if (! m_c->m_model_object)
return; return;
wxBusyCursor wait; wxBusyCursor wait;
// this way we can use that mesh directly. // this way we can use that mesh directly.
// This mesh does not account for the possible Z up SLA offset. // This mesh does not account for the possible Z up SLA offset.
m_mesh = &m_model_object->volumes.front()->mesh(); m_c->m_mesh = &m_c->m_model_object->volumes.front()->mesh();
m_its = &m_mesh->its; 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 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) if (m_c->m_model_object_id != m_c->m_model_object->id() || ! m_c->m_mesh_raycaster)
m_mesh_raycaster.reset(new MeshRaycaster(*m_mesh)); 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(); disable_editing_mode();
} }
// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal // 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. // 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) 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 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(); update_mesh();
const Camera& camera = m_parent.get_camera(); const Camera& camera = m_parent.get_camera();
@ -393,12 +461,27 @@ bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec
// The raycaster query // The raycaster query
Vec3f hit; Vec3f hit;
Vec3f normal; Vec3f normal;
if (m_mesh_raycaster->unproject_on_mesh(mouse_pos, trafo.get_matrix(), camera, hit, normal, m_clipping_plane.get())) { 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. // Return both the point and the facet normal.
pos_and_normal = std::make_pair(hit, normal); pos_and_normal = std::make_pair(hit, normal);
return true; return true;
} }
else }
return false; return false;
} }
@ -459,7 +542,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state(); GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state();
// First collect positions of all the points in world coordinates. // 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)); trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
std::vector<Vec3d> points; std::vector<Vec3d> points;
for (unsigned int i=0; i<m_editing_cache.size(); ++i) 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>()); points_inside.push_back(points[idx].cast<float>());
// Only select/deselect points that are actually visible // 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) if (rectangle_status == GLSelectionRectangle::Deselect)
unselect_point(points_idxs[idx]); unselect_point(points_idxs[idx]);
@ -610,10 +693,10 @@ std::vector<const ConfigOption*> GLGizmoSlaSupports::get_config_options(const st
{ {
std::vector<const ConfigOption*> out; std::vector<const ConfigOption*> out;
if (!m_model_object) if (!m_c->m_model_object)
return out; 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; const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
std::unique_ptr<DynamicPrintConfig> default_cfg = nullptr; 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 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(); return ClippingPlane::ClipsNothing();
else else
return ClippingPlane(-m_clipping_plane->get_normal(), m_clipping_plane->get_data()[3]); 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 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); Vec3f normal(0.f, 1.f, 1.f);
double d = 0.; 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_y = 0.0f;
static float last_h = 0.0f; static float last_h = 0.0f;
if (!m_model_object) if (! m_c->m_model_object)
return; return;
bool first_run = true; // This is a hack to redraw the button when all points are removed, bool first_run = true; // This is a hack to redraw the button when all points are removed,
@ -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); 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); window_width = std::max(std::max(window_width, buttons_width_approx), lock_supports_width_approx);
bool force_refresh = false; bool force_refresh = false;
bool remove_selected = false; bool remove_selected = false;
bool remove_all = false; bool remove_all = false;
@ -827,15 +909,15 @@ RENDER_AGAIN:
m_density_stash = density; m_density_stash = density;
} }
if (slider_edited) { if (slider_edited) {
m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance; m_c->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<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density;
} }
if (slider_released) { if (slider_released) {
m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = m_minimal_point_distance_stash; m_c->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<ConfigOptionInt>("support_points_density_relative", true)->value = (int)m_density_stash;
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Support parameter change"))); 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_c->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<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density;
wxGetApp().obj_list()->update_and_show_object_settings_item(); wxGetApp().obj_list()->update_and_show_object_settings_item();
} }
@ -853,10 +935,10 @@ RENDER_AGAIN:
m_imgui->disabled_end(); m_imgui->disabled_end();
// m_imgui->text(""); // m_imgui->text("");
// m_imgui->text(m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) : // m_imgui->text(m_c->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_c->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_c->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_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(); 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) { if (remove_selected || remove_all) {
force_refresh = false; force_refresh = false;
m_parent.set_as_dirty(); m_parent.set_as_dirty();
@ -952,12 +1028,12 @@ std::string GLGizmoSlaSupports::on_get_name() const
void GLGizmoSlaSupports::on_set_state() 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 // 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) { for (const auto mo : wxGetApp().model().objects) {
if (mo->id() == m_model_object_id) { if (mo->id() == m_c->m_model_object_id) {
m_model_object = mo; m_c->m_model_object = mo;
break; break;
} }
} }
@ -971,19 +1047,20 @@ void GLGizmoSlaSupports::on_set_state()
update_mesh(); update_mesh();
// we'll now reload support points: // we'll now reload support points:
if (m_model_object) if (m_c->m_model_object)
reload_cache(); reload_cache();
m_parent.toggle_model_objects_visibility(false); m_parent.toggle_model_objects_visibility(false);
if (m_model_object) if (m_c->m_model_object && ! m_c->m_cavity_mesh)
m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance); 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. // Set default head diameter from config.
const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().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; 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 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) { if (will_ask) {
wxGetApp().CallAfter([this]() { wxGetApp().CallAfter([this]() {
// Following is called through CallAfter, because otherwise there was a problem // 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_parent.toggle_model_objects_visibility(true);
m_normal_cache.clear(); m_normal_cache.clear();
m_clipping_plane_distance = 0.f; m_clipping_plane_distance = 0.f;
update_clipping_plane();
// Release clippers and the AABB raycaster. // Release clippers and the AABB raycaster.
m_its = nullptr; m_its = nullptr;
m_object_clipper.reset(); m_c->m_object_clipper.reset();
m_supports_clipper.reset(); m_c->m_supports_clipper.reset();
m_mesh_raycaster.reset(); m_c->m_mesh_raycaster.reset();
} }
} }
m_old_state = m_state; m_old_state = m_state;
@ -1051,7 +1129,7 @@ void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar)
{ {
ar(m_clipping_plane_distance, ar(m_clipping_plane_distance,
*m_clipping_plane, *m_clipping_plane,
m_model_object_id, m_c->m_model_object_id,
m_new_point_head_diameter, m_new_point_head_diameter,
m_normal_cache, m_normal_cache,
m_editing_cache, m_editing_cache,
@ -1065,7 +1143,7 @@ void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const
{ {
ar(m_clipping_plane_distance, ar(m_clipping_plane_distance,
*m_clipping_plane, *m_clipping_plane,
m_model_object_id, m_c->m_model_object_id,
m_new_point_head_diameter, m_new_point_head_diameter,
m_normal_cache, m_normal_cache,
m_editing_cache, m_editing_cache,
@ -1143,9 +1221,9 @@ void GLGizmoSlaSupports::editing_mode_apply_changes()
for (const CacheEntry& ce : m_editing_cache) for (const CacheEntry& ce : m_editing_cache)
m_normal_cache.push_back(ce.support_point); m_normal_cache.push_back(ce.support_point);
m_model_object->sla_points_status = sla::PointsStatus::UserModified; m_c->m_model_object->sla_points_status = sla::PointsStatus::UserModified;
m_model_object->sla_support_points.clear(); m_c->m_model_object->sla_support_points.clear();
m_model_object->sla_support_points = m_normal_cache; m_c->m_model_object->sla_support_points = m_normal_cache;
reslice_SLA_supports(); reslice_SLA_supports();
} }
@ -1156,10 +1234,10 @@ void GLGizmoSlaSupports::editing_mode_apply_changes()
void GLGizmoSlaSupports::reload_cache() void GLGizmoSlaSupports::reload_cache()
{ {
m_normal_cache.clear(); 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(); get_data_from_backend();
else 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); m_normal_cache.emplace_back(point);
} }
@ -1168,7 +1246,7 @@ bool GLGizmoSlaSupports::has_backend_supports() const
{ {
// find SlaPrintObject with this ID // find SlaPrintObject with this ID
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { 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 po->is_step_done(slaposSupportPoints);
} }
return false; return false;
@ -1176,7 +1254,7 @@ bool GLGizmoSlaSupports::has_backend_supports() const
void GLGizmoSlaSupports::reslice_SLA_supports(bool postpone_error_messages) 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() 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 // find the respective SLAPrintObject, we need a pointer to it
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) { 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(); m_normal_cache.clear();
const std::vector<sla::SupportPoint>& points = po->get_support_points(); const std::vector<sla::SupportPoint>& points = po->get_support_points();
auto mat = po->trafo().inverse().cast<float>(); auto mat = po->trafo().inverse().cast<float>();
for (unsigned int i=0; i<points.size();++i) 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_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; break;
} }
} }
@ -1210,10 +1288,10 @@ void GLGizmoSlaSupports::auto_generate()
_(L("Are you sure you want to do it?")) + "\n", _(L("Are you sure you want to do it?")) + "\n",
_(L("Warning")), wxICON_WARNING | wxYES | wxNO); _(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"))); Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Autogenerate support points")));
wxGetApp().CallAfter([this]() { reslice_SLA_supports(); }); 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) for (const sla::SupportPoint& sp : m_normal_cache)
m_editing_cache.emplace_back(sp); m_editing_cache.emplace_back(sp);
select_point(NoPoints); 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) { if (m_editing_mode) {
m_editing_mode = false; m_editing_mode = false;
wxGetApp().plater()->leave_gizmos_stack(); 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() ? Vec3d normal = (keep_normal && m_clipping_plane->get_normal() != Vec3d::Zero() ?
m_clipping_plane->get_normal() : -m_parent.get_camera().get_dir_forward()); 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); 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(); m_parent.set_as_dirty();
} }

View file

@ -4,7 +4,7 @@
#include "GLGizmoBase.hpp" #include "GLGizmoBase.hpp"
#include "slic3r/GUI/GLSelectionRectangle.hpp" #include "slic3r/GUI/GLSelectionRectangle.hpp"
#include "libslic3r/SLA/SLACommon.hpp" #include "libslic3r/SLA/Common.hpp"
#include <wx/dialog.h> #include <wx/dialog.h>
#include <cereal/types/vector.hpp> #include <cereal/types/vector.hpp>
@ -21,10 +21,10 @@ enum class SLAGizmoEventType : unsigned char;
class GLGizmoSlaSupports : public GLGizmoBase class GLGizmoSlaSupports : public GLGizmoBase
{ {
private: private:
ModelObject* m_model_object = nullptr; //ModelObject* m_model_object = nullptr;
ObjectID m_model_object_id = 0; //ObjectID m_model_object_id = 0;
int m_active_instance = -1; //int m_active_instance = -1;
float m_active_instance_bb_radius; // to cache the bb //float m_active_instance_bb_radius; // to cache the bb
mutable double m_z_shift = 0.f; mutable double m_z_shift = 0.f;
bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal); 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<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 Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
std::unique_ptr<MeshRaycaster> m_mesh_raycaster; //std::unique_ptr<MeshRaycaster> m_mesh_raycaster;
const TriangleMesh* m_mesh; //const TriangleMesh* m_mesh;
const indexed_triangle_set* m_its; const indexed_triangle_set* m_its;
mutable const TriangleMesh* m_supports_mesh; //mutable int m_old_timestamp = -1;
mutable std::vector<Vec2f> m_triangles; //mutable int m_print_object_idx = -1;
mutable std::vector<Vec2f> m_supports_triangles; //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 { class CacheEntry {
public: public:
@ -72,7 +69,7 @@ private:
}; };
public: 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; ~GLGizmoSlaSupports() override;
void set_sla_support_data(ModelObject* model_object, const Selection& selection); 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); 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_selection_rectangle() const;
void render_points(const Selection& selection, bool picking = false) const; void render_points(const Selection& selection, bool picking = false) const;
void render_clipping_plane(const Selection& selection) const; void render_clipping_plane(const Selection& selection) const;
void render_hollowed_mesh() const;
bool is_mesh_update_necessary() const; bool is_mesh_update_necessary() const;
void update_mesh(); void update_mesh();
bool unsaved_changes() const; bool unsaved_changes() const;
bool m_lock_unique_islands = false; bool m_lock_unique_islands = false;
bool m_editing_mode = false; // Is editing mode active? 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. 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 CacheEntry m_point_before_drag; // undo/redo - so we know what state was edited
float m_old_point_head_diameter = 0.; // the same float m_old_point_head_diameter = 0.; // the same
@ -121,11 +118,12 @@ private:
bool m_selection_empty = true; 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) 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_object_clipper;
mutable std::unique_ptr<MeshClipper> m_supports_clipper; //mutable std::unique_ptr<MeshClipper> m_supports_clipper;
std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const; 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_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; //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, // Methods that do the model_object and editing cache synchronization,

View file

@ -32,5 +32,6 @@ enum class SLAGizmoEventType : unsigned char {
#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp" #include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp" #include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp" #include "slic3r/GUI/Gizmos/GLGizmoCut.hpp"
#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
#endif //slic3r_GLGizmos_hpp_ #endif //slic3r_GLGizmos_hpp_

View file

@ -83,12 +83,16 @@ bool GLGizmosManager::init()
return false; 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 GLGizmoMove3D(m_parent, "move.svg", 0));
m_gizmos.emplace_back(new GLGizmoScale3D(m_parent, "scale.svg", 1)); 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 GLGizmoRotate3D(m_parent, "rotate.svg", 2));
m_gizmos.emplace_back(new GLGizmoFlatten(m_parent, "place.svg", 3)); 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 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) { for (auto& gizmo : m_gizmos) {
if (! gizmo->init()) { if (! gizmo->init()) {
@ -345,6 +349,7 @@ void GLGizmosManager::set_sla_support_data(ModelObject* model_object)
return; return;
dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->set_sla_support_data(model_object, m_parent.get_selection()); 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. // 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()) if (!m_enabled || m_gizmos.empty())
return false; return false;
if (m_current == SlaSupports)
return dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down); 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 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 ClippingPlane::ClipsNothing();
if (m_current == SlaSupports)
return dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->get_sla_clipping_plane(); 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 bool GLGizmosManager::wants_reslice_supports_on_undo() const
@ -401,7 +413,7 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt)
{ {
bool processed = false; bool processed = false;
if (m_current == SlaSupports) { if (m_current == SlaSupports || m_current == Hollow) {
float rot = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta(); 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())) if (gizmo_event((rot > 0.f ? SLAGizmoEventType::MouseWheelUp : SLAGizmoEventType::MouseWheelDown), Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
processed = true; processed = true;
@ -459,7 +471,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
if (evt.LeftDown()) 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 // the gizmo got the event and took some action, there is no need to do anything more
processed = true; processed = true;
else if (!selection.is_empty() && grabber_contains_mouse()) { else if (!selection.is_empty() && grabber_contains_mouse()) {
@ -477,17 +489,17 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
processed = true; 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 // 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; pending_right_up = true;
// event was taken care of by the SlaSupports gizmo // event was taken care of by the SlaSupports gizmo
processed = true; 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 // don't allow dragging objects with the Sla gizmo on
processed = true; 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 // the gizmo got the event and took some action, no need to do anything more here
m_parent.set_as_dirty(); m_parent.set_as_dirty();
@ -563,7 +575,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
processed = true; 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 // 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 // object moving or selecting is suppressed in that case
@ -628,7 +640,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
#endif /* __APPLE__ */ #endif /* __APPLE__ */
{ {
// Sla gizmo selects all support points // 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; processed = true;
break; break;
@ -662,7 +674,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
case 'r' : case 'r' :
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; processed = true;
break; break;
@ -674,7 +686,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
case WXK_DELETE: case WXK_DELETE:
#endif /* __APPLE__ */ #endif /* __APPLE__ */
{ {
if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::Delete)) if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::Delete))
processed = true; processed = true;
break; break;
@ -736,20 +748,31 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
if (evt.GetEventType() == wxEVT_KEY_UP) if (evt.GetEventType() == wxEVT_KEY_UP)
{ {
if (m_current == SlaSupports) if (m_current == SlaSupports || m_current == Hollow)
{ {
bool is_editing = true;
bool is_rectangle_dragging = false;
if (m_current == SlaSupports) {
GLGizmoSlaSupports* gizmo = dynamic_cast<GLGizmoSlaSupports*>(get_current()); 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) if (keyCode == WXK_SHIFT)
{ {
// shift has been just released - SLA gizmo might want to close rectangular selection. // 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; processed = true;
} }
else if (keyCode == WXK_ALT) else if (keyCode == WXK_ALT)
{ {
// alt has been just released - SLA gizmo might want to close rectangular selection. // 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; processed = true;
} }
} }

View file

@ -54,11 +54,13 @@ public:
enum EType : unsigned char enum EType : unsigned char
{ {
// Order must match index in m_gizmos!
Move, Move,
Scale, Scale,
Rotate, Rotate,
Flatten, Flatten,
Cut, Cut,
Hollow,
SlaSupports, SlaSupports,
Undefined Undefined
}; };
@ -111,6 +113,7 @@ private:
MouseCapture m_mouse_capture; MouseCapture m_mouse_capture;
std::string m_tooltip; std::string m_tooltip;
bool m_serializing; bool m_serializing;
std::unique_ptr<CommonGizmosData> m_common_gizmos_data;
public: public:
explicit GLGizmosManager(GLCanvas3D& parent); explicit GLGizmosManager(GLCanvas3D& parent);
@ -168,6 +171,7 @@ public:
void update_data(); void update_data();
EType get_current_type() const { return m_current; } EType get_current_type() const { return m_current; }
GLGizmoBase* get_current() const;
bool is_running() const; bool is_running() const;
bool handle_shortcut(int key); bool handle_shortcut(int key);
@ -216,8 +220,6 @@ private:
float get_scaled_total_height() const; float get_scaled_total_height() const;
float get_scaled_total_width() const; float get_scaled_total_width() const;
GLGizmoBase* get_current() const;
bool generate_icons_texture() const; bool generate_icons_texture() const;
void update_on_off_state(const Vec2d& mouse_pos); void update_on_off_state(const Vec2d& mouse_pos);

155
src/slic3r/GUI/Job.hpp Normal file
View 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

View file

@ -58,7 +58,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
#endif // _WIN32 #endif // _WIN32
// initialize status bar // 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->embed(this);
m_statusbar->set_status_text(_(L("Version")) + " " + m_statusbar->set_status_text(_(L("Version")) + " " +
SLIC3R_VERSION + SLIC3R_VERSION +

View file

@ -135,7 +135,7 @@ public:
Plater* m_plater { nullptr }; Plater* m_plater { nullptr };
wxNotebook* m_tabpanel { nullptr }; wxNotebook* m_tabpanel { nullptr };
wxProgressDialog* m_progress_dialog { nullptr }; wxProgressDialog* m_progress_dialog { nullptr };
std::unique_ptr<ProgressStatusBar> m_statusbar; std::shared_ptr<ProgressStatusBar> m_statusbar;
}; };
} // GUI } // GUI

View file

@ -5,10 +5,6 @@
#include "slic3r/GUI/Camera.hpp" #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> #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, bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane) const 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), 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)); ::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(); Transform3d inv = trafo.inverse();
pt1 = inv * pt1; pt1 = inv * pt1;
pt2 = inv * pt2; pt2 = inv * pt2;
if (! m_AABB_wrapper->m_AABB.intersect_ray( std::vector<sla::EigenMesh3D::hit_result> hits = m_emesh.query_ray_hits(pt1, pt2-pt1);
AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3), if (hits.empty())
AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3),
pt1.cast<float>(), (pt2-pt1).cast<float>(), hits))
return false; // no intersection found 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; unsigned i = 0;
// Remove points that are obscured or cut by the clipping plane // Remove points that are obscured or cut by the clipping plane
if (clipping_plane) { if (clipping_plane) {
for (i=0; i<hits.size(); ++i) 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; break;
if (i==hits.size() || (hits.size()-i) % 2 != 0) { 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: // Now stuff the points in the provided vector and calculate normals if asked about them:
position = m_AABB_wrapper->get_hit_pos(hits[i]); position = hits[i].position().cast<float>();
normal = m_AABB_wrapper->get_hit_normal(hits[i]); normal = hits[i].normal().cast<float>();
return true; return true;
} }
@ -219,24 +157,21 @@ std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo
bool is_obscured = false; bool is_obscured = false;
// Cast a ray in the direction of the camera and look for intersection with the mesh: // 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. // Offset the start of the ray by EPSILON to account for numerical inaccuracies.
if (m_AABB_wrapper->m_AABB.intersect_ray( hits = m_emesh.query_ray_hits((inverse_trafo * pt + direction_to_camera_mesh * EPSILON).cast<double>(),
AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3), direction_to_camera.cast<double>());
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)) {
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, // 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: // 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; is_obscured = true;
// Eradicate all hits that the caller wants to ignore // Eradicate all hits that the caller wants to ignore
for (unsigned j=0; j<hits.size(); ++j) { 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() * hits[j].position())) {
if (clipping_plane && clipping_plane->is_point_clipped(trafo.get_matrix() * m_AABB_wrapper->get_hit_pos(hit).cast<double>())) {
hits.erase(hits.begin()+j); hits.erase(hits.begin()+j);
--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 Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const
{ {
int idx = 0; int idx = 0;
Eigen::Matrix<float, 1, 3> closest_point; Vec3d closest_point;
m_AABB_wrapper->m_AABB.squared_distance( m_emesh.squared_distance(point.cast<double>(), idx, closest_point);
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);
if (normal) { if (normal) {
igl::Hit imag_hit; auto indices = m_emesh.F().row(idx);
imag_hit.id = idx; Vec3d a(m_emesh.V().row(indices(1)) - m_emesh.V().row(indices(0)));
*normal = m_AABB_wrapper->get_hit_normal(imag_hit); 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>();
} }

View file

@ -3,6 +3,7 @@
#include "libslic3r/Point.hpp" #include "libslic3r/Point.hpp"
#include "libslic3r/Geometry.hpp" #include "libslic3r/Geometry.hpp"
#include "libslic3r/SLA/EigenMesh3D.hpp"
#include <cfloat> #include <cfloat>
@ -46,7 +47,7 @@ public:
bool operator!=(const ClippingPlane& cp) const { return ! (*this==cp); } bool operator!=(const ClippingPlane& cp) const { return ! (*this==cp); }
double distance(const Vec3d& pt) const { 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]); return (-get_normal().dot(pt) + m_data[3]);
} }
@ -104,11 +105,11 @@ private:
// whether certain points are visible or obscured by the mesh etc. // whether certain points are visible or obscured by the mesh etc.
class MeshRaycaster { class MeshRaycaster {
public: public:
// The class saves a const* to the mesh, called is responsible // The class makes a copy of the mesh as EigenMesh3D.
// for making sure it does not get invalid. // The pointer can be invalidated after constructor returns.
MeshRaycaster(const TriangleMesh& mesh); MeshRaycaster(const TriangleMesh& mesh)
: m_emesh(mesh)
~MeshRaycaster(); {}
// Given a mouse position, this returns true in case it is on the mesh. // Given a mouse position, this returns true in case it is on the mesh.
bool unproject_on_mesh( bool unproject_on_mesh(
@ -136,10 +137,7 @@ public:
Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const; Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const;
private: private:
// PIMPL wrapper around igl::AABB so I don't have to include the header-only IGL here sla::EigenMesh3D m_emesh;
class AABBWrapper;
std::unique_ptr<AABBWrapper> m_AABB_wrapper;
const TriangleMesh* m_mesh = nullptr;
}; };

View file

@ -39,11 +39,13 @@
#include "libslic3r/GCode/ThumbnailData.hpp" #include "libslic3r/GCode/ThumbnailData.hpp"
#endif // ENABLE_THUMBNAIL_GENERATOR #endif // ENABLE_THUMBNAIL_GENERATOR
#include "libslic3r/Model.hpp" #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/Polygon.hpp"
#include "libslic3r/Print.hpp" #include "libslic3r/Print.hpp"
#include "libslic3r/PrintConfig.hpp" #include "libslic3r/PrintConfig.hpp"
#include "libslic3r/SLAPrint.hpp" #include "libslic3r/SLAPrint.hpp"
#include "libslic3r/SLA/SLARotfinder.hpp"
#include "libslic3r/Utils.hpp" #include "libslic3r/Utils.hpp"
//#include "libslic3r/ClipperUtils.hpp" //#include "libslic3r/ClipperUtils.hpp"
@ -70,6 +72,7 @@
#include "Camera.hpp" #include "Camera.hpp"
#include "Mouse3DController.hpp" #include "Mouse3DController.hpp"
#include "Tab.hpp" #include "Tab.hpp"
#include "Job.hpp"
#include "PresetBundle.hpp" #include "PresetBundle.hpp"
#include "BackgroundSlicingProcess.hpp" #include "BackgroundSlicingProcess.hpp"
#include "ProgressStatusBar.hpp" #include "ProgressStatusBar.hpp"
@ -1503,144 +1506,39 @@ struct Plater::priv
// objects would be frozen for the user. In case of arrange, an animation // 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 shown, or with the optimize orientations, partial results
// could be displayed. // could be displayed.
class Job : public wxEvtHandler class PlaterJob: public Job
{ {
int m_range = 100; priv *m_plater;
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(), "");
}
protected: 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; } priv & plater() { return *m_plater; }
const priv &plater() const { 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. // 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 // Do a full refresh of scene tree, including regenerating
// all the GLVolumes. FIXME The update function shall just // all the GLVolumes. FIXME The update function shall just
// reload the modified matrices. // reload the modified matrices.
if (!was_canceled()) if (!Job::was_canceled())
plater().update(unsigned(UpdateParams::FORCE_FULL_SCREEN_REFRESH)); plater().update(unsigned(UpdateParams::FORCE_FULL_SCREEN_REFRESH));
Job::finalize();
} }
public: public:
Job(priv *_plater) : m_plater(_plater) PlaterJob(priv *_plater)
{ : Job(_plater->statusbar()), 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); }
}; };
enum class Jobs : size_t { enum class Jobs : size_t {
Arrange, Arrange,
Rotoptimize Rotoptimize,
Hollow
}; };
class ArrangeJob : public Job class ArrangeJob : public PlaterJob
{ {
using ArrangePolygon = arrangement::ArrangePolygon; using ArrangePolygon = arrangement::ArrangePolygon;
using ArrangePolygons = arrangement::ArrangePolygons; using ArrangePolygons = arrangement::ArrangePolygons;
@ -1757,7 +1655,7 @@ struct Plater::priv
} }
public: public:
using Job::Job; using PlaterJob::PlaterJob;
int status_range() const override { return int(m_selected.size()); } 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: public:
using Job::Job; using PlaterJob::PlaterJob;
void process() override; 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 // 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 // run at a time. Also, the background process will be stopped if a job is
// started. // started.
@ -1792,6 +1707,7 @@ struct Plater::priv
ArrangeJob arrange_job{m_plater}; ArrangeJob arrange_job{m_plater};
RotoptimizeJob rotoptimize_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 // To create a new job, just define a new subclass of Job, implement
// the process and the optional prepare() and finalize() methods // 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 // if it cannot run concurrently with other jobs in this group
std::vector<std::reference_wrapper<Job>> m_jobs{arrange_job, std::vector<std::reference_wrapper<Job>> m_jobs{arrange_job,
rotoptimize_job}; rotoptimize_job,
hollow_job};
public: public:
ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {} ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {}
@ -1876,7 +1793,7 @@ struct Plater::priv
void reset_all_gizmos(); void reset_all_gizmos();
void update_ui_from_settings(); void update_ui_from_settings();
ProgressStatusBar* statusbar(); std::shared_ptr<ProgressStatusBar> statusbar();
std::string get_config(const std::string &key) const; std::string get_config(const std::string &key) const;
BoundingBoxf bed_shape_bb() const; BoundingBoxf bed_shape_bb() const;
BoundingBox scaled_bed_shape_bb() const; BoundingBox scaled_bed_shape_bb() const;
@ -1901,6 +1818,7 @@ struct Plater::priv
void reset(); void reset();
void mirror(Axis axis); void mirror(Axis axis);
void arrange(); void arrange();
void hollow();
void sla_optimize_rotation(); void sla_optimize_rotation();
void split_object(); void split_object();
void split_volume(); void split_volume();
@ -2291,9 +2209,9 @@ void Plater::priv::update_ui_from_settings()
preview->get_canvas3d()->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 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); 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 // This method will find an optimal orientation for the currently selected item
// Very similar in nature to the arrange method above... // Very similar in nature to the arrange method above...
void Plater::priv::sla_optimize_rotation() { void Plater::priv::sla_optimize_rotation() {
@ -2946,6 +2870,68 @@ void Plater::priv::RotoptimizeJob::process()
: _(L("Orientation found."))); : _(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() void Plater::priv::split_object()
{ {
int obj_idx = get_selected_object_idx(); int obj_idx = get_selected_object_idx();
@ -5066,6 +5052,11 @@ void Plater::export_toolpaths_to_obj() const
p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str()); p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str());
} }
void Plater::hollow()
{
p->hollow();
}
void Plater::reslice() void Plater::reslice()
{ {
// Stop arrange and (or) optimize rotation tasks. // Stop arrange and (or) optimize rotation tasks.

View file

@ -194,6 +194,7 @@ public:
void reload_from_disk(); void reload_from_disk();
bool has_toolpaths_to_export() const; bool has_toolpaths_to_export() const;
void export_toolpaths_to_obj() const; void export_toolpaths_to_obj() const;
void hollow();
void reslice(); void reslice();
void reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages = false); void reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages = false);
void changed_object(int obj_idx); void changed_object(int obj_idx);

View file

@ -505,6 +505,10 @@ const std::vector<std::string>& Preset::sla_print_options()
"pad_object_connector_stride", "pad_object_connector_stride",
"pad_object_connector_width", "pad_object_connector_width",
"pad_object_connector_penetration", "pad_object_connector_penetration",
"hollowing_enable",
"hollowing_min_thickness",
"hollowing_quality",
"hollowing_closing_distance",
"output_filename_format", "output_filename_format",
"default_sla_print_profile", "default_sla_print_profile",
"compatible_printers", "compatible_printers",

View file

@ -11,58 +11,17 @@ namespace Slic3r {
*/ */
class ProgressIndicator { class ProgressIndicator {
public: public:
using CancelFn = std::function<void(void)>; // Cancel function signature.
private: /// Cancel callback function type
float m_state = .0f, m_max = 1.f, m_step; using CancelFn = std::function<void()>;
CancelFn m_cancelfunc = [](){};
public: virtual ~ProgressIndicator() = default;
inline virtual ~ProgressIndicator() {} virtual void set_range(int range) = 0;
virtual void set_cancel_callback(CancelFn = CancelFn()) = 0;
/// Get the maximum of the progress range. virtual void set_progress(int pr) = 0;
float max() const { return m_max; } virtual void set_status_text(const char *) = 0; // utf8 char array
virtual int get_range() const = 0;
/// 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);
}
}; };
} }

View file

@ -15,8 +15,7 @@
namespace Slic3r { namespace Slic3r {
ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id) ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id)
: self{new wxStatusBar(parent ? parent : GUI::wxGetApp().mainframe, : self{new wxStatusBar(parent, id == -1 ? wxID_ANY : id)}
id == -1 ? wxID_ANY : id)}
, m_prog{new wxGauge(self, , m_prog{new wxGauge(self,
wxGA_HORIZONTAL, wxGA_HORIZONTAL,
100, 100,
@ -32,7 +31,6 @@ ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id)
m_prog->Hide(); m_prog->Hide();
m_cancelbutton->Hide(); m_cancelbutton->Hide();
self->SetFont(GUI::wxGetApp().normal_font());
self->SetFieldsCount(3); self->SetFieldsCount(3);
int w[] = {-1, 150, 155}; int w[] = {-1, 150, 155};
self->SetStatusWidths(3, w); self->SetStatusWidths(3, w);
@ -149,8 +147,7 @@ void ProgressStatusBar::run(int rate)
void ProgressStatusBar::embed(wxFrame *frame) void ProgressStatusBar::embed(wxFrame *frame)
{ {
wxFrame* mf = frame ? frame : GUI::wxGetApp().mainframe; if(frame) frame->SetStatusBar(self);
if(mf) mf->SetStatusBar(self);
} }
void ProgressStatusBar::set_status_text(const wxString& txt) void ProgressStatusBar::set_status_text(const wxString& txt)
@ -173,6 +170,11 @@ wxString ProgressStatusBar::get_status_text() const
return self->GetStatusText(); return self->GetStatusText();
} }
void ProgressStatusBar::set_font(const wxFont &font)
{
self->SetFont(font);
}
void ProgressStatusBar::show_cancel_button() void ProgressStatusBar::show_cancel_button()
{ {
if(m_cancelbutton) m_cancelbutton->Show(); if(m_cancelbutton) m_cancelbutton->Show();

View file

@ -6,6 +6,8 @@
#include <functional> #include <functional>
#include <string> #include <string>
#include "ProgressIndicator.hpp"
class wxTimer; class wxTimer;
class wxGauge; class wxGauge;
class wxButton; class wxButton;
@ -14,6 +16,7 @@ class wxStatusBar;
class wxWindow; class wxWindow;
class wxFrame; class wxFrame;
class wxString; class wxString;
class wxFont;
namespace Slic3r { namespace Slic3r {
@ -22,7 +25,7 @@ namespace Slic3r {
* of the Slicer main window. It consists of a message area to the left and a * of the Slicer main window. It consists of a message area to the left and a
* progress indication area to the right with an optional cancel button. * progress indication area to the right with an optional cancel button.
*/ */
class ProgressStatusBar class ProgressStatusBar : public ProgressIndicator
{ {
wxStatusBar *self; // we cheat! It should be the base class but: perl! wxStatusBar *self; // we cheat! It should be the base class but: perl!
wxGauge *m_prog; wxGauge *m_prog;
@ -30,30 +33,29 @@ class ProgressStatusBar
std::unique_ptr<wxTimer> m_timer; std::unique_ptr<wxTimer> m_timer;
public: public:
/// Cancel callback function type
using CancelFn = std::function<void()>;
ProgressStatusBar(wxWindow *parent = nullptr, int id = -1); ProgressStatusBar(wxWindow *parent = nullptr, int id = -1);
~ProgressStatusBar(); ~ProgressStatusBar() override;
int get_progress() const; int get_progress() const;
// if the argument is less than 0 it shows the last state or // if the argument is less than 0 it shows the last state or
// pulses if no state was set before. // pulses if no state was set before.
void set_progress(int); void set_progress(int) override;
int get_range() const; int get_range() const override;
void set_range(int = 100); void set_range(int = 100) override;
void show_progress(bool); void show_progress(bool);
void start_busy(int = 100); void start_busy(int = 100);
void stop_busy(); void stop_busy();
inline bool is_busy() const { return m_busy; } inline bool is_busy() const { return m_busy; }
void set_cancel_callback(CancelFn = CancelFn()); void set_cancel_callback(CancelFn = CancelFn()) override;
inline void reset_cancel_callback() { set_cancel_callback(); } inline void reset_cancel_callback() { set_cancel_callback(); }
void run(int rate); void run(int rate);
void embed(wxFrame *frame = nullptr); void embed(wxFrame *frame = nullptr);
void set_status_text(const wxString& txt); void set_status_text(const wxString& txt);
void set_status_text(const std::string& txt); void set_status_text(const std::string& txt);
void set_status_text(const char *txt); void set_status_text(const char *txt) override;
wxString get_status_text() const; wxString get_status_text() const;
void set_font(const wxFont &font);
// Temporary methods to satisfy Perl side // Temporary methods to satisfy Perl side
void show_cancel_button(); void show_cancel_button();

Some files were not shown because too many files have changed in this diff Show more