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

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

View file

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

View file

@ -17,4 +17,4 @@ ExternalProject_Add_Step(dep_CGAL dep_CGAL_relocation_fix
DEPENDEES install
COMMAND ${CMAKE_COMMAND} -E remove CGALConfig-installation-dirs.cmake
WORKING_DIRECTORY "${DESTDIR}/usr/local/${CMAKE_INSTALL_LIBDIR}/cmake/CGAL"
)
)

3
deps/GMP/GMP.cmake vendored
View file

@ -18,7 +18,8 @@ if (MSVC)
else ()
ExternalProject_Add(dep_GMP
URL https://gmplib.org/download/gmp/gmp-6.1.2.tar.bz2
# URL https://gmplib.org/download/gmp/gmp-6.1.2.tar.bz2
URL https://gmplib.org/download/gmp/gmp-6.2.0.tar.lz
BUILD_IN_SOURCE ON
CONFIGURE_COMMAND ./configure --enable-shared=no --enable-cxx=yes --enable-static=yes "--prefix=${DESTDIR}/usr/local" --with-pic
BUILD_COMMAND make -j

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>
Date: Wed, 16 Oct 2019 17:42:50 +0200
Subject: [PATCH] Build fixes for PrusaSlicer integration
Date: Thu, 23 Jan 2020 17:17:36 +0100
Subject: [PATCH] openvdb-mods
Signed-off-by: tamasmeszaros <meszaros.q@gmail.com>
---
CMakeLists.txt | 3 -
cmake/FindBlosc.cmake | 218 ---------------
cmake/CheckAtomic.cmake | 106 ++++++
cmake/FindBlosc.cmake | 218 ------------
cmake/FindCppUnit.cmake | 4 +-
cmake/FindIlmBase.cmake | 337 ----------------------
cmake/FindOpenEXR.cmake | 329 ----------------------
cmake/FindIlmBase.cmake | 337 ------------------
cmake/FindOpenEXR.cmake | 329 ------------------
cmake/FindOpenVDB.cmake | 19 +-
cmake/FindTBB.cmake | 605 ++++++++++++++++++++--------------------
openvdb/CMakeLists.txt | 13 +-
cmake/FindTBB.cmake | 599 ++++++++++++++++----------------
openvdb/CMakeLists.txt | 16 +-
openvdb/Grid.cc | 3 +
openvdb/PlatformConfig.h | 9 +-
openvdb/cmd/CMakeLists.txt | 4 +-
openvdb/unittest/CMakeLists.txt | 3 +-
openvdb/unittest/TestFile.cc | 2 +-
13 files changed, 336 insertions(+), 1213 deletions(-)
14 files changed, 442 insertions(+), 1210 deletions(-)
create mode 100644 cmake/CheckAtomic.cmake
delete mode 100644 cmake/FindBlosc.cmake
delete mode 100644 cmake/FindIlmBase.cmake
delete mode 100644 cmake/FindOpenEXR.cmake
@ -40,6 +41,119 @@ index 580b353..6d364c1 100644
cmake/FindOpenVDB.cmake
cmake/FindTBB.cmake
cmake/OpenVDBGLFW3Setup.cmake
diff --git a/cmake/CheckAtomic.cmake b/cmake/CheckAtomic.cmake
new file mode 100644
index 0000000..c045e30
--- /dev/null
+++ b/cmake/CheckAtomic.cmake
@@ -0,0 +1,106 @@
+# atomic builtins are required for threading support.
+
+INCLUDE(CheckCXXSourceCompiles)
+INCLUDE(CheckLibraryExists)
+
+# Sometimes linking against libatomic is required for atomic ops, if
+# the platform doesn't support lock-free atomics.
+
+function(check_working_cxx_atomics varname)
+ set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
+ set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++11")
+ CHECK_CXX_SOURCE_COMPILES("
+#include <atomic>
+std::atomic<int> x;
+int main() {
+ return x;
+}
+" ${varname})
+ set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
+endfunction(check_working_cxx_atomics)
+
+function(check_working_cxx_atomics64 varname)
+ set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
+ set(CMAKE_REQUIRED_FLAGS "-std=c++11 ${CMAKE_REQUIRED_FLAGS}")
+ CHECK_CXX_SOURCE_COMPILES("
+#include <atomic>
+#include <cstdint>
+std::atomic<uint64_t> x (0);
+int main() {
+ uint64_t i = x.load(std::memory_order_relaxed);
+ return 0;
+}
+" ${varname})
+ set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
+endfunction(check_working_cxx_atomics64)
+
+
+# This isn't necessary on MSVC, so avoid command-line switch annoyance
+# by only running on GCC-like hosts.
+if (LLVM_COMPILER_IS_GCC_COMPATIBLE)
+ # First check if atomics work without the library.
+ check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITHOUT_LIB)
+ # If not, check if the library exists, and atomics work with it.
+ if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB)
+ check_library_exists(atomic __atomic_fetch_add_4 "" HAVE_LIBATOMIC)
+ if( HAVE_LIBATOMIC )
+ list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic")
+ check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITH_LIB)
+ if (NOT HAVE_CXX_ATOMICS_WITH_LIB)
+ message(FATAL_ERROR "Host compiler must support std::atomic!")
+ endif()
+ else()
+ message(FATAL_ERROR "Host compiler appears to require libatomic, but cannot find it.")
+ endif()
+ endif()
+endif()
+
+# Check for 64 bit atomic operations.
+if(MSVC)
+ set(HAVE_CXX_ATOMICS64_WITHOUT_LIB True)
+else()
+ check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITHOUT_LIB)
+endif()
+
+# If not, check if the library exists, and atomics work with it.
+if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB)
+ check_library_exists(atomic __atomic_load_8 "" HAVE_CXX_LIBATOMICS64)
+ if(HAVE_CXX_LIBATOMICS64)
+ list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic")
+ check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB)
+ if (NOT HAVE_CXX_ATOMICS64_WITH_LIB)
+ message(FATAL_ERROR "Host compiler must support 64-bit std::atomic!")
+ endif()
+ else()
+ message(FATAL_ERROR "Host compiler appears to require libatomic for 64-bit operations, but cannot find it.")
+ endif()
+endif()
+
+## TODO: This define is only used for the legacy atomic operations in
+## llvm's Atomic.h, which should be replaced. Other code simply
+## assumes C++11 <atomic> works.
+CHECK_CXX_SOURCE_COMPILES("
+#ifdef _MSC_VER
+#include <windows.h>
+#endif
+int main() {
+#ifdef _MSC_VER
+ volatile LONG val = 1;
+ MemoryBarrier();
+ InterlockedCompareExchange(&val, 0, 1);
+ InterlockedIncrement(&val);
+ InterlockedDecrement(&val);
+#else
+ volatile unsigned long val = 1;
+ __sync_synchronize();
+ __sync_val_compare_and_swap(&val, 1, 0);
+ __sync_add_and_fetch(&val, 1);
+ __sync_sub_and_fetch(&val, 1);
+#endif
+ return 0;
+ }
+" LLVM_HAS_ATOMICS)
+
+if( NOT LLVM_HAS_ATOMICS )
+ message(STATUS "Warning: LLVM will be built thread-unsafe because atomic builtins are missing")
+endif()
\ No newline at end of file
diff --git a/cmake/FindBlosc.cmake b/cmake/FindBlosc.cmake
deleted file mode 100644
index 5aacfdd..0000000
@ -965,7 +1079,7 @@ index 339c1a2..0000000
- message(FATAL_ERROR "Unable to find OpenEXR")
-endif()
diff --git a/cmake/FindOpenVDB.cmake b/cmake/FindOpenVDB.cmake
index 63a2eda..6211071 100644
index 63a2eda..d9f6d07 100644
--- a/cmake/FindOpenVDB.cmake
+++ b/cmake/FindOpenVDB.cmake
@@ -244,7 +244,7 @@ set(OpenVDB_LIB_COMPONENTS "")
@ -1004,7 +1118,7 @@ index 63a2eda..6211071 100644
)
+
+ if (OPENVDB_USE_STATIC_LIBS)
+ set_target_properties(OpenVDB::${COMPONENT} PROPERTIES
+ set_target_properties(OpenVDB::${COMPONENT} PROPERTIES
+ INTERFACE_COMPILE_DEFINITIONS "OPENVDB_STATICLIB;OPENVDB_OPENEXR_STATICLIB"
+ )
+ endif()
@ -1012,7 +1126,7 @@ index 63a2eda..6211071 100644
endforeach()
diff --git a/cmake/FindTBB.cmake b/cmake/FindTBB.cmake
index bdf9c81..c6bdec9 100644
index bdf9c81..06093a4 100644
--- a/cmake/FindTBB.cmake
+++ b/cmake/FindTBB.cmake
@@ -1,333 +1,332 @@
@ -1022,35 +1136,21 @@ index bdf9c81..c6bdec9 100644
-# All rights reserved. This software is distributed under the
-# Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ )
+# Copyright (c) 2015 Justus Calvin
+#
#
-# Redistributions of source code must retain the above copyright
-# and license notice and the following restrictions and disclaimer.
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
#
-# Redistributions of source code must retain the above copyright
-# and license notice and the following restrictions and disclaimer.
+# FindTBB
+# -------
#
-# * Neither the name of DreamWorks Animation nor the names of
-# its contributors may be used to endorse or promote products derived
-# from this software without specific prior written permission.
+# Find TBB include directories and libraries.
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
@ -1065,7 +1165,14 @@ index bdf9c81..c6bdec9 100644
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-# IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE
-# LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00.
+# Usage:
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
#
-#[=======================================================================[.rst:
-
@ -1142,19 +1249,26 @@ index bdf9c81..c6bdec9 100644
-if(POLICY CMP0057)
- cmake_policy(SET CMP0057 NEW)
-endif()
+# FindTBB
+# -------
+#
+# Find TBB include directories and libraries.
+#
+# Usage:
+#
+# find_package(TBB [major[.minor]] [EXACT]
+# [QUIET] [REQUIRED]
+# [[COMPONENTS] [components...]]
+# [OPTIONAL_COMPONENTS components...])
+# [OPTIONAL_COMPONENTS components...])
+#
+# where the allowed components are tbbmalloc and tbb_preview. Users may modify
+# where the allowed components are tbbmalloc and tbb_preview. Users may modify
+# the behavior of this module with the following variables:
+#
+# * TBB_ROOT_DIR - The base directory the of TBB installation.
+# * TBB_INCLUDE_DIR - The directory that contains the TBB headers files.
+# * TBB_LIBRARY - The directory that contains the TBB library files.
+# * TBB_<library>_LIBRARY - The path of the TBB the corresponding TBB library.
+# These libraries, if specified, override the
+# * TBB_<library>_LIBRARY - The path of the TBB the corresponding TBB library.
+# These libraries, if specified, override the
+# corresponding library search results, where <library>
+# may be tbb, tbb_debug, tbbmalloc, tbbmalloc_debug,
+# tbb_preview, or tbb_preview_debug.
@ -1167,7 +1281,7 @@ index bdf9c81..c6bdec9 100644
+# Users may modify the behavior of this module with the following environment
+# variables:
+#
+# * TBB_INSTALL_DIR
+# * TBB_INSTALL_DIR
+# * TBBROOT
+# * LIBRARY_PATH
+#
@ -1180,15 +1294,15 @@ index bdf9c81..c6bdec9 100644
+# * TBB_VERSION - The full version string
+# * TBB_VERSION_MAJOR - The major version
+# * TBB_VERSION_MINOR - The minor version
+# * TBB_INTERFACE_VERSION - The interface version number defined in
+# * TBB_INTERFACE_VERSION - The interface version number defined in
+# tbb/tbb_stddef.h.
+# * TBB_<library>_LIBRARY_RELEASE - The path of the TBB release version of
+# * TBB_<library>_LIBRARY_RELEASE - The path of the TBB release version of
+# <library>, where <library> may be tbb, tbb_debug,
+# tbbmalloc, tbbmalloc_debug, tbb_preview, or
+# tbbmalloc, tbbmalloc_debug, tbb_preview, or
+# tbb_preview_debug.
+# * TBB_<library>_LIBRARY_DEGUG - The path of the TBB release version of
+# * TBB_<library>_LIBRARY_DEGUG - The path of the TBB release version of
+# <library>, where <library> may be tbb, tbb_debug,
+# tbbmalloc, tbbmalloc_debug, tbb_preview, or
+# tbbmalloc, tbbmalloc_debug, tbb_preview, or
+# tbb_preview_debug.
+#
+# The following varibles should be used to build and link with TBB:
@ -1244,12 +1358,10 @@ index bdf9c81..c6bdec9 100644
- set(TBB_FIND_COMPONENTS ${_TBB_COMPONENT_LIST})
-endif()
+include(FindPackageHandleStandardArgs)
+
+find_package(Threads QUIET REQUIRED)
-# Append TBB_ROOT or $ENV{TBB_ROOT} if set (prioritize the direct cmake var)
-set(_TBB_ROOT_SEARCH_DIR "")
+if(NOT TBB_FOUND)
+find_package(Threads QUIET REQUIRED)
-if(TBB_ROOT)
- list(APPEND _TBB_ROOT_SEARCH_DIR ${TBB_ROOT})
@ -1257,41 +1369,9 @@ index bdf9c81..c6bdec9 100644
- set(_ENV_TBB_ROOT $ENV{TBB_ROOT})
- if(_ENV_TBB_ROOT)
- list(APPEND _TBB_ROOT_SEARCH_DIR ${_ENV_TBB_ROOT})
+ ##################################
+ # Check the build type
+ ##################################
+
+ if(NOT DEFINED TBB_USE_DEBUG_BUILD)
+ if(CMAKE_BUILD_TYPE MATCHES "(Debug|DEBUG|debug)")
+ set(TBB_BUILD_TYPE DEBUG)
+ else()
+ set(TBB_BUILD_TYPE RELEASE)
+ endif()
+ elseif(TBB_USE_DEBUG_BUILD)
+ set(TBB_BUILD_TYPE DEBUG)
+ else()
+ set(TBB_BUILD_TYPE RELEASE)
endif()
- endif()
-endif()
+
+ ##################################
+ # Set the TBB search directories
+ ##################################
+
+ # Define search paths based on user input and environment variables
+ set(TBB_SEARCH_DIR ${TBB_ROOT_DIR} $ENV{TBB_INSTALL_DIR} $ENV{TBBROOT})
+
+ # Define the search directories based on the current platform
+ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+ set(TBB_DEFAULT_SEARCH_DIR "C:/Program Files/Intel/TBB"
+ "C:/Program Files (x86)/Intel/TBB")
+
+ # Set the target architecture
+ if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+ set(TBB_ARCHITECTURE "intel64")
+ else()
+ set(TBB_ARCHITECTURE "ia32")
+ endif()
+if(NOT TBB_FOUND)
-# Additionally try and use pkconfig to find Tbb
-
@ -1339,6 +1419,57 @@ index bdf9c81..c6bdec9 100644
-
- set(Tbb_VERSION ${Tbb_VERSION_MAJOR}.${Tbb_VERSION_MINOR})
-endif()
+ ##################################
+ # Check the build type
+ ##################################
+
+ if(NOT DEFINED TBB_USE_DEBUG_BUILD)
+ if(CMAKE_BUILD_TYPE MATCHES "(Debug|DEBUG|debug)")
+ set(TBB_BUILD_TYPE DEBUG)
+ else()
+ set(TBB_BUILD_TYPE RELEASE)
+ endif()
+ elseif(TBB_USE_DEBUG_BUILD)
+ set(TBB_BUILD_TYPE DEBUG)
+ else()
+ set(TBB_BUILD_TYPE RELEASE)
+ endif()
-# ------------------------------------------------------------------------
-# Search for TBB lib DIR
-# ------------------------------------------------------------------------
+ ##################################
+ # Set the TBB search directories
+ ##################################
-set(_TBB_LIBRARYDIR_SEARCH_DIRS "")
+ # Define search paths based on user input and environment variables
+ set(TBB_SEARCH_DIR ${TBB_ROOT_DIR} $ENV{TBB_INSTALL_DIR} $ENV{TBBROOT})
-# Append to _TBB_LIBRARYDIR_SEARCH_DIRS in priority order
+ # Define the search directories based on the current platform
+ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
+ set(TBB_DEFAULT_SEARCH_DIR "C:/Program Files/Intel/TBB"
+ "C:/Program Files (x86)/Intel/TBB")
-set(_TBB_LIBRARYDIR_SEARCH_DIRS "")
-list(APPEND _TBB_LIBRARYDIR_SEARCH_DIRS
- ${TBB_LIBRARYDIR}
- ${_TBB_ROOT_SEARCH_DIR}
- ${PC_Tbb_LIBRARY_DIRS}
- ${SYSTEM_LIBRARY_PATHS}
-)
+ # Set the target architecture
+ if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+ set(TBB_ARCHITECTURE "intel64")
+ else()
+ set(TBB_ARCHITECTURE "ia32")
+ endif()
-set(TBB_PATH_SUFFIXES
- lib64
- lib
-)
+ # Set the TBB search library path search suffix based on the version of VC
+ if(WINDOWS_STORE)
+ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11_ui")
@ -1352,104 +1483,16 @@ index bdf9c81..c6bdec9 100644
+ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc10")
+ endif()
-# ------------------------------------------------------------------------
-# Search for TBB lib DIR
-# ------------------------------------------------------------------------
-# platform branching
+ # Add the library path search suffix for the VC independent version of TBB
+ list(APPEND TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc_mt")
+
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+ # OS X
+ set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb")
+
+ # TODO: Check to see which C++ library is being used by the compiler.
+ if(NOT ${CMAKE_SYSTEM_VERSION} VERSION_LESS 13.0)
+ # The default C++ library on OS X 10.9 and later is libc++
+ set(TBB_LIB_PATH_SUFFIX "lib/libc++" "lib")
+ else()
+ set(TBB_LIB_PATH_SUFFIX "lib")
+ endif()
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ # Linux
+ set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb")
+
+ # TODO: Check compiler version to see the suffix should be <arch>/gcc4.1 or
+ # <arch>/gcc4.1. For now, assume that the compiler is more recent than
+ # gcc 4.4.x or later.
+ if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
+ set(TBB_LIB_PATH_SUFFIX "lib/intel64/gcc4.4")
+ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$")
+ set(TBB_LIB_PATH_SUFFIX "lib/ia32/gcc4.4")
+ endif()
+ endif()
+
+ ##################################
+ # Find the TBB include dir
+ ##################################
+
+ find_path(TBB_INCLUDE_DIRS tbb/tbb.h
+ HINTS ${TBB_INCLUDE_DIR} ${TBB_SEARCH_DIR}
+ PATHS ${TBB_DEFAULT_SEARCH_DIR}
+ PATH_SUFFIXES include)
+
+ ##################################
+ # Set version strings
+ ##################################
+
+ if(TBB_INCLUDE_DIRS)
+ file(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _tbb_version_file)
+ string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1"
+ TBB_VERSION_MAJOR "${_tbb_version_file}")
+ string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1"
+ TBB_VERSION_MINOR "${_tbb_version_file}")
+ string(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1"
+ TBB_INTERFACE_VERSION "${_tbb_version_file}")
+ set(TBB_VERSION "${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR}")
+ endif()
-set(_TBB_LIBRARYDIR_SEARCH_DIRS "")
+ ##################################
+ # Find TBB components
+ ##################################
-# Append to _TBB_LIBRARYDIR_SEARCH_DIRS in priority order
+ if(TBB_VERSION VERSION_LESS 4.3)
+ set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc tbb)
+ else()
+ set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc_proxy tbbmalloc tbb)
+ endif()
-set(_TBB_LIBRARYDIR_SEARCH_DIRS "")
-list(APPEND _TBB_LIBRARYDIR_SEARCH_DIRS
- ${TBB_LIBRARYDIR}
- ${_TBB_ROOT_SEARCH_DIR}
- ${PC_Tbb_LIBRARY_DIRS}
- ${SYSTEM_LIBRARY_PATHS}
-)
+ if(TBB_STATIC)
+ set(TBB_STATIC_SUFFIX "_static")
+ endif()
-set(TBB_PATH_SUFFIXES
- lib64
- lib
-)
+ # Find each component
+ foreach(_comp ${TBB_SEARCH_COMPOMPONENTS})
+ if(";${TBB_FIND_COMPONENTS};tbb;" MATCHES ";${_comp};")
-# platform branching
+ unset(TBB_${_comp}_LIBRARY_DEBUG CACHE)
+ unset(TBB_${_comp}_LIBRARY_RELEASE CACHE)
-if(UNIX)
- list(INSERT TBB_PATH_SUFFIXES 0 lib/x86_64-linux-gnu)
-endif()
+ # Search for the libraries
+ find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp}${TBB_STATIC_SUFFIX}
+ HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
+ PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
+ PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
+ # OS X
+ set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb")
-if(APPLE)
- if(TBB_FOR_CLANG)
@ -1471,29 +1514,33 @@ index bdf9c81..c6bdec9 100644
- list(GET GCC_VERSION_COMPONENTS 0 GCC_MAJOR)
- list(GET GCC_VERSION_COMPONENTS 1 GCC_MINOR)
- list(INSERT TBB_PATH_SUFFIXES 0 lib/intel64/gcc${GCC_MAJOR}.${GCC_MINOR})
- else()
+ # TODO: Check to see which C++ library is being used by the compiler.
+ if(NOT ${CMAKE_SYSTEM_VERSION} VERSION_LESS 13.0)
+ # The default C++ library on OS X 10.9 and later is libc++
+ set(TBB_LIB_PATH_SUFFIX "lib/libc++" "lib")
else()
- list(INSERT TBB_PATH_SUFFIXES 0 lib/intel64/gcc4.4)
- endif()
- endif()
+ set(TBB_LIB_PATH_SUFFIX "lib")
+ endif()
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ # Linux
+ set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb")
+
+ # TODO: Check compiler version to see the suffix should be <arch>/gcc4.1 or
+ # <arch>/gcc4.1. For now, assume that the compiler is more recent than
+ # gcc 4.4.x or later.
+ if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
+ set(TBB_LIB_PATH_SUFFIX "lib/intel64/gcc4.4")
+ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$")
+ set(TBB_LIB_PATH_SUFFIX "lib/ia32/gcc4.4")
endif()
endif()
-endif()
+ find_library(TBB_${_comp}_LIBRARY_DEBUG ${_comp}${TBB_STATIC_SUFFIX}_debug
+ HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
+ PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
+ PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
-
-if(UNIX AND TBB_USE_STATIC_LIBS)
- set(_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
- set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
-endif()
+ if(TBB_${_comp}_LIBRARY_DEBUG)
+ list(APPEND TBB_LIBRARIES_DEBUG "${TBB_${_comp}_LIBRARY_DEBUG}")
+ endif()
+ if(TBB_${_comp}_LIBRARY_RELEASE)
+ list(APPEND TBB_LIBRARIES_RELEASE "${TBB_${_comp}_LIBRARY_RELEASE}")
+ endif()
+ if(TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE} AND NOT TBB_${_comp}_LIBRARY)
+ set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE}}")
+ endif()
-set(Tbb_LIB_COMPONENTS "")
-
@ -1516,39 +1563,44 @@ index bdf9c81..c6bdec9 100644
- # Extract the directory and apply the matched text (in brackets)
- get_filename_component(Tbb_${COMPONENT}_DIR "${Tbb_${COMPONENT}_LIBRARY}" DIRECTORY)
- set(Tbb_${COMPONENT}_LIBRARY "${Tbb_${COMPONENT}_DIR}/${CMAKE_MATCH_1}")
+ if(TBB_${_comp}_LIBRARY AND EXISTS "${TBB_${_comp}_LIBRARY}")
+ set(TBB_${_comp}_FOUND TRUE)
+ else()
+ set(TBB_${_comp}_FOUND FALSE)
endif()
- endif()
- endif()
+ ##################################
+ # Find the TBB include dir
+ ##################################
+
+ # Mark internal variables as advanced
+ mark_as_advanced(TBB_${_comp}_LIBRARY_RELEASE)
+ mark_as_advanced(TBB_${_comp}_LIBRARY_DEBUG)
+ mark_as_advanced(TBB_${_comp}_LIBRARY)
+ find_path(TBB_INCLUDE_DIRS tbb/tbb.h
+ HINTS ${TBB_INCLUDE_DIR} ${TBB_SEARCH_DIR}
+ PATHS ${TBB_DEFAULT_SEARCH_DIR}
+ PATH_SUFFIXES include)
+
endif()
- endif()
+ endforeach()
+ ##################################
+ # Set version strings
+ ##################################
+
+ if(TBB_INCLUDE_DIRS)
+ file(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _tbb_version_file)
+ string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1"
+ TBB_VERSION_MAJOR "${_tbb_version_file}")
+ string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1"
+ TBB_VERSION_MINOR "${_tbb_version_file}")
+ string(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1"
+ TBB_INTERFACE_VERSION "${_tbb_version_file}")
+ set(TBB_VERSION "${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR}")
endif()
- list(APPEND Tbb_LIB_COMPONENTS ${Tbb_${COMPONENT}_LIBRARY})
+ ##################################
+ # Set compile flags and libraries
+ # Find TBB components
+ ##################################
- if(Tbb_${COMPONENT}_LIBRARY)
- set(TBB_${COMPONENT}_FOUND TRUE)
- else()
+ if(TBB_VERSION VERSION_LESS 4.3)
+ set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc tbb)
else()
- set(TBB_${COMPONENT}_FOUND FALSE)
+ set(TBB_DEFINITIONS_RELEASE "")
+ set(TBB_DEFINITIONS_DEBUG "TBB_USE_DEBUG=1")
+
+ if(TBB_LIBRARIES_${TBB_BUILD_TYPE})
+ set(TBB_LIBRARIES "${TBB_LIBRARIES_${TBB_BUILD_TYPE}}")
+ endif()
+
+ if(NOT MSVC AND NOT TBB_LIBRARIES)
+ set(TBB_LIBRARIES ${TBB_LIBRARIES_RELEASE})
+ set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc_proxy tbbmalloc tbb)
endif()
-endforeach()
@ -1556,61 +1608,51 @@ index bdf9c81..c6bdec9 100644
- set(CMAKE_FIND_LIBRARY_SUFFIXES ${_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES})
- unset(_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES)
-endif()
+ set(TBB_DEFINITIONS "")
+ if (MSVC AND TBB_STATIC)
+ set(TBB_DEFINITIONS __TBB_NO_IMPLICIT_LINKAGE)
+ endif ()
+
+ unset (TBB_STATIC_SUFFIX)
+
+ find_package_handle_standard_args(TBB
+ REQUIRED_VARS TBB_INCLUDE_DIRS TBB_LIBRARIES
+ FAIL_MESSAGE "TBB library cannot be found. Consider set TBBROOT environment variable."
+ HANDLE_COMPONENTS
+ VERSION_VAR TBB_VERSION)
+
+ ##################################
+ # Create targets
+ ##################################
+
+ if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND)
+ add_library(TBB::tbb UNKNOWN IMPORTED)
+ set_target_properties(TBB::tbb PROPERTIES
+ INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS}"
+ INTERFACE_LINK_LIBRARIES "Threads::Threads;${CMAKE_DL_LIBS}"
+ INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS}
+ IMPORTED_LOCATION ${TBB_LIBRARIES})
+ if(TBB_LIBRARIES_RELEASE AND TBB_LIBRARIES_DEBUG)
+ set_target_properties(TBB::tbb PROPERTIES
+ INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS};$<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:${TBB_DEFINITIONS_DEBUG}>;$<$<CONFIG:Release>:${TBB_DEFINITIONS_RELEASE}>"
+ IMPORTED_LOCATION_DEBUG ${TBB_LIBRARIES_DEBUG}
+ IMPORTED_LOCATION_RELWITHDEBINFO ${TBB_LIBRARIES_RELEASE}
+ IMPORTED_LOCATION_RELEASE ${TBB_LIBRARIES_RELEASE}
+ IMPORTED_LOCATION_MINSIZEREL ${TBB_LIBRARIES_RELEASE}
+ )
+ endif()
+ if(TBB_STATIC)
+ set(TBB_STATIC_SUFFIX "_static")
+ endif()
-# ------------------------------------------------------------------------
-# Cache and set TBB_FOUND
-# ------------------------------------------------------------------------
+ mark_as_advanced(TBB_INCLUDE_DIRS TBB_LIBRARIES)
+ # Find each component
+ foreach(_comp ${TBB_SEARCH_COMPOMPONENTS})
+ if(";${TBB_FIND_COMPONENTS};tbb;" MATCHES ";${_comp};")
+
+ unset(TBB_ARCHITECTURE)
+ unset(TBB_BUILD_TYPE)
+ unset(TBB_LIB_PATH_SUFFIX)
+ unset(TBB_DEFAULT_SEARCH_DIR)
+ unset(TBB_${_comp}_LIBRARY_DEBUG CACHE)
+ unset(TBB_${_comp}_LIBRARY_RELEASE CACHE)
+
+ if(TBB_DEBUG)
+ message(STATUS " TBB_FOUND = ${TBB_FOUND}")
+ message(STATUS " TBB_INCLUDE_DIRS = ${TBB_INCLUDE_DIRS}")
+ message(STATUS " TBB_DEFINITIONS = ${TBB_DEFINITIONS}")
+ message(STATUS " TBB_LIBRARIES = ${TBB_LIBRARIES}")
+ message(STATUS " TBB_DEFINITIONS_DEBUG = ${TBB_DEFINITIONS_DEBUG}")
+ message(STATUS " TBB_LIBRARIES_DEBUG = ${TBB_LIBRARIES_DEBUG}")
+ message(STATUS " TBB_DEFINITIONS_RELEASE = ${TBB_DEFINITIONS_RELEASE}")
+ message(STATUS " TBB_LIBRARIES_RELEASE = ${TBB_LIBRARIES_RELEASE}")
+ endif()
+ # Search for the libraries
+ find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp}${TBB_STATIC_SUFFIX}
+ HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
+ PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
+ PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
+
+ find_library(TBB_${_comp}_LIBRARY_DEBUG ${_comp}${TBB_STATIC_SUFFIX}_debug
+ HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
+ PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
+ PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
+
+ if(TBB_${_comp}_LIBRARY_DEBUG)
+ list(APPEND TBB_LIBRARIES_DEBUG "${TBB_${_comp}_LIBRARY_DEBUG}")
+ endif()
+ if(TBB_${_comp}_LIBRARY_RELEASE)
+ list(APPEND TBB_LIBRARIES_RELEASE "${TBB_${_comp}_LIBRARY_RELEASE}")
+ endif()
+ if(TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE} AND NOT TBB_${_comp}_LIBRARY)
+ set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE}}")
+ endif()
+
+ if(TBB_${_comp}_LIBRARY AND EXISTS "${TBB_${_comp}_LIBRARY}")
+ set(TBB_${_comp}_FOUND TRUE)
+ else()
+ set(TBB_${_comp}_FOUND FALSE)
+ endif()
+
+ # Mark internal variables as advanced
+ mark_as_advanced(TBB_${_comp}_LIBRARY_RELEASE)
+ mark_as_advanced(TBB_${_comp}_LIBRARY_DEBUG)
+ mark_as_advanced(TBB_${_comp}_LIBRARY)
-include(FindPackageHandleStandardArgs)
-find_package_handle_standard_args(TBB
@ -1646,13 +1688,82 @@ index bdf9c81..c6bdec9 100644
- INTERFACE_COMPILE_OPTIONS "${Tbb_DEFINITIONS}"
- INTERFACE_INCLUDE_DIRECTORIES "${Tbb_INCLUDE_DIR}"
- )
- endif()
- endforeach()
endif()
endforeach()
-elseif(TBB_FIND_REQUIRED)
- message(FATAL_ERROR "Unable to find TBB")
+
+ ##################################
+ # Set compile flags and libraries
+ ##################################
+
+ set(TBB_DEFINITIONS_RELEASE "")
+ set(TBB_DEFINITIONS_DEBUG "TBB_USE_DEBUG=1")
+
+ if(TBB_LIBRARIES_${TBB_BUILD_TYPE})
+ set(TBB_LIBRARIES "${TBB_LIBRARIES_${TBB_BUILD_TYPE}}")
+ endif()
+
+ if(NOT MSVC AND NOT TBB_LIBRARIES)
+ set(TBB_LIBRARIES ${TBB_LIBRARIES_RELEASE})
+ endif()
+
+ set(TBB_DEFINITIONS "")
+ if (MSVC AND TBB_STATIC)
+ set(TBB_DEFINITIONS __TBB_NO_IMPLICIT_LINKAGE)
+ endif ()
+
+ unset (TBB_STATIC_SUFFIX)
+
+ find_package_handle_standard_args(TBB
+ REQUIRED_VARS TBB_INCLUDE_DIRS TBB_LIBRARIES
+ FAIL_MESSAGE "TBB library cannot be found. Consider set TBBROOT environment variable."
+ HANDLE_COMPONENTS
+ VERSION_VAR TBB_VERSION)
+
+ ##################################
+ # Create targets
+ ##################################
+
+ if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND)
+ add_library(TBB::tbb UNKNOWN IMPORTED)
+ set_target_properties(TBB::tbb PROPERTIES
+ INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS}"
+ INTERFACE_LINK_LIBRARIES "Threads::Threads;${CMAKE_DL_LIBS}"
+ INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS}
+ IMPORTED_LOCATION ${TBB_LIBRARIES})
+ if(TBB_LIBRARIES_RELEASE AND TBB_LIBRARIES_DEBUG)
+ set_target_properties(TBB::tbb PROPERTIES
+ INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS};$<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:${TBB_DEFINITIONS_DEBUG}>;$<$<CONFIG:Release>:${TBB_DEFINITIONS_RELEASE}>"
+ IMPORTED_LOCATION_DEBUG ${TBB_LIBRARIES_DEBUG}
+ IMPORTED_LOCATION_RELWITHDEBINFO ${TBB_LIBRARIES_RELEASE}
+ IMPORTED_LOCATION_RELEASE ${TBB_LIBRARIES_RELEASE}
+ IMPORTED_LOCATION_MINSIZEREL ${TBB_LIBRARIES_RELEASE}
+ )
+ endif()
+ endif()
+
+ mark_as_advanced(TBB_INCLUDE_DIRS TBB_LIBRARIES)
+
+ unset(TBB_ARCHITECTURE)
+ unset(TBB_BUILD_TYPE)
+ unset(TBB_LIB_PATH_SUFFIX)
+ unset(TBB_DEFAULT_SEARCH_DIR)
+
+ if(TBB_DEBUG)
+ message(STATUS " TBB_FOUND = ${TBB_FOUND}")
+ message(STATUS " TBB_INCLUDE_DIRS = ${TBB_INCLUDE_DIRS}")
+ message(STATUS " TBB_DEFINITIONS = ${TBB_DEFINITIONS}")
+ message(STATUS " TBB_LIBRARIES = ${TBB_LIBRARIES}")
+ message(STATUS " TBB_DEFINITIONS_DEBUG = ${TBB_DEFINITIONS_DEBUG}")
+ message(STATUS " TBB_LIBRARIES_DEBUG = ${TBB_LIBRARIES_DEBUG}")
+ message(STATUS " TBB_DEFINITIONS_RELEASE = ${TBB_DEFINITIONS_RELEASE}")
+ message(STATUS " TBB_LIBRARIES_RELEASE = ${TBB_LIBRARIES_RELEASE}")
+ endif()
+
endif()
diff --git a/openvdb/CMakeLists.txt b/openvdb/CMakeLists.txt
index 89301bd..df27aae 100644
index 89301bd..6a3c90c 100644
--- a/openvdb/CMakeLists.txt
+++ b/openvdb/CMakeLists.txt
@@ -78,7 +78,7 @@ else()
@ -1664,7 +1775,21 @@ index 89301bd..df27aae 100644
message(DEPRECATION "Support for TBB versions < ${FUTURE_MINIMUM_TBB_VERSION} "
"is deprecated and will be removed.")
endif()
@@ -185,11 +185,6 @@ if(WIN32)
@@ -129,10 +129,13 @@ endif()
# include paths from shared installs (including houdini) may pull in the wrong
# headers
+include (CheckAtomic)
+
set(OPENVDB_CORE_DEPENDENT_LIBS
Boost::iostreams
Boost::system
IlmBase::Half
+ ${CMAKE_REQUIRED_LIBRARIES}
)
if(USE_EXR)
@@ -185,11 +188,6 @@ if(WIN32)
endif()
endif()
@ -1676,7 +1801,7 @@ index 89301bd..df27aae 100644
##### Core library configuration
set(OPENVDB_LIBRARY_SOURCE_FILES
@@ -374,10 +369,16 @@ set(OPENVDB_LIBRARY_UTIL_INCLUDE_FILES
@@ -374,10 +372,16 @@ set(OPENVDB_LIBRARY_UTIL_INCLUDE_FILES
if(OPENVDB_CORE_SHARED)
add_library(openvdb_shared SHARED ${OPENVDB_LIBRARY_SOURCE_FILES})
@ -1779,5 +1904,5 @@ index df51830..0ab0c12 100644
/// @todo This changes the compressor setting globally.
if (blosc_set_compressor(compname) < 0) continue;
--
2.16.2.windows.1
2.17.1

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(openvdb)
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)
target_link_libraries(meshboolean libslic3r CGAL::CGAL)
target_link_libraries(meshboolean libslic3r)
if (WIN32)
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 <vector>
#include <admesh/stl.h>
#include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/Model.hpp>
#include <libslic3r/SLAPrint.hpp>
#include <libslic3r/SLAPrintSteps.hpp>
#include <libslic3r/MeshBoolean.hpp>
#include <libnest2d/tools/benchmark.h>
#include <boost/nowide/cstdio.hpp>
#include <boost/log/trivial.hpp>
namespace Slic3r {
bool its_write_obj(const Eigen::MatrixXd &V, Eigen::MatrixXi &F, const char *file)
{
FILE *fp = boost::nowide::fopen(file, "w");
if (fp == nullptr) {
BOOST_LOG_TRIVIAL(error) << "stl_write_obj: Couldn't open " << file << " for writing";
return false;
}
for (size_t i = 0; i < V.rows(); ++ i)
fprintf(fp, "v %lf %lf %lf\n", V(i, 0), V(i, 1), V(i, 2));
for (size_t i = 0; i < F.rows(); ++ i)
fprintf(fp, "f %d %d %d\n", F(i, 0) + 1, F(i, 1) + 1, F(i, 2) + 1);
fclose(fp);
return true;
}
void mesh_boolean_test(const std::string &fname)
{
using namespace Eigen;
using namespace std;
// igl::readOFF(TUTORIAL_SHARED_PATH "/cheburashka.off",VA,FA);
// igl::readOFF(TUTORIAL_SHARED_PATH "/decimated-knight.off",VB,FB);
// Plot the mesh with pseudocolors
// igl::opengl::glfw::Viewer viewer;
// Initialize
// update(viewer);
//igl::copyleft::cgal::mesh_boolean(VA,FA,VB,FB,boolean_type,VC,FC,J);
std::cout << fname.c_str() << std::endl;
TriangleMesh mesh;
mesh.ReadSTLFile(fname.c_str());
mesh.repair(true);
its_write_obj(mesh.its, (fname + "-imported0.obj").c_str());
Eigen::MatrixXd VA,VB,VC;
Eigen::VectorXi J,I;
Eigen::MatrixXi FA,FB,FC;
igl::MeshBooleanType boolean_type(igl::MESH_BOOLEAN_TYPE_UNION);
typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
Eigen::MatrixXd V = MapMatrixXfUnaligned(mesh.its.vertices.front().data(), mesh.its.vertices.size(), 3).cast<double>();
Eigen::MatrixXi F = MapMatrixXiUnaligned(mesh.its.indices.front().data(), mesh.its.indices.size(), 3);
its_write_obj(V, F, (fname + "-imported.obj").c_str());
// Self-union to clean up
igl::copyleft::cgal::mesh_boolean(V, F, Eigen::MatrixXd(), Eigen::MatrixXi(), boolean_type, VC, FC);
its_write_obj(VC, FC, (fname + "-fixed.obj").c_str());
}
} // namespace Slic3r
int main(const int argc, const char * argv[])
{
if (argc < 1) return -1;
using namespace Slic3r;
Slic3r::mesh_boolean_test(argv[1]);
if (argc <= 1) return EXIT_FAILURE;
DynamicPrintConfig cfg;
auto model = Model::read_from_file(argv[1], &cfg);
if (model.objects.empty()) return EXIT_SUCCESS;
SLAPrint print;
print.apply(model, cfg);
PrintBase::TaskParams task;
task.to_object_step = slaposHollowing;
print.set_task(task);
print.process();
Benchmark bench;
for (SLAPrintObject *po : print.objects()) {
TriangleMesh holes;
sla::DrainHoles holepts = po->transformed_drainhole_points();
for (auto &hole: holepts)
holes.merge(sla::to_triangle_mesh(hole.to_mesh()));
TriangleMesh hollowed_mesh = po->transformed_mesh();
hollowed_mesh.merge(po->hollowed_interior_mesh());
hollowed_mesh.require_shared_vertices();
holes.require_shared_vertices();
TriangleMesh drilled_mesh_igl = hollowed_mesh;
bench.start();
MeshBoolean::minus(drilled_mesh_igl, holes);
bench.stop();
std::cout << "Mesh boolean duration with IGL: " << bench.getElapsedSec() << std::endl;
TriangleMesh drilled_mesh_cgal = hollowed_mesh;
bench.start();
MeshBoolean::cgal::self_union(drilled_mesh_cgal);
MeshBoolean::cgal::minus(drilled_mesh_cgal, holes);
bench.stop();
std::cout << "Mesh boolean duration with CGAL: " << bench.getElapsedSec() << std::endl;
std::string name("obj"), outf;
outf = name + "igl" + std::to_string(po->model_object()->id().id) + ".obj";
drilled_mesh_igl.WriteOBJFile(outf.c_str());
outf = name + "cgal" + std::to_string(po->model_object()->id().id) + ".obj";
drilled_mesh_cgal.WriteOBJFile(outf.c_str());
}
return 0;
}

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)
target_link_libraries(openvdb_example libslic3r)
if(TARGET OpenVDB::openvdb)
add_executable(openvdb_example openvdb_example.cpp)
target_link_libraries(openvdb_example libslic3r)
target_link_libraries(openvdb_example OpenVDB::openvdb)
endif()

View file

@ -9,6 +9,11 @@ if (MINGW)
add_compile_options(-Wa,-mbig-obj)
endif ()
set(OpenVDBUtils_SOURCES "")
if (TARGET OpenVDB::openvdb)
set(OpenVDBUtils_SOURCES OpenVDBUtils.cpp OpenVDBUtils.hpp)
endif()
add_library(libslic3r STATIC
pchheader.cpp
pchheader.hpp
@ -149,9 +154,9 @@ add_library(libslic3r STATIC
ShortestPath.cpp
ShortestPath.hpp
SLAPrint.cpp
SLAPrintSteps.cpp
SLAPrintSteps.hpp
SLAPrint.hpp
SLA/SLAAutoSupports.hpp
SLA/SLAAutoSupports.cpp
Slicing.cpp
Slicing.hpp
SlicingAdaptive.cpp
@ -180,35 +185,65 @@ add_library(libslic3r STATIC
MinAreaBoundingBox.cpp
miniz_extension.hpp
miniz_extension.cpp
SLA/SLACommon.hpp
SLA/SLABoilerPlate.hpp
SLA/SLAPad.hpp
SLA/SLAPad.cpp
SLA/SLASupportTreeBuilder.hpp
SLA/SLASupportTreeBuildsteps.hpp
SLA/SLASupportTreeBuildsteps.cpp
SLA/SLASupportTreeBuilder.cpp
SLA/SLAConcurrency.hpp
SLA/SLASupportTree.hpp
SLA/SLASupportTree.cpp
SLA/SLASupportTreeIGL.cpp
SLA/SLARotfinder.hpp
SLA/SLARotfinder.cpp
SLA/SLABoostAdapter.hpp
SLA/SLASpatIndex.hpp
SLA/SLARaster.hpp
SLA/SLARaster.cpp
SLA/SLARasterWriter.hpp
SLA/SLARasterWriter.cpp
SimplifyMesh.hpp
SimplifyMeshImpl.hpp
SimplifyMesh.cpp
${OpenVDBUtils_SOURCES}
SLA/Common.hpp
SLA/Common.cpp
SLA/Pad.hpp
SLA/Pad.cpp
SLA/SupportTreeBuilder.hpp
SLA/SupportTreeBuildsteps.hpp
SLA/SupportTreeBuildsteps.cpp
SLA/SupportTreeBuilder.cpp
SLA/Concurrency.hpp
SLA/SupportTree.hpp
SLA/SupportTree.cpp
# SLA/SupportTreeIGL.cpp
SLA/Rotfinder.hpp
SLA/Rotfinder.cpp
SLA/BoostAdapter.hpp
SLA/SpatIndex.hpp
SLA/Raster.hpp
SLA/Raster.cpp
SLA/RasterWriter.hpp
SLA/RasterWriter.cpp
SLA/ConcaveHull.hpp
SLA/ConcaveHull.cpp
SLA/Hollowing.hpp
SLA/Hollowing.cpp
SLA/JobController.hpp
SLA/SupportPoint.hpp
SLA/SupportPointGenerator.hpp
SLA/SupportPointGenerator.cpp
SLA/Contour3D.hpp
SLA/Contour3D.cpp
SLA/EigenMesh3D.hpp
SLA/Clustering.hpp
)
encoding_check(libslic3r)
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE)
if (SLIC3R_STATIC)
set(CGAL_Boost_USE_STATIC_LIBS ON CACHE BOOL "" FORCE)
endif ()
set(CGAL_DO_NOT_WARN_ABOUT_CMAKE_BUILD_TYPE ON CACHE BOOL "" FORCE)
cmake_policy(PUSH)
cmake_policy(SET CMP0011 NEW)
find_package(CGAL REQUIRED)
cmake_policy(POP)
add_library(libslic3r_cgal OBJECT MeshBoolean.cpp MeshBoolean.hpp)
target_include_directories(libslic3r_cgal PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
$<TARGET_PROPERTY:libigl,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_INCLUDE_DIRECTORIES>)
target_compile_definitions(libslic3r_cgal PRIVATE
$<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_COMPILE_DEFINITIONS>)
target_compile_options(libslic3r_cgal PRIVATE
$<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_COMPILE_OPTIONS>)
encoding_check(libslic3r)
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0)
target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${LIBNEST2D_INCLUDES} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
@ -228,10 +263,14 @@ target_link_libraries(libslic3r
qhull
semver
TBB::tbb
# OpenVDB::openvdb
$<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_LINK_LIBRARIES>
${CMAKE_DL_LIBS}
)
if (TARGET OpenVDB::openvdb)
target_link_libraries(libslic3r OpenVDB::openvdb)
endif()
if(WIN32)
target_link_libraries(libslic3r Psapi.lib)
endif()
@ -239,3 +278,9 @@ endif()
if(SLIC3R_PROFILE)
target_link_libraries(slic3r Shiny)
endif()
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE)
endif ()
target_sources(libslic3r PRIVATE $<TARGET_OBJECTS:libslic3r_cgal>)

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_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml";
const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt";
const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt";
const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml";
const char* MODEL_TAG = "model";
@ -385,6 +386,7 @@ namespace Slic3r {
typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap;
typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap;
typedef std::map<int, std::vector<sla::DrainHole>> IdToSlaDrainHolesMap;
// Version of the 3mf file
unsigned int m_version;
@ -403,6 +405,7 @@ namespace Slic3r {
IdToLayerHeightsProfileMap m_layer_heights_profiles;
IdToLayerConfigRangesMap m_layer_config_ranges;
IdToSlaSupportPointsMap m_sla_support_points;
IdToSlaDrainHolesMap m_sla_drain_holes;
std::string m_curr_metadata_name;
std::string m_curr_characters;
std::string m_name;
@ -422,6 +425,7 @@ namespace Slic3r {
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
@ -629,6 +633,11 @@ namespace Slic3r {
// extract sla support points file
_extract_sla_support_points_from_archive(archive, stat);
}
else if (boost::algorithm::iequals(name, SLA_DRAIN_HOLES_FILE))
{
// extract sla support points file
_extract_sla_drain_holes_from_archive(archive, stat);
}
else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE))
{
// extract slic3r print config file
@ -683,6 +692,11 @@ namespace Slic3r {
model_object->sla_support_points = obj_sla_support_points->second;
model_object->sla_points_status = sla::PointsStatus::UserModified;
}
IdToSlaDrainHolesMap::iterator obj_drain_holes = m_sla_drain_holes.find(object.second + 1);
if (obj_drain_holes != m_sla_drain_holes.end() && !obj_drain_holes->second.empty()) {
model_object->sla_drain_holes = obj_drain_holes->second;
}
IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first);
if (obj_metadata != m_objects_metadata.end())
@ -955,8 +969,9 @@ namespace Slic3r {
// Info on format versioning - see 3mf.hpp
int version = 0;
if (!objects.empty() && objects[0].find("support_points_format_version=") != std::string::npos) {
objects[0].erase(objects[0].begin(), objects[0].begin() + 30); // removes the string
std::string key("support_points_format_version=");
if (!objects.empty() && objects[0].find(key) != std::string::npos) {
objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string
version = std::stoi(objects[0]);
objects.erase(objects.begin()); // pop the header
}
@ -1022,6 +1037,90 @@ namespace Slic3r {
}
}
}
void _3MF_Importer::_extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
{
if (stat.m_uncomp_size > 0)
{
std::string buffer(size_t(stat.m_uncomp_size), 0);
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
if (res == 0)
{
add_error("Error while reading sla support points data to buffer");
return;
}
if (buffer.back() == '\n')
buffer.pop_back();
std::vector<std::string> objects;
boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off);
// Info on format versioning - see 3mf.hpp
int version = 0;
std::string key("drain_holes_format_version=");
if (!objects.empty() && objects[0].find(key) != std::string::npos) {
objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string
version = std::stoi(objects[0]);
objects.erase(objects.begin()); // pop the header
}
for (const std::string& object : objects)
{
std::vector<std::string> object_data;
boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off);
if (object_data.size() != 2)
{
add_error("Error while reading object data");
continue;
}
std::vector<std::string> object_data_id;
boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off);
if (object_data_id.size() != 2)
{
add_error("Error while reading object id");
continue;
}
int object_id = std::atoi(object_data_id[1].c_str());
if (object_id == 0)
{
add_error("Found invalid object id");
continue;
}
IdToSlaDrainHolesMap::iterator object_item = m_sla_drain_holes.find(object_id);
if (object_item != m_sla_drain_holes.end())
{
add_error("Found duplicated SLA drain holes");
continue;
}
std::vector<std::string> object_data_points;
boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off);
sla::DrainHoles sla_drain_holes;
if (version == 1) {
for (unsigned int i=0; i<object_data_points.size(); i+=8)
sla_drain_holes.emplace_back(Vec3f{float(std::atof(object_data_points[i+0].c_str())),
float(std::atof(object_data_points[i+1].c_str())),
float(std::atof(object_data_points[i+2].c_str()))},
Vec3f{float(std::atof(object_data_points[i+3].c_str())),
float(std::atof(object_data_points[i+4].c_str())),
float(std::atof(object_data_points[i+5].c_str()))},
float(std::atof(object_data_points[i+6].c_str())),
float(std::atof(object_data_points[i+7].c_str())));
}
if (!sla_drain_holes.empty())
m_sla_drain_holes.insert(IdToSlaDrainHolesMap::value_type(object_id, sla_drain_holes));
}
}
}
bool _3MF_Importer::_extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model)
@ -1904,6 +2003,7 @@ namespace Slic3r {
bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model);
bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config);
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data);
bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model);
@ -2025,6 +2125,14 @@ namespace Slic3r {
boost::filesystem::remove(filename);
return false;
}
if (!_add_sla_drain_holes_file_to_archive(archive, model))
{
close_zip_writer(&archive);
boost::filesystem::remove(filename);
return false;
}
// Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml").
// All custom gcode per height of whole Model are stored here
@ -2482,6 +2590,50 @@ namespace Slic3r {
}
return true;
}
bool _3MF_Exporter::_add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model)
{
const char *const fmt = "object_id=%d|";
std::string out;
unsigned int count = 0;
for (const ModelObject* object : model.objects)
{
++count;
auto& drain_holes = object->sla_drain_holes;
if (!drain_holes.empty())
{
out += string_printf(fmt, count);
// Store the layer height profile as a single space separated list.
for (size_t i = 0; i < drain_holes.size(); ++i)
out += string_printf((i == 0 ? "%f %f %f %f %f %f %f %f" : " %f %f %f %f %f %f %f %f"),
drain_holes[i].pos(0),
drain_holes[i].pos(1),
drain_holes[i].pos(2),
drain_holes[i].normal(0),
drain_holes[i].normal(1),
drain_holes[i].normal(2),
drain_holes[i].radius,
drain_holes[i].height);
out += "\n";
}
}
if (!out.empty())
{
// Adds version header at the beginning:
out = std::string("drain_holes_format_version=") + std::to_string(drain_holes_format_version) + std::string("\n") + out;
if (!mz_zip_writer_add_mem(&archive, SLA_DRAIN_HOLES_FILE.c_str(), static_cast<const void*>(out.data()), out.length(), mz_uint(MZ_DEFAULT_COMPRESSION)))
{
add_error("Unable to add sla support points file to archive");
return false;
}
}
return true;
}
bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config)
{

View file

@ -19,6 +19,10 @@ namespace Slic3r {
enum {
support_points_format_version = 1
};
enum {
drain_holes_format_version = 1
};
class Model;
class DynamicPrintConfig;

View file

@ -355,6 +355,35 @@ bool objparse(const char *path, ObjData &data)
return true;
}
bool objparse(std::istream &stream, ObjData &data)
{
try {
char buf[65536 * 2];
size_t len = 0;
size_t lenPrev = 0;
while ((len = size_t(stream.read(buf + lenPrev, 65536).gcount())) != 0) {
len += lenPrev;
size_t lastLine = 0;
for (size_t i = 0; i < len; ++ i)
if (buf[i] == '\r' || buf[i] == '\n') {
buf[i] = 0;
char *c = buf + lastLine;
while (*c == ' ' || *c == '\t')
++ c;
obj_parseline(c, data);
lastLine = i + 1;
}
lenPrev = len - lastLine;
memmove(buf, buf + lastLine, lenPrev);
}
}
catch (std::bad_alloc&) {
printf("Out of memory\r\n");
}
return true;
}
template<typename T>
bool savevector(FILE *pFile, const std::vector<T> &v)
{

View file

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

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

View file

@ -8,7 +8,8 @@
#include "Point.hpp"
#include "PrintConfig.hpp"
#include "Slicing.hpp"
#include "SLA/SLACommon.hpp"
#include "SLA/SupportPoint.hpp"
#include "SLA/Hollowing.hpp"
#include "TriangleMesh.hpp"
#include "Arrange.hpp"
#include "CustomGCode.hpp"
@ -199,10 +200,13 @@ public:
// This vector holds position of selected support points for SLA. The data are
// saved in mesh coordinates to allow using them for several instances.
// The format is (x, y, z, point_size, supports_island)
std::vector<sla::SupportPoint> sla_support_points;
sla::SupportPoints sla_support_points;
// To keep track of where the points came from (used for synchronization between
// the SLA gizmo and the backend).
sla::PointsStatus sla_points_status = sla::PointsStatus::NoPoints;
sla::PointsStatus sla_points_status = sla::PointsStatus::NoPoints;
// Holes to be drilled into the object so resin can flow out
sla::DrainHoles sla_drain_holes;
/* This vector accumulates the total translation applied to the object by the
center_around_origin() method. Callers might want to apply the same translation
@ -373,7 +377,7 @@ private:
template<class Archive> void serialize(Archive &ar) {
ar(cereal::base_class<ObjectBase>(this));
Internal::StaticSerializationWrapper<ModelConfig> config_wrapper(config);
ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, printable, origin_translation,
ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation,
m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid);
}
};

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->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0.3));
def = this->add("hollowing_enable", coBool);
def->label = L("Enable hollowing");
def->category = L("Hollowing");
def->tooltip = L("Hollow out a model to have an empty interior");
def->mode = comSimple;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("hollowing_min_thickness", coFloat);
def->label = L("Hollowing thickness");
def->category = L("Hollowing");
def->tooltip = L("Minimum wall thickness of a hollowed model.");
def->sidetext = L("mm");
def->min = 1;
def->max = 10;
def->mode = comSimple;
def->set_default_value(new ConfigOptionFloat(3.));
def = this->add("hollowing_quality", coFloat);
def->label = L("Hollowing accuracy");
def->category = L("Hollowing");
def->tooltip = L("Performance vs accuracy of calculation. Lower values may produce unwanted artifacts.");
def->min = 0;
def->max = 1;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0.5));
def = this->add("hollowing_closing_distance", coFloat);
def->label = L("Hollowing closing distance");
def->category = L("Hollowing");
def->tooltip = L("");
def->min = 0;
def->max = 10;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(2.0));
}
void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value)

View file

@ -1017,7 +1017,7 @@ public:
ConfigOptionFloat support_base_height /*= 1.0*/;
// The minimum distance of the pillar base from the model in mm.
ConfigOptionFloat support_base_safety_distance; /*= 1.0*/;
ConfigOptionFloat support_base_safety_distance; /*= 1.0*/
// The default angle for connecting support sticks and junctions.
ConfigOptionFloat support_critical_angle /*= 45*/;
@ -1062,7 +1062,7 @@ public:
// /////////////////////////////////////////////////////////////////////////
// Zero elevation mode parameters:
// - The object pad will be derived from the the model geometry.
// - The object pad will be derived from the model geometry.
// - There will be a gap between the object pad and the generated pad
// according to the support_base_safety_distance parameter.
// - The two pads will be connected with tiny connector sticks
@ -1084,6 +1084,28 @@ public:
// How much should the tiny connectors penetrate into the model body
ConfigOptionFloat pad_object_connector_penetration;
// /////////////////////////////////////////////////////////////////////////
// Model hollowing parameters:
// - Models can be hollowed out as part of the SLA print process
// - Thickness of the hollowed model walls can be adjusted
// -
// - Additional holes will be drilled into the hollow model to allow for
// - resin removal.
// /////////////////////////////////////////////////////////////////////////
ConfigOptionBool hollowing_enable;
// The minimum thickness of the model walls to maintain. Note that the
// resulting walls may be thicker due to smoothing out fine cavities where
// resin could stuck.
ConfigOptionFloat hollowing_min_thickness;
// Indirectly controls the voxel size (resolution) used by openvdb
ConfigOptionFloat hollowing_quality;
// Indirectly controls the minimum size of created cavities.
ConfigOptionFloat hollowing_closing_distance;
protected:
void initialize(StaticCacheBase &cache, const char *base_ptr)
@ -1121,6 +1143,10 @@ protected:
OPT_PTR(pad_object_connector_stride);
OPT_PTR(pad_object_connector_width);
OPT_PTR(pad_object_connector_penetration);
OPT_PTR(hollowing_enable);
OPT_PTR(hollowing_min_thickness);
OPT_PTR(hollowing_quality);
OPT_PTR(hollowing_closing_distance);
}
};

View file

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

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
#ifndef SLARASTERWRITER_HPP
#define SLARASTERWRITER_HPP
#ifndef SLA_RASTERWRITER_HPP
#define SLA_RASTERWRITER_HPP
// For png export of the sliced model
#include <fstream>
@ -9,12 +9,14 @@
#include <map>
#include <array>
#include "libslic3r/PrintConfig.hpp"
#include <libslic3r/SLA/Raster.hpp>
#include <libslic3r/Zipper.hpp>
#include "SLARaster.hpp"
#include "libslic3r/Zipper.hpp"
namespace Slic3r {
namespace Slic3r { namespace sla {
class DynamicPrintConfig;
namespace sla {
// API to write the zipped sla output layers and metadata.
// Implementation uses PNG raster output.
@ -116,7 +118,7 @@ public:
void save(Zipper &zipper, const std::string &prjname = "");
void set_statistics(const PrintStatistics &statistics);
void set_config(const DynamicPrintConfig &cfg);
};

View file

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

View file

@ -1,5 +1,5 @@
#ifndef SLAROTFINDER_HPP
#define SLAROTFINDER_HPP
#ifndef SLA_ROTFINDER_HPP
#define SLA_ROTFINDER_HPP
#include <functional>
#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
#define SPATINDEX_HPP
#ifndef SLA_SPATINDEX_HPP
#define SLA_SPATINDEX_HPP
#include <memory>
#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 "SLAAutoSupports.hpp"
#include "SupportPointGenerator.hpp"
#include "Model.hpp"
#include "ExPolygon.hpp"
#include "SVG.hpp"
@ -18,7 +18,7 @@
namespace Slic3r {
namespace sla {
/*float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
/*float SupportPointGenerator::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
{
n1.normalize();
n2.normalize();
@ -36,7 +36,7 @@ namespace sla {
}
float SLAAutoSupports::get_required_density(float angle) const
float SupportPointGenerator::get_required_density(float angle) const
{
// calculation would be density_0 * cos(angle). To provide one more degree of freedom, we will scale the angle
// to get the user-set density for 45 deg. So it ends up as density_0 * cos(K * angle).
@ -44,27 +44,45 @@ float SLAAutoSupports::get_required_density(float angle) const
return std::max(0.f, float(m_config.density_at_horizontal * cos(K*angle)));
}
float SLAAutoSupports::distance_limit(float angle) const
float SupportPointGenerator::distance_limit(float angle) const
{
return 1./(2.4*get_required_density(angle));
}*/
SLAAutoSupports::SLAAutoSupports(const sla::EigenMesh3D & emesh,
const std::vector<ExPolygons> &slices,
const std::vector<float> & heights,
const Config & config,
std::function<void(void)> throw_on_cancel,
std::function<void(int)> statusfn)
SupportPointGenerator::SupportPointGenerator(
const sla::EigenMesh3D &emesh,
const std::vector<ExPolygons> &slices,
const std::vector<float> & heights,
const Config & config,
std::function<void(void)> throw_on_cancel,
std::function<void(int)> statusfn)
: SupportPointGenerator(emesh, config, throw_on_cancel, statusfn)
{
std::random_device rd;
m_rng.seed(rd());
execute(slices, heights);
}
SupportPointGenerator::SupportPointGenerator(
const EigenMesh3D &emesh,
const SupportPointGenerator::Config &config,
std::function<void ()> throw_on_cancel,
std::function<void (int)> statusfn)
: m_config(config)
, m_emesh(emesh)
, m_throw_on_cancel(throw_on_cancel)
, m_statusfn(statusfn)
{
}
void SupportPointGenerator::execute(const std::vector<ExPolygons> &slices,
const std::vector<float> & heights)
{
process(slices, heights);
project_onto_mesh(m_output);
}
void SLAAutoSupports::project_onto_mesh(std::vector<sla::SupportPoint>& points) const
void SupportPointGenerator::project_onto_mesh(std::vector<sla::SupportPoint>& points) const
{
// The function makes sure that all the points are really exactly placed on the mesh.
@ -77,36 +95,29 @@ void SLAAutoSupports::project_onto_mesh(std::vector<sla::SupportPoint>& points)
m_throw_on_cancel();
Vec3f& p = points[point_id].pos;
// Project the point upward and downward and choose the closer intersection with the mesh.
//bool up = igl::ray_mesh_intersect(p.cast<float>(), Vec3f(0., 0., 1.), m_V, m_F, hit_up);
//bool down = igl::ray_mesh_intersect(p.cast<float>(), Vec3f(0., 0., -1.), m_V, m_F, hit_down);
sla::EigenMesh3D::hit_result hit_up = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., 1.));
sla::EigenMesh3D::hit_result hit_down = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., -1.));
bool up = hit_up.face() != -1;
bool down = hit_down.face() != -1;
bool up = hit_up.is_hit();
bool down = hit_down.is_hit();
if (!up && !down)
continue;
sla::EigenMesh3D::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down;
//int fid = hit.face();
//Vec3f bc(1-hit.u-hit.v, hit.u, hit.v);
//p = (bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2))).cast<float>();
p = p + (hit.distance() * hit.direction()).cast<float>();
}
});
}
static std::vector<SLAAutoSupports::MyLayer> make_layers(
static std::vector<SupportPointGenerator::MyLayer> make_layers(
const std::vector<ExPolygons>& slices, const std::vector<float>& heights,
std::function<void(void)> throw_on_cancel)
{
assert(slices.size() == heights.size());
// Allocate empty layers.
std::vector<SLAAutoSupports::MyLayer> layers;
std::vector<SupportPointGenerator::MyLayer> layers;
layers.reserve(slices.size());
for (size_t i = 0; i < slices.size(); ++ i)
layers.emplace_back(i, heights[i]);
@ -122,7 +133,7 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers(
if ((layer_id % 8) == 0)
// Don't call the following function too often as it flushes CPU write caches due to synchronization primitves.
throw_on_cancel();
SLAAutoSupports::MyLayer &layer = layers[layer_id];
SupportPointGenerator::MyLayer &layer = layers[layer_id];
const ExPolygons &islands = slices[layer_id];
//FIXME WTF?
const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0]));
@ -143,8 +154,8 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers(
if ((layer_id % 2) == 0)
// Don't call the following function too often as it flushes CPU write caches due to synchronization primitves.
throw_on_cancel();
SLAAutoSupports::MyLayer &layer_above = layers[layer_id];
SLAAutoSupports::MyLayer &layer_below = layers[layer_id - 1];
SupportPointGenerator::MyLayer &layer_above = layers[layer_id];
SupportPointGenerator::MyLayer &layer_below = layers[layer_id - 1];
//FIXME WTF?
const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]);
const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports
@ -152,8 +163,8 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers(
const float slope_angle = 75.f * (float(M_PI)/180.f); // smaller number - less supports
const float slope_offset = float(scale_(layer_height / std::tan(slope_angle)));
//FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands.
for (SLAAutoSupports::Structure &top : layer_above.islands) {
for (SLAAutoSupports::Structure &bottom : layer_below.islands) {
for (SupportPointGenerator::Structure &top : layer_above.islands) {
for (SupportPointGenerator::Structure &bottom : layer_below.islands) {
float overlap_area = top.overlap_area(bottom);
if (overlap_area > 0) {
top.islands_below.emplace_back(&bottom, overlap_area);
@ -191,13 +202,13 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers(
return layers;
}
void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights)
void SupportPointGenerator::process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights)
{
#ifdef SLA_AUTOSUPPORTS_DEBUG
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
std::vector<std::pair<ExPolygon, coord_t>> islands;
#endif /* SLA_AUTOSUPPORTS_DEBUG */
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
std::vector<SLAAutoSupports::MyLayer> layers = make_layers(slices, heights, m_throw_on_cancel);
std::vector<SupportPointGenerator::MyLayer> layers = make_layers(slices, heights, m_throw_on_cancel);
PointGrid3D point_grid;
point_grid.cell_size = Vec3f(10.f, 10.f, 10.f);
@ -206,8 +217,8 @@ void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::
double status = 0;
for (unsigned int layer_id = 0; layer_id < layers.size(); ++ layer_id) {
SLAAutoSupports::MyLayer *layer_top = &layers[layer_id];
SLAAutoSupports::MyLayer *layer_bottom = (layer_id > 0) ? &layers[layer_id - 1] : nullptr;
SupportPointGenerator::MyLayer *layer_top = &layers[layer_id];
SupportPointGenerator::MyLayer *layer_bottom = (layer_id > 0) ? &layers[layer_id - 1] : nullptr;
std::vector<float> support_force_bottom;
if (layer_bottom != nullptr) {
support_force_bottom.assign(layer_bottom->islands.size(), 0.f);
@ -263,13 +274,13 @@ void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::
status += increment;
m_statusfn(int(std::round(status)));
#ifdef SLA_AUTOSUPPORTS_DEBUG
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
/*std::string layer_num_str = std::string((i<10 ? "0" : "")) + std::string((i<100 ? "0" : "")) + std::to_string(i);
output_expolygons(expolys_top, "top" + layer_num_str + ".svg");
output_expolygons(diff, "diff" + layer_num_str + ".svg");
if (!islands.empty())
output_expolygons(islands, "islands" + layer_num_str + ".svg");*/
#endif /* SLA_AUTOSUPPORTS_DEBUG */
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
}
}
@ -449,7 +460,7 @@ static inline std::vector<Vec2f> poisson_disk_from_samples(const std::vector<Vec
return out;
}
void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island, bool just_one)
void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island, bool just_one)
{
//int num_of_points = std::max(1, (int)((island.area()*pow(SCALING_FACTOR, 2) * m_config.tear_pressure)/m_config.support_force));
@ -470,9 +481,8 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru
float min_spacing = poisson_radius;
//FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon.
std::random_device rd;
std::mt19937 rng(rd());
std::vector<Vec2f> raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, rng);
std::vector<Vec2f> raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, m_rng);
std::vector<Vec2f> poisson_samples;
for (size_t iter = 0; iter < 4; ++ iter) {
poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius,
@ -488,7 +498,7 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru
min_spacing = std::max(m_config.minimal_distance, min_spacing * coeff);
}
#ifdef SLA_AUTOSUPPORTS_DEBUG
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
{
static int irun = 0;
Slic3r::SVG svg(debug_out_path("SLA_supports-uniformly_cover-%d.svg", irun ++), get_extents(islands));
@ -503,7 +513,7 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru
// assert(! poisson_samples.empty());
if (poisson_samples_target < poisson_samples.size()) {
std::shuffle(poisson_samples.begin(), poisson_samples.end(), rng);
std::shuffle(poisson_samples.begin(), poisson_samples.end(), m_rng);
poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end());
}
for (const Vec2f &pt : poisson_samples) {
@ -528,8 +538,8 @@ void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double
pts.erase(endit, pts.end());
}
#ifdef SLA_AUTOSUPPORTS_DEBUG
void SLAAutoSupports::output_structures(const std::vector<Structure>& structures)
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
void SupportPointGenerator::output_structures(const std::vector<Structure>& structures)
{
for (unsigned int i=0 ; i<structures.size(); ++i) {
std::stringstream ss;
@ -538,7 +548,7 @@ void SLAAutoSupports::output_structures(const std::vector<Structure>& structures
}
}
void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, const std::string &filename)
void SupportPointGenerator::output_expolygons(const ExPolygons& expolys, const std::string &filename)
{
BoundingBox bb(Point(-30000000, -30000000), Point(30000000, 30000000));
Slic3r::SVG svg_cummulative(filename, bb);

View file

@ -1,43 +1,50 @@
#ifndef SLAAUTOSUPPORTS_HPP_
#define SLAAUTOSUPPORTS_HPP_
#ifndef SLA_SUPPORTPOINTGENERATOR_HPP
#define SLA_SUPPORTPOINTGENERATOR_HPP
#include <random>
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/SLA/SupportPoint.hpp>
#include <libslic3r/SLA/EigenMesh3D.hpp>
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/Point.hpp>
#include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/SLA/SLACommon.hpp>
#include <boost/container/small_vector.hpp>
// #define SLA_AUTOSUPPORTS_DEBUG
// #define SLA_SUPPORTPOINTGEN_DEBUG
namespace Slic3r {
namespace sla {
namespace Slic3r { namespace sla {
class SLAAutoSupports {
class SupportPointGenerator {
public:
struct Config {
float density_relative {1.f};
float minimal_distance {1.f};
float head_diameter {0.4f};
///////////////
inline float support_force() const { return 7.7f / density_relative; } // a force one point can support (arbitrary force unit)
inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2)
};
SLAAutoSupports(const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
float density_relative {1.f};
float minimal_distance {1.f};
float head_diameter {0.4f};
///////////////
inline float support_force() const { return 7.7f / density_relative; } // a force one point can support (arbitrary force unit)
inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2)
};
SupportPointGenerator(const EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
SupportPointGenerator(const EigenMesh3D& emesh, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
const std::vector<SupportPoint>& output() const { return m_output; }
std::vector<SupportPoint>& output() { return m_output; }
struct MyLayer;
const std::vector<sla::SupportPoint>& output() { return m_output; }
struct MyLayer;
struct Structure {
Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f &centroid, float area, float h) :
layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), height(h)
#ifdef SLA_AUTOSUPPORTS_DEBUG
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
, unique_id(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()))
#endif /* SLA_AUTOSUPPORTS_DEBUG */
{}
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
{}
MyLayer *layer;
const ExPolygon* polygon = nullptr;
const BoundingBox bbox;
@ -49,24 +56,24 @@ public:
float supports_force_this_layer = 0.f;
float supports_force_inherited = 0.f;
float supports_force_total() const { return this->supports_force_this_layer + this->supports_force_inherited; }
#ifdef SLA_AUTOSUPPORTS_DEBUG
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
std::chrono::milliseconds unique_id;
#endif /* SLA_AUTOSUPPORTS_DEBUG */
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
struct Link {
Link(Structure *island, float overlap_area) : island(island), overlap_area(overlap_area) {}
Link(Structure *island, float overlap_area) : island(island), overlap_area(overlap_area) {}
Structure *island;
float overlap_area;
};
#ifdef NDEBUG
// In release mode, use the optimized container.
// In release mode, use the optimized container.
boost::container::small_vector<Link, 4> islands_above;
boost::container::small_vector<Link, 4> islands_below;
#else
// In debug mode, use the standard vector, which is well handled by debugger visualizer.
std::vector<Link> islands_above;
std::vector<Link> islands_below;
// In debug mode, use the standard vector, which is well handled by debugger visualizer.
std::vector<Link> islands_above;
std::vector<Link> islands_below;
#endif
// Overhangs, that are dangling considerably.
ExPolygons dangling_areas;
@ -74,16 +81,16 @@ public:
ExPolygons overhangs;
// Overhangs, where the surface must slope.
ExPolygons overhangs_slopes;
float overhangs_area;
float overhangs_area = 0.f;
bool overlaps(const Structure &rhs) const {
return this->bbox.overlap(rhs.bbox) && (this->polygon->overlaps(*rhs.polygon) || rhs.polygon->overlaps(*this->polygon));
}
float overlap_area(const Structure &rhs) const {
double out = 0.;
if (this->bbox.overlap(rhs.bbox)) {
Polygons polys = intersection(to_polygons(*this->polygon), to_polygons(*rhs.polygon), false);
for (const Polygon &poly : polys)
Polygons polys = intersection(to_polygons(*this->polygon), to_polygons(*rhs.polygon), false);
for (const Polygon &poly : polys)
out += poly.area();
}
return float(out);
@ -96,13 +103,13 @@ public:
}
Polygons polygons_below() const {
size_t cnt = 0;
for (const Link &below : this->islands_below)
for (const Link &below : this->islands_below)
cnt += 1 + below.island->polygon->holes.size();
Polygons out;
out.reserve(cnt);
for (const Link &below : this->islands_below) {
for (const Link &below : this->islands_below) {
out.emplace_back(below.island->polygon->contour);
append(out, below.island->polygon->holes);
append(out, below.island->polygon->holes);
}
return out;
}
@ -116,19 +123,19 @@ public:
// Positive deficit of the supports. If negative, this area is well supported. If positive, more supports need to be added.
float support_force_deficit(const float tear_pressure) const { return this->area * tear_pressure - this->supports_force_total(); }
};
struct MyLayer {
MyLayer(const size_t layer_id, coordf_t print_z) : layer_id(layer_id), print_z(print_z) {}
MyLayer(const size_t layer_id, coordf_t print_z) : layer_id(layer_id), print_z(print_z) {}
size_t layer_id;
coordf_t print_z;
std::vector<Structure> islands;
};
struct RichSupportPoint {
Vec3f position;
Structure *island;
};
struct PointGrid3D {
struct GridHash {
std::size_t operator()(const Vec3i &cell_id) const {
@ -136,23 +143,23 @@ public:
}
};
typedef std::unordered_multimap<Vec3i, RichSupportPoint, GridHash> Grid;
Vec3f cell_size;
Grid grid;
Vec3i cell_id(const Vec3f &pos) {
return Vec3i(int(floor(pos.x() / cell_size.x())),
int(floor(pos.y() / cell_size.y())),
int(floor(pos.z() / cell_size.z())));
}
void insert(const Vec2f &pos, Structure *island) {
RichSupportPoint pt;
pt.position = Vec3f(pos.x(), pos.y(), float(island->layer->print_z));
pt.position = Vec3f(pos.x(), pos.y(), float(island->layer->print_z));
pt.island = island;
grid.emplace(cell_id(pt.position), pt);
}
bool collides_with(const Vec2f &pos, Structure *island, float radius) {
Vec3f pos3d(pos.x(), pos.y(), float(island->layer->print_z));
Vec3i cell = cell_id(pos3d);
@ -170,41 +177,45 @@ public:
}
return false;
}
private:
bool collides_with(const Vec3f &pos, float radius, Grid::const_iterator it_begin, Grid::const_iterator it_end) {
for (Grid::const_iterator it = it_begin; it != it_end; ++ it) {
float dist2 = (it->second.position - pos).squaredNorm();
float dist2 = (it->second.position - pos).squaredNorm();
if (dist2 < radius * radius)
return true;
}
return false;
}
};
void execute(const std::vector<ExPolygons> &slices,
const std::vector<float> & heights);
void seed(std::mt19937::result_type s) { m_rng.seed(s); }
private:
std::vector<sla::SupportPoint> m_output;
SLAAutoSupports::Config m_config;
std::vector<SupportPoint> m_output;
SupportPointGenerator::Config m_config;
void process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights);
void uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island = false, bool just_one = false);
void project_onto_mesh(std::vector<sla::SupportPoint>& points) const;
void project_onto_mesh(std::vector<SupportPoint>& points) const;
#ifdef SLA_AUTOSUPPORTS_DEBUG
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
static void output_expolygons(const ExPolygons& expolys, const std::string &filename);
static void output_structures(const std::vector<Structure> &structures);
#endif // SLA_AUTOSUPPORTS_DEBUG
const sla::EigenMesh3D& m_emesh;
#endif // SLA_SUPPORTPOINTGEN_DEBUG
const EigenMesh3D& m_emesh;
std::function<void(void)> m_throw_on_cancel;
std::function<void(int)> m_statusfn;
std::mt19937 m_rng;
};
void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance);
} // namespace sla
} // namespace Slic3r
}} // namespace Slic3r::sla
#endif // SLAAUTOSUPPORTS_HPP_
#endif // SUPPORTPOINTGENERATOR_HPP

View file

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

View file

@ -1,12 +1,15 @@
#ifndef SLASUPPORTTREE_HPP
#define SLASUPPORTTREE_HPP
#ifndef SLA_SUPPORTTREE_HPP
#define SLA_SUPPORTTREE_HPP
#include <vector>
#include <memory>
#include <Eigen/Geometry>
#include "SLACommon.hpp"
#include "SLAPad.hpp"
#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/SLA/Pad.hpp>
#include <libslic3r/SLA/EigenMesh3D.hpp>
#include <libslic3r/SLA/SupportPoint.hpp>
#include <libslic3r/SLA/JobController.hpp>
namespace Slic3r {
@ -105,27 +108,6 @@ struct SupportConfig
enum class MeshType { Support, Pad };
/// A Control structure for the support calculation. Consists of the status
/// indicator callback and the stop condition predicate.
struct JobController
{
using StatusFn = std::function<void(unsigned, const std::string&)>;
using StopCond = std::function<bool(void)>;
using CancelFn = std::function<void(void)>;
// This will signal the status of the calculation to the front-end
StatusFn statuscb = [](unsigned, const std::string&){};
// Returns true if the calculation should be aborted.
StopCond stopcondition = [](){ return false; };
// Similar to cancel callback. This should check the stop condition and
// if true, throw an appropriate exception. (TriangleMeshSlicer needs this)
// consider it a hard abort. stopcondition is permits the algorithm to
// terminate itself
CancelFn cancelfn = [](){};
};
struct SupportableMesh
{
EigenMesh3D emesh;

View file

@ -1,5 +1,6 @@
#include "SLASupportTreeBuilder.hpp"
#include "SLASupportTreeBuildsteps.hpp"
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
#include <libslic3r/SLA/Contour3D.hpp>
namespace Slic3r {
namespace sla {
@ -12,7 +13,7 @@ Contour3D sphere(double rho, Portion portion, double fa) {
if(rho <= 1e-6 && rho >= -1e-6) return ret;
auto& vertices = ret.points;
auto& facets = ret.indices;
auto& facets = ret.faces3;
// Algorithm:
// Add points one-by-one to the sphere grid and form facets using relative
@ -102,7 +103,7 @@ Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp)
auto steps = int(ssteps);
auto& points = ret.points;
auto& indices = ret.indices;
auto& indices = ret.faces3;
points.reserve(2*ssteps);
double a = 2*PI/steps;
@ -211,8 +212,8 @@ Head::Head(double r_big_mm,
coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2);
coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1;
mesh.indices.emplace_back(i1s1, i2s1, i2s2);
mesh.indices.emplace_back(i1s1, i2s2, i1s2);
mesh.faces3.emplace_back(i1s1, i2s1, i2s2);
mesh.faces3.emplace_back(i1s1, i2s2, i1s2);
}
auto i1s1 = coord_t(s1.points.size()) - coord_t(steps);
@ -220,8 +221,8 @@ Head::Head(double r_big_mm,
auto i1s2 = coord_t(s1.points.size());
auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1;
mesh.indices.emplace_back(i2s2, i2s1, i1s1);
mesh.indices.emplace_back(i1s2, i2s2, i1s1);
mesh.faces3.emplace_back(i2s2, i2s1, i1s1);
mesh.faces3.emplace_back(i1s2, i2s2, i1s1);
// To simplify further processing, we translate the mesh so that the
// last vertex of the pointing sphere (the pinpoint) will be at (0,0,0)
@ -240,7 +241,7 @@ Pillar::Pillar(const Vec3d &jp, const Vec3d &endp, double radius, size_t st):
// move the data.
Contour3D body = cylinder(radius, height, st, endp);
mesh.points.swap(body.points);
mesh.indices.swap(body.indices);
mesh.faces3.swap(body.faces3);
}
}
@ -275,7 +276,7 @@ Pillar &Pillar::add_base(double baseheight, double radius)
base.points.emplace_back(endpt);
base.points.emplace_back(ep);
auto& indices = base.indices;
auto& indices = base.faces3;
auto hcenter = int(base.points.size() - 1);
auto lcenter = int(base.points.size() - 2);
auto offs = int(steps);
@ -466,7 +467,7 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh() const
return m_meshcache;
}
m_meshcache = mesh(merged);
m_meshcache = to_triangle_mesh(merged);
// The mesh will be passed by const-pointer to TriangleMeshSlicer,
// which will need this.

View file

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

View file

@ -1,4 +1,4 @@
#include "SLASupportTreeBuildsteps.hpp"
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
#include <libnest2d/optimizers/nlopt/genetic.hpp>
#include <libnest2d/optimizers/nlopt/subplex.hpp>
@ -7,8 +7,16 @@
namespace Slic3r {
namespace sla {
static const Vec3d DOWN = {0.0, 0.0, -1.0};
using libnest2d::opt::initvals;
using libnest2d::opt::bound;
using libnest2d::opt::StopCriteria;
using libnest2d::opt::GeneticOptimizer;
using libnest2d::opt::SubplexOptimizer;
SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder,
const SupportableMesh &sm)
const SupportableMesh &sm)
: m_cfg(sm.cfg)
, m_mesh(sm.emesh)
, m_support_pts(sm.pts)
@ -30,7 +38,7 @@ SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder,
}
bool SupportTreeBuildsteps::execute(SupportTreeBuilder & builder,
const SupportableMesh &sm)
const SupportableMesh &sm)
{
if(sm.pts.empty()) return false;
@ -560,9 +568,7 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
double radius,
long head_id)
{
// People were killed for this number (seriously)
static const double SQR2 = std::sqrt(2.0);
static const Vec3d DOWN = {0.0, 0.0, -1.0};
const double SLOPE = 1. / std::cos(m_cfg.bridge_slope);
double gndlvl = m_builder.ground_level;
Vec3d endp = {jp(X), jp(Y), gndlvl};
@ -573,38 +579,47 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
bool can_add_base = true;
bool normal_mode = true;
// If in zero elevation mode and the pillar is too close to the model body,
// the support pillar can not be placed in the gap between the model and
// the pad, and the pillar bases must not touch the model body either.
// To solve this, a corrector bridge is inserted between the starting point
// (jp) and the new pillar.
if (m_cfg.object_elevation_mm < EPSILON
&& (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) {
// Get the distance from the mesh. This can be later optimized
// to get the distance in 2D plane because we are dealing with
// the ground level only.
normal_mode = false;
// The min distance needed to move away from the model in XY plane.
double current_d = min_dist - dist;
double current_bride_d = SLOPE * current_d;
// get a suitable direction for the corrector bridge. It is the
// original sourcedir's azimuth but the polar angle is saturated to the
// configured bridge slope.
auto [polar, azimuth] = dir_to_spheric(sourcedir);
polar = PI - m_cfg.bridge_slope;
auto dir = spheric_to_dir(polar, azimuth).normalized();
normal_mode = false;
double mind = min_dist - dist;
double azimuth = std::atan2(sourcedir(Y), sourcedir(X));
double sinpolar = std::sin(PI - m_cfg.bridge_slope);
double cospolar = std::cos(PI - m_cfg.bridge_slope);
double cosazm = std::cos(azimuth);
double sinazm = std::sin(azimuth);
auto dir = Vec3d(cosazm * sinpolar, sinazm * sinpolar, cospolar)
.normalized();
using namespace libnest2d::opt;
StopCriteria scr;
scr.stop_score = min_dist;
SubplexOptimizer solver(scr);
// Search for a distance along the corrector bridge to move the endpoint
// sufficiently away form the model body. The first few optimization
// cycles should succeed here.
auto result = solver.optimize_max(
[this, dir, jp, gndlvl](double mv) {
Vec3d endpt = jp + SQR2 * mv * dir;
Vec3d endpt = jp + mv * dir;
endpt(Z) = gndlvl;
return std::sqrt(m_mesh.squared_distance(endpt));
},
initvals(mind), bound(0.0, 2 * min_dist));
initvals(current_bride_d),
bound(0.0, m_cfg.max_bridge_length_mm - current_bride_d));
mind = std::get<0>(result.optimum);
endp = jp + SQR2 * mind * dir;
endp = jp + std::get<0>(result.optimum) * dir;
Vec3d pgnd = {endp(X), endp(Y), gndlvl};
can_add_base = result.score > min_dist;
@ -623,7 +638,7 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
else {
// If the new endpoint is below ground, do not make a pillar
if (endp(Z) < gndlvl)
endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off
endp = endp - SLOPE * (gndlvl - endp(Z)) * dir; // back off
else {
auto hit = bridge_mesh_intersect(endp, DOWN, radius);
@ -685,11 +700,6 @@ void SupportTreeBuildsteps::filter()
// not be enough space for the pinhead. Filtering is applied for
// these reasons.
using libnest2d::opt::bound;
using libnest2d::opt::initvals;
using libnest2d::opt::GeneticOptimizer;
using libnest2d::opt::StopCriteria;
ccr::SpinningMutex mutex;
auto addfn = [&mutex](PtIndices &container, unsigned val) {
std::lock_guard<ccr::SpinningMutex> lk(mutex);
@ -708,10 +718,7 @@ void SupportTreeBuildsteps::filter()
// (Quaternion::FromTwoVectors) and apply the rotation to the
// arrow head.
double z = n(2);
double r = 1.0; // for normalized vector
double polar = std::acos(z / r);
double azimuth = std::atan2(n(1), n(0));
auto [polar, azimuth] = dir_to_spheric(n);
// skip if the tilt is not sane
if(polar >= PI - m_cfg.normal_cutoff_angle) {
@ -729,9 +736,7 @@ void SupportTreeBuildsteps::filter()
double pin_r = double(m_support_pts[fidx].head_front_radius);
// Reassemble the now corrected normal
auto nn = Vec3d(std::cos(azimuth) * std::sin(polar),
std::sin(azimuth) * std::sin(polar),
std::cos(polar)).normalized();
auto nn = spheric_to_dir(polar, azimuth).normalized();
// check available distance
EigenMesh3D::hit_result t
@ -757,9 +762,7 @@ void SupportTreeBuildsteps::filter()
auto oresult = solver.optimize_max(
[this, pin_r, w, hp](double plr, double azm)
{
auto dir = Vec3d(std::cos(azm) * std::sin(plr),
std::sin(azm) * std::sin(plr),
std::cos(plr)).normalized();
auto dir = spheric_to_dir(plr, azm).normalized();
double score = pinhead_mesh_intersect(
hp, dir, pin_r, m_cfg.head_back_radius_mm, w);
@ -767,18 +770,15 @@ void SupportTreeBuildsteps::filter()
return score;
},
initvals(polar, azimuth), // start with what we have
bound(3 * PI / 4,
PI), // Must not exceed the tilt limit
bound(3 * PI / 4, PI), // Must not exceed the tilt limit
bound(-PI, PI) // azimuth can be a full search
);
if(oresult.score > w) {
polar = std::get<0>(oresult.optimum);
azimuth = std::get<1>(oresult.optimum);
nn = Vec3d(std::cos(azimuth) * std::sin(polar),
std::sin(azimuth) * std::sin(polar),
std::cos(polar)).normalized();
t = oresult.score;
nn = spheric_to_dir(polar, azimuth).normalized();
t = EigenMesh3D::hit_result(oresult.score);
}
}
@ -837,16 +837,17 @@ void SupportTreeBuildsteps::classify()
m_thr();
auto& head = m_builder.head(i);
Vec3d n(0, 0, -1);
double r = head.r_back_mm;
Vec3d headjp = head.junction_point();
// collision check
auto hit = bridge_mesh_intersect(headjp, n, r);
auto hit = bridge_mesh_intersect(headjp, DOWN, r);
if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i);
else if(m_cfg.ground_facing_only) head.invalidate();
else m_iheads_onmodel.emplace_back(std::make_pair(i, hit));
else m_iheads_onmodel.emplace_back(i);
m_head_to_ground_scans[i] = hit;
}
// We want to search for clusters of points that are far enough
@ -893,13 +894,14 @@ void SupportTreeBuildsteps::routing_to_ground()
// get the current cluster centroid
auto & thr = m_thr;
const auto &points = m_points;
long lcid = cluster_centroid(
long lcid = cluster_centroid(
cl, [&points](size_t idx) { return points.row(long(idx)); },
[thr](const Vec3d &p1, const Vec3d &p2) {
thr();
return distance(Vec2d(p1(X), p1(Y)), Vec2d(p2(X), p2(Y)));
});
assert(lcid >= 0);
unsigned hid = cl[size_t(lcid)]; // Head ID
@ -944,192 +946,138 @@ void SupportTreeBuildsteps::routing_to_ground()
}
}
bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir)
{
auto hjp = head.junction_point();
double r = head.r_back_mm;
double t = bridge_mesh_intersect(hjp, dir, head.r_back_mm);
double d = 0, tdown = 0;
t = std::min(t, m_cfg.max_bridge_length_mm);
while (d < t && !std::isinf(tdown = bridge_mesh_intersect(hjp + d * dir, DOWN, r)))
d += r;
if(!std::isinf(tdown)) return false;
Vec3d endp = hjp + d * dir;
m_builder.add_bridge(head.id, endp);
m_builder.add_junction(endp, head.r_back_mm);
this->create_ground_pillar(endp, dir, head.r_back_mm);
return true;
}
bool SupportTreeBuildsteps::connect_to_ground(Head &head)
{
if (connect_to_ground(head, head.dir)) return true;
// Optimize bridge direction:
// Straight path failed so we will try to search for a suitable
// direction out of the cavity.
auto [polar, azimuth] = dir_to_spheric(head.dir);
StopCriteria stc;
stc.max_iterations = m_cfg.optimizer_max_iterations;
stc.relative_score_difference = m_cfg.optimizer_rel_score_diff;
stc.stop_score = 1e6;
GeneticOptimizer solver(stc);
solver.seed(0); // we want deterministic behavior
double r_back = head.r_back_mm;
Vec3d hjp = head.junction_point();
auto oresult = solver.optimize_max(
[this, hjp, r_back](double plr, double azm) {
Vec3d n = spheric_to_dir(plr, azm).normalized();
return bridge_mesh_intersect(hjp, n, r_back);
},
initvals(polar, azimuth), // let's start with what we have
bound(3*PI/4, PI), // Must not exceed the slope limit
bound(-PI, PI) // azimuth can be a full range search
);
Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
return connect_to_ground(head, bridgedir);
}
bool SupportTreeBuildsteps::connect_to_model_body(Head &head)
{
if (head.id <= ID_UNSET) return false;
auto it = m_head_to_ground_scans.find(unsigned(head.id));
if (it == m_head_to_ground_scans.end()) return false;
auto &hit = it->second;
Vec3d hjp = head.junction_point();
double zangle = std::asin(hit.direction()(Z));
zangle = std::max(zangle, PI/4);
double h = std::sin(zangle) * head.fullwidth();
// The width of the tail head that we would like to have...
h = std::min(hit.distance() - head.r_back_mm, h);
if(h <= 0.) return false;
Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h};
auto center_hit = m_mesh.query_ray_hit(hjp, DOWN);
double hitdiff = center_hit.distance() - hit.distance();
Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm?
center_hit.position() : hit.position();
head.transform();
long pillar_id = m_builder.add_pillar(head.id, endp, head.r_back_mm);
Pillar &pill = m_builder.pillar(pillar_id);
Vec3d taildir = endp - hitp;
double dist = distance(endp, hitp) + m_cfg.head_penetration_mm;
double w = dist - 2 * head.r_pin_mm - head.r_back_mm;
if (w < 0.) {
BOOST_LOG_TRIVIAL(error) << "Pinhead width is negative!";
w = 0.;
}
Head tailhead(head.r_back_mm, head.r_pin_mm, w,
m_cfg.head_penetration_mm, taildir, hitp);
tailhead.transform();
pill.base = tailhead.mesh;
m_pillar_index.guarded_insert(pill.endpoint(), pill.id);
return true;
}
void SupportTreeBuildsteps::routing_to_model()
{
// We need to check if there is an easy way out to the bed surface.
// If it can be routed there with a bridge shorter than
// min_bridge_distance.
// First we want to index the available pillars. The best is to connect
// these points to the available pillars
auto routedown = [this](Head& head, const Vec3d& dir, double dist)
{
head.transform();
Vec3d endp = head.junction_point() + dist * dir;
m_builder.add_bridge(head.id, endp);
m_builder.add_junction(endp, head.r_back_mm);
this->create_ground_pillar(endp, dir, head.r_back_mm);
};
std::vector<unsigned> modelpillars;
ccr::SpinningMutex mutex;
auto onmodelfn =
[this, routedown, &modelpillars, &mutex]
(const std::pair<unsigned, EigenMesh3D::hit_result> &el, size_t)
{
ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(),
[this] (const unsigned idx, size_t) {
m_thr();
unsigned idx = el.first;
EigenMesh3D::hit_result hit = el.second;
auto& head = m_builder.head(idx);
Vec3d hjp = head.junction_point();
// /////////////////////////////////////////////////////////////////
// Search nearby pillar
// /////////////////////////////////////////////////////////////////
if(search_pillar_and_connect(head)) { head.transform(); return; }
// /////////////////////////////////////////////////////////////////
// Try straight path
// /////////////////////////////////////////////////////////////////
// Cannot connect to nearby pillar. We will try to search for
// a route to the ground.
if(connect_to_ground(head)) { head.transform(); return; }
double t = bridge_mesh_intersect(hjp, head.dir, head.r_back_mm);
double d = 0, tdown = 0;
Vec3d dirdown(0.0, 0.0, -1.0);
t = std::min(t, m_cfg.max_bridge_length_mm);
while(d < t && !std::isinf(tdown = bridge_mesh_intersect(
hjp + d*head.dir,
dirdown, head.r_back_mm))) {
d += head.r_back_mm;
}
if(std::isinf(tdown)) { // we heave found a route to the ground
routedown(head, head.dir, d); return;
}
// /////////////////////////////////////////////////////////////////
// Optimize bridge direction
// /////////////////////////////////////////////////////////////////
// Straight path failed so we will try to search for a suitable
// direction out of the cavity.
// Get the spherical representation of the normal. its easier to
// work with.
double z = head.dir(Z);
double r = 1.0; // for normalized vector
double polar = std::acos(z / r);
double azimuth = std::atan2(head.dir(Y), head.dir(X));
using libnest2d::opt::bound;
using libnest2d::opt::initvals;
using libnest2d::opt::GeneticOptimizer;
using libnest2d::opt::StopCriteria;
StopCriteria stc;
stc.max_iterations = m_cfg.optimizer_max_iterations;
stc.relative_score_difference = m_cfg.optimizer_rel_score_diff;
stc.stop_score = 1e6;
GeneticOptimizer solver(stc);
solver.seed(0); // we want deterministic behavior
double r_back = head.r_back_mm;
auto oresult = solver.optimize_max(
[this, hjp, r_back](double plr, double azm)
{
Vec3d n = Vec3d(std::cos(azm) * std::sin(plr),
std::sin(azm) * std::sin(plr),
std::cos(plr)).normalized();
return bridge_mesh_intersect(hjp, n, r_back);
},
initvals(polar, azimuth), // let's start with what we have
bound(3*PI/4, PI), // Must not exceed the slope limit
bound(-PI, PI) // azimuth can be a full range search
);
d = 0; t = oresult.score;
polar = std::get<0>(oresult.optimum);
azimuth = std::get<1>(oresult.optimum);
Vec3d bridgedir = Vec3d(std::cos(azimuth) * std::sin(polar),
std::sin(azimuth) * std::sin(polar),
std::cos(polar)).normalized();
t = std::min(t, m_cfg.max_bridge_length_mm);
while(d < t && !std::isinf(tdown = bridge_mesh_intersect(
hjp + d*bridgedir,
dirdown,
head.r_back_mm))) {
d += head.r_back_mm;
}
if(std::isinf(tdown)) { // we heave found a route to the ground
routedown(head, bridgedir, d); return;
}
// /////////////////////////////////////////////////////////////////
// Route to model body
// /////////////////////////////////////////////////////////////////
double zangle = std::asin(hit.direction()(Z));
zangle = std::max(zangle, PI/4);
double h = std::sin(zangle) * head.fullwidth();
// The width of the tail head that we would like to have...
h = std::min(hit.distance() - head.r_back_mm, h);
if(h > 0) {
Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h};
auto center_hit = m_mesh.query_ray_hit(hjp, dirdown);
double hitdiff = center_hit.distance() - hit.distance();
Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm?
center_hit.position() : hit.position();
head.transform();
long pillar_id = m_builder.add_pillar(head.id, endp, head.r_back_mm);
Pillar &pill = m_builder.pillar(pillar_id);
Vec3d taildir = endp - hitp;
double dist = distance(endp, hitp) + m_cfg.head_penetration_mm;
double w = dist - 2 * head.r_pin_mm - head.r_back_mm;
if (w < 0.) {
BOOST_LOG_TRIVIAL(error) << "Pinhead width is negative!";
w = 0.;
}
Head tailhead(head.r_back_mm,
head.r_pin_mm,
w,
m_cfg.head_penetration_mm,
taildir,
hitp);
tailhead.transform();
pill.base = tailhead.mesh;
// Experimental: add the pillar to the index for cascading
std::lock_guard<ccr::SpinningMutex> lk(mutex);
modelpillars.emplace_back(unsigned(pill.id));
return;
}
// No route to the ground, so connect to the model body as a last resort
if (connect_to_model_body(head)) { return; }
// We have failed to route this head.
BOOST_LOG_TRIVIAL(warning)
<< "Failed to route model facing support point."
<< " ID: " << idx;
<< "Failed to route model facing support point. ID: " << idx;
head.invalidate();
};
ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(), onmodelfn);
for(auto pillid : modelpillars) {
auto& pillar = m_builder.pillar(pillid);
m_pillar_index.insert(pillar.endpoint(), pillid);
}
});
}
void SupportTreeBuildsteps::interconnect_pillars()
@ -1280,7 +1228,8 @@ void SupportTreeBuildsteps::interconnect_pillars()
spts[n] = s;
// Check the path vertically down
auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r);
Vec3d check_from = s + Vec3d{0., 0., pillar().r};
auto hr = bridge_mesh_intersect(check_from, DOWN, pillar().r);
Vec3d gndsp{s(X), s(Y), gnd};
// If the path is clear, check for pillar base collisions
@ -1360,12 +1309,11 @@ void SupportTreeBuildsteps::routing_headless()
Vec3d n = m_support_nmls.row(i); // mesh outward normal
Vec3d sp = sph - n * HWIDTH_MM; // stick head start point
Vec3d dir = {0, 0, -1};
Vec3d sj = sp + R * n; // stick start point
// This is only for checking
double idist = bridge_mesh_intersect(sph, dir, R, true);
double realdist = ray_mesh_intersect(sj, dir);
double idist = bridge_mesh_intersect(sph, DOWN, R, true);
double realdist = ray_mesh_intersect(sj, DOWN);
double dist = realdist;
if (std::isinf(dist)) dist = sph(Z) - m_builder.ground_level;
@ -1378,7 +1326,7 @@ void SupportTreeBuildsteps::routing_headless()
}
bool use_endball = !std::isinf(realdist);
Vec3d ej = sj + (dist + HWIDTH_MM) * dir;
Vec3d ej = sj + (dist + HWIDTH_MM) * DOWN ;
m_builder.add_compact_bridge(sp, ej, n, R, use_endball);
}
}

View file

@ -3,7 +3,8 @@
#include <cstdint>
#include "SLASupportTreeBuilder.hpp"
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
#include <libslic3r/SLA/Clustering.hpp>
namespace Slic3r {
namespace sla {
@ -19,6 +20,32 @@ inline Vec2d to_vec2(const Vec3d& v3) {
return {v3(X), v3(Y)};
}
inline std::pair<double, double> dir_to_spheric(const Vec3d &n, double norm = 1.)
{
double z = n.z();
double r = norm;
double polar = std::acos(z / r);
double azimuth = std::atan2(n(1), n(0));
return {polar, azimuth};
}
inline Vec3d spheric_to_dir(double polar, double azimuth)
{
return {std::cos(azimuth) * std::sin(polar),
std::sin(azimuth) * std::sin(polar), std::cos(polar)};
}
inline Vec3d spheric_to_dir(const std::tuple<double, double> &v)
{
auto [plr, azm] = v;
return spheric_to_dir(plr, azm);
}
inline Vec3d spheric_to_dir(const std::pair<double, double> &v)
{
return spheric_to_dir(v.first, v.second);
}
// This function returns the position of the centroid in the input 'clust'
// vector of point indices.
template<class DistFn>
@ -150,10 +177,10 @@ class SupportTreeBuildsteps {
using PtIndices = std::vector<unsigned>;
PtIndices m_iheads; // support points with pinhead
PtIndices m_iheads_onmodel;
PtIndices m_iheadless; // headless support points
// supp. pts. connecting to model: point index and the ray hit data
std::vector<std::pair<unsigned, EigenMesh3D::hit_result>> m_iheads_onmodel;
std::map<unsigned, EigenMesh3D::hit_result> m_head_to_ground_scans;
// normals for support points from model faces.
PointSet m_support_nmls;
@ -222,15 +249,29 @@ class SupportTreeBuildsteps {
// For connecting a head to a nearby pillar.
bool connect_to_nearpillar(const Head& head, long nearpillar_id);
// Find route for a head to the ground. Inserts additional bridge from the
// head to the pillar if cannot create pillar directly.
// The optional dir parameter is the direction of the bridge which is the
// direction of the pinhead if omitted.
bool connect_to_ground(Head& head, const Vec3d &dir);
inline bool connect_to_ground(Head& head);
bool connect_to_model_body(Head &head);
bool search_pillar_and_connect(const Head& head);
// This is a proxy function for pillar creation which will mind the gap
// between the pad and the model bottom in zero elevation mode.
// jp is the starting junction point which needs to be routed down.
// sourcedir is the allowed direction of an optional bridge between the
// jp junction and the final pillar.
void create_ground_pillar(const Vec3d &jp,
const Vec3d &sourcedir,
double radius,
long head_id = ID_UNSET);
public:
SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm);

View file

@ -1,6 +1,6 @@
#include <cmath>
#include "SLA/SLASupportTree.hpp"
#include "SLA/SLABoilerPlate.hpp"
#include "SLA/SLACommon.hpp"
#include "SLA/SLASpatIndex.hpp"
// Workaround: IGL signed_distance.h will define PI in the igl namespace.
@ -228,6 +228,26 @@ EigenMesh3D::EigenMesh3D(const EigenMesh3D &other):
m_V(other.m_V), m_F(other.m_F), m_ground_level(other.m_ground_level),
m_aabb( new AABBImpl(*other.m_aabb) ) {}
EigenMesh3D::EigenMesh3D(const Contour3D &other)
{
m_V.resize(Eigen::Index(other.points.size()), 3);
m_F.resize(Eigen::Index(other.faces3.size() + 2 * other.faces4.size()), 3);
for (Eigen::Index i = 0; i < Eigen::Index(other.points.size()); ++i)
m_V.row(i) = other.points[size_t(i)];
for (Eigen::Index i = 0; i < Eigen::Index(other.faces3.size()); ++i)
m_F.row(i) = other.faces3[size_t(i)];
size_t N = other.faces3.size() + 2 * other.faces4.size();
for (size_t i = other.faces3.size(); i < N; i += 2) {
size_t quad_idx = (i - other.faces3.size()) / 2;
auto & quad = other.faces4[quad_idx];
m_F.row(Eigen::Index(i)) = Vec3i{quad(0), quad(1), quad(2)};
m_F.row(Eigen::Index(i + 1)) = Vec3i{quad(2), quad(3), quad(0)};
}
}
EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other)
{
m_V = other.m_V;
@ -252,6 +272,31 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
return ret;
}
std::vector<EigenMesh3D::hit_result>
EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
{
std::vector<EigenMesh3D::hit_result> outs;
std::vector<igl::Hit> hits;
m_aabb->intersect_ray(m_V, m_F, s, dir, hits);
// The sort is necessary, the hits are not always sorted.
std::sort(hits.begin(), hits.end(),
[](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
// Convert the igl::Hit into hit_result
outs.reserve(hits.size());
for (const igl::Hit& hit : hits) {
outs.emplace_back(EigenMesh3D::hit_result(*this));
outs.back().m_t = double(hit.t);
outs.back().m_dir = dir;
outs.back().m_source = s;
if(!std::isinf(hit.t) && !std::isnan(hit.t))
outs.back().m_face_id = hit.id;
}
return outs;
}
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const {
double sign = 0; double sqdst = 0; int i = 0; Vec3d c;

File diff suppressed because it is too large Load diff

View file

@ -3,8 +3,8 @@
#include <mutex>
#include "PrintBase.hpp"
//#include "PrintExport.hpp"
#include "SLA/SLARasterWriter.hpp"
#include "SLA/RasterWriter.hpp"
#include "SLA/SupportTree.hpp"
#include "Point.hpp"
#include "MTUtils.hpp"
#include "Zipper.hpp"
@ -19,7 +19,9 @@ enum SLAPrintStep : unsigned int {
};
enum SLAPrintObjectStep : unsigned int {
slaposHollowing,
slaposObjectSlice,
slaposDrillHolesIfHollowed,
slaposSupportPoints,
slaposSupportTree,
slaposPad,
@ -73,13 +75,17 @@ public:
// Support mesh is only valid if this->is_step_done(slaposSupportTree) is true.
const TriangleMesh& support_mesh() const;
// Get a pad mesh centered around origin in XY, and with zero rotation around Z applied.
// Support mesh is only valid if this->is_step_done(slaposBasePool) is true.
// Support mesh is only valid if this->is_step_done(slaposPad) is true.
const TriangleMesh& pad_mesh() const;
// Ready after this->is_step_done(slaposHollowing) is true
const TriangleMesh& hollowed_interior_mesh() const;
// This will return the transformed mesh which is cached
const TriangleMesh& transformed_mesh() const;
std::vector<sla::SupportPoint> transformed_support_points() const;
sla::SupportPoints transformed_support_points() const;
sla::DrainHoles transformed_drainhole_points() const;
// Get the needed Z elevation for the model geometry if supports should be
// displayed. This Z offset should also be applied to the support
@ -287,9 +293,35 @@ private:
// Caching the transformed (m_trafo) raw mesh of the object
mutable CachedObject<TriangleMesh> m_transformed_rmesh;
class SupportData;
class SupportData : public sla::SupportableMesh
{
public:
sla::SupportTree::UPtr support_tree_ptr; // the supports
std::vector<ExPolygons> support_slices; // sliced supports
inline SupportData(const TriangleMesh &t)
: sla::SupportableMesh{t, {}, {}}
{}
sla::SupportTree::UPtr &create_support_tree(const sla::JobController &ctl)
{
support_tree_ptr = sla::SupportTree::create(*this, ctl);
return support_tree_ptr;
}
};
std::unique_ptr<SupportData> m_supportdata;
class HollowingData
{
public:
TriangleMesh interior;
// std::vector<drillpoints>
};
std::unique_ptr<HollowingData> m_hollowing_data;
};
using PrintObjects = std::vector<SLAPrintObject*>;
@ -339,7 +371,9 @@ class SLAPrint : public PrintBaseWithState<SLAPrintStep, slapsCount>
{
private: // Prevents erroneous use by other classes.
typedef PrintBaseWithState<SLAPrintStep, slapsCount> Inherited;
class Steps; // See SLAPrintSteps.cpp
public:
SLAPrint(): m_stepmask(slapsCount, true) {}
@ -401,8 +435,8 @@ public:
template<class Container> void transformed_slices(Container&& c) {
m_transformed_slices = std::forward<Container>(c);
}
friend void SLAPrint::process();
friend class SLAPrint::Steps;
public:
@ -478,6 +512,19 @@ private:
friend SLAPrintObject;
};
// Helper functions:
bool is_zero_elevation(const SLAPrintObjectConfig &c);
sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c);
sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c);
sla::PadConfig make_pad_cfg(const SLAPrintObjectConfig& c);
bool validate_pad(const TriangleMesh &pad, const sla::PadConfig &pcfg);
} // namespace Slic3r
#endif /* slic3r_SLAPrint_hpp_ */

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);
}
TriangleMesh::TriangleMesh(const indexed_triangle_set &M)
{
stl.stats.type = inmemory;
// count facets and allocate memory
stl.stats.number_of_facets = uint32_t(M.indices.size());
stl.stats.original_num_facets = int(stl.stats.number_of_facets);
stl_allocate(&stl);
for (uint32_t i = 0; i < stl.stats.number_of_facets; ++ i) {
stl_facet facet;
facet.vertex[0] = M.vertices[size_t(M.indices[i](0))];
facet.vertex[1] = M.vertices[size_t(M.indices[i](1))];
facet.vertex[2] = M.vertices[size_t(M.indices[i](2))];
facet.extra[0] = 0;
facet.extra[1] = 0;
stl_normal normal;
stl_calculate_normal(normal, &facet);
stl_normalize_vector(normal);
facet.normal = normal;
stl.facet_start[i] = facet;
}
stl_get_size(&stl);
}
// #define SLIC3R_TRACE_REPAIR
void TriangleMesh::repair(bool update_shared_vertices)

View file

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

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);
}
template<class...Args>
std::string string_printf(const char *const fmt, Args &&...args)
{
static const size_t INITIAL_LEN = 1024;
std::vector<char> buffer(INITIAL_LEN, '\0');
int bufflen = snprintf(buffer.data(), INITIAL_LEN - 1, fmt, std::forward<Args>(args)...);
if (bufflen >= int(INITIAL_LEN)) {
buffer.resize(size_t(bufflen) + 1);
snprintf(buffer.data(), buffer.size(), fmt, std::forward<Args>(args)...);
}
return std::string(buffer.begin(), buffer.begin() + bufflen);
}
} // namespace Slic3r
#endif

View file

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

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)
{
m_render_sla_auxiliaries = visible;
for (GLVolume* vol : m_volumes.volumes) {
if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)
&& vol->composite_id.volume_id < 0)
vol->is_active = visible;
}
m_render_sla_auxiliaries = visible;
}
void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx)
@ -6061,6 +6061,8 @@ void GLCanvas3D::_load_sla_shells()
unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size();
for (const SLAPrintObject::Instance& instance : obj->instances()) {
add_volume(*obj, 0, instance, obj->transformed_mesh(), GLVolume::MODEL_COLOR[0], true);
if (! obj->hollowed_interior_mesh().empty())
add_volume(*obj, -int(slaposHollowing), instance, obj->hollowed_interior_mesh(), GLVolume::MODEL_COLOR[0], false);
// Set the extruder_id and volume_id to achieve the same color as in the 3D scene when
// through the update_volumes_colors_by_extruder() call.
m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id();

View file

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

View file

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

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

View file

@ -30,7 +30,7 @@ static const float CONSTRAINED_COLOR[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
class ImGuiWrapper;
class CommonGizmosData;
class GLGizmoBase
{
@ -99,9 +99,13 @@ protected:
mutable std::vector<Grabber> m_grabbers;
ImGuiWrapper* m_imgui;
bool m_first_input_window_render;
CommonGizmosData* m_c = nullptr;
public:
GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
GLGizmoBase(GLCanvas3D& parent,
const std::string& icon_filename,
unsigned int sprite_id,
CommonGizmosData* common_data = nullptr);
virtual ~GLGizmoBase() {}
bool init() { return on_init(); }
@ -179,6 +183,34 @@ protected:
// were not interpolated by alpha blending or multi sampling.
extern unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue);
class MeshRaycaster;
class MeshClipper;
class CommonGizmosData {
public:
const TriangleMesh* mesh() const {
return (! m_mesh ? nullptr : (m_cavity_mesh ? m_cavity_mesh.get() : m_mesh));
}
ModelObject* m_model_object = nullptr;
const TriangleMesh* m_mesh;
std::unique_ptr<MeshRaycaster> m_mesh_raycaster;
std::unique_ptr<MeshClipper> m_object_clipper;
std::unique_ptr<MeshClipper> m_supports_clipper;
std::unique_ptr<TriangleMesh> m_cavity_mesh;
std::unique_ptr<GLVolume> m_volume_with_cavity;
int m_active_instance = -1;
float m_active_instance_bb_radius = 0;
ObjectID m_model_object_id = 0;
int m_print_object_idx = -1;
int m_print_objects_count = -1;
int m_old_timestamp = -1;
};
} // namespace GUI
} // namespace Slic3r

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 GUI {
GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
: GLGizmoBase(parent, icon_filename, sprite_id)
GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, CommonGizmosData* cd)
: GLGizmoBase(parent, icon_filename, sprite_id, cd)
, m_quadric(nullptr)
, m_its(nullptr)
{
@ -64,23 +64,35 @@ bool GLGizmoSlaSupports::on_init()
void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const Selection& selection)
{
if (! model_object || selection.is_empty()) {
m_model_object = nullptr;
m_c->m_model_object = nullptr;
return;
}
if (m_model_object != model_object || m_model_object_id != model_object->id()) {
m_model_object = model_object;
m_print_object_idx = -1;
}
bool something_changed = false;
m_active_instance = selection.get_instance_idx();
if (m_c->m_model_object != model_object
|| m_c->m_model_object_id != model_object->id()
|| m_c->m_active_instance != selection.get_instance_idx()) {
m_c->m_model_object = model_object;
m_c->m_print_object_idx = -1;
m_c->m_active_instance = selection.get_instance_idx();
something_changed = true;
}
if (model_object && selection.is_from_single_instance())
{
// Cache the bb - it's needed for dealing with the clipping plane quite often
// It could be done inside update_mesh but one has to account for scaling of the instance.
//FIXME calling ModelObject::instance_bounding_box() is expensive!
m_active_instance_bb_radius = m_model_object->instance_bounding_box(m_active_instance).radius();
if (something_changed) {
m_c->m_active_instance_bb_radius = m_c->m_model_object->instance_bounding_box(m_c->m_active_instance).radius();
if (m_state == On) {
m_parent.toggle_model_objects_visibility(false);
m_parent.toggle_model_objects_visibility(! m_c->m_cavity_mesh, m_c->m_model_object, m_c->m_active_instance);
m_parent.toggle_sla_auxiliaries_visibility(! m_editing_mode, m_c->m_model_object, m_c->m_active_instance);
}
else
m_parent.toggle_model_objects_visibility(true, nullptr, -1);
}
if (is_mesh_update_necessary()) {
update_mesh();
@ -88,15 +100,8 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S
}
// If we triggered autogeneration before, check backend and fetch results if they are there
if (m_model_object->sla_points_status == sla::PointsStatus::Generating)
if (m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating)
get_data_from_backend();
if (m_state == On) {
m_parent.toggle_model_objects_visibility(false);
m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance);
}
else
m_parent.toggle_model_objects_visibility(true, nullptr, -1);
}
}
@ -106,16 +111,16 @@ void GLGizmoSlaSupports::on_render() const
{
const Selection& selection = m_parent.get_selection();
// If current m_model_object does not match selection, ask GLCanvas3D to turn us off
// If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off
if (m_state == On
&& (m_model_object != selection.get_model()->objects[selection.get_object_idx()]
|| m_active_instance != selection.get_instance_idx()
|| m_model_object_id != m_model_object->id())) {
&& (m_c->m_model_object != selection.get_model()->objects[selection.get_object_idx()]
|| m_c->m_active_instance != selection.get_instance_idx()
|| m_c->m_model_object_id != m_c->m_model_object->id())) {
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS));
return;
}
if (! m_its || ! m_mesh)
if (! m_its || ! m_c->m_mesh)
const_cast<GLGizmoSlaSupports*>(this)->update_mesh();
glsafe(::glEnable(GL_BLEND));
@ -123,6 +128,8 @@ void GLGizmoSlaSupports::on_render() const
m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z();
render_hollowed_mesh();
if (m_quadric != nullptr && selection.is_from_single_instance())
render_points(selection, false);
@ -134,9 +141,32 @@ void GLGizmoSlaSupports::on_render() const
void GLGizmoSlaSupports::render_hollowed_mesh() const
{
if (m_c->m_volume_with_cavity) {
m_c->m_volume_with_cavity->set_sla_shift_z(m_z_shift);
m_parent.get_shader().start_using();
GLint current_program_id;
glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, &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
{
if (m_clipping_plane_distance == 0.f)
if (m_clipping_plane_distance == 0.f || m_c->m_mesh->empty())
return;
// Get transformation of the instance
@ -154,66 +184,66 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const
1.));
// Now initialize the TMS for the object, perform the cut and save the result.
if (! m_object_clipper) {
m_object_clipper.reset(new MeshClipper);
m_object_clipper->set_mesh(*m_mesh);
if (! m_c->m_object_clipper) {
m_c->m_object_clipper.reset(new MeshClipper);
m_c->m_object_clipper->set_mesh(*m_c->mesh());
}
m_object_clipper->set_plane(*m_clipping_plane);
m_object_clipper->set_transformation(trafo);
m_c->m_object_clipper->set_plane(*m_clipping_plane);
m_c->m_object_clipper->set_transformation(trafo);
// Next, ask the backend if supports are already calculated. If so, we are gonna cut them too.
// First we need a pointer to the respective SLAPrintObject. The index into objects vector is
// cached so we don't have todo it on each render. We only search for the po if needed:
if (m_print_object_idx < 0 || (int)m_parent.sla_print()->objects().size() != m_print_objects_count) {
m_print_objects_count = m_parent.sla_print()->objects().size();
m_print_object_idx = -1;
if (m_c->m_print_object_idx < 0 || (int)m_parent.sla_print()->objects().size() != m_c->m_print_objects_count) {
m_c->m_print_objects_count = m_parent.sla_print()->objects().size();
m_c->m_print_object_idx = -1;
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
++m_print_object_idx;
if (po->model_object()->id() == m_model_object->id())
++m_c->m_print_object_idx;
if (po->model_object()->id() == m_c->m_model_object->id())
break;
}
}
if (m_print_object_idx >= 0) {
const SLAPrintObject* print_object = m_parent.sla_print()->objects()[m_print_object_idx];
if (m_c->m_print_object_idx >= 0) {
const SLAPrintObject* print_object = m_parent.sla_print()->objects()[m_c->m_print_object_idx];
if (print_object->is_step_done(slaposSupportTree)) {
if (print_object->is_step_done(slaposSupportTree) && !print_object->get_mesh(slaposSupportTree).empty()) {
// If the supports are already calculated, save the timestamp of the respective step
// so we can later tell they were recalculated.
size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp;
if (! m_supports_clipper || (int)timestamp != m_old_timestamp) {
if (! m_c->m_supports_clipper || (int)timestamp != m_c->m_old_timestamp) {
// The timestamp has changed.
m_supports_clipper.reset(new MeshClipper);
m_c->m_supports_clipper.reset(new MeshClipper);
// The mesh should already have the shared vertices calculated.
m_supports_clipper->set_mesh(print_object->support_mesh());
m_old_timestamp = timestamp;
m_c->m_supports_clipper->set_mesh(print_object->support_mesh());
m_c->m_old_timestamp = timestamp;
}
m_supports_clipper->set_plane(*m_clipping_plane);
m_supports_clipper->set_transformation(supports_trafo);
m_c->m_supports_clipper->set_plane(*m_clipping_plane);
m_c->m_supports_clipper->set_transformation(supports_trafo);
}
else
// The supports are not valid. We better dump the cached data.
m_supports_clipper.reset();
m_c->m_supports_clipper.reset();
}
// At this point we have the triangulated cuts for both the object and supports - let's render.
if (! m_object_clipper->get_triangles().empty()) {
if (! m_c->m_object_clipper->get_triangles().empty()) {
::glPushMatrix();
::glColor3f(1.0f, 0.37f, 0.0f);
::glBegin(GL_TRIANGLES);
for (const Vec3f& point : m_object_clipper->get_triangles())
for (const Vec3f& point : m_c->m_object_clipper->get_triangles())
::glVertex3f(point(0), point(1), point(2));
::glEnd();
::glPopMatrix();
}
if (m_supports_clipper && ! m_supports_clipper->get_triangles().empty() && !m_editing_mode) {
if (m_c->m_supports_clipper && ! m_c->m_supports_clipper->get_triangles().empty() && !m_editing_mode) {
// The supports are hidden in the editing mode, so it makes no sense to render the cuts.
::glPushMatrix();
::glColor3f(1.0f, 0.f, 0.37f);
::glBegin(GL_TRIANGLES);
for (const Vec3f& point : m_supports_clipper->get_triangles())
for (const Vec3f& point : m_c->m_supports_clipper->get_triangles())
::glVertex3f(point(0), point(1), point(2));
::glEnd();
::glPopMatrix();
@ -230,6 +260,7 @@ void GLGizmoSlaSupports::on_render_for_picking() const
glsafe(::glEnable(GL_DEPTH_TEST));
render_points(selection, true);
render_hollowed_mesh();
}
void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const
@ -298,7 +329,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
if (m_editing_mode) {
// in case the normal is not yet cached, find and cache it
if (m_editing_cache[i].normal == Vec3f::Zero())
m_mesh_raycaster->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal);
m_c->m_mesh_raycaster->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal);
Eigen::Quaterniond q;
q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast<double>());
@ -327,6 +358,44 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
glsafe(::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive));
}
// Now render the drain holes:
if (! m_c->m_cavity_mesh) {
render_color[0] = 0.7f;
render_color[1] = 0.7f;
render_color[2] = 0.7f;
render_color[3] = 0.7f;
glsafe(::glColor4fv(render_color));
for (const sla::DrainHole& drain_hole : m_c->m_model_object->sla_drain_holes) {
// Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
glsafe(::glPushMatrix());
glsafe(::glTranslatef(drain_hole.pos(0), drain_hole.pos(1), drain_hole.pos(2)));
glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data()));
if (vol->is_left_handed())
glFrontFace(GL_CW);
// Matrices set, we can render the point mark now.
Eigen::Quaterniond q;
q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * (-drain_hole.normal).cast<double>());
Eigen::AngleAxisd aa(q);
glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2)));
glsafe(::glPushMatrix());
glsafe(::glTranslated(0., 0., -drain_hole.height));
::gluCylinder(m_quadric, drain_hole.radius, drain_hole.radius, drain_hole.height, 24, 1);
glsafe(::glTranslated(0., 0., drain_hole.height));
::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1);
glsafe(::glTranslated(0., 0., -drain_hole.height));
glsafe(::glRotatef(180.f, 1.f, 0.f, 0.f));
::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1);
glsafe(::glPopMatrix());
if (vol->is_left_handed())
glFrontFace(GL_CCW);
glsafe(::glPopMatrix());
}
}
if (!picking)
glsafe(::glDisable(GL_LIGHTING));
@ -340,7 +409,7 @@ bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const
if (m_clipping_plane_distance == 0.f)
return false;
Vec3d transformed_point = m_model_object->instances.front()->get_transformation().get_matrix() * point;
Vec3d transformed_point = m_c->m_model_object->instances[m_c->m_active_instance]->get_transformation().get_matrix() * point;
transformed_point(2) += m_z_shift;
return m_clipping_plane->is_point_clipped(transformed_point);
}
@ -349,39 +418,38 @@ bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const
bool GLGizmoSlaSupports::is_mesh_update_necessary() const
{
return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty())
&& ((m_model_object->id() != m_model_object_id) || m_its == nullptr);
return ((m_state == On) && (m_c->m_model_object != nullptr) && !m_c->m_model_object->instances.empty())
&& ((m_c->m_model_object->id() != m_c->m_model_object_id) || m_its == nullptr);
}
void GLGizmoSlaSupports::update_mesh()
{
if (! m_model_object)
if (! m_c->m_model_object)
return;
wxBusyCursor wait;
// this way we can use that mesh directly.
// This mesh does not account for the possible Z up SLA offset.
m_mesh = &m_model_object->volumes.front()->mesh();
m_its = &m_mesh->its;
m_c->m_mesh = &m_c->m_model_object->volumes.front()->mesh();
m_its = &m_c->m_mesh->its;
// If this is different mesh than last time or if the AABB tree is uninitialized, recalculate it.
if (m_model_object_id != m_model_object->id() || ! m_mesh_raycaster)
m_mesh_raycaster.reset(new MeshRaycaster(*m_mesh));
if (m_c->m_model_object_id != m_c->m_model_object->id() || ! m_c->m_mesh_raycaster)
m_c->m_mesh_raycaster.reset(new MeshRaycaster(*m_c->mesh()));
m_model_object_id = m_model_object->id();
m_c->m_model_object_id = m_c->m_model_object->id();
disable_editing_mode();
}
// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal
// Return false if no intersection was found, true otherwise.
bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal)
{
// if the gizmo doesn't have the V, F structures for igl, calculate them first:
if (! m_mesh_raycaster)
if (! m_c->m_mesh_raycaster)
update_mesh();
const Camera& camera = m_parent.get_camera();
@ -393,13 +461,28 @@ bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec
// The raycaster query
Vec3f hit;
Vec3f normal;
if (m_mesh_raycaster->unproject_on_mesh(mouse_pos, trafo.get_matrix(), camera, hit, normal, m_clipping_plane.get())) {
// Return both the point and the facet normal.
pos_and_normal = std::make_pair(hit, normal);
return true;
if (m_c->m_mesh_raycaster->unproject_on_mesh(mouse_pos, trafo.get_matrix(), camera, hit, normal, m_clipping_plane.get())) {
// Check whether the hit is in a hole
bool in_hole = false;
// In case the hollowed and drilled mesh is available, we can allow
// placing points in holes, because they should never end up
// on surface that's been drilled away.
if (! m_c->m_cavity_mesh) {
for (const sla::DrainHole& hole : m_c->m_model_object->sla_drain_holes) {
if (hole.is_inside(hit)) {
in_hole = true;
break;
}
}
}
if (! in_hole) {
// Return both the point and the facet normal.
pos_and_normal = std::make_pair(hit, normal);
return true;
}
}
else
return false;
return false;
}
// Following function is called from GLCanvas3D to inform the gizmo about a mouse/keyboard event.
@ -459,7 +542,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state();
// First collect positions of all the points in world coordinates.
Geometry::Transformation trafo = m_model_object->instances[m_active_instance]->get_transformation();
Geometry::Transformation trafo = m_c->m_model_object->instances[m_c->m_active_instance]->get_transformation();
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
std::vector<Vec3d> points;
for (unsigned int i=0; i<m_editing_cache.size(); ++i)
@ -472,7 +555,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
points_inside.push_back(points[idx].cast<float>());
// Only select/deselect points that are actually visible
for (size_t idx : m_mesh_raycaster->get_unobscured_idxs(trafo, m_parent.get_camera(), points_inside, m_clipping_plane.get()))
for (size_t idx : m_c->m_mesh_raycaster->get_unobscured_idxs(trafo, m_parent.get_camera(), points_inside, m_clipping_plane.get()))
{
if (rectangle_status == GLSelectionRectangle::Deselect)
unselect_point(points_idxs[idx]);
@ -610,10 +693,10 @@ std::vector<const ConfigOption*> GLGizmoSlaSupports::get_config_options(const st
{
std::vector<const ConfigOption*> out;
if (!m_model_object)
if (!m_c->m_model_object)
return out;
const DynamicPrintConfig& object_cfg = m_model_object->config;
const DynamicPrintConfig& object_cfg = m_c->m_model_object->config;
const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
std::unique_ptr<DynamicPrintConfig> default_cfg = nullptr;
@ -636,7 +719,7 @@ std::vector<const ConfigOption*> GLGizmoSlaSupports::get_config_options(const st
ClippingPlane GLGizmoSlaSupports::get_sla_clipping_plane() const
{
if (!m_model_object || m_state == Off || m_clipping_plane_distance == 0.f)
if (!m_c->m_model_object || m_state == Off || m_clipping_plane_distance == 0.f)
return ClippingPlane::ClipsNothing();
else
return ClippingPlane(-m_clipping_plane->get_normal(), m_clipping_plane->get_data()[3]);
@ -665,7 +748,7 @@ void GLGizmoSlaSupports::find_intersecting_facets(const igl::AABB<Eigen::MatrixX
void GLGizmoSlaSupports::make_line_segments() const
{
TriangleMeshSlicer tms(&m_model_object->volumes.front()->mesh);
TriangleMeshSlicer tms(&m_c->m_model_object->volumes.front()->mesh);
Vec3f normal(0.f, 1.f, 1.f);
double d = 0.;
@ -689,7 +772,7 @@ void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_l
static float last_y = 0.0f;
static float last_h = 0.0f;
if (!m_model_object)
if (! m_c->m_model_object)
return;
bool first_run = true; // This is a hack to redraw the button when all points are removed,
@ -699,7 +782,7 @@ RENDER_AGAIN:
//const ImVec2 window_size(m_imgui->scaled(18.f, 16.f));
//ImGui::SetNextWindowPos(ImVec2(x, y - std::max(0.f, y+window_size.y-bottom_limit) ));
//ImGui::SetNextWindowSize(ImVec2(window_size));
m_imgui->begin(on_get_name(), ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoCollapse);
// adjust window position to avoid overlap the view toolbar
@ -728,7 +811,6 @@ RENDER_AGAIN:
float window_width = minimal_slider_width + std::max(std::max(settings_sliders_left, clipping_slider_left), diameter_slider_left);
window_width = std::max(std::max(window_width, buttons_width_approx), lock_supports_width_approx);
bool force_refresh = false;
bool remove_selected = false;
bool remove_all = false;
@ -827,15 +909,15 @@ RENDER_AGAIN:
m_density_stash = density;
}
if (slider_edited) {
m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance;
m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density;
m_c->m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance;
m_c->m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density;
}
if (slider_released) {
m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = m_minimal_point_distance_stash;
m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)m_density_stash;
m_c->m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = m_minimal_point_distance_stash;
m_c->m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)m_density_stash;
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Support parameter change")));
m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance;
m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density;
m_c->m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance;
m_c->m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density;
wxGetApp().obj_list()->update_and_show_object_settings_item();
}
@ -853,10 +935,10 @@ RENDER_AGAIN:
m_imgui->disabled_end();
// m_imgui->text("");
// m_imgui->text(m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) :
// (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? _(L("Autogenerated points (no modifications)")) :
// (m_model_object->sla_points_status == sla::PointsStatus::UserModified ? _(L("User-modified points")) :
// (m_model_object->sla_points_status == sla::PointsStatus::Generating ? _(L("Generation in progress...")) : "UNKNOWN STATUS"))));
// m_imgui->text(m_c->m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) :
// (m_c->m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? _(L("Autogenerated points (no modifications)")) :
// (m_c->m_model_object->sla_points_status == sla::PointsStatus::UserModified ? _(L("User-modified points")) :
// (m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating ? _(L("Generation in progress...")) : "UNKNOWN STATUS"))));
}
@ -890,12 +972,6 @@ RENDER_AGAIN:
m_imgui->end();
if (m_editing_mode != m_old_editing_state) { // user toggled between editing/non-editing mode
m_parent.toggle_sla_auxiliaries_visibility(!m_editing_mode, m_model_object, m_active_instance);
force_refresh = true;
}
m_old_editing_state = m_editing_mode;
if (remove_selected || remove_all) {
force_refresh = false;
m_parent.set_as_dirty();
@ -952,12 +1028,12 @@ std::string GLGizmoSlaSupports::on_get_name() const
void GLGizmoSlaSupports::on_set_state()
{
// m_model_object pointer can be invalid (for instance because of undo/redo action),
// m_c->m_model_object pointer can be invalid (for instance because of undo/redo action),
// we should recover it from the object id
m_model_object = nullptr;
m_c->m_model_object = nullptr;
for (const auto mo : wxGetApp().model().objects) {
if (mo->id() == m_model_object_id) {
m_model_object = mo;
if (mo->id() == m_c->m_model_object_id) {
m_c->m_model_object = mo;
break;
}
}
@ -971,19 +1047,20 @@ void GLGizmoSlaSupports::on_set_state()
update_mesh();
// we'll now reload support points:
if (m_model_object)
if (m_c->m_model_object)
reload_cache();
m_parent.toggle_model_objects_visibility(false);
if (m_model_object)
m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance);
if (m_c->m_model_object && ! m_c->m_cavity_mesh)
m_parent.toggle_model_objects_visibility(true, m_c->m_model_object, m_c->m_active_instance);
m_parent.toggle_sla_auxiliaries_visibility(! m_editing_mode, m_c->m_model_object, m_c->m_active_instance);
// Set default head diameter from config.
const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
m_new_point_head_diameter = static_cast<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value;
}
if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
bool will_ask = m_model_object && m_editing_mode && unsaved_changes();
bool will_ask = m_c->m_model_object && m_editing_mode && unsaved_changes();
if (will_ask) {
wxGetApp().CallAfter([this]() {
// Following is called through CallAfter, because otherwise there was a problem
@ -1005,11 +1082,12 @@ void GLGizmoSlaSupports::on_set_state()
m_parent.toggle_model_objects_visibility(true);
m_normal_cache.clear();
m_clipping_plane_distance = 0.f;
update_clipping_plane();
// Release clippers and the AABB raycaster.
m_its = nullptr;
m_object_clipper.reset();
m_supports_clipper.reset();
m_mesh_raycaster.reset();
m_c->m_object_clipper.reset();
m_c->m_supports_clipper.reset();
m_c->m_mesh_raycaster.reset();
}
}
m_old_state = m_state;
@ -1051,7 +1129,7 @@ void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar)
{
ar(m_clipping_plane_distance,
*m_clipping_plane,
m_model_object_id,
m_c->m_model_object_id,
m_new_point_head_diameter,
m_normal_cache,
m_editing_cache,
@ -1065,7 +1143,7 @@ void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const
{
ar(m_clipping_plane_distance,
*m_clipping_plane,
m_model_object_id,
m_c->m_model_object_id,
m_new_point_head_diameter,
m_normal_cache,
m_editing_cache,
@ -1143,9 +1221,9 @@ void GLGizmoSlaSupports::editing_mode_apply_changes()
for (const CacheEntry& ce : m_editing_cache)
m_normal_cache.push_back(ce.support_point);
m_model_object->sla_points_status = sla::PointsStatus::UserModified;
m_model_object->sla_support_points.clear();
m_model_object->sla_support_points = m_normal_cache;
m_c->m_model_object->sla_points_status = sla::PointsStatus::UserModified;
m_c->m_model_object->sla_support_points.clear();
m_c->m_model_object->sla_support_points = m_normal_cache;
reslice_SLA_supports();
}
@ -1156,10 +1234,10 @@ void GLGizmoSlaSupports::editing_mode_apply_changes()
void GLGizmoSlaSupports::reload_cache()
{
m_normal_cache.clear();
if (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated || m_model_object->sla_points_status == sla::PointsStatus::Generating)
if (m_c->m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated || m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating)
get_data_from_backend();
else
for (const sla::SupportPoint& point : m_model_object->sla_support_points)
for (const sla::SupportPoint& point : m_c->m_model_object->sla_support_points)
m_normal_cache.emplace_back(point);
}
@ -1168,7 +1246,7 @@ bool GLGizmoSlaSupports::has_backend_supports() const
{
// find SlaPrintObject with this ID
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
if (po->model_object()->id() == m_model_object->id())
if (po->model_object()->id() == m_c->m_model_object->id())
return po->is_step_done(slaposSupportPoints);
}
return false;
@ -1176,7 +1254,7 @@ bool GLGizmoSlaSupports::has_backend_supports() const
void GLGizmoSlaSupports::reslice_SLA_supports(bool postpone_error_messages) const
{
wxGetApp().CallAfter([this, postpone_error_messages]() { wxGetApp().plater()->reslice_SLA_supports(*m_model_object, postpone_error_messages); });
wxGetApp().CallAfter([this, postpone_error_messages]() { wxGetApp().plater()->reslice_SLA_supports(*m_c->m_model_object, postpone_error_messages); });
}
void GLGizmoSlaSupports::get_data_from_backend()
@ -1186,14 +1264,14 @@ void GLGizmoSlaSupports::get_data_from_backend()
// find the respective SLAPrintObject, we need a pointer to it
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
if (po->model_object()->id() == m_model_object->id()) {
if (po->model_object()->id() == m_c->m_model_object->id()) {
m_normal_cache.clear();
const std::vector<sla::SupportPoint>& points = po->get_support_points();
auto mat = po->trafo().inverse().cast<float>();
for (unsigned int i=0; i<points.size();++i)
m_normal_cache.emplace_back(sla::SupportPoint(mat * points[i].pos, points[i].head_front_radius, points[i].is_new_island));
m_model_object->sla_points_status = sla::PointsStatus::AutoGenerated;
m_c->m_model_object->sla_points_status = sla::PointsStatus::AutoGenerated;
break;
}
}
@ -1210,10 +1288,10 @@ void GLGizmoSlaSupports::auto_generate()
_(L("Are you sure you want to do it?")) + "\n",
_(L("Warning")), wxICON_WARNING | wxYES | wxNO);
if (m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) {
if (m_c->m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) {
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Autogenerate support points")));
wxGetApp().CallAfter([this]() { reslice_SLA_supports(); });
m_model_object->sla_points_status = sla::PointsStatus::Generating;
m_c->m_model_object->sla_points_status = sla::PointsStatus::Generating;
}
}
@ -1227,6 +1305,9 @@ void GLGizmoSlaSupports::switch_to_editing_mode()
for (const sla::SupportPoint& sp : m_normal_cache)
m_editing_cache.emplace_back(sp);
select_point(NoPoints);
m_parent.toggle_sla_auxiliaries_visibility(false, m_c->m_model_object, m_c->m_active_instance);
m_parent.set_as_dirty();
}
@ -1235,6 +1316,8 @@ void GLGizmoSlaSupports::disable_editing_mode()
if (m_editing_mode) {
m_editing_mode = false;
wxGetApp().plater()->leave_gizmos_stack();
m_parent.toggle_sla_auxiliaries_visibility(true, m_c->m_model_object, m_c->m_active_instance);
m_parent.set_as_dirty();
}
}
@ -1258,9 +1341,9 @@ void GLGizmoSlaSupports::update_clipping_plane(bool keep_normal) const
Vec3d normal = (keep_normal && m_clipping_plane->get_normal() != Vec3d::Zero() ?
m_clipping_plane->get_normal() : -m_parent.get_camera().get_dir_forward());
const Vec3d& center = m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift);
const Vec3d& center = m_c->m_model_object->instances[m_c->m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift);
float dist = normal.dot(center);
*m_clipping_plane = ClippingPlane(normal, (dist - (-m_active_instance_bb_radius) - m_clipping_plane_distance * 2*m_active_instance_bb_radius));
*m_clipping_plane = ClippingPlane(normal, (dist - (-m_c->m_active_instance_bb_radius) - m_clipping_plane_distance * 2*m_c->m_active_instance_bb_radius));
m_parent.set_as_dirty();
}

View file

@ -4,7 +4,7 @@
#include "GLGizmoBase.hpp"
#include "slic3r/GUI/GLSelectionRectangle.hpp"
#include "libslic3r/SLA/SLACommon.hpp"
#include "libslic3r/SLA/Common.hpp"
#include <wx/dialog.h>
#include <cereal/types/vector.hpp>
@ -21,10 +21,10 @@ enum class SLAGizmoEventType : unsigned char;
class GLGizmoSlaSupports : public GLGizmoBase
{
private:
ModelObject* m_model_object = nullptr;
ObjectID m_model_object_id = 0;
int m_active_instance = -1;
float m_active_instance_bb_radius; // to cache the bb
//ModelObject* m_model_object = nullptr;
//ObjectID m_model_object_id = 0;
//int m_active_instance = -1;
//float m_active_instance_bb_radius; // to cache the bb
mutable double m_z_shift = 0.f;
bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal);
@ -34,15 +34,12 @@ private:
typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
std::unique_ptr<MeshRaycaster> m_mesh_raycaster;
const TriangleMesh* m_mesh;
//std::unique_ptr<MeshRaycaster> m_mesh_raycaster;
//const TriangleMesh* m_mesh;
const indexed_triangle_set* m_its;
mutable const TriangleMesh* m_supports_mesh;
mutable std::vector<Vec2f> m_triangles;
mutable std::vector<Vec2f> m_supports_triangles;
mutable int m_old_timestamp = -1;
mutable int m_print_object_idx = -1;
mutable int m_print_objects_count = -1;
//mutable int m_old_timestamp = -1;
//mutable int m_print_object_idx = -1;
//mutable int m_print_objects_count = -1;
class CacheEntry {
public:
@ -72,7 +69,7 @@ private:
};
public:
GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, CommonGizmosData* cd);
~GLGizmoSlaSupports() override;
void set_sla_support_data(ModelObject* model_object, const Selection& selection);
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
@ -93,13 +90,13 @@ private:
//void render_selection_rectangle() const;
void render_points(const Selection& selection, bool picking = false) const;
void render_clipping_plane(const Selection& selection) const;
void render_hollowed_mesh() const;
bool is_mesh_update_necessary() const;
void update_mesh();
bool unsaved_changes() const;
bool m_lock_unique_islands = false;
bool m_editing_mode = false; // Is editing mode active?
bool m_old_editing_state = false; // To keep track of whether the user toggled between the modes (needed for imgui refreshes).
float m_new_point_head_diameter; // Size of a new point.
CacheEntry m_point_before_drag; // undo/redo - so we know what state was edited
float m_old_point_head_diameter = 0.; // the same
@ -121,11 +118,12 @@ private:
bool m_selection_empty = true;
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
mutable std::unique_ptr<MeshClipper> m_object_clipper;
mutable std::unique_ptr<MeshClipper> m_supports_clipper;
//mutable std::unique_ptr<MeshClipper> m_object_clipper;
//mutable std::unique_ptr<MeshClipper> m_supports_clipper;
std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const;
bool is_mesh_point_clipped(const Vec3d& point) const;
bool is_point_in_hole(const Vec3f& pt) const;
//void find_intersecting_facets(const igl::AABB<Eigen::MatrixXf, 3>* aabb, const Vec3f& normal, double offset, std::vector<unsigned int>& out) const;
// Methods that do the model_object and editing cache synchronization,

View file

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

View file

@ -83,12 +83,16 @@ bool GLGizmosManager::init()
return false;
}
m_common_gizmos_data.reset(new CommonGizmosData());
// Order of gizmos in the vector must match order in EType!
m_gizmos.emplace_back(new GLGizmoMove3D(m_parent, "move.svg", 0));
m_gizmos.emplace_back(new GLGizmoScale3D(m_parent, "scale.svg", 1));
m_gizmos.emplace_back(new GLGizmoRotate3D(m_parent, "rotate.svg", 2));
m_gizmos.emplace_back(new GLGizmoFlatten(m_parent, "place.svg", 3));
m_gizmos.emplace_back(new GLGizmoCut(m_parent, "cut.svg", 4));
m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 5));
m_gizmos.emplace_back(new GLGizmoHollow(m_parent, "hollow.svg", 5, m_common_gizmos_data.get()));
m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 6, m_common_gizmos_data.get()));
for (auto& gizmo : m_gizmos) {
if (! gizmo->init()) {
@ -345,6 +349,7 @@ void GLGizmosManager::set_sla_support_data(ModelObject* model_object)
return;
dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->set_sla_support_data(model_object, m_parent.get_selection());
dynamic_cast<GLGizmoHollow*>(m_gizmos[Hollow].get())->set_sla_support_data(model_object, m_parent.get_selection());
}
// Returns true if the gizmo used the event to do something, false otherwise.
@ -353,15 +358,22 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
if (!m_enabled || m_gizmos.empty())
return false;
return dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
if (m_current == SlaSupports)
return dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
if (m_current == Hollow)
return dynamic_cast<GLGizmoHollow*>(m_gizmos[Hollow].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
return false;
}
ClippingPlane GLGizmosManager::get_sla_clipping_plane() const
{
if (!m_enabled || m_current != SlaSupports || m_gizmos.empty())
if (!m_enabled || (m_current != SlaSupports && m_current != Hollow) || m_gizmos.empty())
return ClippingPlane::ClipsNothing();
return dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->get_sla_clipping_plane();
if (m_current == SlaSupports)
return dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->get_sla_clipping_plane();
else
return dynamic_cast<GLGizmoHollow*>(m_gizmos[Hollow].get())->get_sla_clipping_plane();
}
bool GLGizmosManager::wants_reslice_supports_on_undo() const
@ -401,7 +413,7 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt)
{
bool processed = false;
if (m_current == SlaSupports) {
if (m_current == SlaSupports || m_current == Hollow) {
float rot = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta();
if (gizmo_event((rot > 0.f ? SLAGizmoEventType::MouseWheelUp : SLAGizmoEventType::MouseWheelDown), Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
processed = true;
@ -459,7 +471,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
if (evt.LeftDown())
{
if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
// the gizmo got the event and took some action, there is no need to do anything more
processed = true;
else if (!selection.is_empty() && grabber_contains_mouse()) {
@ -477,17 +489,17 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
processed = true;
}
}
else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::RightDown))
else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::RightDown))
{
// we need to set the following right up as processed to avoid showing the context menu if the user release the mouse over the object
pending_right_up = true;
// event was taken care of by the SlaSupports gizmo
processed = true;
}
else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports))
else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports || m_current == Hollow))
// don't allow dragging objects with the Sla gizmo on
processed = true;
else if (evt.Dragging() && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
else if (evt.Dragging() && (m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
{
// the gizmo got the event and took some action, no need to do anything more here
m_parent.set_as_dirty();
@ -513,10 +525,10 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
case Scale:
{
// Apply new temporary scale factors
TransformationType transformation_type(TransformationType::Local_Absolute_Joint);
if (evt.AltDown())
transformation_type.set_independent();
selection.scale(get_scale(), transformation_type);
TransformationType transformation_type(TransformationType::Local_Absolute_Joint);
if (evt.AltDown())
transformation_type.set_independent();
selection.scale(get_scale(), transformation_type);
if (evt.ControlDown())
selection.translate(get_scale_offset(), true);
wxGetApp().obj_manipul()->set_dirty();
@ -563,7 +575,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
processed = true;
}
else if (evt.LeftUp() && (m_current == SlaSupports) && !m_parent.is_mouse_dragging())
else if (evt.LeftUp() && (m_current == SlaSupports || m_current == Hollow) && !m_parent.is_mouse_dragging())
{
// in case SLA gizmo is selected, we just pass the LeftUp event and stop processing - neither
// object moving or selecting is suppressed in that case
@ -628,7 +640,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
#endif /* __APPLE__ */
{
// Sla gizmo selects all support points
if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::SelectAll))
if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::SelectAll))
processed = true;
break;
@ -662,7 +674,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
case 'r' :
case 'R' :
{
if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::ResetClippingPlane))
if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::ResetClippingPlane))
processed = true;
break;
@ -674,7 +686,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
case WXK_DELETE:
#endif /* __APPLE__ */
{
if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::Delete))
if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::Delete))
processed = true;
break;
@ -695,7 +707,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
{
if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::ManualEditing))
processed = true;
break;
}
case 'F':
@ -736,20 +748,31 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
if (evt.GetEventType() == wxEVT_KEY_UP)
{
if (m_current == SlaSupports)
if (m_current == SlaSupports || m_current == Hollow)
{
GLGizmoSlaSupports* gizmo = dynamic_cast<GLGizmoSlaSupports*>(get_current());
bool is_editing = true;
bool is_rectangle_dragging = false;
if (m_current == SlaSupports) {
GLGizmoSlaSupports* gizmo = dynamic_cast<GLGizmoSlaSupports*>(get_current());
is_editing = gizmo->is_in_editing_mode();
is_rectangle_dragging = gizmo->is_selection_rectangle_dragging();
}
else {
GLGizmoHollow* gizmo = dynamic_cast<GLGizmoHollow*>(get_current());
is_rectangle_dragging = gizmo->is_selection_rectangle_dragging();
}
if (keyCode == WXK_SHIFT)
{
// shift has been just released - SLA gizmo might want to close rectangular selection.
if (gizmo_event(SLAGizmoEventType::ShiftUp) || (gizmo->is_in_editing_mode() && gizmo->is_selection_rectangle_dragging()))
if (gizmo_event(SLAGizmoEventType::ShiftUp) || (is_editing && is_rectangle_dragging))
processed = true;
}
else if (keyCode == WXK_ALT)
{
// alt has been just released - SLA gizmo might want to close rectangular selection.
if (gizmo_event(SLAGizmoEventType::AltUp) || (gizmo->is_in_editing_mode() && gizmo->is_selection_rectangle_dragging()))
if (gizmo_event(SLAGizmoEventType::AltUp) || (is_editing && is_rectangle_dragging))
processed = true;
}
}

View file

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

155
src/slic3r/GUI/Job.hpp Normal file
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
// initialize status bar
m_statusbar.reset(new ProgressStatusBar(this));
m_statusbar = std::make_shared<ProgressStatusBar>(this);
m_statusbar->set_font(GUI::wxGetApp().normal_font());
m_statusbar->embed(this);
m_statusbar->set_status_text(_(L("Version")) + " " +
SLIC3R_VERSION +

View file

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

View file

@ -5,10 +5,6 @@
#include "slic3r/GUI/Camera.hpp"
// There is an L function in igl that would be overridden by our localization macro.
#undef L
#include <igl/AABB.h>
#include <GL/glew.h>
@ -99,57 +95,6 @@ void MeshClipper::recalculate_triangles()
}
class MeshRaycaster::AABBWrapper {
public:
AABBWrapper(const TriangleMesh* mesh);
~AABBWrapper() { m_AABB.deinit(); }
typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
igl::AABB<MapMatrixXfUnaligned, 3> m_AABB;
Vec3f get_hit_pos(const igl::Hit& hit) const;
Vec3f get_hit_normal(const igl::Hit& hit) const;
private:
const TriangleMesh* m_mesh;
};
MeshRaycaster::AABBWrapper::AABBWrapper(const TriangleMesh* mesh)
: m_mesh(mesh)
{
m_AABB.init(
MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3),
MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3));
}
MeshRaycaster::MeshRaycaster(const TriangleMesh& mesh)
: m_AABB_wrapper(new AABBWrapper(&mesh)), m_mesh(&mesh)
{
}
// Define the default destructor here. This is needed for the PIMPL with
// unique_ptr to work, the AABBWrapper is complete here.
MeshRaycaster::~MeshRaycaster() = default;
Vec3f MeshRaycaster::AABBWrapper::get_hit_pos(const igl::Hit& hit) const
{
const stl_triangle_vertex_indices& indices = m_mesh->its.indices[hit.id];
return Vec3f((1-hit.u-hit.v) * m_mesh->its.vertices[indices(0)]
+ hit.u * m_mesh->its.vertices[indices(1)]
+ hit.v * m_mesh->its.vertices[indices(2)]);
}
Vec3f MeshRaycaster::AABBWrapper::get_hit_normal(const igl::Hit& hit) const
{
const stl_triangle_vertex_indices& indices = m_mesh->its.indices[hit.id];
Vec3f a(m_mesh->its.vertices[indices(1)] - m_mesh->its.vertices[indices(0)]);
Vec3f b(m_mesh->its.vertices[indices(2)] - m_mesh->its.vertices[indices(0)]);
return Vec3f(a.cross(b));
}
bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane) const
@ -163,27 +108,20 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 0., model_mat.data(), proj_mat.data(), viewport.data(), &pt1(0), &pt1(1), &pt1(2));
::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 1., model_mat.data(), proj_mat.data(), viewport.data(), &pt2(0), &pt2(1), &pt2(2));
std::vector<igl::Hit> hits;
Transform3d inv = trafo.inverse();
pt1 = inv * pt1;
pt2 = inv * pt2;
if (! m_AABB_wrapper->m_AABB.intersect_ray(
AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3),
AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3),
pt1.cast<float>(), (pt2-pt1).cast<float>(), hits))
std::vector<sla::EigenMesh3D::hit_result> hits = m_emesh.query_ray_hits(pt1, pt2-pt1);
if (hits.empty())
return false; // no intersection found
std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
unsigned i = 0;
// Remove points that are obscured or cut by the clipping plane
if (clipping_plane) {
for (i=0; i<hits.size(); ++i)
if (! clipping_plane->is_point_clipped(trafo * m_AABB_wrapper->get_hit_pos(hits[i]).cast<double>()))
if (! clipping_plane->is_point_clipped(trafo * hits[i].position()))
break;
if (i==hits.size() || (hits.size()-i) % 2 != 0) {
@ -194,8 +132,8 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
}
// Now stuff the points in the provided vector and calculate normals if asked about them:
position = m_AABB_wrapper->get_hit_pos(hits[i]);
normal = m_AABB_wrapper->get_hit_normal(hits[i]);
position = hits[i].position().cast<float>();
normal = hits[i].normal().cast<float>();
return true;
}
@ -219,24 +157,21 @@ std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo
bool is_obscured = false;
// Cast a ray in the direction of the camera and look for intersection with the mesh:
std::vector<igl::Hit> hits;
std::vector<sla::EigenMesh3D::hit_result> hits;
// Offset the start of the ray by EPSILON to account for numerical inaccuracies.
if (m_AABB_wrapper->m_AABB.intersect_ray(
AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3),
AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3),
inverse_trafo * pt + direction_to_camera_mesh * EPSILON, direction_to_camera_mesh, hits)) {
hits = m_emesh.query_ray_hits((inverse_trafo * pt + direction_to_camera_mesh * EPSILON).cast<double>(),
direction_to_camera.cast<double>());
std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; });
if (! hits.empty()) {
// If the closest hit facet normal points in the same direction as the ray,
// we are looking through the mesh and should therefore discard the point:
if (m_AABB_wrapper->get_hit_normal(hits.front()).dot(direction_to_camera_mesh) > 0.f)
if (hits.front().normal().dot(direction_to_camera_mesh.cast<double>()) > 0)
is_obscured = true;
// Eradicate all hits that the caller wants to ignore
for (unsigned j=0; j<hits.size(); ++j) {
const igl::Hit& hit = hits[j];
if (clipping_plane && clipping_plane->is_point_clipped(trafo.get_matrix() * m_AABB_wrapper->get_hit_pos(hit).cast<double>())) {
if (clipping_plane && clipping_plane->is_point_clipped(trafo.get_matrix() * hits[j].position())) {
hits.erase(hits.begin()+j);
--j;
}
@ -257,17 +192,15 @@ std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo
Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const
{
int idx = 0;
Eigen::Matrix<float, 1, 3> closest_point;
m_AABB_wrapper->m_AABB.squared_distance(
AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3),
AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3),
point, idx, closest_point);
Vec3d closest_point;
m_emesh.squared_distance(point.cast<double>(), idx, closest_point);
if (normal) {
igl::Hit imag_hit;
imag_hit.id = idx;
*normal = m_AABB_wrapper->get_hit_normal(imag_hit);
auto indices = m_emesh.F().row(idx);
Vec3d a(m_emesh.V().row(indices(1)) - m_emesh.V().row(indices(0)));
Vec3d b(m_emesh.V().row(indices(2)) - m_emesh.V().row(indices(0)));
*normal = Vec3f(a.cross(b).cast<float>());
}
return closest_point;
return closest_point.cast<float>();
}

View file

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

View file

@ -39,11 +39,13 @@
#include "libslic3r/GCode/ThumbnailData.hpp"
#endif // ENABLE_THUMBNAIL_GENERATOR
#include "libslic3r/Model.hpp"
#include "libslic3r/SLA/Hollowing.hpp"
#include "libslic3r/SLA/Rotfinder.hpp"
#include "libslic3r/SLA/SupportPoint.hpp"
#include "libslic3r/Polygon.hpp"
#include "libslic3r/Print.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/SLAPrint.hpp"
#include "libslic3r/SLA/SLARotfinder.hpp"
#include "libslic3r/Utils.hpp"
//#include "libslic3r/ClipperUtils.hpp"
@ -70,6 +72,7 @@
#include "Camera.hpp"
#include "Mouse3DController.hpp"
#include "Tab.hpp"
#include "Job.hpp"
#include "PresetBundle.hpp"
#include "BackgroundSlicingProcess.hpp"
#include "ProgressStatusBar.hpp"
@ -1503,144 +1506,39 @@ struct Plater::priv
// objects would be frozen for the user. In case of arrange, an animation
// could be shown, or with the optimize orientations, partial results
// could be displayed.
class Job : public wxEvtHandler
class PlaterJob: public Job
{
int m_range = 100;
boost::thread m_thread;
priv * m_plater = nullptr;
std::atomic<bool> m_running{false}, m_canceled{false};
bool m_finalized = false;
void run()
{
m_running.store(true);
process();
m_running.store(false);
// ensure to call the last status to finalize the job
update_status(status_range(), "");
}
priv *m_plater;
protected:
// status range for a particular job
virtual int status_range() const { return 100; }
// status update, to be used from the work thread (process() method)
void update_status(int st, const wxString &msg = "")
{
auto evt = new wxThreadEvent();
evt->SetInt(st);
evt->SetString(msg);
wxQueueEvent(this, evt);
}
priv & plater() { return *m_plater; }
const priv &plater() const { return *m_plater; }
bool was_canceled() const { return m_canceled.load(); }
// Launched just before start(), a job can use it to prepare internals
virtual void prepare() {}
// Launched when the job is finished. It refreshes the 3Dscene by def.
virtual void finalize()
void finalize() override
{
// Do a full refresh of scene tree, including regenerating
// all the GLVolumes. FIXME The update function shall just
// reload the modified matrices.
if (!was_canceled())
if (!Job::was_canceled())
plater().update(unsigned(UpdateParams::FORCE_FULL_SCREEN_REFRESH));
Job::finalize();
}
public:
Job(priv *_plater) : m_plater(_plater)
{
Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) {
auto msg = evt.GetString();
if (!msg.empty())
plater().statusbar()->set_status_text(msg);
if (m_finalized) return;
plater().statusbar()->set_progress(evt.GetInt());
if (evt.GetInt() == status_range()) {
// set back the original range and cancel callback
plater().statusbar()->set_range(m_range);
plater().statusbar()->set_cancel_callback();
wxEndBusyCursor();
finalize();
// dont do finalization again for the same process
m_finalized = true;
}
});
}
Job(const Job &) = delete;
Job(Job &&) = delete;
Job &operator=(const Job &) = delete;
Job &operator=(Job &&) = delete;
virtual void process() = 0;
void start()
{ // Start the job. No effect if the job is already running
if (!m_running.load()) {
prepare();
// Save the current status indicatior range and push the new one
m_range = plater().statusbar()->get_range();
plater().statusbar()->set_range(status_range());
// init cancellation flag and set the cancel callback
m_canceled.store(false);
plater().statusbar()->set_cancel_callback(
[this]() { m_canceled.store(true); });
m_finalized = false;
// Changing cursor to busy
wxBeginBusyCursor();
try { // Execute the job
m_thread = create_thread([this] { this->run(); });
} catch (std::exception &) {
update_status(status_range(),
_(L("ERROR: not enough resources to "
"execute a new job.")));
}
// The state changes will be undone when the process hits the
// last status value, in the status update handler (see ctor)
}
}
// To wait for the running job and join the threads. False is
// returned if the timeout has been reached and the job is still
// running. Call cancel() before this fn if you want to explicitly
// end the job.
bool join(int timeout_ms = 0)
{
if (!m_thread.joinable()) return true;
if (timeout_ms <= 0)
m_thread.join();
else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
return false;
return true;
}
bool is_running() const { return m_running.load(); }
void cancel() { m_canceled.store(true); }
PlaterJob(priv *_plater)
: Job(_plater->statusbar()), m_plater(_plater)
{}
};
enum class Jobs : size_t {
Arrange,
Rotoptimize
Rotoptimize,
Hollow
};
class ArrangeJob : public Job
class ArrangeJob : public PlaterJob
{
using ArrangePolygon = arrangement::ArrangePolygon;
using ArrangePolygons = arrangement::ArrangePolygons;
@ -1757,7 +1655,7 @@ struct Plater::priv
}
public:
using Job::Job;
using PlaterJob::PlaterJob;
int status_range() const override { return int(m_selected.size()); }
@ -1774,13 +1672,30 @@ struct Plater::priv
}
};
class RotoptimizeJob : public Job
class RotoptimizeJob : public PlaterJob
{
public:
using Job::Job;
using PlaterJob::PlaterJob;
void process() override;
};
class HollowJob : public PlaterJob
{
public:
using PlaterJob::PlaterJob;
void prepare() override;
void process() override;
void finalize() override;
private:
GLGizmoHollow * get_gizmo();
const GLGizmoHollow * get_gizmo() const;
std::unique_ptr<TriangleMesh> m_output_mesh;
std::unique_ptr<MeshRaycaster> m_output_raycaster;
const TriangleMesh* m_object_mesh = nullptr;
sla::HollowingConfig m_cfg;
};
// Jobs defined inside the group class will be managed so that only one can
// run at a time. Also, the background process will be stopped if a job is
// started.
@ -1792,6 +1707,7 @@ struct Plater::priv
ArrangeJob arrange_job{m_plater};
RotoptimizeJob rotoptimize_job{m_plater};
HollowJob hollow_job{m_plater};
// To create a new job, just define a new subclass of Job, implement
// the process and the optional prepare() and finalize() methods
@ -1799,7 +1715,8 @@ struct Plater::priv
// if it cannot run concurrently with other jobs in this group
std::vector<std::reference_wrapper<Job>> m_jobs{arrange_job,
rotoptimize_job};
rotoptimize_job,
hollow_job};
public:
ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {}
@ -1876,7 +1793,7 @@ struct Plater::priv
void reset_all_gizmos();
void update_ui_from_settings();
ProgressStatusBar* statusbar();
std::shared_ptr<ProgressStatusBar> statusbar();
std::string get_config(const std::string &key) const;
BoundingBoxf bed_shape_bb() const;
BoundingBox scaled_bed_shape_bb() const;
@ -1901,6 +1818,7 @@ struct Plater::priv
void reset();
void mirror(Axis axis);
void arrange();
void hollow();
void sla_optimize_rotation();
void split_object();
void split_volume();
@ -2291,9 +2209,9 @@ void Plater::priv::update_ui_from_settings()
preview->get_canvas3d()->update_ui_from_settings();
}
ProgressStatusBar* Plater::priv::statusbar()
std::shared_ptr<ProgressStatusBar> Plater::priv::statusbar()
{
return main_frame->m_statusbar.get();
return main_frame->m_statusbar;
}
std::string Plater::priv::get_config(const std::string &key) const
@ -2815,6 +2733,12 @@ void Plater::priv::arrange()
m_ui_jobs.start(Jobs::Arrange);
}
void Plater::priv::hollow()
{
this->take_snapshot(_(L("Hollow")));
m_ui_jobs.start(Jobs::Hollow);
}
// This method will find an optimal orientation for the currently selected item
// Very similar in nature to the arrange method above...
void Plater::priv::sla_optimize_rotation() {
@ -2946,12 +2870,74 @@ void Plater::priv::RotoptimizeJob::process()
: _(L("Orientation found.")));
}
void Plater::priv::HollowJob::prepare()
{
const GLGizmosManager& gizmo_manager = plater().q->canvas3D()->get_gizmos_manager();
const GLGizmoHollow* gizmo_hollow = dynamic_cast<const GLGizmoHollow*>(gizmo_manager.get_current());
assert(gizmo_hollow);
auto hlw_data = gizmo_hollow->get_hollowing_parameters();
m_object_mesh = hlw_data.first;
m_cfg = hlw_data.second;
m_output_mesh.reset();
}
void Plater::priv::HollowJob::process()
{
sla::JobController ctl;
ctl.stopcondition = [this]{ return was_canceled(); };
ctl.statuscb = [this](unsigned st, const std::string &s) {
if (st < 100) update_status(int(st), s);
};
std::unique_ptr<TriangleMesh> omesh =
sla::generate_interior(*m_object_mesh, m_cfg, ctl);
if (omesh && !omesh->empty()) {
m_output_mesh.reset(new TriangleMesh{*m_object_mesh});
m_output_mesh->merge(*omesh);
m_output_mesh->require_shared_vertices();
update_status(90, _(L("Indexing hollowed object")));
m_output_raycaster.reset(new MeshRaycaster(*m_output_mesh));
update_status(100, was_canceled() ? _(L("Hollowing cancelled.")) :
_(L("Hollowing done.")));
} else {
update_status(100, _(L("Hollowing failed.")));
}
}
void Plater::priv::HollowJob::finalize()
{
if (auto gizmo = get_gizmo()) {
gizmo->update_mesh_raycaster(std::move(m_output_raycaster));
gizmo->update_hollowed_mesh(std::move(m_output_mesh));
}
}
GLGizmoHollow *Plater::priv::HollowJob::get_gizmo()
{
const GLGizmosManager& gizmo_manager = plater().q->canvas3D()->get_gizmos_manager();
auto ret = dynamic_cast<GLGizmoHollow*>(gizmo_manager.get_current());
assert(ret);
return ret;
}
const GLGizmoHollow *Plater::priv::HollowJob::get_gizmo() const
{
const GLGizmosManager& gizmo_manager = plater().q->canvas3D()->get_gizmos_manager();
auto ret = dynamic_cast<const GLGizmoHollow*>(gizmo_manager.get_current());
assert(ret);
return ret;
}
void Plater::priv::split_object()
{
int obj_idx = get_selected_object_idx();
if (obj_idx == -1)
return;
// we clone model object because split_object() adds the split volumes
// into the same model object, thus causing duplicates when we call load_model_objects()
Model new_model = model;
@ -5066,6 +5052,11 @@ void Plater::export_toolpaths_to_obj() const
p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str());
}
void Plater::hollow()
{
p->hollow();
}
void Plater::reslice()
{
// Stop arrange and (or) optimize rotation tasks.

View file

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

View file

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

View file

@ -11,58 +11,17 @@ namespace Slic3r {
*/
class ProgressIndicator {
public:
using CancelFn = std::function<void(void)>; // Cancel function signature.
private:
float m_state = .0f, m_max = 1.f, m_step;
CancelFn m_cancelfunc = [](){};
public:
inline virtual ~ProgressIndicator() {}
/// Get the maximum of the progress range.
float max() const { return m_max; }
/// Get the current progress state
float state() const { return m_state; }
/// Set the maximum of the progress range
virtual void max(float maxval) { m_max = maxval; }
/// Set the current state of the progress.
virtual void state(float val) { m_state = val; }
/**
* @brief Number of states int the progress. Can be used instead of giving a
* maximum value.
*/
virtual void states(unsigned statenum) {
m_step = m_max / statenum;
}
/// Message shown on the next status update.
virtual void message(const std::string&) = 0;
/// Title of the operation.
virtual void title(const std::string&) = 0;
/// Formatted message for the next status update. Works just like sprintf.
virtual void message_fmt(const std::string& fmt, ...);
/// Set up a cancel callback for the operation if feasible.
virtual void on_cancel(CancelFn func = CancelFn()) { m_cancelfunc = func; }
/**
* Explicitly shut down the progress indicator and call the associated
* callback.
*/
virtual void cancel() { m_cancelfunc(); }
/// Convenience function to call message and status update in one function.
void update(float st, const std::string& msg) {
message(msg); state(st);
}
/// Cancel callback function type
using CancelFn = std::function<void()>;
virtual ~ProgressIndicator() = default;
virtual void set_range(int range) = 0;
virtual void set_cancel_callback(CancelFn = CancelFn()) = 0;
virtual void set_progress(int pr) = 0;
virtual void set_status_text(const char *) = 0; // utf8 char array
virtual int get_range() const = 0;
};
}

View file

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

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