mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-24 01:01:15 -06:00
Merge branch 'lm_tm_hollowing'
This commit is contained in:
commit
ef30270048
115 changed files with 17393 additions and 2919 deletions
|
|
@ -104,7 +104,7 @@ list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules/)
|
||||||
|
|
||||||
enable_testing ()
|
enable_testing ()
|
||||||
|
|
||||||
# Enable C++11 language standard.
|
# Enable C++17 language standard.
|
||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
|
@ -426,6 +426,12 @@ if(SLIC3R_STATIC)
|
||||||
set(USE_BLOSC TRUE)
|
set(USE_BLOSC TRUE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
find_package(OpenVDB 5.0 REQUIRED COMPONENTS openvdb)
|
||||||
|
if(OpenVDB_FOUND)
|
||||||
|
slic3r_remap_configs(IlmBase::Half RelWithDebInfo Release)
|
||||||
|
slic3r_remap_configs(Blosc::blosc RelWithDebInfo Release)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(TOP_LEVEL_PROJECT_DIR ${PROJECT_SOURCE_DIR})
|
set(TOP_LEVEL_PROJECT_DIR ${PROJECT_SOURCE_DIR})
|
||||||
function(prusaslicer_copy_dlls target)
|
function(prusaslicer_copy_dlls target)
|
||||||
if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
|
if ("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
|
||||||
|
|
@ -449,8 +455,6 @@ function(prusaslicer_copy_dlls target)
|
||||||
|
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
#find_package(OpenVDB 5.0 COMPONENTS openvdb)
|
|
||||||
#slic3r_remap_configs(IlmBase::Half RelWithDebInfo Release)
|
|
||||||
|
|
||||||
# libslic3r, PrusaSlicer GUI and the PrusaSlicer executable.
|
# libslic3r, PrusaSlicer GUI and the PrusaSlicer executable.
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
|
|
|
||||||
106
cmake/modules/CheckAtomic.cmake
Normal file
106
cmake/modules/CheckAtomic.cmake
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
# atomic builtins are required for threading support.
|
||||||
|
|
||||||
|
INCLUDE(CheckCXXSourceCompiles)
|
||||||
|
INCLUDE(CheckLibraryExists)
|
||||||
|
|
||||||
|
# Sometimes linking against libatomic is required for atomic ops, if
|
||||||
|
# the platform doesn't support lock-free atomics.
|
||||||
|
|
||||||
|
function(check_working_cxx_atomics varname)
|
||||||
|
set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
|
||||||
|
set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++11")
|
||||||
|
CHECK_CXX_SOURCE_COMPILES("
|
||||||
|
#include <atomic>
|
||||||
|
std::atomic<int> x;
|
||||||
|
int main() {
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
" ${varname})
|
||||||
|
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||||
|
endfunction(check_working_cxx_atomics)
|
||||||
|
|
||||||
|
function(check_working_cxx_atomics64 varname)
|
||||||
|
set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
|
||||||
|
set(CMAKE_REQUIRED_FLAGS "-std=c++11 ${CMAKE_REQUIRED_FLAGS}")
|
||||||
|
CHECK_CXX_SOURCE_COMPILES("
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
std::atomic<uint64_t> x (0);
|
||||||
|
int main() {
|
||||||
|
uint64_t i = x.load(std::memory_order_relaxed);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
" ${varname})
|
||||||
|
set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||||
|
endfunction(check_working_cxx_atomics64)
|
||||||
|
|
||||||
|
|
||||||
|
# This isn't necessary on MSVC, so avoid command-line switch annoyance
|
||||||
|
# by only running on GCC-like hosts.
|
||||||
|
if (LLVM_COMPILER_IS_GCC_COMPATIBLE)
|
||||||
|
# First check if atomics work without the library.
|
||||||
|
check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITHOUT_LIB)
|
||||||
|
# If not, check if the library exists, and atomics work with it.
|
||||||
|
if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB)
|
||||||
|
check_library_exists(atomic __atomic_fetch_add_4 "" HAVE_LIBATOMIC)
|
||||||
|
if( HAVE_LIBATOMIC )
|
||||||
|
list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic")
|
||||||
|
check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITH_LIB)
|
||||||
|
if (NOT HAVE_CXX_ATOMICS_WITH_LIB)
|
||||||
|
message(FATAL_ERROR "Host compiler must support std::atomic!")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Host compiler appears to require libatomic, but cannot find it.")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Check for 64 bit atomic operations.
|
||||||
|
if(MSVC)
|
||||||
|
set(HAVE_CXX_ATOMICS64_WITHOUT_LIB True)
|
||||||
|
else()
|
||||||
|
check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITHOUT_LIB)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# If not, check if the library exists, and atomics work with it.
|
||||||
|
if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB)
|
||||||
|
check_library_exists(atomic __atomic_load_8 "" HAVE_CXX_LIBATOMICS64)
|
||||||
|
if(HAVE_CXX_LIBATOMICS64)
|
||||||
|
list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic")
|
||||||
|
check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB)
|
||||||
|
if (NOT HAVE_CXX_ATOMICS64_WITH_LIB)
|
||||||
|
message(FATAL_ERROR "Host compiler must support 64-bit std::atomic!")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Host compiler appears to require libatomic for 64-bit operations, but cannot find it.")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
## TODO: This define is only used for the legacy atomic operations in
|
||||||
|
## llvm's Atomic.h, which should be replaced. Other code simply
|
||||||
|
## assumes C++11 <atomic> works.
|
||||||
|
CHECK_CXX_SOURCE_COMPILES("
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
int main() {
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
volatile LONG val = 1;
|
||||||
|
MemoryBarrier();
|
||||||
|
InterlockedCompareExchange(&val, 0, 1);
|
||||||
|
InterlockedIncrement(&val);
|
||||||
|
InterlockedDecrement(&val);
|
||||||
|
#else
|
||||||
|
volatile unsigned long val = 1;
|
||||||
|
__sync_synchronize();
|
||||||
|
__sync_val_compare_and_swap(&val, 1, 0);
|
||||||
|
__sync_add_and_fetch(&val, 1);
|
||||||
|
__sync_sub_and_fetch(&val, 1);
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
" LLVM_HAS_ATOMICS)
|
||||||
|
|
||||||
|
if( NOT LLVM_HAS_ATOMICS )
|
||||||
|
message(STATUS "Warning: LLVM will be built thread-unsafe because atomic builtins are missing")
|
||||||
|
endif()
|
||||||
|
|
@ -108,6 +108,18 @@ if(POLICY CMP0074)
|
||||||
cmake_policy(SET CMP0074 NEW)
|
cmake_policy(SET CMP0074 NEW)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(OpenVDB_FIND_QUIETLY)
|
||||||
|
set (_quiet "QUIET")
|
||||||
|
else()
|
||||||
|
set (_quiet "")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(OpenVDB_FIND_REQUIRED)
|
||||||
|
set (_required "REQUIRED")
|
||||||
|
else()
|
||||||
|
set (_required "")
|
||||||
|
endif()
|
||||||
|
|
||||||
# Include utility functions for version information
|
# Include utility functions for version information
|
||||||
include(${CMAKE_CURRENT_LIST_DIR}/OpenVDBUtils.cmake)
|
include(${CMAKE_CURRENT_LIST_DIR}/OpenVDBUtils.cmake)
|
||||||
|
|
||||||
|
|
@ -146,7 +158,7 @@ set(_OPENVDB_ROOT_SEARCH_DIR "")
|
||||||
|
|
||||||
# Additionally try and use pkconfig to find OpenVDB
|
# Additionally try and use pkconfig to find OpenVDB
|
||||||
|
|
||||||
find_package(PkgConfig)
|
find_package(PkgConfig ${_quiet} )
|
||||||
pkg_check_modules(PC_OpenVDB QUIET OpenVDB)
|
pkg_check_modules(PC_OpenVDB QUIET OpenVDB)
|
||||||
|
|
||||||
# ------------------------------------------------------------------------
|
# ------------------------------------------------------------------------
|
||||||
|
|
@ -230,12 +242,14 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
|
||||||
else()
|
else()
|
||||||
set(OpenVDB_${COMPONENT}_FOUND FALSE)
|
set(OpenVDB_${COMPONENT}_FOUND FALSE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
set(OpenVDB_${COMPONENT}_LIBRARY ${OpenVDB_${COMPONENT}_LIBRARY_RELEASE})
|
||||||
else ()
|
else ()
|
||||||
string(TOUPPER "${CMAKE_BUILD_TYPE}" _BUILD_TYPE)
|
string(TOUPPER "${CMAKE_BUILD_TYPE}" _BUILD_TYPE)
|
||||||
|
|
||||||
set(OpenVDB_${COMPONENT}_LIBRARY ${OpenVDB_${COMPONENT}_LIBRARY_${_BUILD_TYPE}})
|
set(OpenVDB_${COMPONENT}_LIBRARY ${OpenVDB_${COMPONENT}_LIBRARY_${_BUILD_TYPE}})
|
||||||
|
|
||||||
if (NOT MSVC AND NOT OpenVDB_${COMPONENT}_LIBRARY)
|
if (NOT OpenVDB_${COMPONENT}_LIBRARY)
|
||||||
set(OpenVDB_${COMPONENT}_LIBRARY ${OpenVDB_${COMPONENT}_LIBRARY_RELEASE})
|
set(OpenVDB_${COMPONENT}_LIBRARY ${OpenVDB_${COMPONENT}_LIBRARY_RELEASE})
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
|
@ -247,6 +261,7 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
|
||||||
set(OpenVDB_${COMPONENT}_FOUND FALSE)
|
set(OpenVDB_${COMPONENT}_FOUND FALSE)
|
||||||
endif()
|
endif()
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
if(UNIX AND OPENVDB_USE_STATIC_LIBS)
|
if(UNIX AND OPENVDB_USE_STATIC_LIBS)
|
||||||
|
|
@ -280,7 +295,7 @@ OPENVDB_ABI_VERSION_FROM_PRINT(
|
||||||
ABI OpenVDB_ABI
|
ABI OpenVDB_ABI
|
||||||
)
|
)
|
||||||
|
|
||||||
if(NOT OpenVDB_FIND_QUIET)
|
if(NOT OpenVDB_FIND_QUIETLY)
|
||||||
if(NOT OpenVDB_ABI)
|
if(NOT OpenVDB_ABI)
|
||||||
message(WARNING "Unable to determine OpenVDB ABI version from OpenVDB "
|
message(WARNING "Unable to determine OpenVDB ABI version from OpenVDB "
|
||||||
"installation. The library major version \"${OpenVDB_MAJOR_VERSION}\" "
|
"installation. The library major version \"${OpenVDB_MAJOR_VERSION}\" "
|
||||||
|
|
@ -298,7 +313,17 @@ endif()
|
||||||
|
|
||||||
# Add standard dependencies
|
# Add standard dependencies
|
||||||
|
|
||||||
find_package(IlmBase COMPONENTS Half)
|
macro(just_fail msg)
|
||||||
|
set(OpenVDB_FOUND FALSE)
|
||||||
|
if(OpenVDB_FIND_REQUIRED)
|
||||||
|
message(FATAL_ERROR ${msg})
|
||||||
|
elseif(NOT OpenVDB_FIND_QUIETLY)
|
||||||
|
message(WARNING ${msg})
|
||||||
|
endif()
|
||||||
|
return()
|
||||||
|
endmacro()
|
||||||
|
|
||||||
|
find_package(IlmBase QUIET COMPONENTS Half)
|
||||||
if(NOT IlmBase_FOUND)
|
if(NOT IlmBase_FOUND)
|
||||||
pkg_check_modules(IlmBase QUIET IlmBase)
|
pkg_check_modules(IlmBase QUIET IlmBase)
|
||||||
endif()
|
endif()
|
||||||
|
|
@ -306,20 +331,20 @@ if (IlmBase_FOUND AND NOT TARGET IlmBase::Half)
|
||||||
message(STATUS "Falling back to IlmBase found by pkg-config...")
|
message(STATUS "Falling back to IlmBase found by pkg-config...")
|
||||||
|
|
||||||
find_library(IlmHalf_LIBRARY NAMES Half)
|
find_library(IlmHalf_LIBRARY NAMES Half)
|
||||||
if(IlmHalf_LIBRARY-NOTFOUND)
|
if(IlmHalf_LIBRARY-NOTFOUND OR NOT IlmBase_INCLUDE_DIRS)
|
||||||
message(FATAL_ERROR "IlmBase::Half can not be found!")
|
just_fail("IlmBase::Half can not be found!")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_library(IlmBase::Half UNKNOWN IMPORTED)
|
add_library(IlmBase::Half UNKNOWN IMPORTED)
|
||||||
set_target_properties(IlmBase::Half PROPERTIES
|
set_target_properties(IlmBase::Half PROPERTIES
|
||||||
IMPORTED_LOCATION "${IlmHalf_LIBRARY}"
|
IMPORTED_LOCATION "${IlmHalf_LIBRARY}"
|
||||||
INTERFACE_INCLUDE_DIRECTORIES ${IlmBase_INCLUDE_DIRS})
|
INTERFACE_INCLUDE_DIRECTORIES "${IlmBase_INCLUDE_DIRS}")
|
||||||
elseif(NOT IlmBase_FOUND)
|
elseif(NOT IlmBase_FOUND)
|
||||||
message(FATAL_ERROR "IlmBase::Half can not be found!")
|
just_fail("IlmBase::Half can not be found!")
|
||||||
endif()
|
endif()
|
||||||
find_package(TBB REQUIRED COMPONENTS tbb)
|
find_package(TBB ${_quiet} ${_required} COMPONENTS tbb)
|
||||||
find_package(ZLIB REQUIRED)
|
find_package(ZLIB ${_quiet} ${_required})
|
||||||
find_package(Boost REQUIRED COMPONENTS iostreams system)
|
find_package(Boost ${_quiet} ${_required} COMPONENTS iostreams system )
|
||||||
|
|
||||||
# Use GetPrerequisites to see which libraries this OpenVDB lib has linked to
|
# Use GetPrerequisites to see which libraries this OpenVDB lib has linked to
|
||||||
# which we can query for optional deps. This basically runs ldd/otoll/objdump
|
# which we can query for optional deps. This basically runs ldd/otoll/objdump
|
||||||
|
|
@ -380,7 +405,7 @@ unset(_OPENVDB_PREREQUISITE_LIST)
|
||||||
unset(_HAS_DEP)
|
unset(_HAS_DEP)
|
||||||
|
|
||||||
if(OpenVDB_USES_BLOSC)
|
if(OpenVDB_USES_BLOSC)
|
||||||
find_package(Blosc )
|
find_package(Blosc QUIET)
|
||||||
if(NOT Blosc_FOUND OR NOT TARGET Blosc::blosc)
|
if(NOT Blosc_FOUND OR NOT TARGET Blosc::blosc)
|
||||||
message(STATUS "find_package could not find Blosc. Using fallback blosc search...")
|
message(STATUS "find_package could not find Blosc. Using fallback blosc search...")
|
||||||
find_path(Blosc_INCLUDE_DIR blosc.h)
|
find_path(Blosc_INCLUDE_DIR blosc.h)
|
||||||
|
|
@ -392,25 +417,25 @@ if(OpenVDB_USES_BLOSC)
|
||||||
IMPORTED_LOCATION "${Blosc_LIBRARY}"
|
IMPORTED_LOCATION "${Blosc_LIBRARY}"
|
||||||
INTERFACE_INCLUDE_DIRECTORIES ${Blosc_INCLUDE_DIR})
|
INTERFACE_INCLUDE_DIRECTORIES ${Blosc_INCLUDE_DIR})
|
||||||
elseif()
|
elseif()
|
||||||
message(FATAL_ERROR "Blosc library can not be found!")
|
just_fail("Blosc library can not be found!")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(OpenVDB_USES_LOG4CPLUS)
|
if(OpenVDB_USES_LOG4CPLUS)
|
||||||
find_package(Log4cplus REQUIRED)
|
find_package(Log4cplus ${_quiet} ${_required})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(OpenVDB_USES_ILM)
|
if(OpenVDB_USES_ILM)
|
||||||
find_package(IlmBase REQUIRED)
|
find_package(IlmBase ${_quiet} ${_required})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(OpenVDB_USES_EXR)
|
if(OpenVDB_USES_EXR)
|
||||||
find_package(OpenEXR REQUIRED)
|
find_package(OpenEXR ${_quiet} ${_required})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(UNIX)
|
if(UNIX)
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads ${_quiet} ${_required})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Set deps. Note that the order here is important. If we're building against
|
# Set deps. Note that the order here is important. If we're building against
|
||||||
|
|
@ -500,6 +525,7 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
|
||||||
IMPORTED_LINK_DEPENDENT_LIBRARIES "${_OPENVDB_HIDDEN_DEPENDENCIES}" # non visible deps
|
IMPORTED_LINK_DEPENDENT_LIBRARIES "${_OPENVDB_HIDDEN_DEPENDENCIES}" # non visible deps
|
||||||
INTERFACE_LINK_LIBRARIES "${_OPENVDB_VISIBLE_DEPENDENCIES}" # visible deps (headers)
|
INTERFACE_LINK_LIBRARIES "${_OPENVDB_VISIBLE_DEPENDENCIES}" # visible deps (headers)
|
||||||
INTERFACE_COMPILE_FEATURES cxx_std_11
|
INTERFACE_COMPILE_FEATURES cxx_std_11
|
||||||
|
IMPORTED_LOCATION "${OpenVDB_${COMPONENT}_LIBRARY}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if (_is_multi)
|
if (_is_multi)
|
||||||
|
|
@ -507,10 +533,6 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
|
||||||
IMPORTED_LOCATION_RELEASE "${OpenVDB_${COMPONENT}_LIBRARY_RELEASE}"
|
IMPORTED_LOCATION_RELEASE "${OpenVDB_${COMPONENT}_LIBRARY_RELEASE}"
|
||||||
IMPORTED_LOCATION_DEBUG "${OpenVDB_${COMPONENT}_LIBRARY_DEBUG}"
|
IMPORTED_LOCATION_DEBUG "${OpenVDB_${COMPONENT}_LIBRARY_DEBUG}"
|
||||||
)
|
)
|
||||||
else ()
|
|
||||||
set_target_properties(OpenVDB::${COMPONENT} PROPERTIES
|
|
||||||
IMPORTED_LOCATION "${OpenVDB_${COMPONENT}_LIBRARY}"
|
|
||||||
)
|
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (OPENVDB_USE_STATIC_LIBS)
|
if (OPENVDB_USE_STATIC_LIBS)
|
||||||
|
|
@ -521,7 +543,7 @@ foreach(COMPONENT ${OpenVDB_FIND_COMPONENTS})
|
||||||
endif()
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
if(OpenVDB_FOUND AND NOT ${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY)
|
if(OpenVDB_FOUND AND NOT OpenVDB_FIND_QUIETLY)
|
||||||
message(STATUS "OpenVDB libraries: ${OpenVDB_LIBRARIES}")
|
message(STATUS "OpenVDB libraries: ${OpenVDB_LIBRARIES}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,9 @@ function(OPENVDB_ABI_VERSION_FROM_PRINT OPENVDB_PRINT)
|
||||||
cmake_parse_arguments(_VDB "QUIET" "ABI" "" ${ARGN})
|
cmake_parse_arguments(_VDB "QUIET" "ABI" "" ${ARGN})
|
||||||
|
|
||||||
if(NOT EXISTS ${OPENVDB_PRINT})
|
if(NOT EXISTS ${OPENVDB_PRINT})
|
||||||
|
if(NOT OpenVDB_FIND_QUIETLY)
|
||||||
message(WARNING "vdb_print not found! ${OPENVDB_PRINT}")
|
message(WARNING "vdb_print not found! ${OPENVDB_PRINT}")
|
||||||
|
endif()
|
||||||
return()
|
return()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
@ -148,7 +150,9 @@ function(OPENVDB_ABI_VERSION_FROM_PRINT OPENVDB_PRINT)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(${_VDB_PRINT_RETURN_STATUS})
|
if(${_VDB_PRINT_RETURN_STATUS})
|
||||||
|
if(NOT OpenVDB_FIND_QUIETLY)
|
||||||
message(WARNING "vdb_print returned with status ${_VDB_PRINT_RETURN_STATUS}")
|
message(WARNING "vdb_print returned with status ${_VDB_PRINT_RETURN_STATUS}")
|
||||||
|
endif()
|
||||||
return()
|
return()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
|
||||||
3
deps/GMP/GMP.cmake
vendored
3
deps/GMP/GMP.cmake
vendored
|
|
@ -18,7 +18,8 @@ if (MSVC)
|
||||||
|
|
||||||
else ()
|
else ()
|
||||||
ExternalProject_Add(dep_GMP
|
ExternalProject_Add(dep_GMP
|
||||||
URL https://gmplib.org/download/gmp/gmp-6.1.2.tar.bz2
|
# URL https://gmplib.org/download/gmp/gmp-6.1.2.tar.bz2
|
||||||
|
URL https://gmplib.org/download/gmp/gmp-6.2.0.tar.lz
|
||||||
BUILD_IN_SOURCE ON
|
BUILD_IN_SOURCE ON
|
||||||
CONFIGURE_COMMAND ./configure --enable-shared=no --enable-cxx=yes --enable-static=yes "--prefix=${DESTDIR}/usr/local" --with-pic
|
CONFIGURE_COMMAND ./configure --enable-shared=no --enable-cxx=yes --enable-static=yes "--prefix=${DESTDIR}/usr/local" --with-pic
|
||||||
BUILD_COMMAND make -j
|
BUILD_COMMAND make -j
|
||||||
|
|
|
||||||
635
deps/openvdb-mods.patch
vendored
635
deps/openvdb-mods.patch
vendored
|
|
@ -1,24 +1,25 @@
|
||||||
From dbe038fce8a15ddc9a5c83ec5156d7bc9e178015 Mon Sep 17 00:00:00 2001
|
From d359098d9989ac7dbd149611d6ac941529fb4157 Mon Sep 17 00:00:00 2001
|
||||||
From: tamasmeszaros <meszaros.q@gmail.com>
|
From: tamasmeszaros <meszaros.q@gmail.com>
|
||||||
Date: Wed, 16 Oct 2019 17:42:50 +0200
|
Date: Thu, 23 Jan 2020 17:17:36 +0100
|
||||||
Subject: [PATCH] Build fixes for PrusaSlicer integration
|
Subject: [PATCH] openvdb-mods
|
||||||
|
|
||||||
Signed-off-by: tamasmeszaros <meszaros.q@gmail.com>
|
|
||||||
---
|
---
|
||||||
CMakeLists.txt | 3 -
|
CMakeLists.txt | 3 -
|
||||||
cmake/FindBlosc.cmake | 218 ---------------
|
cmake/CheckAtomic.cmake | 106 ++++++
|
||||||
|
cmake/FindBlosc.cmake | 218 ------------
|
||||||
cmake/FindCppUnit.cmake | 4 +-
|
cmake/FindCppUnit.cmake | 4 +-
|
||||||
cmake/FindIlmBase.cmake | 337 ----------------------
|
cmake/FindIlmBase.cmake | 337 ------------------
|
||||||
cmake/FindOpenEXR.cmake | 329 ----------------------
|
cmake/FindOpenEXR.cmake | 329 ------------------
|
||||||
cmake/FindOpenVDB.cmake | 19 +-
|
cmake/FindOpenVDB.cmake | 19 +-
|
||||||
cmake/FindTBB.cmake | 605 ++++++++++++++++++++--------------------
|
cmake/FindTBB.cmake | 599 ++++++++++++++++----------------
|
||||||
openvdb/CMakeLists.txt | 13 +-
|
openvdb/CMakeLists.txt | 16 +-
|
||||||
openvdb/Grid.cc | 3 +
|
openvdb/Grid.cc | 3 +
|
||||||
openvdb/PlatformConfig.h | 9 +-
|
openvdb/PlatformConfig.h | 9 +-
|
||||||
openvdb/cmd/CMakeLists.txt | 4 +-
|
openvdb/cmd/CMakeLists.txt | 4 +-
|
||||||
openvdb/unittest/CMakeLists.txt | 3 +-
|
openvdb/unittest/CMakeLists.txt | 3 +-
|
||||||
openvdb/unittest/TestFile.cc | 2 +-
|
openvdb/unittest/TestFile.cc | 2 +-
|
||||||
13 files changed, 336 insertions(+), 1213 deletions(-)
|
14 files changed, 442 insertions(+), 1210 deletions(-)
|
||||||
|
create mode 100644 cmake/CheckAtomic.cmake
|
||||||
delete mode 100644 cmake/FindBlosc.cmake
|
delete mode 100644 cmake/FindBlosc.cmake
|
||||||
delete mode 100644 cmake/FindIlmBase.cmake
|
delete mode 100644 cmake/FindIlmBase.cmake
|
||||||
delete mode 100644 cmake/FindOpenEXR.cmake
|
delete mode 100644 cmake/FindOpenEXR.cmake
|
||||||
|
|
@ -40,6 +41,119 @@ index 580b353..6d364c1 100644
|
||||||
cmake/FindOpenVDB.cmake
|
cmake/FindOpenVDB.cmake
|
||||||
cmake/FindTBB.cmake
|
cmake/FindTBB.cmake
|
||||||
cmake/OpenVDBGLFW3Setup.cmake
|
cmake/OpenVDBGLFW3Setup.cmake
|
||||||
|
diff --git a/cmake/CheckAtomic.cmake b/cmake/CheckAtomic.cmake
|
||||||
|
new file mode 100644
|
||||||
|
index 0000000..c045e30
|
||||||
|
--- /dev/null
|
||||||
|
+++ b/cmake/CheckAtomic.cmake
|
||||||
|
@@ -0,0 +1,106 @@
|
||||||
|
+# atomic builtins are required for threading support.
|
||||||
|
+
|
||||||
|
+INCLUDE(CheckCXXSourceCompiles)
|
||||||
|
+INCLUDE(CheckLibraryExists)
|
||||||
|
+
|
||||||
|
+# Sometimes linking against libatomic is required for atomic ops, if
|
||||||
|
+# the platform doesn't support lock-free atomics.
|
||||||
|
+
|
||||||
|
+function(check_working_cxx_atomics varname)
|
||||||
|
+ set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
|
||||||
|
+ set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -std=c++11")
|
||||||
|
+ CHECK_CXX_SOURCE_COMPILES("
|
||||||
|
+#include <atomic>
|
||||||
|
+std::atomic<int> x;
|
||||||
|
+int main() {
|
||||||
|
+ return x;
|
||||||
|
+}
|
||||||
|
+" ${varname})
|
||||||
|
+ set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||||
|
+endfunction(check_working_cxx_atomics)
|
||||||
|
+
|
||||||
|
+function(check_working_cxx_atomics64 varname)
|
||||||
|
+ set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS})
|
||||||
|
+ set(CMAKE_REQUIRED_FLAGS "-std=c++11 ${CMAKE_REQUIRED_FLAGS}")
|
||||||
|
+ CHECK_CXX_SOURCE_COMPILES("
|
||||||
|
+#include <atomic>
|
||||||
|
+#include <cstdint>
|
||||||
|
+std::atomic<uint64_t> x (0);
|
||||||
|
+int main() {
|
||||||
|
+ uint64_t i = x.load(std::memory_order_relaxed);
|
||||||
|
+ return 0;
|
||||||
|
+}
|
||||||
|
+" ${varname})
|
||||||
|
+ set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS})
|
||||||
|
+endfunction(check_working_cxx_atomics64)
|
||||||
|
+
|
||||||
|
+
|
||||||
|
+# This isn't necessary on MSVC, so avoid command-line switch annoyance
|
||||||
|
+# by only running on GCC-like hosts.
|
||||||
|
+if (LLVM_COMPILER_IS_GCC_COMPATIBLE)
|
||||||
|
+ # First check if atomics work without the library.
|
||||||
|
+ check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITHOUT_LIB)
|
||||||
|
+ # If not, check if the library exists, and atomics work with it.
|
||||||
|
+ if(NOT HAVE_CXX_ATOMICS_WITHOUT_LIB)
|
||||||
|
+ check_library_exists(atomic __atomic_fetch_add_4 "" HAVE_LIBATOMIC)
|
||||||
|
+ if( HAVE_LIBATOMIC )
|
||||||
|
+ list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic")
|
||||||
|
+ check_working_cxx_atomics(HAVE_CXX_ATOMICS_WITH_LIB)
|
||||||
|
+ if (NOT HAVE_CXX_ATOMICS_WITH_LIB)
|
||||||
|
+ message(FATAL_ERROR "Host compiler must support std::atomic!")
|
||||||
|
+ endif()
|
||||||
|
+ else()
|
||||||
|
+ message(FATAL_ERROR "Host compiler appears to require libatomic, but cannot find it.")
|
||||||
|
+ endif()
|
||||||
|
+ endif()
|
||||||
|
+endif()
|
||||||
|
+
|
||||||
|
+# Check for 64 bit atomic operations.
|
||||||
|
+if(MSVC)
|
||||||
|
+ set(HAVE_CXX_ATOMICS64_WITHOUT_LIB True)
|
||||||
|
+else()
|
||||||
|
+ check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITHOUT_LIB)
|
||||||
|
+endif()
|
||||||
|
+
|
||||||
|
+# If not, check if the library exists, and atomics work with it.
|
||||||
|
+if(NOT HAVE_CXX_ATOMICS64_WITHOUT_LIB)
|
||||||
|
+ check_library_exists(atomic __atomic_load_8 "" HAVE_CXX_LIBATOMICS64)
|
||||||
|
+ if(HAVE_CXX_LIBATOMICS64)
|
||||||
|
+ list(APPEND CMAKE_REQUIRED_LIBRARIES "atomic")
|
||||||
|
+ check_working_cxx_atomics64(HAVE_CXX_ATOMICS64_WITH_LIB)
|
||||||
|
+ if (NOT HAVE_CXX_ATOMICS64_WITH_LIB)
|
||||||
|
+ message(FATAL_ERROR "Host compiler must support 64-bit std::atomic!")
|
||||||
|
+ endif()
|
||||||
|
+ else()
|
||||||
|
+ message(FATAL_ERROR "Host compiler appears to require libatomic for 64-bit operations, but cannot find it.")
|
||||||
|
+ endif()
|
||||||
|
+endif()
|
||||||
|
+
|
||||||
|
+## TODO: This define is only used for the legacy atomic operations in
|
||||||
|
+## llvm's Atomic.h, which should be replaced. Other code simply
|
||||||
|
+## assumes C++11 <atomic> works.
|
||||||
|
+CHECK_CXX_SOURCE_COMPILES("
|
||||||
|
+#ifdef _MSC_VER
|
||||||
|
+#include <windows.h>
|
||||||
|
+#endif
|
||||||
|
+int main() {
|
||||||
|
+#ifdef _MSC_VER
|
||||||
|
+ volatile LONG val = 1;
|
||||||
|
+ MemoryBarrier();
|
||||||
|
+ InterlockedCompareExchange(&val, 0, 1);
|
||||||
|
+ InterlockedIncrement(&val);
|
||||||
|
+ InterlockedDecrement(&val);
|
||||||
|
+#else
|
||||||
|
+ volatile unsigned long val = 1;
|
||||||
|
+ __sync_synchronize();
|
||||||
|
+ __sync_val_compare_and_swap(&val, 1, 0);
|
||||||
|
+ __sync_add_and_fetch(&val, 1);
|
||||||
|
+ __sync_sub_and_fetch(&val, 1);
|
||||||
|
+#endif
|
||||||
|
+ return 0;
|
||||||
|
+ }
|
||||||
|
+" LLVM_HAS_ATOMICS)
|
||||||
|
+
|
||||||
|
+if( NOT LLVM_HAS_ATOMICS )
|
||||||
|
+ message(STATUS "Warning: LLVM will be built thread-unsafe because atomic builtins are missing")
|
||||||
|
+endif()
|
||||||
|
\ No newline at end of file
|
||||||
diff --git a/cmake/FindBlosc.cmake b/cmake/FindBlosc.cmake
|
diff --git a/cmake/FindBlosc.cmake b/cmake/FindBlosc.cmake
|
||||||
deleted file mode 100644
|
deleted file mode 100644
|
||||||
index 5aacfdd..0000000
|
index 5aacfdd..0000000
|
||||||
|
|
@ -965,7 +1079,7 @@ index 339c1a2..0000000
|
||||||
- message(FATAL_ERROR "Unable to find OpenEXR")
|
- message(FATAL_ERROR "Unable to find OpenEXR")
|
||||||
-endif()
|
-endif()
|
||||||
diff --git a/cmake/FindOpenVDB.cmake b/cmake/FindOpenVDB.cmake
|
diff --git a/cmake/FindOpenVDB.cmake b/cmake/FindOpenVDB.cmake
|
||||||
index 63a2eda..6211071 100644
|
index 63a2eda..d9f6d07 100644
|
||||||
--- a/cmake/FindOpenVDB.cmake
|
--- a/cmake/FindOpenVDB.cmake
|
||||||
+++ b/cmake/FindOpenVDB.cmake
|
+++ b/cmake/FindOpenVDB.cmake
|
||||||
@@ -244,7 +244,7 @@ set(OpenVDB_LIB_COMPONENTS "")
|
@@ -244,7 +244,7 @@ set(OpenVDB_LIB_COMPONENTS "")
|
||||||
|
|
@ -1012,7 +1126,7 @@ index 63a2eda..6211071 100644
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
diff --git a/cmake/FindTBB.cmake b/cmake/FindTBB.cmake
|
diff --git a/cmake/FindTBB.cmake b/cmake/FindTBB.cmake
|
||||||
index bdf9c81..c6bdec9 100644
|
index bdf9c81..06093a4 100644
|
||||||
--- a/cmake/FindTBB.cmake
|
--- a/cmake/FindTBB.cmake
|
||||||
+++ b/cmake/FindTBB.cmake
|
+++ b/cmake/FindTBB.cmake
|
||||||
@@ -1,333 +1,332 @@
|
@@ -1,333 +1,332 @@
|
||||||
|
|
@ -1022,35 +1136,21 @@ index bdf9c81..c6bdec9 100644
|
||||||
-# All rights reserved. This software is distributed under the
|
-# All rights reserved. This software is distributed under the
|
||||||
-# Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ )
|
-# Mozilla Public License 2.0 ( http://www.mozilla.org/MPL/2.0/ )
|
||||||
+# Copyright (c) 2015 Justus Calvin
|
+# Copyright (c) 2015 Justus Calvin
|
||||||
+#
|
#
|
||||||
|
-# Redistributions of source code must retain the above copyright
|
||||||
|
-# and license notice and the following restrictions and disclaimer.
|
||||||
+# Permission is hereby granted, free of charge, to any person obtaining a copy
|
+# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
+# of this software and associated documentation files (the "Software"), to deal
|
+# of this software and associated documentation files (the "Software"), to deal
|
||||||
+# in the Software without restriction, including without limitation the rights
|
+# in the Software without restriction, including without limitation the rights
|
||||||
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
+# copies of the Software, and to permit persons to whom the Software is
|
+# copies of the Software, and to permit persons to whom the Software is
|
||||||
+# furnished to do so, subject to the following conditions:
|
+# furnished to do so, subject to the following conditions:
|
||||||
+#
|
|
||||||
+# The above copyright notice and this permission notice shall be included in all
|
|
||||||
+# copies or substantial portions of the Software.
|
|
||||||
+#
|
|
||||||
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
+# SOFTWARE.
|
|
||||||
+
|
|
||||||
#
|
|
||||||
-# Redistributions of source code must retain the above copyright
|
|
||||||
-# and license notice and the following restrictions and disclaimer.
|
|
||||||
+# FindTBB
|
|
||||||
+# -------
|
|
||||||
#
|
#
|
||||||
-# * Neither the name of DreamWorks Animation nor the names of
|
-# * Neither the name of DreamWorks Animation nor the names of
|
||||||
-# its contributors may be used to endorse or promote products derived
|
-# its contributors may be used to endorse or promote products derived
|
||||||
-# from this software without specific prior written permission.
|
-# from this software without specific prior written permission.
|
||||||
+# Find TBB include directories and libraries.
|
+# The above copyright notice and this permission notice shall be included in all
|
||||||
|
+# copies or substantial portions of the Software.
|
||||||
#
|
#
|
||||||
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
|
@ -1065,7 +1165,14 @@ index bdf9c81..c6bdec9 100644
|
||||||
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
-# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
-# IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE
|
-# IN NO EVENT SHALL THE COPYRIGHT HOLDERS' AND CONTRIBUTORS' AGGREGATE
|
||||||
-# LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00.
|
-# LIABILITY FOR ALL CLAIMS REGARDLESS OF THEIR BASIS EXCEED US$250.00.
|
||||||
+# Usage:
|
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
+# SOFTWARE.
|
||||||
|
+
|
||||||
#
|
#
|
||||||
-#[=======================================================================[.rst:
|
-#[=======================================================================[.rst:
|
||||||
-
|
-
|
||||||
|
|
@ -1142,6 +1249,13 @@ index bdf9c81..c6bdec9 100644
|
||||||
-if(POLICY CMP0057)
|
-if(POLICY CMP0057)
|
||||||
- cmake_policy(SET CMP0057 NEW)
|
- cmake_policy(SET CMP0057 NEW)
|
||||||
-endif()
|
-endif()
|
||||||
|
+# FindTBB
|
||||||
|
+# -------
|
||||||
|
+#
|
||||||
|
+# Find TBB include directories and libraries.
|
||||||
|
+#
|
||||||
|
+# Usage:
|
||||||
|
+#
|
||||||
+# find_package(TBB [major[.minor]] [EXACT]
|
+# find_package(TBB [major[.minor]] [EXACT]
|
||||||
+# [QUIET] [REQUIRED]
|
+# [QUIET] [REQUIRED]
|
||||||
+# [[COMPONENTS] [components...]]
|
+# [[COMPONENTS] [components...]]
|
||||||
|
|
@ -1244,12 +1358,10 @@ index bdf9c81..c6bdec9 100644
|
||||||
- set(TBB_FIND_COMPONENTS ${_TBB_COMPONENT_LIST})
|
- set(TBB_FIND_COMPONENTS ${_TBB_COMPONENT_LIST})
|
||||||
-endif()
|
-endif()
|
||||||
+include(FindPackageHandleStandardArgs)
|
+include(FindPackageHandleStandardArgs)
|
||||||
+
|
|
||||||
+find_package(Threads QUIET REQUIRED)
|
|
||||||
|
|
||||||
-# Append TBB_ROOT or $ENV{TBB_ROOT} if set (prioritize the direct cmake var)
|
-# Append TBB_ROOT or $ENV{TBB_ROOT} if set (prioritize the direct cmake var)
|
||||||
-set(_TBB_ROOT_SEARCH_DIR "")
|
-set(_TBB_ROOT_SEARCH_DIR "")
|
||||||
+if(NOT TBB_FOUND)
|
+find_package(Threads QUIET REQUIRED)
|
||||||
|
|
||||||
-if(TBB_ROOT)
|
-if(TBB_ROOT)
|
||||||
- list(APPEND _TBB_ROOT_SEARCH_DIR ${TBB_ROOT})
|
- list(APPEND _TBB_ROOT_SEARCH_DIR ${TBB_ROOT})
|
||||||
|
|
@ -1257,41 +1369,9 @@ index bdf9c81..c6bdec9 100644
|
||||||
- set(_ENV_TBB_ROOT $ENV{TBB_ROOT})
|
- set(_ENV_TBB_ROOT $ENV{TBB_ROOT})
|
||||||
- if(_ENV_TBB_ROOT)
|
- if(_ENV_TBB_ROOT)
|
||||||
- list(APPEND _TBB_ROOT_SEARCH_DIR ${_ENV_TBB_ROOT})
|
- list(APPEND _TBB_ROOT_SEARCH_DIR ${_ENV_TBB_ROOT})
|
||||||
+ ##################################
|
|
||||||
+ # 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()
|
||||||
+
|
-endif()
|
||||||
+ ##################################
|
+if(NOT TBB_FOUND)
|
||||||
+ # Set the TBB search directories
|
|
||||||
+ ##################################
|
|
||||||
+
|
|
||||||
+ # Define search paths based on user input and environment variables
|
|
||||||
+ set(TBB_SEARCH_DIR ${TBB_ROOT_DIR} $ENV{TBB_INSTALL_DIR} $ENV{TBBROOT})
|
|
||||||
+
|
|
||||||
+ # Define the search directories based on the current platform
|
|
||||||
+ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
|
||||||
+ set(TBB_DEFAULT_SEARCH_DIR "C:/Program Files/Intel/TBB"
|
|
||||||
+ "C:/Program Files (x86)/Intel/TBB")
|
|
||||||
+
|
|
||||||
+ # Set the target architecture
|
|
||||||
+ if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
|
||||||
+ set(TBB_ARCHITECTURE "intel64")
|
|
||||||
+ else()
|
|
||||||
+ set(TBB_ARCHITECTURE "ia32")
|
|
||||||
+ endif()
|
|
||||||
|
|
||||||
-# Additionally try and use pkconfig to find Tbb
|
-# Additionally try and use pkconfig to find Tbb
|
||||||
-
|
-
|
||||||
|
|
@ -1339,6 +1419,57 @@ index bdf9c81..c6bdec9 100644
|
||||||
-
|
-
|
||||||
- set(Tbb_VERSION ${Tbb_VERSION_MAJOR}.${Tbb_VERSION_MINOR})
|
- set(Tbb_VERSION ${Tbb_VERSION_MAJOR}.${Tbb_VERSION_MINOR})
|
||||||
-endif()
|
-endif()
|
||||||
|
+ ##################################
|
||||||
|
+ # Check the build type
|
||||||
|
+ ##################################
|
||||||
|
+
|
||||||
|
+ if(NOT DEFINED TBB_USE_DEBUG_BUILD)
|
||||||
|
+ if(CMAKE_BUILD_TYPE MATCHES "(Debug|DEBUG|debug)")
|
||||||
|
+ set(TBB_BUILD_TYPE DEBUG)
|
||||||
|
+ else()
|
||||||
|
+ set(TBB_BUILD_TYPE RELEASE)
|
||||||
|
+ endif()
|
||||||
|
+ elseif(TBB_USE_DEBUG_BUILD)
|
||||||
|
+ set(TBB_BUILD_TYPE DEBUG)
|
||||||
|
+ else()
|
||||||
|
+ set(TBB_BUILD_TYPE RELEASE)
|
||||||
|
+ endif()
|
||||||
|
|
||||||
|
-# ------------------------------------------------------------------------
|
||||||
|
-# Search for TBB lib DIR
|
||||||
|
-# ------------------------------------------------------------------------
|
||||||
|
+ ##################################
|
||||||
|
+ # Set the TBB search directories
|
||||||
|
+ ##################################
|
||||||
|
|
||||||
|
-set(_TBB_LIBRARYDIR_SEARCH_DIRS "")
|
||||||
|
+ # Define search paths based on user input and environment variables
|
||||||
|
+ set(TBB_SEARCH_DIR ${TBB_ROOT_DIR} $ENV{TBB_INSTALL_DIR} $ENV{TBBROOT})
|
||||||
|
|
||||||
|
-# Append to _TBB_LIBRARYDIR_SEARCH_DIRS in priority order
|
||||||
|
+ # Define the search directories based on the current platform
|
||||||
|
+ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
||||||
|
+ set(TBB_DEFAULT_SEARCH_DIR "C:/Program Files/Intel/TBB"
|
||||||
|
+ "C:/Program Files (x86)/Intel/TBB")
|
||||||
|
|
||||||
|
-set(_TBB_LIBRARYDIR_SEARCH_DIRS "")
|
||||||
|
-list(APPEND _TBB_LIBRARYDIR_SEARCH_DIRS
|
||||||
|
- ${TBB_LIBRARYDIR}
|
||||||
|
- ${_TBB_ROOT_SEARCH_DIR}
|
||||||
|
- ${PC_Tbb_LIBRARY_DIRS}
|
||||||
|
- ${SYSTEM_LIBRARY_PATHS}
|
||||||
|
-)
|
||||||
|
+ # Set the target architecture
|
||||||
|
+ if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||||
|
+ set(TBB_ARCHITECTURE "intel64")
|
||||||
|
+ else()
|
||||||
|
+ set(TBB_ARCHITECTURE "ia32")
|
||||||
|
+ endif()
|
||||||
|
|
||||||
|
-set(TBB_PATH_SUFFIXES
|
||||||
|
- lib64
|
||||||
|
- lib
|
||||||
|
-)
|
||||||
+ # Set the TBB search library path search suffix based on the version of VC
|
+ # Set the TBB search library path search suffix based on the version of VC
|
||||||
+ if(WINDOWS_STORE)
|
+ if(WINDOWS_STORE)
|
||||||
+ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11_ui")
|
+ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc11_ui")
|
||||||
|
|
@ -1352,104 +1483,16 @@ index bdf9c81..c6bdec9 100644
|
||||||
+ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc10")
|
+ set(TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc10")
|
||||||
+ endif()
|
+ endif()
|
||||||
|
|
||||||
-# ------------------------------------------------------------------------
|
-# platform branching
|
||||||
-# Search for TBB lib DIR
|
|
||||||
-# ------------------------------------------------------------------------
|
|
||||||
+ # Add the library path search suffix for the VC independent version of TBB
|
+ # Add the library path search suffix for the VC independent version of TBB
|
||||||
+ list(APPEND TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc_mt")
|
+ list(APPEND TBB_LIB_PATH_SUFFIX "lib/${TBB_ARCHITECTURE}/vc_mt")
|
||||||
+
|
|
||||||
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
|
||||||
+ # OS X
|
|
||||||
+ set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb")
|
|
||||||
+
|
|
||||||
+ # TODO: Check to see which C++ library is being used by the compiler.
|
|
||||||
+ if(NOT ${CMAKE_SYSTEM_VERSION} VERSION_LESS 13.0)
|
|
||||||
+ # The default C++ library on OS X 10.9 and later is libc++
|
|
||||||
+ set(TBB_LIB_PATH_SUFFIX "lib/libc++" "lib")
|
|
||||||
+ else()
|
|
||||||
+ set(TBB_LIB_PATH_SUFFIX "lib")
|
|
||||||
+ endif()
|
|
||||||
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
|
||||||
+ # Linux
|
|
||||||
+ set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb")
|
|
||||||
+
|
|
||||||
+ # TODO: Check compiler version to see the suffix should be <arch>/gcc4.1 or
|
|
||||||
+ # <arch>/gcc4.1. For now, assume that the compiler is more recent than
|
|
||||||
+ # gcc 4.4.x or later.
|
|
||||||
+ if(CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")
|
|
||||||
+ set(TBB_LIB_PATH_SUFFIX "lib/intel64/gcc4.4")
|
|
||||||
+ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^i.86$")
|
|
||||||
+ set(TBB_LIB_PATH_SUFFIX "lib/ia32/gcc4.4")
|
|
||||||
+ endif()
|
|
||||||
+ endif()
|
|
||||||
+
|
|
||||||
+ ##################################
|
|
||||||
+ # Find the TBB include dir
|
|
||||||
+ ##################################
|
|
||||||
+
|
|
||||||
+ find_path(TBB_INCLUDE_DIRS tbb/tbb.h
|
|
||||||
+ HINTS ${TBB_INCLUDE_DIR} ${TBB_SEARCH_DIR}
|
|
||||||
+ PATHS ${TBB_DEFAULT_SEARCH_DIR}
|
|
||||||
+ PATH_SUFFIXES include)
|
|
||||||
+
|
|
||||||
+ ##################################
|
|
||||||
+ # Set version strings
|
|
||||||
+ ##################################
|
|
||||||
+
|
|
||||||
+ if(TBB_INCLUDE_DIRS)
|
|
||||||
+ file(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _tbb_version_file)
|
|
||||||
+ string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1"
|
|
||||||
+ TBB_VERSION_MAJOR "${_tbb_version_file}")
|
|
||||||
+ string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1"
|
|
||||||
+ TBB_VERSION_MINOR "${_tbb_version_file}")
|
|
||||||
+ string(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1"
|
|
||||||
+ TBB_INTERFACE_VERSION "${_tbb_version_file}")
|
|
||||||
+ set(TBB_VERSION "${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR}")
|
|
||||||
+ endif()
|
|
||||||
|
|
||||||
-set(_TBB_LIBRARYDIR_SEARCH_DIRS "")
|
|
||||||
+ ##################################
|
|
||||||
+ # Find TBB components
|
|
||||||
+ ##################################
|
|
||||||
|
|
||||||
-# Append to _TBB_LIBRARYDIR_SEARCH_DIRS in priority order
|
|
||||||
+ if(TBB_VERSION VERSION_LESS 4.3)
|
|
||||||
+ set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc tbb)
|
|
||||||
+ else()
|
|
||||||
+ set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc_proxy tbbmalloc tbb)
|
|
||||||
+ endif()
|
|
||||||
|
|
||||||
-set(_TBB_LIBRARYDIR_SEARCH_DIRS "")
|
|
||||||
-list(APPEND _TBB_LIBRARYDIR_SEARCH_DIRS
|
|
||||||
- ${TBB_LIBRARYDIR}
|
|
||||||
- ${_TBB_ROOT_SEARCH_DIR}
|
|
||||||
- ${PC_Tbb_LIBRARY_DIRS}
|
|
||||||
- ${SYSTEM_LIBRARY_PATHS}
|
|
||||||
-)
|
|
||||||
+ if(TBB_STATIC)
|
|
||||||
+ set(TBB_STATIC_SUFFIX "_static")
|
|
||||||
+ endif()
|
|
||||||
|
|
||||||
-set(TBB_PATH_SUFFIXES
|
|
||||||
- lib64
|
|
||||||
- lib
|
|
||||||
-)
|
|
||||||
+ # Find each component
|
|
||||||
+ foreach(_comp ${TBB_SEARCH_COMPOMPONENTS})
|
|
||||||
+ if(";${TBB_FIND_COMPONENTS};tbb;" MATCHES ";${_comp};")
|
|
||||||
|
|
||||||
-# platform branching
|
|
||||||
+ unset(TBB_${_comp}_LIBRARY_DEBUG CACHE)
|
|
||||||
+ unset(TBB_${_comp}_LIBRARY_RELEASE CACHE)
|
|
||||||
|
|
||||||
-if(UNIX)
|
-if(UNIX)
|
||||||
- list(INSERT TBB_PATH_SUFFIXES 0 lib/x86_64-linux-gnu)
|
- list(INSERT TBB_PATH_SUFFIXES 0 lib/x86_64-linux-gnu)
|
||||||
-endif()
|
-endif()
|
||||||
+ # Search for the libraries
|
+ elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
|
||||||
+ find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp}${TBB_STATIC_SUFFIX}
|
+ # OS X
|
||||||
+ HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
|
+ set(TBB_DEFAULT_SEARCH_DIR "/opt/intel/tbb")
|
||||||
+ PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
|
|
||||||
+ PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
|
|
||||||
|
|
||||||
-if(APPLE)
|
-if(APPLE)
|
||||||
- if(TBB_FOR_CLANG)
|
- if(TBB_FOR_CLANG)
|
||||||
|
|
@ -1471,29 +1514,33 @@ index bdf9c81..c6bdec9 100644
|
||||||
- list(GET GCC_VERSION_COMPONENTS 0 GCC_MAJOR)
|
- list(GET GCC_VERSION_COMPONENTS 0 GCC_MAJOR)
|
||||||
- list(GET GCC_VERSION_COMPONENTS 1 GCC_MINOR)
|
- list(GET GCC_VERSION_COMPONENTS 1 GCC_MINOR)
|
||||||
- list(INSERT TBB_PATH_SUFFIXES 0 lib/intel64/gcc${GCC_MAJOR}.${GCC_MINOR})
|
- list(INSERT TBB_PATH_SUFFIXES 0 lib/intel64/gcc${GCC_MAJOR}.${GCC_MINOR})
|
||||||
- else()
|
+ # TODO: Check to see which C++ library is being used by the compiler.
|
||||||
|
+ if(NOT ${CMAKE_SYSTEM_VERSION} VERSION_LESS 13.0)
|
||||||
|
+ # The default C++ library on OS X 10.9 and later is libc++
|
||||||
|
+ set(TBB_LIB_PATH_SUFFIX "lib/libc++" "lib")
|
||||||
|
else()
|
||||||
- list(INSERT TBB_PATH_SUFFIXES 0 lib/intel64/gcc4.4)
|
- list(INSERT TBB_PATH_SUFFIXES 0 lib/intel64/gcc4.4)
|
||||||
|
+ 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()
|
-endif()
|
||||||
- endif()
|
-
|
||||||
-endif()
|
|
||||||
+ find_library(TBB_${_comp}_LIBRARY_DEBUG ${_comp}${TBB_STATIC_SUFFIX}_debug
|
|
||||||
+ HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
|
|
||||||
+ PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
|
|
||||||
+ PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
|
|
||||||
|
|
||||||
-if(UNIX AND TBB_USE_STATIC_LIBS)
|
-if(UNIX AND TBB_USE_STATIC_LIBS)
|
||||||
- set(_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
|
- set(_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES})
|
||||||
- set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
|
- set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
|
||||||
-endif()
|
-endif()
|
||||||
+ if(TBB_${_comp}_LIBRARY_DEBUG)
|
|
||||||
+ list(APPEND TBB_LIBRARIES_DEBUG "${TBB_${_comp}_LIBRARY_DEBUG}")
|
|
||||||
+ endif()
|
|
||||||
+ if(TBB_${_comp}_LIBRARY_RELEASE)
|
|
||||||
+ list(APPEND TBB_LIBRARIES_RELEASE "${TBB_${_comp}_LIBRARY_RELEASE}")
|
|
||||||
+ endif()
|
|
||||||
+ if(TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE} AND NOT TBB_${_comp}_LIBRARY)
|
|
||||||
+ set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE}}")
|
|
||||||
+ endif()
|
|
||||||
|
|
||||||
-set(Tbb_LIB_COMPONENTS "")
|
-set(Tbb_LIB_COMPONENTS "")
|
||||||
-
|
-
|
||||||
|
|
@ -1516,39 +1563,44 @@ index bdf9c81..c6bdec9 100644
|
||||||
- # Extract the directory and apply the matched text (in brackets)
|
- # Extract the directory and apply the matched text (in brackets)
|
||||||
- get_filename_component(Tbb_${COMPONENT}_DIR "${Tbb_${COMPONENT}_LIBRARY}" DIRECTORY)
|
- get_filename_component(Tbb_${COMPONENT}_DIR "${Tbb_${COMPONENT}_LIBRARY}" DIRECTORY)
|
||||||
- set(Tbb_${COMPONENT}_LIBRARY "${Tbb_${COMPONENT}_DIR}/${CMAKE_MATCH_1}")
|
- set(Tbb_${COMPONENT}_LIBRARY "${Tbb_${COMPONENT}_DIR}/${CMAKE_MATCH_1}")
|
||||||
+ if(TBB_${_comp}_LIBRARY AND EXISTS "${TBB_${_comp}_LIBRARY}")
|
|
||||||
+ set(TBB_${_comp}_FOUND TRUE)
|
|
||||||
+ else()
|
|
||||||
+ set(TBB_${_comp}_FOUND FALSE)
|
|
||||||
endif()
|
|
||||||
+
|
|
||||||
+ # Mark internal variables as advanced
|
|
||||||
+ mark_as_advanced(TBB_${_comp}_LIBRARY_RELEASE)
|
|
||||||
+ mark_as_advanced(TBB_${_comp}_LIBRARY_DEBUG)
|
|
||||||
+ mark_as_advanced(TBB_${_comp}_LIBRARY)
|
|
||||||
+
|
|
||||||
endif()
|
|
||||||
- endif()
|
- endif()
|
||||||
+ endforeach()
|
- endif()
|
||||||
|
+ ##################################
|
||||||
|
+ # Find the TBB include dir
|
||||||
|
+ ##################################
|
||||||
|
+
|
||||||
|
+ find_path(TBB_INCLUDE_DIRS tbb/tbb.h
|
||||||
|
+ HINTS ${TBB_INCLUDE_DIR} ${TBB_SEARCH_DIR}
|
||||||
|
+ PATHS ${TBB_DEFAULT_SEARCH_DIR}
|
||||||
|
+ PATH_SUFFIXES include)
|
||||||
|
+
|
||||||
|
+ ##################################
|
||||||
|
+ # Set version strings
|
||||||
|
+ ##################################
|
||||||
|
+
|
||||||
|
+ if(TBB_INCLUDE_DIRS)
|
||||||
|
+ file(READ "${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h" _tbb_version_file)
|
||||||
|
+ string(REGEX REPLACE ".*#define TBB_VERSION_MAJOR ([0-9]+).*" "\\1"
|
||||||
|
+ TBB_VERSION_MAJOR "${_tbb_version_file}")
|
||||||
|
+ string(REGEX REPLACE ".*#define TBB_VERSION_MINOR ([0-9]+).*" "\\1"
|
||||||
|
+ TBB_VERSION_MINOR "${_tbb_version_file}")
|
||||||
|
+ string(REGEX REPLACE ".*#define TBB_INTERFACE_VERSION ([0-9]+).*" "\\1"
|
||||||
|
+ TBB_INTERFACE_VERSION "${_tbb_version_file}")
|
||||||
|
+ set(TBB_VERSION "${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR}")
|
||||||
|
endif()
|
||||||
|
|
||||||
- list(APPEND Tbb_LIB_COMPONENTS ${Tbb_${COMPONENT}_LIBRARY})
|
- list(APPEND Tbb_LIB_COMPONENTS ${Tbb_${COMPONENT}_LIBRARY})
|
||||||
+ ##################################
|
+ ##################################
|
||||||
+ # Set compile flags and libraries
|
+ # Find TBB components
|
||||||
+ ##################################
|
+ ##################################
|
||||||
|
|
||||||
- if(Tbb_${COMPONENT}_LIBRARY)
|
- if(Tbb_${COMPONENT}_LIBRARY)
|
||||||
- set(TBB_${COMPONENT}_FOUND TRUE)
|
- set(TBB_${COMPONENT}_FOUND TRUE)
|
||||||
- else()
|
+ if(TBB_VERSION VERSION_LESS 4.3)
|
||||||
|
+ set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc tbb)
|
||||||
|
else()
|
||||||
- set(TBB_${COMPONENT}_FOUND FALSE)
|
- set(TBB_${COMPONENT}_FOUND FALSE)
|
||||||
+ set(TBB_DEFINITIONS_RELEASE "")
|
+ set(TBB_SEARCH_COMPOMPONENTS tbb_preview tbbmalloc_proxy tbbmalloc tbb)
|
||||||
+ set(TBB_DEFINITIONS_DEBUG "TBB_USE_DEBUG=1")
|
|
||||||
+
|
|
||||||
+ if(TBB_LIBRARIES_${TBB_BUILD_TYPE})
|
|
||||||
+ set(TBB_LIBRARIES "${TBB_LIBRARIES_${TBB_BUILD_TYPE}}")
|
|
||||||
+ endif()
|
|
||||||
+
|
|
||||||
+ if(NOT MSVC AND NOT TBB_LIBRARIES)
|
|
||||||
+ set(TBB_LIBRARIES ${TBB_LIBRARIES_RELEASE})
|
|
||||||
endif()
|
endif()
|
||||||
-endforeach()
|
-endforeach()
|
||||||
|
|
||||||
|
|
@ -1556,61 +1608,51 @@ index bdf9c81..c6bdec9 100644
|
||||||
- set(CMAKE_FIND_LIBRARY_SUFFIXES ${_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES})
|
- set(CMAKE_FIND_LIBRARY_SUFFIXES ${_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES})
|
||||||
- unset(_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES)
|
- unset(_TBB_ORIG_CMAKE_FIND_LIBRARY_SUFFIXES)
|
||||||
-endif()
|
-endif()
|
||||||
+ set(TBB_DEFINITIONS "")
|
+ if(TBB_STATIC)
|
||||||
+ if (MSVC AND TBB_STATIC)
|
+ set(TBB_STATIC_SUFFIX "_static")
|
||||||
+ set(TBB_DEFINITIONS __TBB_NO_IMPLICIT_LINKAGE)
|
|
||||||
+ endif ()
|
|
||||||
+
|
|
||||||
+ unset (TBB_STATIC_SUFFIX)
|
|
||||||
+
|
|
||||||
+ find_package_handle_standard_args(TBB
|
|
||||||
+ REQUIRED_VARS TBB_INCLUDE_DIRS TBB_LIBRARIES
|
|
||||||
+ FAIL_MESSAGE "TBB library cannot be found. Consider set TBBROOT environment variable."
|
|
||||||
+ HANDLE_COMPONENTS
|
|
||||||
+ VERSION_VAR TBB_VERSION)
|
|
||||||
+
|
|
||||||
+ ##################################
|
|
||||||
+ # Create targets
|
|
||||||
+ ##################################
|
|
||||||
+
|
|
||||||
+ if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND)
|
|
||||||
+ add_library(TBB::tbb UNKNOWN IMPORTED)
|
|
||||||
+ set_target_properties(TBB::tbb PROPERTIES
|
|
||||||
+ INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS}"
|
|
||||||
+ INTERFACE_LINK_LIBRARIES "Threads::Threads;${CMAKE_DL_LIBS}"
|
|
||||||
+ INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS}
|
|
||||||
+ IMPORTED_LOCATION ${TBB_LIBRARIES})
|
|
||||||
+ if(TBB_LIBRARIES_RELEASE AND TBB_LIBRARIES_DEBUG)
|
|
||||||
+ set_target_properties(TBB::tbb PROPERTIES
|
|
||||||
+ INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS};$<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:${TBB_DEFINITIONS_DEBUG}>;$<$<CONFIG:Release>:${TBB_DEFINITIONS_RELEASE}>"
|
|
||||||
+ IMPORTED_LOCATION_DEBUG ${TBB_LIBRARIES_DEBUG}
|
|
||||||
+ IMPORTED_LOCATION_RELWITHDEBINFO ${TBB_LIBRARIES_RELEASE}
|
|
||||||
+ IMPORTED_LOCATION_RELEASE ${TBB_LIBRARIES_RELEASE}
|
|
||||||
+ IMPORTED_LOCATION_MINSIZEREL ${TBB_LIBRARIES_RELEASE}
|
|
||||||
+ )
|
|
||||||
+ endif()
|
|
||||||
+ endif()
|
+ endif()
|
||||||
|
|
||||||
-# ------------------------------------------------------------------------
|
-# ------------------------------------------------------------------------
|
||||||
-# Cache and set TBB_FOUND
|
-# Cache and set TBB_FOUND
|
||||||
-# ------------------------------------------------------------------------
|
-# ------------------------------------------------------------------------
|
||||||
+ mark_as_advanced(TBB_INCLUDE_DIRS TBB_LIBRARIES)
|
+ # Find each component
|
||||||
|
+ foreach(_comp ${TBB_SEARCH_COMPOMPONENTS})
|
||||||
|
+ if(";${TBB_FIND_COMPONENTS};tbb;" MATCHES ";${_comp};")
|
||||||
+
|
+
|
||||||
+ unset(TBB_ARCHITECTURE)
|
+ unset(TBB_${_comp}_LIBRARY_DEBUG CACHE)
|
||||||
+ unset(TBB_BUILD_TYPE)
|
+ unset(TBB_${_comp}_LIBRARY_RELEASE CACHE)
|
||||||
+ unset(TBB_LIB_PATH_SUFFIX)
|
|
||||||
+ unset(TBB_DEFAULT_SEARCH_DIR)
|
|
||||||
+
|
+
|
||||||
+ if(TBB_DEBUG)
|
+ # Search for the libraries
|
||||||
+ message(STATUS " TBB_FOUND = ${TBB_FOUND}")
|
+ find_library(TBB_${_comp}_LIBRARY_RELEASE ${_comp}${TBB_STATIC_SUFFIX}
|
||||||
+ message(STATUS " TBB_INCLUDE_DIRS = ${TBB_INCLUDE_DIRS}")
|
+ HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
|
||||||
+ message(STATUS " TBB_DEFINITIONS = ${TBB_DEFINITIONS}")
|
+ PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
|
||||||
+ message(STATUS " TBB_LIBRARIES = ${TBB_LIBRARIES}")
|
+ PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
|
||||||
+ message(STATUS " TBB_DEFINITIONS_DEBUG = ${TBB_DEFINITIONS_DEBUG}")
|
+
|
||||||
+ message(STATUS " TBB_LIBRARIES_DEBUG = ${TBB_LIBRARIES_DEBUG}")
|
+ find_library(TBB_${_comp}_LIBRARY_DEBUG ${_comp}${TBB_STATIC_SUFFIX}_debug
|
||||||
+ message(STATUS " TBB_DEFINITIONS_RELEASE = ${TBB_DEFINITIONS_RELEASE}")
|
+ HINTS ${TBB_LIBRARY} ${TBB_SEARCH_DIR}
|
||||||
+ message(STATUS " TBB_LIBRARIES_RELEASE = ${TBB_LIBRARIES_RELEASE}")
|
+ PATHS ${TBB_DEFAULT_SEARCH_DIR} ENV LIBRARY_PATH
|
||||||
|
+ PATH_SUFFIXES ${TBB_LIB_PATH_SUFFIX})
|
||||||
|
+
|
||||||
|
+ if(TBB_${_comp}_LIBRARY_DEBUG)
|
||||||
|
+ list(APPEND TBB_LIBRARIES_DEBUG "${TBB_${_comp}_LIBRARY_DEBUG}")
|
||||||
+ endif()
|
+ endif()
|
||||||
|
+ if(TBB_${_comp}_LIBRARY_RELEASE)
|
||||||
|
+ list(APPEND TBB_LIBRARIES_RELEASE "${TBB_${_comp}_LIBRARY_RELEASE}")
|
||||||
|
+ endif()
|
||||||
|
+ if(TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE} AND NOT TBB_${_comp}_LIBRARY)
|
||||||
|
+ set(TBB_${_comp}_LIBRARY "${TBB_${_comp}_LIBRARY_${TBB_BUILD_TYPE}}")
|
||||||
|
+ endif()
|
||||||
|
+
|
||||||
|
+ if(TBB_${_comp}_LIBRARY AND EXISTS "${TBB_${_comp}_LIBRARY}")
|
||||||
|
+ set(TBB_${_comp}_FOUND TRUE)
|
||||||
|
+ else()
|
||||||
|
+ set(TBB_${_comp}_FOUND FALSE)
|
||||||
|
+ endif()
|
||||||
|
+
|
||||||
|
+ # Mark internal variables as advanced
|
||||||
|
+ mark_as_advanced(TBB_${_comp}_LIBRARY_RELEASE)
|
||||||
|
+ mark_as_advanced(TBB_${_comp}_LIBRARY_DEBUG)
|
||||||
|
+ mark_as_advanced(TBB_${_comp}_LIBRARY)
|
||||||
|
|
||||||
-include(FindPackageHandleStandardArgs)
|
-include(FindPackageHandleStandardArgs)
|
||||||
-find_package_handle_standard_args(TBB
|
-find_package_handle_standard_args(TBB
|
||||||
|
|
@ -1646,13 +1688,82 @@ index bdf9c81..c6bdec9 100644
|
||||||
- INTERFACE_COMPILE_OPTIONS "${Tbb_DEFINITIONS}"
|
- INTERFACE_COMPILE_OPTIONS "${Tbb_DEFINITIONS}"
|
||||||
- INTERFACE_INCLUDE_DIRECTORIES "${Tbb_INCLUDE_DIR}"
|
- INTERFACE_INCLUDE_DIRECTORIES "${Tbb_INCLUDE_DIR}"
|
||||||
- )
|
- )
|
||||||
- endif()
|
endif()
|
||||||
- endforeach()
|
endforeach()
|
||||||
-elseif(TBB_FIND_REQUIRED)
|
-elseif(TBB_FIND_REQUIRED)
|
||||||
- message(FATAL_ERROR "Unable to find TBB")
|
- message(FATAL_ERROR "Unable to find TBB")
|
||||||
|
+
|
||||||
|
+ ##################################
|
||||||
|
+ # Set compile flags and libraries
|
||||||
|
+ ##################################
|
||||||
|
+
|
||||||
|
+ set(TBB_DEFINITIONS_RELEASE "")
|
||||||
|
+ set(TBB_DEFINITIONS_DEBUG "TBB_USE_DEBUG=1")
|
||||||
|
+
|
||||||
|
+ if(TBB_LIBRARIES_${TBB_BUILD_TYPE})
|
||||||
|
+ set(TBB_LIBRARIES "${TBB_LIBRARIES_${TBB_BUILD_TYPE}}")
|
||||||
|
+ endif()
|
||||||
|
+
|
||||||
|
+ if(NOT MSVC AND NOT TBB_LIBRARIES)
|
||||||
|
+ set(TBB_LIBRARIES ${TBB_LIBRARIES_RELEASE})
|
||||||
|
+ endif()
|
||||||
|
+
|
||||||
|
+ set(TBB_DEFINITIONS "")
|
||||||
|
+ if (MSVC AND TBB_STATIC)
|
||||||
|
+ set(TBB_DEFINITIONS __TBB_NO_IMPLICIT_LINKAGE)
|
||||||
|
+ endif ()
|
||||||
|
+
|
||||||
|
+ unset (TBB_STATIC_SUFFIX)
|
||||||
|
+
|
||||||
|
+ find_package_handle_standard_args(TBB
|
||||||
|
+ REQUIRED_VARS TBB_INCLUDE_DIRS TBB_LIBRARIES
|
||||||
|
+ FAIL_MESSAGE "TBB library cannot be found. Consider set TBBROOT environment variable."
|
||||||
|
+ HANDLE_COMPONENTS
|
||||||
|
+ VERSION_VAR TBB_VERSION)
|
||||||
|
+
|
||||||
|
+ ##################################
|
||||||
|
+ # Create targets
|
||||||
|
+ ##################################
|
||||||
|
+
|
||||||
|
+ if(NOT CMAKE_VERSION VERSION_LESS 3.0 AND TBB_FOUND)
|
||||||
|
+ add_library(TBB::tbb UNKNOWN IMPORTED)
|
||||||
|
+ set_target_properties(TBB::tbb PROPERTIES
|
||||||
|
+ INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS}"
|
||||||
|
+ INTERFACE_LINK_LIBRARIES "Threads::Threads;${CMAKE_DL_LIBS}"
|
||||||
|
+ INTERFACE_INCLUDE_DIRECTORIES ${TBB_INCLUDE_DIRS}
|
||||||
|
+ IMPORTED_LOCATION ${TBB_LIBRARIES})
|
||||||
|
+ if(TBB_LIBRARIES_RELEASE AND TBB_LIBRARIES_DEBUG)
|
||||||
|
+ set_target_properties(TBB::tbb PROPERTIES
|
||||||
|
+ INTERFACE_COMPILE_DEFINITIONS "${TBB_DEFINITIONS};$<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:${TBB_DEFINITIONS_DEBUG}>;$<$<CONFIG:Release>:${TBB_DEFINITIONS_RELEASE}>"
|
||||||
|
+ IMPORTED_LOCATION_DEBUG ${TBB_LIBRARIES_DEBUG}
|
||||||
|
+ IMPORTED_LOCATION_RELWITHDEBINFO ${TBB_LIBRARIES_RELEASE}
|
||||||
|
+ IMPORTED_LOCATION_RELEASE ${TBB_LIBRARIES_RELEASE}
|
||||||
|
+ IMPORTED_LOCATION_MINSIZEREL ${TBB_LIBRARIES_RELEASE}
|
||||||
|
+ )
|
||||||
|
+ endif()
|
||||||
|
+ endif()
|
||||||
|
+
|
||||||
|
+ mark_as_advanced(TBB_INCLUDE_DIRS TBB_LIBRARIES)
|
||||||
|
+
|
||||||
|
+ unset(TBB_ARCHITECTURE)
|
||||||
|
+ unset(TBB_BUILD_TYPE)
|
||||||
|
+ unset(TBB_LIB_PATH_SUFFIX)
|
||||||
|
+ unset(TBB_DEFAULT_SEARCH_DIR)
|
||||||
|
+
|
||||||
|
+ if(TBB_DEBUG)
|
||||||
|
+ message(STATUS " TBB_FOUND = ${TBB_FOUND}")
|
||||||
|
+ message(STATUS " TBB_INCLUDE_DIRS = ${TBB_INCLUDE_DIRS}")
|
||||||
|
+ message(STATUS " TBB_DEFINITIONS = ${TBB_DEFINITIONS}")
|
||||||
|
+ message(STATUS " TBB_LIBRARIES = ${TBB_LIBRARIES}")
|
||||||
|
+ message(STATUS " TBB_DEFINITIONS_DEBUG = ${TBB_DEFINITIONS_DEBUG}")
|
||||||
|
+ message(STATUS " TBB_LIBRARIES_DEBUG = ${TBB_LIBRARIES_DEBUG}")
|
||||||
|
+ message(STATUS " TBB_DEFINITIONS_RELEASE = ${TBB_DEFINITIONS_RELEASE}")
|
||||||
|
+ message(STATUS " TBB_LIBRARIES_RELEASE = ${TBB_LIBRARIES_RELEASE}")
|
||||||
|
+ endif()
|
||||||
|
+
|
||||||
endif()
|
endif()
|
||||||
diff --git a/openvdb/CMakeLists.txt b/openvdb/CMakeLists.txt
|
diff --git a/openvdb/CMakeLists.txt b/openvdb/CMakeLists.txt
|
||||||
index 89301bd..df27aae 100644
|
index 89301bd..6a3c90c 100644
|
||||||
--- a/openvdb/CMakeLists.txt
|
--- a/openvdb/CMakeLists.txt
|
||||||
+++ b/openvdb/CMakeLists.txt
|
+++ b/openvdb/CMakeLists.txt
|
||||||
@@ -78,7 +78,7 @@ else()
|
@@ -78,7 +78,7 @@ else()
|
||||||
|
|
@ -1664,7 +1775,21 @@ index 89301bd..df27aae 100644
|
||||||
message(DEPRECATION "Support for TBB versions < ${FUTURE_MINIMUM_TBB_VERSION} "
|
message(DEPRECATION "Support for TBB versions < ${FUTURE_MINIMUM_TBB_VERSION} "
|
||||||
"is deprecated and will be removed.")
|
"is deprecated and will be removed.")
|
||||||
endif()
|
endif()
|
||||||
@@ -185,11 +185,6 @@ if(WIN32)
|
@@ -129,10 +129,13 @@ endif()
|
||||||
|
# include paths from shared installs (including houdini) may pull in the wrong
|
||||||
|
# headers
|
||||||
|
|
||||||
|
+include (CheckAtomic)
|
||||||
|
+
|
||||||
|
set(OPENVDB_CORE_DEPENDENT_LIBS
|
||||||
|
Boost::iostreams
|
||||||
|
Boost::system
|
||||||
|
IlmBase::Half
|
||||||
|
+ ${CMAKE_REQUIRED_LIBRARIES}
|
||||||
|
)
|
||||||
|
|
||||||
|
if(USE_EXR)
|
||||||
|
@@ -185,11 +188,6 @@ if(WIN32)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
@ -1676,7 +1801,7 @@ index 89301bd..df27aae 100644
|
||||||
##### Core library configuration
|
##### Core library configuration
|
||||||
|
|
||||||
set(OPENVDB_LIBRARY_SOURCE_FILES
|
set(OPENVDB_LIBRARY_SOURCE_FILES
|
||||||
@@ -374,10 +369,16 @@ set(OPENVDB_LIBRARY_UTIL_INCLUDE_FILES
|
@@ -374,10 +372,16 @@ set(OPENVDB_LIBRARY_UTIL_INCLUDE_FILES
|
||||||
|
|
||||||
if(OPENVDB_CORE_SHARED)
|
if(OPENVDB_CORE_SHARED)
|
||||||
add_library(openvdb_shared SHARED ${OPENVDB_LIBRARY_SOURCE_FILES})
|
add_library(openvdb_shared SHARED ${OPENVDB_LIBRARY_SOURCE_FILES})
|
||||||
|
|
@ -1779,5 +1904,5 @@ index df51830..0ab0c12 100644
|
||||||
/// @todo This changes the compressor setting globally.
|
/// @todo This changes the compressor setting globally.
|
||||||
if (blosc_set_compressor(compname) < 0) continue;
|
if (blosc_set_compressor(compname) < 0) continue;
|
||||||
--
|
--
|
||||||
2.16.2.windows.1
|
2.17.1
|
||||||
|
|
||||||
|
|
|
||||||
80
resources/icons/hollow.svg
Normal file
80
resources/icons/hollow.svg
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 128 128" enable-background="new 0 0 128 128" xml:space="preserve">
|
||||||
|
<g id="hollow">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path fill="#FFFFFF" d="M64,121.87c-0.39,0-0.77-0.11-1.11-0.34l-17.93-11.95c-0.92-0.61-1.17-1.85-0.55-2.77
|
||||||
|
s1.86-1.17,2.77-0.55L64,117.47l17.03-11.35c0.92-0.61,2.16-0.36,2.77,0.55c0.61,0.92,0.36,2.16-0.55,2.77l-18.14,12.09
|
||||||
|
C64.77,121.76,64.39,121.87,64,121.87z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#FFFFFF" d="M90.96,103.89c-0.65,0-1.28-0.31-1.67-0.89c-0.61-0.92-0.36-2.16,0.55-2.77l20.04-13.36V41.13L64,10.53
|
||||||
|
L18.11,41.13v45.75l20.03,13.36c0.92,0.61,1.17,1.85,0.55,2.77c-0.61,0.92-1.85,1.17-2.77,0.55L15,89.61
|
||||||
|
c-0.56-0.37-0.89-1-0.89-1.66V40.06c0-0.67,0.33-1.29,0.89-1.66L62.89,6.47c0.67-0.45,1.55-0.45,2.22,0L113,38.39
|
||||||
|
c0.56,0.37,0.89,1,0.89,1.66v47.89c0,0.67-0.33,1.29-0.89,1.66l-20.93,13.95C91.73,103.79,91.34,103.89,90.96,103.89z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<path fill="#ED6B21" d="M67.33,23.81c-0.38,0-0.77-0.11-1.11-0.34L64,22l-2.22,1.48c-0.92,0.61-2.16,0.36-2.77-0.55
|
||||||
|
c-0.61-0.92-0.36-2.16,0.55-2.77l3.33-2.22c0.67-0.45,1.55-0.45,2.22,0l3.33,2.22c0.92,0.61,1.17,1.85,0.55,2.77
|
||||||
|
C68.61,23.5,67.97,23.81,67.33,23.81z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#ED6B21" d="M36.09,40.2c-0.65,0-1.28-0.31-1.67-0.89c-0.61-0.92-0.36-2.16,0.55-2.77l5.46-3.64
|
||||||
|
c0.92-0.61,2.16-0.36,2.77,0.55c0.61,0.92,0.36,2.16-0.55,2.77l-5.46,3.64C36.86,40.09,36.48,40.2,36.09,40.2z M48.38,32.01
|
||||||
|
c-0.65,0-1.28-0.31-1.67-0.89c-0.61-0.92-0.36-2.16,0.55-2.77l5.46-3.64c0.92-0.61,2.16-0.36,2.77,0.55s0.36,2.16-0.55,2.77
|
||||||
|
l-5.46,3.64C49.15,31.9,48.77,32.01,48.38,32.01z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#ED6B21" d="M25.94,50.97c-1.1,0-2-0.9-2-2v-4c0-0.67,0.33-1.29,0.89-1.66l3.33-2.22c0.92-0.62,2.16-0.36,2.77,0.55
|
||||||
|
s0.36,2.16-0.55,2.77l-2.44,1.62v2.93C27.94,50.07,27.04,50.97,25.94,50.97z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#ED6B21" d="M25.94,70.29c-1.1,0-2-0.9-2-2v-8.59c0-1.1,0.9-2,2-2s2,0.9,2,2v8.59C27.94,69.4,27.04,70.29,25.94,70.29
|
||||||
|
z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#ED6B21" d="M29.26,87.25c-0.38,0-0.77-0.11-1.11-0.34l-3.33-2.22c-0.56-0.37-0.89-1-0.89-1.66v-4c0-1.1,0.9-2,2-2
|
||||||
|
s2,0.9,2,2v2.93l2.44,1.62c0.92,0.61,1.17,1.85,0.55,2.77C30.54,86.94,29.91,87.25,29.26,87.25z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#ED6B21" d="M53.84,103.64c-0.38,0-0.77-0.11-1.11-0.34l-5.46-3.64c-0.92-0.61-1.17-1.85-0.55-2.77
|
||||||
|
c0.61-0.92,1.85-1.17,2.77-0.55l5.46,3.64c0.92,0.61,1.17,1.85,0.55,2.77C55.12,103.32,54.49,103.64,53.84,103.64z M41.55,95.44
|
||||||
|
c-0.38,0-0.77-0.11-1.11-0.34l-5.46-3.64c-0.92-0.61-1.17-1.85-0.55-2.77c0.61-0.92,1.86-1.17,2.77-0.55l5.46,3.64
|
||||||
|
c0.92,0.61,1.17,1.85,0.55,2.77C42.83,95.13,42.2,95.44,41.55,95.44z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#ED6B21" d="M64,110.41c-0.39,0-0.77-0.11-1.11-0.34l-3.33-2.22c-0.92-0.61-1.17-1.85-0.55-2.77
|
||||||
|
c0.61-0.92,1.85-1.17,2.77-0.55L64,106l2.22-1.48c0.92-0.61,2.16-0.37,2.77,0.55c0.61,0.92,0.36,2.16-0.55,2.77l-3.33,2.22
|
||||||
|
C64.77,110.29,64.39,110.41,64,110.41z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#ED6B21" d="M74.16,103.64c-0.65,0-1.28-0.31-1.67-0.89c-0.61-0.92-0.36-2.16,0.55-2.77l5.46-3.64
|
||||||
|
c0.92-0.61,2.16-0.36,2.77,0.55c0.61,0.92,0.36,2.16-0.55,2.77l-5.46,3.64C74.92,103.53,74.54,103.64,74.16,103.64z M86.45,95.44
|
||||||
|
c-0.65,0-1.28-0.31-1.67-0.89c-0.61-0.92-0.36-2.16,0.55-2.77l5.46-3.64c0.92-0.61,2.16-0.36,2.77,0.55
|
||||||
|
c0.61,0.92,0.36,2.16-0.55,2.77l-5.46,3.64C87.21,95.33,86.83,95.44,86.45,95.44z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#ED6B21" d="M98.74,87.25c-0.65,0-1.28-0.31-1.67-0.89c-0.61-0.92-0.36-2.16,0.55-2.77l2.44-1.62v-2.93
|
||||||
|
c0-1.1,0.9-2,2-2s2,0.9,2,2v4c0,0.67-0.33,1.29-0.89,1.66l-3.33,2.22C99.5,87.14,99.12,87.25,98.74,87.25z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#ED6B21" d="M102.06,70.29c-1.1,0-2-0.9-2-2v-8.59c0-1.1,0.9-2,2-2s2,0.9,2,2v8.59
|
||||||
|
C104.06,69.4,103.17,70.29,102.06,70.29z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#ED6B21" d="M102.06,50.97c-1.1,0-2-0.9-2-2v-2.93l-2.44-1.62c-0.92-0.61-1.17-1.85-0.55-2.77s1.85-1.17,2.77-0.55
|
||||||
|
l3.33,2.22c0.56,0.37,0.89,1,0.89,1.66v4C104.06,50.07,103.17,50.97,102.06,50.97z"/>
|
||||||
|
</g>
|
||||||
|
<g>
|
||||||
|
<path fill="#ED6B21" d="M91.91,40.2c-0.38,0-0.77-0.11-1.11-0.34l-5.46-3.64c-0.92-0.61-1.17-1.85-0.55-2.77
|
||||||
|
c0.61-0.92,1.85-1.17,2.77-0.55l5.46,3.64c0.92,0.61,1.17,1.85,0.55,2.77C93.19,39.89,92.55,40.2,91.91,40.2z M79.62,32.01
|
||||||
|
c-0.38,0-0.77-0.11-1.11-0.34l-5.46-3.64c-0.92-0.61-1.17-1.85-0.55-2.77s1.85-1.17,2.77-0.55l5.46,3.64
|
||||||
|
c0.92,0.61,1.17,1.85,0.55,2.77C80.9,31.69,80.26,32.01,79.62,32.01z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 4.6 KiB |
25
resources/icons/hollowing.svg
Normal file
25
resources/icons/hollowing.svg
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||||
|
<g id="support">
|
||||||
|
<polygon fill="none" stroke="#808080" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="8,1
|
||||||
|
2.31,4.79 2.31,8.57 2.31,8.79 2.31,10.47 8,14.25 13.69,10.47 13.69,8.79 13.69,8.57 13.69,4.79 "/>
|
||||||
|
|
||||||
|
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="12.69" y1="15" x2="12.69" y2="12.44"/>
|
||||||
|
|
||||||
|
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="13.87" y1="15" x2="13.87" y2="11.64"/>
|
||||||
|
|
||||||
|
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="2.13" y1="15" x2="2.13" y2="11.64"/>
|
||||||
|
|
||||||
|
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="3.33" y1="15" x2="3.33" y2="12.44"/>
|
||||||
|
|
||||||
|
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="4.51" y1="15" x2="4.51" y2="13.22"/>
|
||||||
|
|
||||||
|
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="5.66" y1="15" x2="5.66" y2="14"/>
|
||||||
|
|
||||||
|
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="10.34" y1="15" x2="10.34" y2="14"/>
|
||||||
|
|
||||||
|
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="11.5" y1="15" x2="11.5" y2="13.22"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
25
resources/icons/white/hollowing.svg
Normal file
25
resources/icons/white/hollowing.svg
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
|
||||||
|
<g id="support">
|
||||||
|
<polygon fill="none" stroke="#808080" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="8,1
|
||||||
|
2.31,4.79 2.31,8.57 2.31,8.79 2.31,10.47 8,14.25 13.69,10.47 13.69,8.79 13.69,8.57 13.69,4.79 "/>
|
||||||
|
|
||||||
|
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="12.69" y1="15" x2="12.69" y2="12.44"/>
|
||||||
|
|
||||||
|
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="13.87" y1="15" x2="13.87" y2="11.64"/>
|
||||||
|
|
||||||
|
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="2.13" y1="15" x2="2.13" y2="11.64"/>
|
||||||
|
|
||||||
|
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="3.33" y1="15" x2="3.33" y2="12.44"/>
|
||||||
|
|
||||||
|
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="4.51" y1="15" x2="4.51" y2="13.22"/>
|
||||||
|
|
||||||
|
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="5.66" y1="15" x2="5.66" y2="14"/>
|
||||||
|
|
||||||
|
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="10.34" y1="15" x2="10.34" y2="14"/>
|
||||||
|
|
||||||
|
<line fill="none" stroke="#ED6B21" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="11.5" y1="15" x2="11.5" y2="13.22"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -1,3 +1,4 @@
|
||||||
#add_subdirectory(slasupporttree)
|
#add_subdirectory(slasupporttree)
|
||||||
#add_subdirectory(openvdb)
|
#add_subdirectory(openvdb)
|
||||||
add_subdirectory(meshboolean)
|
add_subdirectory(meshboolean)
|
||||||
|
add_subdirectory(opencsg)
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,6 @@
|
||||||
if (SLIC3R_STATIC)
|
|
||||||
set(CGAL_Boost_USE_STATIC_LIBS ON)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
find_package(CGAL REQUIRED)
|
|
||||||
|
|
||||||
add_executable(meshboolean MeshBoolean.cpp)
|
add_executable(meshboolean MeshBoolean.cpp)
|
||||||
|
|
||||||
target_link_libraries(meshboolean libslic3r CGAL::CGAL)
|
target_link_libraries(meshboolean libslic3r)
|
||||||
|
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
prusaslicer_copy_dlls(meshboolean)
|
prusaslicer_copy_dlls(meshboolean)
|
||||||
|
|
|
||||||
|
|
@ -1,85 +1,76 @@
|
||||||
#include <libslic3r/TriangleMesh.hpp>
|
|
||||||
#undef PI
|
|
||||||
#include <igl/readOFF.h>
|
|
||||||
//#undef IGL_STATIC_LIBRARY
|
|
||||||
#include <igl/copyleft/cgal/mesh_boolean.h>
|
|
||||||
|
|
||||||
#include <Eigen/Core>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <admesh/stl.h>
|
#include <libslic3r/TriangleMesh.hpp>
|
||||||
|
#include <libslic3r/Model.hpp>
|
||||||
|
#include <libslic3r/SLAPrint.hpp>
|
||||||
|
#include <libslic3r/SLAPrintSteps.hpp>
|
||||||
|
#include <libslic3r/MeshBoolean.hpp>
|
||||||
|
|
||||||
|
#include <libnest2d/tools/benchmark.h>
|
||||||
|
|
||||||
#include <boost/nowide/cstdio.hpp>
|
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
bool its_write_obj(const Eigen::MatrixXd &V, Eigen::MatrixXi &F, const char *file)
|
|
||||||
{
|
|
||||||
|
|
||||||
FILE *fp = boost::nowide::fopen(file, "w");
|
|
||||||
if (fp == nullptr) {
|
|
||||||
BOOST_LOG_TRIVIAL(error) << "stl_write_obj: Couldn't open " << file << " for writing";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < V.rows(); ++ i)
|
|
||||||
fprintf(fp, "v %lf %lf %lf\n", V(i, 0), V(i, 1), V(i, 2));
|
|
||||||
for (size_t i = 0; i < F.rows(); ++ i)
|
|
||||||
fprintf(fp, "f %d %d %d\n", F(i, 0) + 1, F(i, 1) + 1, F(i, 2) + 1);
|
|
||||||
fclose(fp);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void mesh_boolean_test(const std::string &fname)
|
|
||||||
{
|
|
||||||
using namespace Eigen;
|
|
||||||
using namespace std;
|
|
||||||
// igl::readOFF(TUTORIAL_SHARED_PATH "/cheburashka.off",VA,FA);
|
|
||||||
// igl::readOFF(TUTORIAL_SHARED_PATH "/decimated-knight.off",VB,FB);
|
|
||||||
// Plot the mesh with pseudocolors
|
|
||||||
// igl::opengl::glfw::Viewer viewer;
|
|
||||||
|
|
||||||
// Initialize
|
|
||||||
// update(viewer);
|
|
||||||
|
|
||||||
//igl::copyleft::cgal::mesh_boolean(VA,FA,VB,FB,boolean_type,VC,FC,J);
|
|
||||||
|
|
||||||
|
|
||||||
std::cout << fname.c_str() << std::endl;
|
|
||||||
TriangleMesh mesh;
|
|
||||||
|
|
||||||
mesh.ReadSTLFile(fname.c_str());
|
|
||||||
mesh.repair(true);
|
|
||||||
its_write_obj(mesh.its, (fname + "-imported0.obj").c_str());
|
|
||||||
|
|
||||||
|
|
||||||
Eigen::MatrixXd VA,VB,VC;
|
|
||||||
Eigen::VectorXi J,I;
|
|
||||||
Eigen::MatrixXi FA,FB,FC;
|
|
||||||
igl::MeshBooleanType boolean_type(igl::MESH_BOOLEAN_TYPE_UNION);
|
|
||||||
|
|
||||||
|
|
||||||
typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
|
|
||||||
typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
|
|
||||||
|
|
||||||
Eigen::MatrixXd V = MapMatrixXfUnaligned(mesh.its.vertices.front().data(), mesh.its.vertices.size(), 3).cast<double>();
|
|
||||||
Eigen::MatrixXi F = MapMatrixXiUnaligned(mesh.its.indices.front().data(), mesh.its.indices.size(), 3);
|
|
||||||
|
|
||||||
its_write_obj(V, F, (fname + "-imported.obj").c_str());
|
|
||||||
// Self-union to clean up
|
|
||||||
igl::copyleft::cgal::mesh_boolean(V, F, Eigen::MatrixXd(), Eigen::MatrixXi(), boolean_type, VC, FC);
|
|
||||||
|
|
||||||
its_write_obj(VC, FC, (fname + "-fixed.obj").c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
int main(const int argc, const char * argv[])
|
int main(const int argc, const char * argv[])
|
||||||
{
|
{
|
||||||
if (argc < 1) return -1;
|
using namespace Slic3r;
|
||||||
|
|
||||||
Slic3r::mesh_boolean_test(argv[1]);
|
if (argc <= 1) return EXIT_FAILURE;
|
||||||
|
|
||||||
|
DynamicPrintConfig cfg;
|
||||||
|
auto model = Model::read_from_file(argv[1], &cfg);
|
||||||
|
|
||||||
|
if (model.objects.empty()) return EXIT_SUCCESS;
|
||||||
|
|
||||||
|
SLAPrint print;
|
||||||
|
print.apply(model, cfg);
|
||||||
|
PrintBase::TaskParams task;
|
||||||
|
task.to_object_step = slaposHollowing;
|
||||||
|
|
||||||
|
print.set_task(task);
|
||||||
|
print.process();
|
||||||
|
|
||||||
|
Benchmark bench;
|
||||||
|
|
||||||
|
for (SLAPrintObject *po : print.objects()) {
|
||||||
|
TriangleMesh holes;
|
||||||
|
sla::DrainHoles holepts = po->transformed_drainhole_points();
|
||||||
|
|
||||||
|
for (auto &hole: holepts)
|
||||||
|
holes.merge(sla::to_triangle_mesh(hole.to_mesh()));
|
||||||
|
|
||||||
|
TriangleMesh hollowed_mesh = po->transformed_mesh();
|
||||||
|
hollowed_mesh.merge(po->hollowed_interior_mesh());
|
||||||
|
|
||||||
|
hollowed_mesh.require_shared_vertices();
|
||||||
|
holes.require_shared_vertices();
|
||||||
|
|
||||||
|
TriangleMesh drilled_mesh_igl = hollowed_mesh;
|
||||||
|
bench.start();
|
||||||
|
MeshBoolean::minus(drilled_mesh_igl, holes);
|
||||||
|
bench.stop();
|
||||||
|
|
||||||
|
std::cout << "Mesh boolean duration with IGL: " << bench.getElapsedSec() << std::endl;
|
||||||
|
|
||||||
|
TriangleMesh drilled_mesh_cgal = hollowed_mesh;
|
||||||
|
bench.start();
|
||||||
|
MeshBoolean::cgal::self_union(drilled_mesh_cgal);
|
||||||
|
MeshBoolean::cgal::minus(drilled_mesh_cgal, holes);
|
||||||
|
bench.stop();
|
||||||
|
|
||||||
|
std::cout << "Mesh boolean duration with CGAL: " << bench.getElapsedSec() << std::endl;
|
||||||
|
|
||||||
|
std::string name("obj"), outf;
|
||||||
|
outf = name + "igl" + std::to_string(po->model_object()->id().id) + ".obj";
|
||||||
|
drilled_mesh_igl.WriteOBJFile(outf.c_str());
|
||||||
|
|
||||||
|
outf = name + "cgal" + std::to_string(po->model_object()->id().id) + ".obj";
|
||||||
|
drilled_mesh_cgal.WriteOBJFile(outf.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
29
sandboxes/opencsg/CMakeLists.txt
Normal file
29
sandboxes/opencsg/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
cmake_minimum_required(VERSION 3.0)
|
||||||
|
|
||||||
|
project(OpenCSG-example)
|
||||||
|
|
||||||
|
add_executable(opencsg_example WIN32
|
||||||
|
main.cpp
|
||||||
|
Engine.hpp Engine.cpp
|
||||||
|
ShaderCSGDisplay.hpp ShaderCSGDisplay.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/ProgressStatusBar.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/I18N.hpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../../src/slic3r/GUI/I18N.cpp)
|
||||||
|
|
||||||
|
find_package(wxWidgets 3.1 REQUIRED COMPONENTS core base gl html)
|
||||||
|
find_package(OpenGL REQUIRED)
|
||||||
|
find_package(GLEW REQUIRED)
|
||||||
|
find_package(OpenCSG REQUIRED)
|
||||||
|
include(${wxWidgets_USE_FILE})
|
||||||
|
|
||||||
|
target_link_libraries(opencsg_example libslic3r)
|
||||||
|
target_include_directories(opencsg_example PRIVATE ${wxWidgets_INCLUDE_DIRS})
|
||||||
|
target_compile_definitions(opencsg_example PRIVATE ${wxWidgets_DEFINITIONS})
|
||||||
|
|
||||||
|
slic3r_remap_configs(OpenCSG::opencsg RelWithDebInfo Release)
|
||||||
|
target_link_libraries(opencsg_example ${wxWidgets_LIBRARIES}
|
||||||
|
OpenCSG::opencsg
|
||||||
|
GLEW::GLEW
|
||||||
|
OpenGL::GL
|
||||||
|
#-lXrandr -lXext -lX11
|
||||||
|
)
|
||||||
498
sandboxes/opencsg/Engine.cpp
Normal file
498
sandboxes/opencsg/Engine.cpp
Normal file
|
|
@ -0,0 +1,498 @@
|
||||||
|
#include "Engine.hpp"
|
||||||
|
#include <libslic3r/Utils.hpp>
|
||||||
|
#include <libslic3r/SLAPrint.hpp>
|
||||||
|
|
||||||
|
#include <GL/glew.h>
|
||||||
|
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
#define HAS_GLSAFE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAS_GLSAFE
|
||||||
|
extern void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name);
|
||||||
|
inline void glAssertRecentCall() { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); }
|
||||||
|
#define glsafe(cmd) do { cmd; glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
|
||||||
|
#define glcheck() do { glAssertRecentCallImpl(__FILE__, __LINE__, __FUNCTION__); } while (false)
|
||||||
|
|
||||||
|
void glAssertRecentCallImpl(const char *file_name, unsigned int line, const char *function_name)
|
||||||
|
{
|
||||||
|
GLenum err = glGetError();
|
||||||
|
if (err == GL_NO_ERROR)
|
||||||
|
return;
|
||||||
|
const char *sErr = 0;
|
||||||
|
switch (err) {
|
||||||
|
case GL_INVALID_ENUM: sErr = "Invalid Enum"; break;
|
||||||
|
case GL_INVALID_VALUE: sErr = "Invalid Value"; break;
|
||||||
|
// be aware that GL_INVALID_OPERATION is generated if glGetError is executed between the execution of glBegin and the corresponding execution of glEnd
|
||||||
|
case GL_INVALID_OPERATION: sErr = "Invalid Operation"; break;
|
||||||
|
case GL_STACK_OVERFLOW: sErr = "Stack Overflow"; break;
|
||||||
|
case GL_STACK_UNDERFLOW: sErr = "Stack Underflow"; break;
|
||||||
|
case GL_OUT_OF_MEMORY: sErr = "Out Of Memory"; break;
|
||||||
|
default: sErr = "Unknown"; break;
|
||||||
|
}
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "OpenGL error in " << file_name << ":" << line << ", function " << function_name << "() : " << (int)err << " - " << sErr;
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
inline void glAssertRecentCall() { }
|
||||||
|
#define glsafe(cmd) cmd
|
||||||
|
#define glcheck()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Slic3r { namespace GL {
|
||||||
|
|
||||||
|
Scene::Scene() = default;
|
||||||
|
Scene::~Scene() = default;
|
||||||
|
|
||||||
|
void CSGDisplay::render_scene()
|
||||||
|
{
|
||||||
|
GLfloat color[] = {1.f, 1.f, 0.f, 0.f};
|
||||||
|
glsafe(::glColor4fv(color));
|
||||||
|
|
||||||
|
if (m_csgsettings.is_enabled()) {
|
||||||
|
OpenCSG::render(m_scene_cache.primitives_csg);
|
||||||
|
glDepthFunc(GL_EQUAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& p : m_scene_cache.primitives_csg) p->render();
|
||||||
|
if (m_csgsettings.is_enabled()) glDepthFunc(GL_LESS);
|
||||||
|
|
||||||
|
for (auto& p : m_scene_cache.primitives_free) p->render();
|
||||||
|
|
||||||
|
glFlush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scene::set_print(uqptr<SLAPrint> &&print)
|
||||||
|
{
|
||||||
|
m_print = std::move(print);
|
||||||
|
|
||||||
|
// Notify displays
|
||||||
|
call(&Listener::on_scene_updated, m_listeners, *this);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoundingBoxf3 Scene::get_bounding_box() const
|
||||||
|
{
|
||||||
|
return m_print->model().bounding_box();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSGDisplay::SceneCache::clear()
|
||||||
|
{
|
||||||
|
primitives_csg.clear();
|
||||||
|
primitives_free.clear();
|
||||||
|
primitives.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
shptr<Primitive> CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh)
|
||||||
|
{
|
||||||
|
auto p = std::make_shared<Primitive>();
|
||||||
|
p->load_mesh(mesh);
|
||||||
|
primitives.emplace_back(p);
|
||||||
|
primitives_free.emplace_back(p.get());
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
shptr<Primitive> CSGDisplay::SceneCache::add_mesh(const TriangleMesh &mesh,
|
||||||
|
OpenCSG::Operation o,
|
||||||
|
unsigned c)
|
||||||
|
{
|
||||||
|
auto p = std::make_shared<Primitive>(o, c);
|
||||||
|
p->load_mesh(mesh);
|
||||||
|
primitives.emplace_back(p);
|
||||||
|
primitives_csg.emplace_back(p.get());
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndexedVertexArray::push_geometry(float x, float y, float z, float nx, float ny, float nz)
|
||||||
|
{
|
||||||
|
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
|
||||||
|
if (this->vertices_and_normals_interleaved_VBO_id != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this->vertices_and_normals_interleaved.size() + 6 > this->vertices_and_normals_interleaved.capacity())
|
||||||
|
this->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(this->vertices_and_normals_interleaved.size() + 6));
|
||||||
|
this->vertices_and_normals_interleaved.emplace_back(nx);
|
||||||
|
this->vertices_and_normals_interleaved.emplace_back(ny);
|
||||||
|
this->vertices_and_normals_interleaved.emplace_back(nz);
|
||||||
|
this->vertices_and_normals_interleaved.emplace_back(x);
|
||||||
|
this->vertices_and_normals_interleaved.emplace_back(y);
|
||||||
|
this->vertices_and_normals_interleaved.emplace_back(z);
|
||||||
|
|
||||||
|
this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndexedVertexArray::push_triangle(int idx1, int idx2, int idx3) {
|
||||||
|
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
|
||||||
|
if (this->vertices_and_normals_interleaved_VBO_id != 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this->triangle_indices.size() + 3 > this->vertices_and_normals_interleaved.capacity())
|
||||||
|
this->triangle_indices.reserve(next_highest_power_of_2(this->triangle_indices.size() + 3));
|
||||||
|
this->triangle_indices.emplace_back(idx1);
|
||||||
|
this->triangle_indices.emplace_back(idx2);
|
||||||
|
this->triangle_indices.emplace_back(idx3);
|
||||||
|
this->triangle_indices_size = this->triangle_indices.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndexedVertexArray::load_mesh(const TriangleMesh &mesh)
|
||||||
|
{
|
||||||
|
assert(triangle_indices.empty() && vertices_and_normals_interleaved_size == 0);
|
||||||
|
assert(quad_indices.empty() && triangle_indices_size == 0);
|
||||||
|
assert(vertices_and_normals_interleaved.size() % 6 == 0 && quad_indices_size == vertices_and_normals_interleaved.size());
|
||||||
|
|
||||||
|
this->vertices_and_normals_interleaved.reserve(this->vertices_and_normals_interleaved.size() + 3 * 3 * 2 * mesh.facets_count());
|
||||||
|
|
||||||
|
int vertices_count = 0;
|
||||||
|
for (size_t i = 0; i < mesh.stl.stats.number_of_facets; ++i) {
|
||||||
|
const stl_facet &facet = mesh.stl.facet_start[i];
|
||||||
|
for (int j = 0; j < 3; ++j)
|
||||||
|
this->push_geometry(facet.vertex[j](0), facet.vertex[j](1), facet.vertex[j](2), facet.normal(0), facet.normal(1), facet.normal(2));
|
||||||
|
|
||||||
|
this->push_triangle(vertices_count, vertices_count + 1, vertices_count + 2);
|
||||||
|
vertices_count += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndexedVertexArray::finalize_geometry()
|
||||||
|
{
|
||||||
|
assert(this->vertices_and_normals_interleaved_VBO_id == 0);
|
||||||
|
assert(this->triangle_indices_VBO_id == 0);
|
||||||
|
assert(this->quad_indices_VBO_id == 0);
|
||||||
|
|
||||||
|
if (!this->vertices_and_normals_interleaved.empty()) {
|
||||||
|
glsafe(
|
||||||
|
::glGenBuffers(1, &this->vertices_and_normals_interleaved_VBO_id));
|
||||||
|
glsafe(::glBindBuffer(GL_ARRAY_BUFFER,
|
||||||
|
this->vertices_and_normals_interleaved_VBO_id));
|
||||||
|
glsafe(
|
||||||
|
::glBufferData(GL_ARRAY_BUFFER,
|
||||||
|
GLsizeiptr(
|
||||||
|
this->vertices_and_normals_interleaved.size() *
|
||||||
|
4),
|
||||||
|
this->vertices_and_normals_interleaved.data(),
|
||||||
|
GL_STATIC_DRAW));
|
||||||
|
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||||
|
this->vertices_and_normals_interleaved.clear();
|
||||||
|
}
|
||||||
|
if (!this->triangle_indices.empty()) {
|
||||||
|
glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_id));
|
||||||
|
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
||||||
|
this->triangle_indices_VBO_id));
|
||||||
|
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
||||||
|
GLsizeiptr(this->triangle_indices.size() * 4),
|
||||||
|
this->triangle_indices.data(), GL_STATIC_DRAW));
|
||||||
|
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||||
|
this->triangle_indices.clear();
|
||||||
|
}
|
||||||
|
if (!this->quad_indices.empty()) {
|
||||||
|
glsafe(::glGenBuffers(1, &this->quad_indices_VBO_id));
|
||||||
|
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
||||||
|
this->quad_indices_VBO_id));
|
||||||
|
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
||||||
|
GLsizeiptr(this->quad_indices.size() * 4),
|
||||||
|
this->quad_indices.data(), GL_STATIC_DRAW));
|
||||||
|
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||||
|
this->quad_indices.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndexedVertexArray::release_geometry()
|
||||||
|
{
|
||||||
|
if (this->vertices_and_normals_interleaved_VBO_id) {
|
||||||
|
glsafe(
|
||||||
|
::glDeleteBuffers(1,
|
||||||
|
&this->vertices_and_normals_interleaved_VBO_id));
|
||||||
|
this->vertices_and_normals_interleaved_VBO_id = 0;
|
||||||
|
}
|
||||||
|
if (this->triangle_indices_VBO_id) {
|
||||||
|
glsafe(::glDeleteBuffers(1, &this->triangle_indices_VBO_id));
|
||||||
|
this->triangle_indices_VBO_id = 0;
|
||||||
|
}
|
||||||
|
if (this->quad_indices_VBO_id) {
|
||||||
|
glsafe(::glDeleteBuffers(1, &this->quad_indices_VBO_id));
|
||||||
|
this->quad_indices_VBO_id = 0;
|
||||||
|
}
|
||||||
|
this->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndexedVertexArray::render() const
|
||||||
|
{
|
||||||
|
assert(this->vertices_and_normals_interleaved_VBO_id != 0);
|
||||||
|
assert(this->triangle_indices_VBO_id != 0 ||
|
||||||
|
this->quad_indices_VBO_id != 0);
|
||||||
|
|
||||||
|
glsafe(::glBindBuffer(GL_ARRAY_BUFFER,
|
||||||
|
this->vertices_and_normals_interleaved_VBO_id));
|
||||||
|
glsafe(::glVertexPointer(3, GL_FLOAT, 6 * sizeof(float),
|
||||||
|
reinterpret_cast<const void *>(3 * sizeof(float))));
|
||||||
|
glsafe(::glNormalPointer(GL_FLOAT, 6 * sizeof(float), nullptr));
|
||||||
|
|
||||||
|
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
||||||
|
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
|
||||||
|
|
||||||
|
// Render using the Vertex Buffer Objects.
|
||||||
|
if (this->triangle_indices_size > 0) {
|
||||||
|
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
||||||
|
this->triangle_indices_VBO_id));
|
||||||
|
glsafe(::glDrawElements(GL_TRIANGLES,
|
||||||
|
GLsizei(this->triangle_indices_size),
|
||||||
|
GL_UNSIGNED_INT, nullptr));
|
||||||
|
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||||
|
}
|
||||||
|
if (this->quad_indices_size > 0) {
|
||||||
|
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,
|
||||||
|
this->quad_indices_VBO_id));
|
||||||
|
glsafe(::glDrawElements(GL_QUADS, GLsizei(this->quad_indices_size),
|
||||||
|
GL_UNSIGNED_INT, nullptr));
|
||||||
|
glsafe(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
|
||||||
|
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
|
||||||
|
|
||||||
|
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndexedVertexArray::clear() {
|
||||||
|
this->vertices_and_normals_interleaved.clear();
|
||||||
|
this->triangle_indices.clear();
|
||||||
|
this->quad_indices.clear();
|
||||||
|
vertices_and_normals_interleaved_size = 0;
|
||||||
|
triangle_indices_size = 0;
|
||||||
|
quad_indices_size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IndexedVertexArray::shrink_to_fit() {
|
||||||
|
this->vertices_and_normals_interleaved.shrink_to_fit();
|
||||||
|
this->triangle_indices.shrink_to_fit();
|
||||||
|
this->quad_indices.shrink_to_fit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Volume::render()
|
||||||
|
{
|
||||||
|
glsafe(::glPushMatrix());
|
||||||
|
glsafe(::glMultMatrixd(m_trafo.get_matrix().data()));
|
||||||
|
m_geom.render();
|
||||||
|
glsafe(::glPopMatrix());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display::clear_screen()
|
||||||
|
{
|
||||||
|
glViewport(0, 0, GLsizei(m_size.x()), GLsizei(m_size.y()));
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
Display::~Display()
|
||||||
|
{
|
||||||
|
OpenCSG::freeResources();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display::set_active(long width, long height)
|
||||||
|
{
|
||||||
|
if (!m_initialized) {
|
||||||
|
glewInit();
|
||||||
|
m_initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// gray background
|
||||||
|
glClearColor(0.9f, 0.9f, 0.9f, 1.0f);
|
||||||
|
|
||||||
|
// Enable two OpenGL lights
|
||||||
|
GLfloat light_diffuse[] = { 1.0f, 1.0f, 0.0f, 1.0f}; // White diffuse light
|
||||||
|
GLfloat light_position0[] = {-1.0f, -1.0f, -1.0f, 0.0f}; // Infinite light location
|
||||||
|
GLfloat light_position1[] = { 1.0f, 1.0f, 1.0f, 0.0f}; // Infinite light location
|
||||||
|
|
||||||
|
glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);
|
||||||
|
glLightfv(GL_LIGHT0, GL_POSITION, light_position0);
|
||||||
|
glEnable(GL_LIGHT0);
|
||||||
|
glLightfv(GL_LIGHT1, GL_DIFFUSE, light_diffuse);
|
||||||
|
glLightfv(GL_LIGHT1, GL_POSITION, light_position1);
|
||||||
|
glEnable(GL_LIGHT1);
|
||||||
|
glEnable(GL_LIGHTING);
|
||||||
|
glEnable(GL_NORMALIZE);
|
||||||
|
|
||||||
|
// Use depth buffering for hidden surface elimination
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
glEnable(GL_STENCIL_TEST);
|
||||||
|
|
||||||
|
set_screen_size(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display::set_screen_size(long width, long height)
|
||||||
|
{
|
||||||
|
if (m_size.x() != width || m_size.y() != height)
|
||||||
|
m_camera->set_screen(width, height);
|
||||||
|
|
||||||
|
m_size = {width, height};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Display::repaint()
|
||||||
|
{
|
||||||
|
clear_screen();
|
||||||
|
|
||||||
|
m_camera->view();
|
||||||
|
render_scene();
|
||||||
|
|
||||||
|
m_fps_counter.update();
|
||||||
|
|
||||||
|
swap_buffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::on_scene_updated(const Scene &scene)
|
||||||
|
{
|
||||||
|
const SLAPrint *print = scene.get_print();
|
||||||
|
if (!print) return;
|
||||||
|
|
||||||
|
auto bb = scene.get_bounding_box();
|
||||||
|
double d = std::max(std::max(bb.size().x(), bb.size().y()), bb.size().z());
|
||||||
|
m_wheel_pos = long(2 * d);
|
||||||
|
|
||||||
|
call_cameras(&Camera::set_zoom, m_wheel_pos);
|
||||||
|
call(&Display::on_scene_updated, m_displays, scene);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::on_scroll(long v, long d, MouseInput::WheelAxis /*wa*/)
|
||||||
|
{
|
||||||
|
m_wheel_pos += v / d;
|
||||||
|
|
||||||
|
call_cameras(&Camera::set_zoom, m_wheel_pos);
|
||||||
|
call(&Display::repaint, m_displays);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::on_moved_to(long x, long y)
|
||||||
|
{
|
||||||
|
if (m_left_btn) {
|
||||||
|
call_cameras(&Camera::rotate, (Vec2i{x, y} - m_mouse_pos).cast<float>());
|
||||||
|
call(&Display::repaint, m_displays);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_mouse_pos = {x, y};
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSGDisplay::apply_csgsettings(const CSGSettings &settings)
|
||||||
|
{
|
||||||
|
using namespace OpenCSG;
|
||||||
|
|
||||||
|
bool needupdate = m_csgsettings.get_convexity() != settings.get_convexity();
|
||||||
|
|
||||||
|
m_csgsettings = settings;
|
||||||
|
setOption(AlgorithmSetting, m_csgsettings.get_algo());
|
||||||
|
setOption(DepthComplexitySetting, m_csgsettings.get_depth_algo());
|
||||||
|
setOption(DepthBoundsOptimization, m_csgsettings.get_optimization());
|
||||||
|
|
||||||
|
if (needupdate) {
|
||||||
|
for (OpenCSG::Primitive * p : m_scene_cache.primitives_csg)
|
||||||
|
if (p->getConvexity() > 1)
|
||||||
|
p->setConvexity(m_csgsettings.get_convexity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CSGDisplay::on_scene_updated(const Scene &scene)
|
||||||
|
{
|
||||||
|
const SLAPrint *print = scene.get_print();
|
||||||
|
if (!print) return;
|
||||||
|
|
||||||
|
m_scene_cache.clear();
|
||||||
|
|
||||||
|
for (const SLAPrintObject *po : print->objects()) {
|
||||||
|
const ModelObject *mo = po->model_object();
|
||||||
|
TriangleMesh msh = mo->raw_mesh();
|
||||||
|
|
||||||
|
sla::DrainHoles holedata = mo->sla_drain_holes;
|
||||||
|
|
||||||
|
for (const ModelInstance *mi : mo->instances) {
|
||||||
|
|
||||||
|
TriangleMesh mshinst = msh;
|
||||||
|
auto interior = po->hollowed_interior_mesh();
|
||||||
|
interior.transform(po->trafo().inverse());
|
||||||
|
|
||||||
|
mshinst.merge(interior);
|
||||||
|
mshinst.require_shared_vertices();
|
||||||
|
|
||||||
|
mi->transform_mesh(&mshinst);
|
||||||
|
|
||||||
|
auto bb = mshinst.bounding_box();
|
||||||
|
auto center = bb.center().cast<float>();
|
||||||
|
mshinst.translate(-center);
|
||||||
|
|
||||||
|
mshinst.require_shared_vertices();
|
||||||
|
m_scene_cache.add_mesh(mshinst, OpenCSG::Intersection,
|
||||||
|
m_csgsettings.get_convexity());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const sla::DrainHole &holept : holedata) {
|
||||||
|
TriangleMesh holemesh = sla::to_triangle_mesh(holept.to_mesh());
|
||||||
|
holemesh.require_shared_vertices();
|
||||||
|
m_scene_cache.add_mesh(holemesh, OpenCSG::Subtraction, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Camera::view()
|
||||||
|
{
|
||||||
|
glMatrixMode(GL_MODELVIEW);
|
||||||
|
glLoadIdentity();
|
||||||
|
gluLookAt(0.0, m_zoom, 0.0, /* eye is at (0,zoom,0) */
|
||||||
|
m_referene.x(), m_referene.y(), m_referene.z(),
|
||||||
|
0.0, 0.0, 1.0); /* up is in positive Y direction */
|
||||||
|
|
||||||
|
// TODO Could have been set in prevoius gluLookAt in first argument
|
||||||
|
glRotatef(m_rot.y(), 1.0, 0.0, 0.0);
|
||||||
|
glRotatef(m_rot.x(), 0.0, 0.0, 1.0);
|
||||||
|
|
||||||
|
if (m_clip_z > 0.) {
|
||||||
|
GLdouble plane[] = {0., 0., 1., m_clip_z};
|
||||||
|
glClipPlane(GL_CLIP_PLANE0, plane);
|
||||||
|
glEnable(GL_CLIP_PLANE0);
|
||||||
|
} else {
|
||||||
|
glDisable(GL_CLIP_PLANE0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PerspectiveCamera::set_screen(long width, long height)
|
||||||
|
{
|
||||||
|
// Setup the view of the CSG shape
|
||||||
|
glMatrixMode(GL_PROJECTION);
|
||||||
|
glLoadIdentity();
|
||||||
|
gluPerspective(45.0, width / double(height), .1, 200.0);
|
||||||
|
glMatrixMode(GL_MODELVIEW);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool enable_multisampling(bool e)
|
||||||
|
{
|
||||||
|
if (!e) { glDisable(GL_MULTISAMPLE); return false; }
|
||||||
|
|
||||||
|
GLint is_ms_context;
|
||||||
|
glGetIntegerv(GL_SAMPLE_BUFFERS, &is_ms_context);
|
||||||
|
|
||||||
|
if (is_ms_context) { glEnable(GL_MULTISAMPLE); return true; }
|
||||||
|
else return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseInput::Listener::~Listener() = default;
|
||||||
|
|
||||||
|
void FpsCounter::update()
|
||||||
|
{
|
||||||
|
++m_frames;
|
||||||
|
|
||||||
|
TimePoint msec = Clock::now();
|
||||||
|
|
||||||
|
double seconds_window = to_sec(msec - m_window);
|
||||||
|
m_fps = 0.5 * m_fps + 0.5 * (m_frames / seconds_window);
|
||||||
|
|
||||||
|
if (to_sec(msec - m_last) >= m_resolution) {
|
||||||
|
m_last = msec;
|
||||||
|
for (auto &l : m_listeners) l(m_fps);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds_window >= m_window_size) {
|
||||||
|
m_frames = 0;
|
||||||
|
m_window = msec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // namespace Slic3r::GL
|
||||||
493
sandboxes/opencsg/Engine.hpp
Normal file
493
sandboxes/opencsg/Engine.hpp
Normal file
|
|
@ -0,0 +1,493 @@
|
||||||
|
#ifndef SLIC3R_OCSG_EXMP_ENGINE_HPP
|
||||||
|
#define SLIC3R_OCSG_EXMP_ENGINE_HPP
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
#include <libslic3r/Geometry.hpp>
|
||||||
|
#include <libslic3r/Model.hpp>
|
||||||
|
#include <libslic3r/TriangleMesh.hpp>
|
||||||
|
#include <libslic3r/SLA/Hollowing.hpp>
|
||||||
|
#include <opencsg/opencsg.h>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class SLAPrint;
|
||||||
|
|
||||||
|
namespace GL {
|
||||||
|
|
||||||
|
// Simple shorthands for smart pointers
|
||||||
|
template<class T> using shptr = std::shared_ptr<T>;
|
||||||
|
template<class T> using uqptr = std::unique_ptr<T>;
|
||||||
|
template<class T> using wkptr = std::weak_ptr<T>;
|
||||||
|
|
||||||
|
template<class T, class A = std::allocator<T>> using vector = std::vector<T, A>;
|
||||||
|
|
||||||
|
// remove empty weak pointers from a vector
|
||||||
|
template<class L> inline void cleanup(vector<std::weak_ptr<L>> &listeners) {
|
||||||
|
auto it = std::remove_if(listeners.begin(), listeners.end(),
|
||||||
|
[](auto &l) { return !l.lock(); });
|
||||||
|
listeners.erase(it, listeners.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call a class method on each element of a vector of objects (weak pointers)
|
||||||
|
// of the same type.
|
||||||
|
template<class F, class L, class...Args>
|
||||||
|
inline void call(F &&f, vector<std::weak_ptr<L>> &listeners, Args&&... args) {
|
||||||
|
for (auto &l : listeners)
|
||||||
|
if (auto p = l.lock()) ((p.get())->*f)(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A representation of a mouse input for the engine.
|
||||||
|
class MouseInput
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum WheelAxis { waVertical, waHorizontal };
|
||||||
|
|
||||||
|
// Interface to implement if an object wants to receive notifications
|
||||||
|
// about mouse events.
|
||||||
|
class Listener {
|
||||||
|
public:
|
||||||
|
virtual ~Listener();
|
||||||
|
|
||||||
|
virtual void on_left_click_down() {}
|
||||||
|
virtual void on_left_click_up() {}
|
||||||
|
virtual void on_right_click_down() {}
|
||||||
|
virtual void on_right_click_up() {}
|
||||||
|
virtual void on_double_click() {}
|
||||||
|
virtual void on_scroll(long /*v*/, long /*delta*/, WheelAxis ) {}
|
||||||
|
virtual void on_moved_to(long /*x*/, long /*y*/) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
vector<wkptr<Listener>> m_listeners;
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual ~MouseInput() = default;
|
||||||
|
|
||||||
|
virtual void left_click_down()
|
||||||
|
{
|
||||||
|
call(&Listener::on_left_click_down, m_listeners);
|
||||||
|
}
|
||||||
|
virtual void left_click_up()
|
||||||
|
{
|
||||||
|
call(&Listener::on_left_click_up, m_listeners);
|
||||||
|
}
|
||||||
|
virtual void right_click_down()
|
||||||
|
{
|
||||||
|
call(&Listener::on_right_click_down, m_listeners);
|
||||||
|
}
|
||||||
|
virtual void right_click_up()
|
||||||
|
{
|
||||||
|
call(&Listener::on_right_click_up, m_listeners);
|
||||||
|
}
|
||||||
|
virtual void double_click()
|
||||||
|
{
|
||||||
|
call(&Listener::on_double_click, m_listeners);
|
||||||
|
}
|
||||||
|
virtual void scroll(long v, long d, WheelAxis wa)
|
||||||
|
{
|
||||||
|
call(&Listener::on_scroll, m_listeners, v, d, wa);
|
||||||
|
}
|
||||||
|
virtual void move_to(long x, long y)
|
||||||
|
{
|
||||||
|
call(&Listener::on_moved_to, m_listeners, x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_listener(shptr<Listener> listener)
|
||||||
|
{
|
||||||
|
m_listeners.emplace_back(listener);
|
||||||
|
cleanup(m_listeners);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// This is a stripped down version of Slic3r::IndexedVertexArray
|
||||||
|
class IndexedVertexArray {
|
||||||
|
public:
|
||||||
|
~IndexedVertexArray() { release_geometry(); }
|
||||||
|
|
||||||
|
// Vertices and their normals, interleaved to be used by void
|
||||||
|
// glInterleavedArrays(GL_N3F_V3F, 0, x)
|
||||||
|
vector<float> vertices_and_normals_interleaved;
|
||||||
|
vector<int> triangle_indices;
|
||||||
|
vector<int> quad_indices;
|
||||||
|
|
||||||
|
// When the geometry data is loaded into the graphics card as Vertex
|
||||||
|
// Buffer Objects, the above mentioned std::vectors are cleared and the
|
||||||
|
// following variables keep their original length.
|
||||||
|
size_t vertices_and_normals_interleaved_size{ 0 };
|
||||||
|
size_t triangle_indices_size{ 0 };
|
||||||
|
size_t quad_indices_size{ 0 };
|
||||||
|
|
||||||
|
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
|
||||||
|
// Zero if the VBOs are not sent to GPU yet.
|
||||||
|
unsigned int vertices_and_normals_interleaved_VBO_id{ 0 };
|
||||||
|
unsigned int triangle_indices_VBO_id{ 0 };
|
||||||
|
unsigned int quad_indices_VBO_id{ 0 };
|
||||||
|
|
||||||
|
|
||||||
|
void push_geometry(float x, float y, float z, float nx, float ny, float nz);
|
||||||
|
|
||||||
|
inline void push_geometry(
|
||||||
|
double x, double y, double z, double nx, double ny, double nz)
|
||||||
|
{
|
||||||
|
push_geometry(float(x), float(y), float(z), float(nx), float(ny), float(nz));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void push_geometry(const Vec3d &p, const Vec3d &n)
|
||||||
|
{
|
||||||
|
push_geometry(p(0), p(1), p(2), n(0), n(1), n(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
void push_triangle(int idx1, int idx2, int idx3);
|
||||||
|
|
||||||
|
void load_mesh(const TriangleMesh &mesh);
|
||||||
|
|
||||||
|
inline bool has_VBOs() const
|
||||||
|
{
|
||||||
|
return vertices_and_normals_interleaved_VBO_id != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize the initialization of the geometry & indices,
|
||||||
|
// upload the geometry and indices to OpenGL VBO objects
|
||||||
|
// and shrink the allocated data, possibly relasing it if it has been
|
||||||
|
// loaded into the VBOs.
|
||||||
|
void finalize_geometry();
|
||||||
|
// Release the geometry data, release OpenGL VBOs.
|
||||||
|
void release_geometry();
|
||||||
|
|
||||||
|
void render() const;
|
||||||
|
|
||||||
|
// Is there any geometry data stored?
|
||||||
|
bool empty() const { return vertices_and_normals_interleaved_size == 0; }
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
// Shrink the internal storage to tighly fit the data stored.
|
||||||
|
void shrink_to_fit();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to enable or disable multisampling.
|
||||||
|
bool enable_multisampling(bool e = true);
|
||||||
|
|
||||||
|
class Volume {
|
||||||
|
IndexedVertexArray m_geom;
|
||||||
|
Geometry::Transformation m_trafo;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
void render();
|
||||||
|
|
||||||
|
void translation(const Vec3d &offset) { m_trafo.set_offset(offset); }
|
||||||
|
void rotation(const Vec3d &rot) { m_trafo.set_rotation(rot); }
|
||||||
|
void scale(const Vec3d &scaleing) { m_trafo.set_scaling_factor(scaleing); }
|
||||||
|
void scale(double s) { scale({s, s, s}); }
|
||||||
|
|
||||||
|
inline void load_mesh(const TriangleMesh &mesh)
|
||||||
|
{
|
||||||
|
m_geom.load_mesh(mesh);
|
||||||
|
m_geom.finalize_geometry();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// A primitive that can be used with OpenCSG rendering algorithms.
|
||||||
|
// Does a similar job to GLVolume.
|
||||||
|
class Primitive : public Volume, public OpenCSG::Primitive
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using OpenCSG::Primitive::Primitive;
|
||||||
|
|
||||||
|
Primitive() : OpenCSG::Primitive(OpenCSG::Intersection, 1) {}
|
||||||
|
|
||||||
|
void render() override { Volume::render(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// A simple representation of a camera in a 3D scene
|
||||||
|
class Camera {
|
||||||
|
protected:
|
||||||
|
Vec2f m_rot = {0., 0.};
|
||||||
|
Vec3d m_referene = {0., 0., 0.};
|
||||||
|
double m_zoom = 0.;
|
||||||
|
double m_clip_z = 0.;
|
||||||
|
public:
|
||||||
|
|
||||||
|
virtual ~Camera() = default;
|
||||||
|
|
||||||
|
virtual void view();
|
||||||
|
virtual void set_screen(long width, long height) = 0;
|
||||||
|
|
||||||
|
void set_rotation(const Vec2f &rotation) { m_rot = rotation; }
|
||||||
|
void rotate(const Vec2f &rotation) { m_rot += rotation; }
|
||||||
|
void set_zoom(double z) { m_zoom = z; }
|
||||||
|
void set_reference_point(const Vec3d &p) { m_referene = p; }
|
||||||
|
void set_clip_z(double z) { m_clip_z = z; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Reset a camera object
|
||||||
|
inline void reset(Camera &cam)
|
||||||
|
{
|
||||||
|
cam.set_rotation({0., 0.});
|
||||||
|
cam.set_zoom(0.);
|
||||||
|
cam.set_reference_point({0., 0., 0.});
|
||||||
|
cam.set_clip_z(0.);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Specialization of a camera which shows in perspective projection
|
||||||
|
class PerspectiveCamera: public Camera {
|
||||||
|
public:
|
||||||
|
|
||||||
|
void set_screen(long width, long height) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A simple counter of FPS. Subscribed objects will receive updates of the
|
||||||
|
// current fps.
|
||||||
|
class FpsCounter {
|
||||||
|
vector<std::function<void(double)>> m_listeners;
|
||||||
|
|
||||||
|
using Clock = std::chrono::high_resolution_clock;
|
||||||
|
using Duration = Clock::duration;
|
||||||
|
using TimePoint = Clock::time_point;
|
||||||
|
|
||||||
|
int m_frames = 0;
|
||||||
|
TimePoint m_last = Clock::now(), m_window = m_last;
|
||||||
|
|
||||||
|
double m_resolution = 0.1, m_window_size = 1.0;
|
||||||
|
double m_fps = 0.;
|
||||||
|
|
||||||
|
static double to_sec(Duration d)
|
||||||
|
{
|
||||||
|
return d.count() * double(Duration::period::num) / Duration::period::den;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
void update();
|
||||||
|
|
||||||
|
void add_listener(std::function<void(double)> lst)
|
||||||
|
{
|
||||||
|
m_listeners.emplace_back(lst);
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_listeners() { m_listeners = {}; }
|
||||||
|
|
||||||
|
void set_notification_interval(double seconds);
|
||||||
|
void set_measure_window_size(double seconds);
|
||||||
|
|
||||||
|
double get_notification_interval() const { return m_resolution; }
|
||||||
|
double get_mesure_window_size() const { return m_window_size; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Collection of the used OpenCSG library settings.
|
||||||
|
class CSGSettings {
|
||||||
|
public:
|
||||||
|
static const constexpr unsigned DEFAULT_CONVEXITY = 10;
|
||||||
|
|
||||||
|
private:
|
||||||
|
OpenCSG::Algorithm m_csgalg = OpenCSG::Algorithm::Automatic;
|
||||||
|
OpenCSG::DepthComplexityAlgorithm m_depth_algo = OpenCSG::NoDepthComplexitySampling;
|
||||||
|
OpenCSG::Optimization m_optim = OpenCSG::OptimizationDefault;
|
||||||
|
bool m_enable = true;
|
||||||
|
unsigned int m_convexity = DEFAULT_CONVEXITY;
|
||||||
|
|
||||||
|
public:
|
||||||
|
int get_algo() const { return int(m_csgalg); }
|
||||||
|
void set_algo(int alg)
|
||||||
|
{
|
||||||
|
if (alg < OpenCSG::Algorithm::AlgorithmUnused)
|
||||||
|
m_csgalg = OpenCSG::Algorithm(alg);
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_depth_algo() const { return int(m_depth_algo); }
|
||||||
|
void set_depth_algo(int alg)
|
||||||
|
{
|
||||||
|
if (alg < OpenCSG::DepthComplexityAlgorithmUnused)
|
||||||
|
m_depth_algo = OpenCSG::DepthComplexityAlgorithm(alg);
|
||||||
|
}
|
||||||
|
|
||||||
|
int get_optimization() const { return int(m_optim); }
|
||||||
|
void set_optimization(int o)
|
||||||
|
{
|
||||||
|
if (o < OpenCSG::Optimization::OptimizationUnused)
|
||||||
|
m_optim = OpenCSG::Optimization(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
void enable_csg(bool en = true) { m_enable = en; }
|
||||||
|
bool is_enabled() const { return m_enable; }
|
||||||
|
|
||||||
|
unsigned get_convexity() const { return m_convexity; }
|
||||||
|
void set_convexity(unsigned c) { m_convexity = c; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// The scene is a wrapper around SLAPrint which holds the data to be visualized.
|
||||||
|
class Scene
|
||||||
|
{
|
||||||
|
uqptr<SLAPrint> m_print;
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Subscribers will be notified if the model is changed. This might be a
|
||||||
|
// display which will have to load the meshes and repaint itself when
|
||||||
|
// the scene data changes.
|
||||||
|
// eg. We load a new 3mf through the UI, this will notify the controller
|
||||||
|
// associated with the scene and all the displays that the controller is
|
||||||
|
// connected with.
|
||||||
|
class Listener {
|
||||||
|
public:
|
||||||
|
virtual ~Listener() = default;
|
||||||
|
virtual void on_scene_updated(const Scene &scene) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Scene();
|
||||||
|
~Scene();
|
||||||
|
|
||||||
|
void set_print(uqptr<SLAPrint> &&print);
|
||||||
|
const SLAPrint * get_print() const { return m_print.get(); }
|
||||||
|
|
||||||
|
BoundingBoxf3 get_bounding_box() const;
|
||||||
|
|
||||||
|
void add_listener(shptr<Listener> listener)
|
||||||
|
{
|
||||||
|
m_listeners.emplace_back(listener);
|
||||||
|
cleanup(m_listeners);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
vector<wkptr<Listener>> m_listeners;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The basic Display. This is almost just an interface but will do all the
|
||||||
|
// initialization and show the fps values. Overriding the render_scene is
|
||||||
|
// needed to show the scene content. The specific method of displaying the
|
||||||
|
// scene is up the the particular implementation (OpenCSG or other screen space
|
||||||
|
// boolean algorithms)
|
||||||
|
class Display : public Scene::Listener
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
Vec2i m_size;
|
||||||
|
bool m_initialized = false;
|
||||||
|
|
||||||
|
shptr<Camera> m_camera;
|
||||||
|
FpsCounter m_fps_counter;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit Display(shptr<Camera> camera = nullptr)
|
||||||
|
: m_camera(camera ? camera : std::make_shared<PerspectiveCamera>())
|
||||||
|
{}
|
||||||
|
|
||||||
|
~Display() override;
|
||||||
|
|
||||||
|
shptr<const Camera> get_camera() const { return m_camera; }
|
||||||
|
shptr<Camera> get_camera() { return m_camera; }
|
||||||
|
void set_camera(shptr<Camera> cam) { m_camera = cam; }
|
||||||
|
|
||||||
|
virtual void swap_buffers() = 0;
|
||||||
|
virtual void set_active(long width, long height);
|
||||||
|
virtual void set_screen_size(long width, long height);
|
||||||
|
Vec2i get_screen_size() const { return m_size; }
|
||||||
|
|
||||||
|
virtual void repaint();
|
||||||
|
|
||||||
|
bool is_initialized() const { return m_initialized; }
|
||||||
|
|
||||||
|
virtual void clear_screen();
|
||||||
|
virtual void render_scene() {}
|
||||||
|
|
||||||
|
template<class _FpsCounter> void set_fps_counter(_FpsCounter &&fpsc)
|
||||||
|
{
|
||||||
|
m_fps_counter = std::forward<_FpsCounter>(fpsc);
|
||||||
|
}
|
||||||
|
|
||||||
|
const FpsCounter &get_fps_counter() const { return m_fps_counter; }
|
||||||
|
FpsCounter &get_fps_counter() { return m_fps_counter; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Special dispaly using OpenCSG for rendering the scene.
|
||||||
|
class CSGDisplay : public Display {
|
||||||
|
protected:
|
||||||
|
CSGSettings m_csgsettings;
|
||||||
|
|
||||||
|
// Cache the renderable primitives. These will be fetched when the scene
|
||||||
|
// is modified.
|
||||||
|
struct SceneCache {
|
||||||
|
vector<shptr<Primitive>> primitives;
|
||||||
|
vector<Primitive *> primitives_free;
|
||||||
|
vector<OpenCSG::Primitive *> primitives_csg;
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
shptr<Primitive> add_mesh(const TriangleMesh &mesh);
|
||||||
|
shptr<Primitive> add_mesh(const TriangleMesh &mesh,
|
||||||
|
OpenCSG::Operation op,
|
||||||
|
unsigned covexity);
|
||||||
|
} m_scene_cache;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Receive or apply the new settings.
|
||||||
|
const CSGSettings & get_csgsettings() const { return m_csgsettings; }
|
||||||
|
void apply_csgsettings(const CSGSettings &settings);
|
||||||
|
|
||||||
|
void render_scene() override;
|
||||||
|
|
||||||
|
void on_scene_updated(const Scene &scene) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// The controller is a hub which dispatches mouse events to the connected
|
||||||
|
// displays. It keeps track of the mouse wheel position, the states whether
|
||||||
|
// the mouse is being held, dragged, etc... All the connected displays will
|
||||||
|
// mirror the camera movement (if there is more than one display).
|
||||||
|
class Controller : public std::enable_shared_from_this<Controller>,
|
||||||
|
public MouseInput::Listener,
|
||||||
|
public Scene::Listener
|
||||||
|
{
|
||||||
|
long m_wheel_pos = 0;
|
||||||
|
Vec2i m_mouse_pos, m_mouse_pos_rprev, m_mouse_pos_lprev;
|
||||||
|
bool m_left_btn = false, m_right_btn = false;
|
||||||
|
|
||||||
|
shptr<Scene> m_scene;
|
||||||
|
vector<wkptr<Display>> m_displays;
|
||||||
|
|
||||||
|
// Call a method of Camera on all the cameras of the attached displays
|
||||||
|
template<class F, class...Args>
|
||||||
|
void call_cameras(F &&f, Args&&... args) {
|
||||||
|
for (wkptr<Display> &l : m_displays)
|
||||||
|
if (auto disp = l.lock()) if (auto cam = disp->get_camera())
|
||||||
|
(cam.get()->*f)(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
// Set the scene that will be controlled.
|
||||||
|
void set_scene(shptr<Scene> scene)
|
||||||
|
{
|
||||||
|
m_scene = scene;
|
||||||
|
m_scene->add_listener(shared_from_this());
|
||||||
|
}
|
||||||
|
|
||||||
|
const Scene * get_scene() const { return m_scene.get(); }
|
||||||
|
|
||||||
|
void add_display(shptr<Display> disp)
|
||||||
|
{
|
||||||
|
m_displays.emplace_back(disp);
|
||||||
|
cleanup(m_displays);
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_displays() { m_displays = {}; }
|
||||||
|
|
||||||
|
void on_scene_updated(const Scene &scene) override;
|
||||||
|
|
||||||
|
void on_left_click_down() override { m_left_btn = true; }
|
||||||
|
void on_left_click_up() override { m_left_btn = false; }
|
||||||
|
void on_right_click_down() override { m_right_btn = true; }
|
||||||
|
void on_right_click_up() override { m_right_btn = false; }
|
||||||
|
|
||||||
|
void on_scroll(long v, long d, MouseInput::WheelAxis wa) override;
|
||||||
|
void on_moved_to(long x, long y) override;
|
||||||
|
|
||||||
|
void move_clip_plane(double z) { call_cameras(&Camera::set_clip_z, z); }
|
||||||
|
};
|
||||||
|
|
||||||
|
}} // namespace Slic3r::GL
|
||||||
|
#endif // SLIC3R_OCSG_EXMP_ENGINE_HPP
|
||||||
68
sandboxes/opencsg/ShaderCSGDisplay.cpp
Normal file
68
sandboxes/opencsg/ShaderCSGDisplay.cpp
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
#include "ShaderCSGDisplay.hpp"
|
||||||
|
#include "libslic3r/SLAPrint.hpp"
|
||||||
|
#include <GL/glew.h>
|
||||||
|
|
||||||
|
namespace Slic3r { namespace GL {
|
||||||
|
|
||||||
|
void ShaderCSGDisplay::add_mesh(const TriangleMesh &mesh)
|
||||||
|
{
|
||||||
|
auto v = std::make_shared<CSGVolume>();
|
||||||
|
v->load_mesh(mesh);
|
||||||
|
m_volumes.emplace_back(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderCSGDisplay::render_scene()
|
||||||
|
{
|
||||||
|
GLfloat color[] = {1.f, 1.f, 0.f, 0.f};
|
||||||
|
glColor4fv(color);
|
||||||
|
glDepthFunc(GL_LESS);
|
||||||
|
for (auto &v : m_volumes) v->render();
|
||||||
|
glFlush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShaderCSGDisplay::on_scene_updated(const Scene &scene)
|
||||||
|
{
|
||||||
|
// TriangleMesh mesh = print->objects().front()->hollowed_interior_mesh();
|
||||||
|
// Look at CSGDisplay::on_scene_updated to see how its done there.
|
||||||
|
|
||||||
|
const SLAPrint *print = scene.get_print();
|
||||||
|
if (!print) return;
|
||||||
|
|
||||||
|
m_volumes.clear();
|
||||||
|
|
||||||
|
for (const SLAPrintObject *po : print->objects()) {
|
||||||
|
const ModelObject *mo = po->model_object();
|
||||||
|
TriangleMesh msh = mo->raw_mesh();
|
||||||
|
|
||||||
|
sla::DrainHoles holedata = mo->sla_drain_holes;
|
||||||
|
|
||||||
|
for (const ModelInstance *mi : mo->instances) {
|
||||||
|
|
||||||
|
TriangleMesh mshinst = msh;
|
||||||
|
auto interior = po->hollowed_interior_mesh();
|
||||||
|
interior.transform(po->trafo().inverse());
|
||||||
|
|
||||||
|
mshinst.merge(interior);
|
||||||
|
mshinst.require_shared_vertices();
|
||||||
|
|
||||||
|
mi->transform_mesh(&mshinst);
|
||||||
|
|
||||||
|
auto bb = mshinst.bounding_box();
|
||||||
|
auto center = bb.center().cast<float>();
|
||||||
|
mshinst.translate(-center);
|
||||||
|
|
||||||
|
mshinst.require_shared_vertices();
|
||||||
|
add_mesh(mshinst);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const sla::DrainHole &holept : holedata) {
|
||||||
|
TriangleMesh holemesh = sla::to_triangle_mesh(holept.to_mesh());
|
||||||
|
holemesh.require_shared_vertices();
|
||||||
|
add_mesh(holemesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // namespace Slic3r::GL
|
||||||
27
sandboxes/opencsg/ShaderCSGDisplay.hpp
Normal file
27
sandboxes/opencsg/ShaderCSGDisplay.hpp
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
#ifndef SHADERCSGDISPLAY_HPP
|
||||||
|
#define SHADERCSGDISPLAY_HPP
|
||||||
|
|
||||||
|
#include "Engine.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r { namespace GL {
|
||||||
|
|
||||||
|
class CSGVolume: public Volume
|
||||||
|
{
|
||||||
|
// Extend...
|
||||||
|
};
|
||||||
|
|
||||||
|
class ShaderCSGDisplay: public Display {
|
||||||
|
protected:
|
||||||
|
vector<shptr<CSGVolume>> m_volumes;
|
||||||
|
|
||||||
|
void add_mesh(const TriangleMesh &mesh);
|
||||||
|
public:
|
||||||
|
|
||||||
|
void render_scene() override;
|
||||||
|
|
||||||
|
void on_scene_updated(const Scene &scene) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
}}
|
||||||
|
|
||||||
|
#endif // SHADERCSGDISPLAY_HPP
|
||||||
734
sandboxes/opencsg/main.cpp
Normal file
734
sandboxes/opencsg/main.cpp
Normal file
|
|
@ -0,0 +1,734 @@
|
||||||
|
#include <iostream>
|
||||||
|
#include <utility>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "Engine.hpp"
|
||||||
|
#include "ShaderCSGDisplay.hpp"
|
||||||
|
|
||||||
|
#include <GL/glew.h>
|
||||||
|
|
||||||
|
#include <opencsg/opencsg.h>
|
||||||
|
// For compilers that support precompilation, includes "wx/wx.h".
|
||||||
|
#include <wx/wxprec.h>
|
||||||
|
#ifndef WX_PRECOMP
|
||||||
|
#include <wx/wx.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <wx/slider.h>
|
||||||
|
#include <wx/tglbtn.h>
|
||||||
|
#include <wx/combobox.h>
|
||||||
|
#include <wx/spinctrl.h>
|
||||||
|
#include <wx/msgdlg.h>
|
||||||
|
#include <wx/glcanvas.h>
|
||||||
|
#include <wx/cmdline.h>
|
||||||
|
|
||||||
|
#include "libslic3r/Model.hpp"
|
||||||
|
#include "libslic3r/Format/3mf.hpp"
|
||||||
|
#include "libslic3r/SLAPrint.hpp"
|
||||||
|
|
||||||
|
#include "slic3r/GUI/Job.hpp"
|
||||||
|
#include "slic3r/GUI/ProgressStatusBar.hpp"
|
||||||
|
|
||||||
|
using namespace Slic3r::GL;
|
||||||
|
|
||||||
|
class Renderer {
|
||||||
|
protected:
|
||||||
|
wxGLCanvas *m_canvas;
|
||||||
|
shptr<wxGLContext> m_context;
|
||||||
|
public:
|
||||||
|
|
||||||
|
Renderer(wxGLCanvas *c): m_canvas{c} {
|
||||||
|
auto ctx = new wxGLContext(m_canvas);
|
||||||
|
if (!ctx || !ctx->IsOK()) {
|
||||||
|
wxMessageBox("Could not create OpenGL context.", "Error",
|
||||||
|
wxOK | wxICON_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_context.reset(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
wxGLContext * context() { return m_context.get(); }
|
||||||
|
const wxGLContext * context() const { return m_context.get(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tell the CSGDisplay how to swap buffers and set the gl context.
|
||||||
|
class OCSGRenderer: public Renderer, public Slic3r::GL::CSGDisplay {
|
||||||
|
public:
|
||||||
|
|
||||||
|
OCSGRenderer(wxGLCanvas *c): Renderer{c} {}
|
||||||
|
|
||||||
|
void set_active(long w, long h) override
|
||||||
|
{
|
||||||
|
m_canvas->SetCurrent(*m_context);
|
||||||
|
Slic3r::GL::Display::set_active(w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
void swap_buffers() override { m_canvas->SwapBuffers(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tell the CSGDisplay how to swap buffers and set the gl context.
|
||||||
|
class ShaderCSGRenderer : public Renderer, public Slic3r::GL::ShaderCSGDisplay {
|
||||||
|
public:
|
||||||
|
|
||||||
|
ShaderCSGRenderer(wxGLCanvas *c): Renderer{c} {}
|
||||||
|
|
||||||
|
void set_active(long w, long h) override
|
||||||
|
{
|
||||||
|
m_canvas->SetCurrent(*m_context);
|
||||||
|
Slic3r::GL::Display::set_active(w, h);
|
||||||
|
}
|
||||||
|
|
||||||
|
void swap_buffers() override { m_canvas->SwapBuffers(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
// The opengl rendering facility. Here we implement the rendering objects.
|
||||||
|
class Canvas: public wxGLCanvas
|
||||||
|
{
|
||||||
|
// One display is active at a time, the OCSGRenderer by default.
|
||||||
|
shptr<Slic3r::GL::Display> m_display;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
template<class...Args>
|
||||||
|
Canvas(Args &&...args): wxGLCanvas(std::forward<Args>(args)...) {}
|
||||||
|
|
||||||
|
shptr<Slic3r::GL::Display> get_display() const { return m_display; }
|
||||||
|
|
||||||
|
void set_display(shptr<Slic3r::GL::Display> d) { m_display = d; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Enumerate possible mouse events, we will record them.
|
||||||
|
enum EEvents { LCLK_U, RCLK_U, LCLK_D, RCLK_D, DDCLK, SCRL, MV };
|
||||||
|
struct Event
|
||||||
|
{
|
||||||
|
EEvents type;
|
||||||
|
long a, b;
|
||||||
|
Event(EEvents t, long x = 0, long y = 0) : type{t}, a{x}, b{y} {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a special mouse input adapter, which can store (record) the received
|
||||||
|
// mouse signals into a file and play back the stored events later.
|
||||||
|
class RecorderMouseInput: public MouseInput {
|
||||||
|
std::vector<Event> m_events;
|
||||||
|
bool m_recording = false, m_playing = false;
|
||||||
|
|
||||||
|
public:
|
||||||
|
void left_click_down() override
|
||||||
|
{
|
||||||
|
if (m_recording) m_events.emplace_back(LCLK_D);
|
||||||
|
if (!m_playing) MouseInput::left_click_down();
|
||||||
|
}
|
||||||
|
void left_click_up() override
|
||||||
|
{
|
||||||
|
if (m_recording) m_events.emplace_back(LCLK_U);
|
||||||
|
if (!m_playing) MouseInput::left_click_up();
|
||||||
|
}
|
||||||
|
void right_click_down() override
|
||||||
|
{
|
||||||
|
if (m_recording) m_events.emplace_back(RCLK_D);
|
||||||
|
if (!m_playing) MouseInput::right_click_down();
|
||||||
|
}
|
||||||
|
void right_click_up() override
|
||||||
|
{
|
||||||
|
if (m_recording) m_events.emplace_back(RCLK_U);
|
||||||
|
if (!m_playing) MouseInput::right_click_up();
|
||||||
|
}
|
||||||
|
void double_click() override
|
||||||
|
{
|
||||||
|
if (m_recording) m_events.emplace_back(DDCLK);
|
||||||
|
if (!m_playing) MouseInput::double_click();
|
||||||
|
}
|
||||||
|
void scroll(long v, long d, WheelAxis wa) override
|
||||||
|
{
|
||||||
|
if (m_recording) m_events.emplace_back(SCRL, v, d);
|
||||||
|
if (!m_playing) MouseInput::scroll(v, d, wa);
|
||||||
|
}
|
||||||
|
void move_to(long x, long y) override
|
||||||
|
{
|
||||||
|
if (m_recording) m_events.emplace_back(MV, x, y);
|
||||||
|
if (!m_playing) MouseInput::move_to(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void save(std::ostream &stream)
|
||||||
|
{
|
||||||
|
for (const Event &evt : m_events)
|
||||||
|
stream << evt.type << " " << evt.a << " " << evt.b << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void load(std::istream &stream)
|
||||||
|
{
|
||||||
|
m_events.clear();
|
||||||
|
while (stream.good()) {
|
||||||
|
int type; long a, b;
|
||||||
|
stream >> type >> a >> b;
|
||||||
|
m_events.emplace_back(EEvents(type), a, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void record(bool r) { m_recording = r; if (r) m_events.clear(); }
|
||||||
|
|
||||||
|
void play()
|
||||||
|
{
|
||||||
|
m_playing = true;
|
||||||
|
for (const Event &evt : m_events) {
|
||||||
|
switch (evt.type) {
|
||||||
|
case LCLK_U: MouseInput::left_click_up(); break;
|
||||||
|
case LCLK_D: MouseInput::left_click_down(); break;
|
||||||
|
case RCLK_U: MouseInput::right_click_up(); break;
|
||||||
|
case RCLK_D: MouseInput::right_click_down(); break;
|
||||||
|
case DDCLK: MouseInput::double_click(); break;
|
||||||
|
case SCRL: MouseInput::scroll(evt.a, evt.b, WheelAxis::waVertical); break;
|
||||||
|
case MV: MouseInput::move_to(evt.a, evt.b); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxTheApp->Yield();
|
||||||
|
if (!m_playing)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m_playing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() { m_playing = false; }
|
||||||
|
bool is_playing() const { return m_playing; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// The top level frame of the application.
|
||||||
|
class MyFrame: public wxFrame
|
||||||
|
{
|
||||||
|
// Instantiate the 3D engine.
|
||||||
|
shptr<Scene> m_scene; // Model
|
||||||
|
shptr<Canvas> m_canvas; // Views store
|
||||||
|
shptr<OCSGRenderer> m_ocsgdisplay; // View
|
||||||
|
shptr<ShaderCSGRenderer> m_shadercsg_display; // Another view
|
||||||
|
shptr<Controller> m_ctl; // Controller
|
||||||
|
|
||||||
|
// Add a status bar with progress indication.
|
||||||
|
shptr<Slic3r::GUI::ProgressStatusBar> m_stbar;
|
||||||
|
|
||||||
|
RecorderMouseInput m_mouse;
|
||||||
|
|
||||||
|
// When loading a Model from 3mf and preparing it, we use a separate thread.
|
||||||
|
class SLAJob: public Slic3r::GUI::Job {
|
||||||
|
MyFrame *m_parent;
|
||||||
|
std::unique_ptr<Slic3r::SLAPrint> m_print;
|
||||||
|
std::string m_fname;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SLAJob(MyFrame *frame, const std::string &fname)
|
||||||
|
: Slic3r::GUI::Job{frame->m_stbar}
|
||||||
|
, m_parent{frame}
|
||||||
|
, m_fname{fname}
|
||||||
|
{}
|
||||||
|
|
||||||
|
// Runs in separate thread
|
||||||
|
void process() override;
|
||||||
|
|
||||||
|
const std::string & get_project_fname() const { return m_fname; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
// Runs in the UI thread.
|
||||||
|
void finalize() override
|
||||||
|
{
|
||||||
|
m_parent->m_scene->set_print(std::move(m_print));
|
||||||
|
m_parent->m_stbar->set_status_text(
|
||||||
|
wxString::Format("Model %s loaded.", m_fname));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
uqptr<SLAJob> m_ui_job;
|
||||||
|
|
||||||
|
// To keep track of the running average of measured fps values.
|
||||||
|
double m_fps_avg = 0.;
|
||||||
|
|
||||||
|
// We need the record button across methods
|
||||||
|
wxToggleButton *m_record_btn;
|
||||||
|
wxComboBox * m_alg_select;
|
||||||
|
wxComboBox * m_depth_select;
|
||||||
|
wxComboBox * m_optimization_select;
|
||||||
|
wxSpinCtrl * m_convexity_spin;
|
||||||
|
wxToggleButton *m_csg_toggle;
|
||||||
|
wxToggleButton *m_ms_toggle;
|
||||||
|
wxStaticText *m_fpstext;
|
||||||
|
|
||||||
|
CSGSettings m_csg_settings;
|
||||||
|
|
||||||
|
void read_csg_settings(const wxCmdLineParser &parser);
|
||||||
|
|
||||||
|
void set_renderer_algorithm(const wxString &alg);
|
||||||
|
|
||||||
|
void activate_canvas_display();
|
||||||
|
|
||||||
|
public:
|
||||||
|
MyFrame(const wxString & title,
|
||||||
|
const wxPoint & pos,
|
||||||
|
const wxSize & size,
|
||||||
|
const wxCmdLineParser &parser);
|
||||||
|
|
||||||
|
// Grab a 3mf and load (hollow it out) within the UI job.
|
||||||
|
void load_model(const std::string &fname) {
|
||||||
|
m_ui_job = std::make_unique<SLAJob>(this, fname);
|
||||||
|
m_ui_job->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load a previously stored mouse event log and play it back.
|
||||||
|
void play_back_mouse(const std::string &events_fname)
|
||||||
|
{
|
||||||
|
std::fstream stream(events_fname, std::fstream::in);
|
||||||
|
|
||||||
|
if (stream.good()) {
|
||||||
|
std::string model_name;
|
||||||
|
std::getline(stream, model_name);
|
||||||
|
load_model(model_name);
|
||||||
|
|
||||||
|
while (!m_ui_job->is_finalized())
|
||||||
|
wxTheApp->Yield();;
|
||||||
|
|
||||||
|
int w, h;
|
||||||
|
stream >> w >> h;
|
||||||
|
SetSize(w, h);
|
||||||
|
|
||||||
|
m_mouse.load(stream);
|
||||||
|
if (m_record_btn) m_record_btn->Disable();
|
||||||
|
m_mouse.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Canvas * canvas() { return m_canvas.get(); }
|
||||||
|
const Canvas * canvas() const { return m_canvas.get(); }
|
||||||
|
|
||||||
|
// Bind the canvas mouse events to a class implementing MouseInput interface
|
||||||
|
void bind_canvas_events(MouseInput &msinput);
|
||||||
|
|
||||||
|
double get_fps_average() const { return m_fps_avg; }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Possible OpenCSG configuration values. Will be used on the command line and
|
||||||
|
// on the UI widgets.
|
||||||
|
static const std::vector<wxString> CSG_ALGS = {"Auto", "Goldfeather", "SCS", "EnricoShader"};
|
||||||
|
static const std::vector<wxString> CSG_DEPTH = {"Off", "OcclusionQuery", "On"};
|
||||||
|
static const std::vector<wxString> CSG_OPT = { "Default", "ForceOn", "On", "Off" };
|
||||||
|
|
||||||
|
inline long get_idx(const wxString &a, const std::vector<wxString> &v)
|
||||||
|
{
|
||||||
|
auto it = std::find(v.begin(), v.end(), a.ToStdString());
|
||||||
|
return it - v.begin();
|
||||||
|
};
|
||||||
|
|
||||||
|
class App : public wxApp {
|
||||||
|
MyFrame *m_frame = nullptr;
|
||||||
|
wxString m_fname;
|
||||||
|
public:
|
||||||
|
bool OnInit() override {
|
||||||
|
|
||||||
|
wxCmdLineParser parser(argc, argv);
|
||||||
|
|
||||||
|
parser.AddOption("p", "play", "play back file", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
|
||||||
|
parser.AddOption("a", "algorithm", "OpenCSG algorithm [Auto|Goldfeather|SCS]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
|
||||||
|
parser.AddOption("d", "depth", "OpenCSG depth strategy [Off|OcclusionQuery|On]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
|
||||||
|
parser.AddOption("o", "optimization", "OpenCSG optimization strategy [Default|ForceOn|On|Off]", wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL);
|
||||||
|
parser.AddOption("c", "convexity", "OpenCSG convexity parameter for generic meshes", wxCMD_LINE_VAL_NUMBER, wxCMD_LINE_PARAM_OPTIONAL);
|
||||||
|
parser.AddSwitch("", "disable-csg", "Disable csg rendering", wxCMD_LINE_PARAM_OPTIONAL);
|
||||||
|
|
||||||
|
parser.Parse();
|
||||||
|
|
||||||
|
bool is_play = parser.Found("play", &m_fname);
|
||||||
|
|
||||||
|
m_frame = new MyFrame("PrusaSlicer OpenCSG Demo", wxDefaultPosition, wxSize(1024, 768), parser);
|
||||||
|
|
||||||
|
if (is_play) {
|
||||||
|
Bind(wxEVT_IDLE, &App::Play, this);
|
||||||
|
m_frame->Show( true );
|
||||||
|
} else m_frame->Show( true );
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Play(wxIdleEvent &) {
|
||||||
|
Unbind(wxEVT_IDLE, &App::Play, this);
|
||||||
|
m_frame->play_back_mouse(m_fname.ToStdString());
|
||||||
|
m_frame->Destroy();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
wxIMPLEMENT_APP(App);
|
||||||
|
|
||||||
|
void MyFrame::read_csg_settings(const wxCmdLineParser &parser)
|
||||||
|
{
|
||||||
|
wxString alg;
|
||||||
|
parser.Found("algorithm", &alg);
|
||||||
|
|
||||||
|
wxString depth;
|
||||||
|
parser.Found("depth", &depth);
|
||||||
|
|
||||||
|
wxString opt;
|
||||||
|
parser.Found("optimization", &opt);
|
||||||
|
|
||||||
|
long convexity = 1;
|
||||||
|
parser.Found("convexity", &convexity);
|
||||||
|
|
||||||
|
bool csg_off = parser.Found("disable-csg");
|
||||||
|
|
||||||
|
if (auto a = get_idx(alg, CSG_ALGS) < OpenCSG::AlgorithmUnused)
|
||||||
|
m_csg_settings.set_algo(OpenCSG::Algorithm(a));
|
||||||
|
|
||||||
|
if (auto a = get_idx(depth, CSG_DEPTH) < OpenCSG::DepthComplexityAlgorithmUnused)
|
||||||
|
m_csg_settings.set_depth_algo(OpenCSG::DepthComplexityAlgorithm(a));
|
||||||
|
|
||||||
|
if (auto a = get_idx(opt, CSG_OPT) < OpenCSG::OptimizationUnused)
|
||||||
|
m_csg_settings.set_optimization(OpenCSG::Optimization(a));
|
||||||
|
|
||||||
|
m_csg_settings.set_convexity(unsigned(convexity));
|
||||||
|
m_csg_settings.enable_csg(!csg_off);
|
||||||
|
|
||||||
|
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyFrame::set_renderer_algorithm(const wxString &alg)
|
||||||
|
{
|
||||||
|
long alg_idx = get_idx(alg, CSG_ALGS);
|
||||||
|
if (alg_idx < 0 || alg_idx >= long(CSG_ALGS.size())) return;
|
||||||
|
|
||||||
|
// If there is a valid display in place, save its camera.
|
||||||
|
auto cam = m_canvas->get_display() ?
|
||||||
|
m_canvas->get_display()->get_camera() : nullptr;
|
||||||
|
|
||||||
|
if (alg == "EnricoShader") {
|
||||||
|
m_alg_select->SetSelection(int(alg_idx));
|
||||||
|
m_depth_select->Disable();
|
||||||
|
m_optimization_select->Disable();
|
||||||
|
m_csg_toggle->Disable();
|
||||||
|
|
||||||
|
m_ocsgdisplay.reset();
|
||||||
|
canvas()->set_display(nullptr);
|
||||||
|
m_shadercsg_display = std::make_shared<ShaderCSGRenderer>(canvas());
|
||||||
|
canvas()->set_display(m_shadercsg_display);
|
||||||
|
} else {
|
||||||
|
if (m_csg_settings.get_algo() > 0) m_depth_select->Enable(true);
|
||||||
|
m_alg_select->SetSelection(m_csg_settings.get_algo());
|
||||||
|
m_depth_select->SetSelection(m_csg_settings.get_depth_algo());
|
||||||
|
m_optimization_select->SetSelection(m_csg_settings.get_optimization());
|
||||||
|
m_convexity_spin->SetValue(int(m_csg_settings.get_convexity()));
|
||||||
|
m_csg_toggle->SetValue(m_csg_settings.is_enabled());
|
||||||
|
m_optimization_select->Enable();
|
||||||
|
m_csg_toggle->Enable();
|
||||||
|
|
||||||
|
m_shadercsg_display.reset();
|
||||||
|
canvas()->set_display(nullptr);
|
||||||
|
m_ocsgdisplay = std::make_shared<OCSGRenderer>(canvas());
|
||||||
|
m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
||||||
|
canvas()->set_display(m_ocsgdisplay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cam)
|
||||||
|
m_canvas->get_display()->set_camera(cam);
|
||||||
|
|
||||||
|
m_ctl->remove_displays();
|
||||||
|
m_ctl->add_display(m_canvas->get_display());
|
||||||
|
m_canvas->get_display()->get_fps_counter().add_listener([this](double fps) {
|
||||||
|
m_fpstext->SetLabel(wxString::Format("fps: %.2f", fps));
|
||||||
|
m_fps_avg = 0.9 * m_fps_avg + 0.1 * fps;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (IsShown()) {
|
||||||
|
activate_canvas_display();
|
||||||
|
m_canvas->get_display()->on_scene_updated(*m_scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyFrame::activate_canvas_display()
|
||||||
|
{
|
||||||
|
const wxSize ClientSize = m_canvas->GetClientSize();
|
||||||
|
m_canvas->get_display()->set_active(ClientSize.x, ClientSize.y);
|
||||||
|
enable_multisampling(m_ms_toggle->GetValue());
|
||||||
|
|
||||||
|
m_canvas->Bind(wxEVT_PAINT, [this](wxPaintEvent &) {
|
||||||
|
// This is required even though dc is not used otherwise.
|
||||||
|
wxPaintDC dc(m_canvas.get());
|
||||||
|
const wxSize csize = m_canvas->GetClientSize();
|
||||||
|
m_canvas->get_display()->set_screen_size(csize.x, csize.y);
|
||||||
|
m_canvas->get_display()->repaint();
|
||||||
|
});
|
||||||
|
|
||||||
|
m_canvas->Bind(wxEVT_SIZE, [this](wxSizeEvent &) {
|
||||||
|
const wxSize csize = m_canvas->GetClientSize();
|
||||||
|
m_canvas->get_display()->set_screen_size(csize.x, csize.y);
|
||||||
|
m_canvas->get_display()->repaint();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Do the repaint continuously
|
||||||
|
m_canvas->Bind(wxEVT_IDLE, [this](wxIdleEvent &evt) {
|
||||||
|
m_canvas->get_display()->repaint();
|
||||||
|
evt.RequestMore();
|
||||||
|
});
|
||||||
|
|
||||||
|
bind_canvas_events(m_mouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
MyFrame::MyFrame(const wxString &title, const wxPoint &pos, const wxSize &size,
|
||||||
|
const wxCmdLineParser &parser):
|
||||||
|
wxFrame(nullptr, wxID_ANY, title, pos, size)
|
||||||
|
{
|
||||||
|
wxMenu *menuFile = new wxMenu;
|
||||||
|
menuFile->Append(wxID_OPEN);
|
||||||
|
menuFile->Append(wxID_EXIT);
|
||||||
|
wxMenuBar *menuBar = new wxMenuBar;
|
||||||
|
menuBar->Append( menuFile, "&File" );
|
||||||
|
SetMenuBar( menuBar );
|
||||||
|
|
||||||
|
m_stbar = std::make_shared<Slic3r::GUI::ProgressStatusBar>(this);
|
||||||
|
m_stbar->embed(this);
|
||||||
|
|
||||||
|
SetStatusText( "Welcome to wxWidgets!" );
|
||||||
|
|
||||||
|
int attribList[] =
|
||||||
|
{WX_GL_RGBA, WX_GL_DOUBLEBUFFER,
|
||||||
|
// RGB channels each should be allocated with 8 bit depth. One
|
||||||
|
// should almost certainly get these bit depths by default.
|
||||||
|
WX_GL_MIN_RED, 8, WX_GL_MIN_GREEN, 8, WX_GL_MIN_BLUE, 8,
|
||||||
|
// Requesting an 8 bit alpha channel. Interestingly, the NVIDIA
|
||||||
|
// drivers would most likely work with some alpha plane, but
|
||||||
|
// glReadPixels would not return the alpha channel on NVIDIA if
|
||||||
|
// not requested when the GL context is created.
|
||||||
|
WX_GL_MIN_ALPHA, 8, WX_GL_DEPTH_SIZE, 8, WX_GL_STENCIL_SIZE, 8,
|
||||||
|
WX_GL_SAMPLE_BUFFERS, GL_TRUE, WX_GL_SAMPLES, 4, 0};
|
||||||
|
|
||||||
|
m_scene = std::make_shared<Scene>();
|
||||||
|
m_ctl = std::make_shared<Controller>();
|
||||||
|
m_ctl->set_scene(m_scene);
|
||||||
|
|
||||||
|
m_canvas = std::make_shared<Canvas>(this, wxID_ANY, attribList,
|
||||||
|
wxDefaultPosition, wxDefaultSize,
|
||||||
|
wxWANTS_CHARS | wxFULL_REPAINT_ON_RESIZE);
|
||||||
|
|
||||||
|
read_csg_settings(parser);
|
||||||
|
|
||||||
|
wxPanel *control_panel = new wxPanel(this);
|
||||||
|
|
||||||
|
auto controlsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
auto slider_sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
auto console_sizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|
||||||
|
auto slider = new wxSlider(control_panel, wxID_ANY, 0, 0, 100,
|
||||||
|
wxDefaultPosition, wxDefaultSize,
|
||||||
|
wxSL_VERTICAL);
|
||||||
|
slider_sizer->Add(slider, 1, wxEXPAND);
|
||||||
|
|
||||||
|
m_ms_toggle = new wxToggleButton(control_panel, wxID_ANY, "Multisampling");
|
||||||
|
console_sizer->Add(m_ms_toggle, 0, wxALL | wxEXPAND, 5);
|
||||||
|
|
||||||
|
m_csg_toggle = new wxToggleButton(control_panel, wxID_ANY, "CSG");
|
||||||
|
m_csg_toggle->SetValue(true);
|
||||||
|
console_sizer->Add(m_csg_toggle, 0, wxALL | wxEXPAND, 5);
|
||||||
|
|
||||||
|
auto add_combobox = [control_panel, console_sizer]
|
||||||
|
(const wxString &label, const std::vector<wxString> &list)
|
||||||
|
{
|
||||||
|
auto widget = new wxComboBox(control_panel, wxID_ANY, list[0],
|
||||||
|
wxDefaultPosition, wxDefaultSize,
|
||||||
|
int(list.size()), list.data());
|
||||||
|
|
||||||
|
auto sz = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
sz->Add(new wxStaticText(control_panel, wxID_ANY, label), 0,
|
||||||
|
wxALL | wxALIGN_CENTER, 5);
|
||||||
|
sz->Add(widget, 1, wxALL | wxEXPAND, 5);
|
||||||
|
console_sizer->Add(sz, 0, wxEXPAND);
|
||||||
|
return widget;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto add_spinctl = [control_panel, console_sizer]
|
||||||
|
(const wxString &label, int initial, int min, int max)
|
||||||
|
{
|
||||||
|
auto widget = new wxSpinCtrl(
|
||||||
|
control_panel, wxID_ANY,
|
||||||
|
wxString::Format("%d", initial),
|
||||||
|
wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, min, max,
|
||||||
|
initial);
|
||||||
|
|
||||||
|
auto sz = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
sz->Add(new wxStaticText(control_panel, wxID_ANY, label), 0,
|
||||||
|
wxALL | wxALIGN_CENTER, 5);
|
||||||
|
sz->Add(widget, 1, wxALL | wxEXPAND, 5);
|
||||||
|
console_sizer->Add(sz, 0, wxEXPAND);
|
||||||
|
return widget;
|
||||||
|
};
|
||||||
|
|
||||||
|
m_convexity_spin = add_spinctl("Convexity", CSGSettings::DEFAULT_CONVEXITY, 0, 100);
|
||||||
|
|
||||||
|
m_alg_select = add_combobox("Algorithm", CSG_ALGS);
|
||||||
|
m_depth_select = add_combobox("Depth Complexity", CSG_DEPTH);
|
||||||
|
m_optimization_select = add_combobox("Optimization", CSG_OPT);
|
||||||
|
|
||||||
|
m_fpstext = new wxStaticText(control_panel, wxID_ANY, "");
|
||||||
|
console_sizer->Add(m_fpstext, 0, wxALL, 5);
|
||||||
|
|
||||||
|
m_record_btn = new wxToggleButton(control_panel, wxID_ANY, "Record");
|
||||||
|
console_sizer->Add(m_record_btn, 0, wxALL | wxEXPAND, 5);
|
||||||
|
|
||||||
|
controlsizer->Add(slider_sizer, 0, wxEXPAND);
|
||||||
|
controlsizer->Add(console_sizer, 1, wxEXPAND);
|
||||||
|
|
||||||
|
control_panel->SetSizer(controlsizer);
|
||||||
|
|
||||||
|
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
|
sizer->Add(m_canvas.get(), 1, wxEXPAND);
|
||||||
|
sizer->Add(control_panel, 0, wxEXPAND);
|
||||||
|
SetSizer(sizer);
|
||||||
|
|
||||||
|
wxString alg;
|
||||||
|
if (!parser.Found("algorithm", &alg)) alg = "Auto";
|
||||||
|
|
||||||
|
set_renderer_algorithm(alg);
|
||||||
|
|
||||||
|
Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent &evt){
|
||||||
|
if (m_canvas) RemoveChild(m_canvas.get());
|
||||||
|
m_canvas.reset();
|
||||||
|
if (!m_mouse.is_playing()) evt.Skip();
|
||||||
|
else m_mouse.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
Bind(wxEVT_MENU, [this](wxCommandEvent &) {
|
||||||
|
wxFileDialog dlg(this, "Select project file", wxEmptyString,
|
||||||
|
wxEmptyString, "*.3mf", wxFD_OPEN|wxFD_FILE_MUST_EXIST);
|
||||||
|
|
||||||
|
if (dlg.ShowModal() == wxID_OK) load_model(dlg.GetPath().ToStdString());
|
||||||
|
}, wxID_OPEN);
|
||||||
|
|
||||||
|
Bind(wxEVT_MENU, [this](wxCommandEvent &) { Close(true); }, wxID_EXIT);
|
||||||
|
|
||||||
|
Bind(wxEVT_SHOW, [this](wxShowEvent &) {
|
||||||
|
activate_canvas_display();
|
||||||
|
});
|
||||||
|
|
||||||
|
Bind(wxEVT_SLIDER, [this, slider](wxCommandEvent &) {
|
||||||
|
m_ctl->move_clip_plane(double(slider->GetValue()));
|
||||||
|
});
|
||||||
|
|
||||||
|
m_ms_toggle->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &){
|
||||||
|
enable_multisampling(m_ms_toggle->GetValue());
|
||||||
|
m_canvas->get_display()->repaint();
|
||||||
|
});
|
||||||
|
|
||||||
|
m_csg_toggle->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &){
|
||||||
|
CSGSettings stt = m_ocsgdisplay->get_csgsettings();
|
||||||
|
stt.enable_csg(m_csg_toggle->GetValue());
|
||||||
|
m_ocsgdisplay->apply_csgsettings(stt);
|
||||||
|
});
|
||||||
|
|
||||||
|
m_alg_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
|
||||||
|
wxString alg = m_alg_select->GetValue();
|
||||||
|
int sel = m_alg_select->GetSelection();
|
||||||
|
m_csg_settings.set_algo(sel);
|
||||||
|
set_renderer_algorithm(alg);
|
||||||
|
});
|
||||||
|
|
||||||
|
m_depth_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
|
||||||
|
int sel = m_depth_select->GetSelection();
|
||||||
|
m_csg_settings.set_depth_algo(sel);
|
||||||
|
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
||||||
|
});
|
||||||
|
|
||||||
|
m_optimization_select->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent &) {
|
||||||
|
int sel = m_optimization_select->GetSelection();
|
||||||
|
m_csg_settings.set_optimization(sel);
|
||||||
|
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
||||||
|
});
|
||||||
|
|
||||||
|
m_convexity_spin->Bind(wxEVT_SPINCTRL, [this](wxSpinEvent &) {
|
||||||
|
int c = m_convexity_spin->GetValue();
|
||||||
|
if (c > 0) {
|
||||||
|
m_csg_settings.set_convexity(unsigned(c));
|
||||||
|
if (m_ocsgdisplay) m_ocsgdisplay->apply_csgsettings(m_csg_settings);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
m_record_btn->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &) {
|
||||||
|
if (!m_ui_job) {
|
||||||
|
m_stbar->set_status_text("No project loaded!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_record_btn->GetValue()) {
|
||||||
|
if (auto c = m_canvas->get_display()->get_camera()) reset(*c);
|
||||||
|
m_ctl->on_scene_updated(*m_scene);
|
||||||
|
m_mouse.record(true);
|
||||||
|
} else {
|
||||||
|
m_mouse.record(false);
|
||||||
|
wxFileDialog dlg(this, "Select output file",
|
||||||
|
wxEmptyString, wxEmptyString, "*.events",
|
||||||
|
wxFD_SAVE|wxFD_OVERWRITE_PROMPT);
|
||||||
|
|
||||||
|
if (dlg.ShowModal() == wxID_OK) {
|
||||||
|
std::fstream stream(dlg.GetPath().ToStdString(),
|
||||||
|
std::fstream::out);
|
||||||
|
|
||||||
|
if (stream.good()) {
|
||||||
|
stream << m_ui_job->get_project_fname() << "\n";
|
||||||
|
wxSize winsize = GetSize();
|
||||||
|
stream << winsize.x << " " << winsize.y << "\n";
|
||||||
|
m_mouse.save(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyFrame::bind_canvas_events(MouseInput &ms)
|
||||||
|
{
|
||||||
|
m_canvas->Bind(wxEVT_MOUSEWHEEL, [&ms](wxMouseEvent &evt) {
|
||||||
|
ms.scroll(evt.GetWheelRotation(), evt.GetWheelDelta(),
|
||||||
|
evt.GetWheelAxis() == wxMOUSE_WHEEL_VERTICAL ?
|
||||||
|
Slic3r::GL::MouseInput::waVertical :
|
||||||
|
Slic3r::GL::MouseInput::waHorizontal);
|
||||||
|
});
|
||||||
|
|
||||||
|
m_canvas->Bind(wxEVT_MOTION, [&ms](wxMouseEvent &evt) {
|
||||||
|
ms.move_to(evt.GetPosition().x, evt.GetPosition().y);
|
||||||
|
});
|
||||||
|
|
||||||
|
m_canvas->Bind(wxEVT_RIGHT_DOWN, [&ms](wxMouseEvent & /*evt*/) {
|
||||||
|
ms.right_click_down();
|
||||||
|
});
|
||||||
|
|
||||||
|
m_canvas->Bind(wxEVT_RIGHT_UP, [&ms](wxMouseEvent & /*evt*/) {
|
||||||
|
ms.right_click_up();
|
||||||
|
});
|
||||||
|
|
||||||
|
m_canvas->Bind(wxEVT_LEFT_DOWN, [&ms](wxMouseEvent & /*evt*/) {
|
||||||
|
ms.left_click_down();
|
||||||
|
});
|
||||||
|
|
||||||
|
m_canvas->Bind(wxEVT_LEFT_UP, [&ms](wxMouseEvent & /*evt*/) {
|
||||||
|
ms.left_click_up();
|
||||||
|
});
|
||||||
|
|
||||||
|
ms.add_listener(m_ctl);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyFrame::SLAJob::process()
|
||||||
|
{
|
||||||
|
using SlStatus = Slic3r::PrintBase::SlicingStatus;
|
||||||
|
|
||||||
|
Slic3r::DynamicPrintConfig cfg;
|
||||||
|
auto model = Slic3r::Model::read_from_file(m_fname, &cfg);
|
||||||
|
|
||||||
|
m_print = std::make_unique<Slic3r::SLAPrint>();
|
||||||
|
m_print->apply(model, cfg);
|
||||||
|
|
||||||
|
Slic3r::PrintBase::TaskParams params;
|
||||||
|
params.to_object_step = Slic3r::slaposHollowing;
|
||||||
|
m_print->set_task(params);
|
||||||
|
|
||||||
|
m_print->set_status_callback([this](const SlStatus &status) {
|
||||||
|
update_status(status.percent, status.text);
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
m_print->process();
|
||||||
|
} catch(std::exception &e) {
|
||||||
|
update_status(0, wxString("Exception during processing: ") + e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//int main() {}
|
||||||
|
|
@ -1,2 +1,7 @@
|
||||||
|
if(TARGET OpenVDB::openvdb)
|
||||||
add_executable(openvdb_example openvdb_example.cpp)
|
add_executable(openvdb_example openvdb_example.cpp)
|
||||||
|
|
||||||
target_link_libraries(openvdb_example libslic3r)
|
target_link_libraries(openvdb_example libslic3r)
|
||||||
|
target_link_libraries(openvdb_example OpenVDB::openvdb)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,11 @@ if (MINGW)
|
||||||
add_compile_options(-Wa,-mbig-obj)
|
add_compile_options(-Wa,-mbig-obj)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
set(OpenVDBUtils_SOURCES "")
|
||||||
|
if (TARGET OpenVDB::openvdb)
|
||||||
|
set(OpenVDBUtils_SOURCES OpenVDBUtils.cpp OpenVDBUtils.hpp)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_library(libslic3r STATIC
|
add_library(libslic3r STATIC
|
||||||
pchheader.cpp
|
pchheader.cpp
|
||||||
pchheader.hpp
|
pchheader.hpp
|
||||||
|
|
@ -149,9 +154,9 @@ add_library(libslic3r STATIC
|
||||||
ShortestPath.cpp
|
ShortestPath.cpp
|
||||||
ShortestPath.hpp
|
ShortestPath.hpp
|
||||||
SLAPrint.cpp
|
SLAPrint.cpp
|
||||||
|
SLAPrintSteps.cpp
|
||||||
|
SLAPrintSteps.hpp
|
||||||
SLAPrint.hpp
|
SLAPrint.hpp
|
||||||
SLA/SLAAutoSupports.hpp
|
|
||||||
SLA/SLAAutoSupports.cpp
|
|
||||||
Slicing.cpp
|
Slicing.cpp
|
||||||
Slicing.hpp
|
Slicing.hpp
|
||||||
SlicingAdaptive.cpp
|
SlicingAdaptive.cpp
|
||||||
|
|
@ -180,35 +185,65 @@ add_library(libslic3r STATIC
|
||||||
MinAreaBoundingBox.cpp
|
MinAreaBoundingBox.cpp
|
||||||
miniz_extension.hpp
|
miniz_extension.hpp
|
||||||
miniz_extension.cpp
|
miniz_extension.cpp
|
||||||
SLA/SLACommon.hpp
|
SimplifyMesh.hpp
|
||||||
SLA/SLABoilerPlate.hpp
|
SimplifyMeshImpl.hpp
|
||||||
SLA/SLAPad.hpp
|
SimplifyMesh.cpp
|
||||||
SLA/SLAPad.cpp
|
${OpenVDBUtils_SOURCES}
|
||||||
SLA/SLASupportTreeBuilder.hpp
|
SLA/Common.hpp
|
||||||
SLA/SLASupportTreeBuildsteps.hpp
|
SLA/Common.cpp
|
||||||
SLA/SLASupportTreeBuildsteps.cpp
|
SLA/Pad.hpp
|
||||||
SLA/SLASupportTreeBuilder.cpp
|
SLA/Pad.cpp
|
||||||
SLA/SLAConcurrency.hpp
|
SLA/SupportTreeBuilder.hpp
|
||||||
SLA/SLASupportTree.hpp
|
SLA/SupportTreeBuildsteps.hpp
|
||||||
SLA/SLASupportTree.cpp
|
SLA/SupportTreeBuildsteps.cpp
|
||||||
SLA/SLASupportTreeIGL.cpp
|
SLA/SupportTreeBuilder.cpp
|
||||||
SLA/SLARotfinder.hpp
|
SLA/Concurrency.hpp
|
||||||
SLA/SLARotfinder.cpp
|
SLA/SupportTree.hpp
|
||||||
SLA/SLABoostAdapter.hpp
|
SLA/SupportTree.cpp
|
||||||
SLA/SLASpatIndex.hpp
|
# SLA/SupportTreeIGL.cpp
|
||||||
SLA/SLARaster.hpp
|
SLA/Rotfinder.hpp
|
||||||
SLA/SLARaster.cpp
|
SLA/Rotfinder.cpp
|
||||||
SLA/SLARasterWriter.hpp
|
SLA/BoostAdapter.hpp
|
||||||
SLA/SLARasterWriter.cpp
|
SLA/SpatIndex.hpp
|
||||||
|
SLA/Raster.hpp
|
||||||
|
SLA/Raster.cpp
|
||||||
|
SLA/RasterWriter.hpp
|
||||||
|
SLA/RasterWriter.cpp
|
||||||
SLA/ConcaveHull.hpp
|
SLA/ConcaveHull.hpp
|
||||||
SLA/ConcaveHull.cpp
|
SLA/ConcaveHull.cpp
|
||||||
|
SLA/Hollowing.hpp
|
||||||
|
SLA/Hollowing.cpp
|
||||||
|
SLA/JobController.hpp
|
||||||
|
SLA/SupportPoint.hpp
|
||||||
|
SLA/SupportPointGenerator.hpp
|
||||||
|
SLA/SupportPointGenerator.cpp
|
||||||
|
SLA/Contour3D.hpp
|
||||||
|
SLA/Contour3D.cpp
|
||||||
|
SLA/EigenMesh3D.hpp
|
||||||
|
SLA/Clustering.hpp
|
||||||
)
|
)
|
||||||
|
|
||||||
encoding_check(libslic3r)
|
if (SLIC3R_STATIC)
|
||||||
|
set(CGAL_Boost_USE_STATIC_LIBS ON CACHE BOOL "" FORCE)
|
||||||
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
|
|
||||||
add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE)
|
|
||||||
endif ()
|
endif ()
|
||||||
|
set(CGAL_DO_NOT_WARN_ABOUT_CMAKE_BUILD_TYPE ON CACHE BOOL "" FORCE)
|
||||||
|
|
||||||
|
cmake_policy(PUSH)
|
||||||
|
cmake_policy(SET CMP0011 NEW)
|
||||||
|
find_package(CGAL REQUIRED)
|
||||||
|
cmake_policy(POP)
|
||||||
|
|
||||||
|
add_library(libslic3r_cgal OBJECT MeshBoolean.cpp MeshBoolean.hpp)
|
||||||
|
target_include_directories(libslic3r_cgal PRIVATE
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
$<TARGET_PROPERTY:libigl,INTERFACE_INCLUDE_DIRECTORIES>
|
||||||
|
$<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_INCLUDE_DIRECTORIES>)
|
||||||
|
target_compile_definitions(libslic3r_cgal PRIVATE
|
||||||
|
$<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_COMPILE_DEFINITIONS>)
|
||||||
|
target_compile_options(libslic3r_cgal PRIVATE
|
||||||
|
$<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_COMPILE_OPTIONS>)
|
||||||
|
|
||||||
|
encoding_check(libslic3r)
|
||||||
|
|
||||||
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0)
|
target_compile_definitions(libslic3r PUBLIC -DUSE_TBB -DTBB_USE_CAPTURED_EXCEPTION=0)
|
||||||
target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${LIBNEST2D_INCLUDES} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
target_include_directories(libslic3r PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${LIBNEST2D_INCLUDES} PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
@ -228,10 +263,14 @@ target_link_libraries(libslic3r
|
||||||
qhull
|
qhull
|
||||||
semver
|
semver
|
||||||
TBB::tbb
|
TBB::tbb
|
||||||
# OpenVDB::openvdb
|
$<TARGET_PROPERTY:CGAL::CGAL,INTERFACE_LINK_LIBRARIES>
|
||||||
${CMAKE_DL_LIBS}
|
${CMAKE_DL_LIBS}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (TARGET OpenVDB::openvdb)
|
||||||
|
target_link_libraries(libslic3r OpenVDB::openvdb)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(libslic3r Psapi.lib)
|
target_link_libraries(libslic3r Psapi.lib)
|
||||||
endif()
|
endif()
|
||||||
|
|
@ -239,3 +278,9 @@ endif()
|
||||||
if(SLIC3R_PROFILE)
|
if(SLIC3R_PROFILE)
|
||||||
target_link_libraries(slic3r Shiny)
|
target_link_libraries(slic3r Shiny)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if (SLIC3R_PCH AND NOT SLIC3R_SYNTAXONLY)
|
||||||
|
add_precompiled_header(libslic3r pchheader.hpp FORCEINCLUDE)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
target_sources(libslic3r PRIVATE $<TARGET_OBJECTS:libslic3r_cgal>)
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ const std::string MODEL_CONFIG_FILE = "Metadata/Slic3r_PE_model.config";
|
||||||
const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt";
|
const std::string LAYER_HEIGHTS_PROFILE_FILE = "Metadata/Slic3r_PE_layer_heights_profile.txt";
|
||||||
const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml";
|
const std::string LAYER_CONFIG_RANGES_FILE = "Metadata/Prusa_Slicer_layer_config_ranges.xml";
|
||||||
const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt";
|
const std::string SLA_SUPPORT_POINTS_FILE = "Metadata/Slic3r_PE_sla_support_points.txt";
|
||||||
|
const std::string SLA_DRAIN_HOLES_FILE = "Metadata/Slic3r_PE_sla_drain_holes.txt";
|
||||||
const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml";
|
const std::string CUSTOM_GCODE_PER_PRINT_Z_FILE = "Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml";
|
||||||
|
|
||||||
const char* MODEL_TAG = "model";
|
const char* MODEL_TAG = "model";
|
||||||
|
|
@ -385,6 +386,7 @@ namespace Slic3r {
|
||||||
typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
|
typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
|
||||||
typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap;
|
typedef std::map<int, t_layer_config_ranges> IdToLayerConfigRangesMap;
|
||||||
typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap;
|
typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap;
|
||||||
|
typedef std::map<int, std::vector<sla::DrainHole>> IdToSlaDrainHolesMap;
|
||||||
|
|
||||||
// Version of the 3mf file
|
// Version of the 3mf file
|
||||||
unsigned int m_version;
|
unsigned int m_version;
|
||||||
|
|
@ -403,6 +405,7 @@ namespace Slic3r {
|
||||||
IdToLayerHeightsProfileMap m_layer_heights_profiles;
|
IdToLayerHeightsProfileMap m_layer_heights_profiles;
|
||||||
IdToLayerConfigRangesMap m_layer_config_ranges;
|
IdToLayerConfigRangesMap m_layer_config_ranges;
|
||||||
IdToSlaSupportPointsMap m_sla_support_points;
|
IdToSlaSupportPointsMap m_sla_support_points;
|
||||||
|
IdToSlaDrainHolesMap m_sla_drain_holes;
|
||||||
std::string m_curr_metadata_name;
|
std::string m_curr_metadata_name;
|
||||||
std::string m_curr_characters;
|
std::string m_curr_characters;
|
||||||
std::string m_name;
|
std::string m_name;
|
||||||
|
|
@ -422,6 +425,7 @@ namespace Slic3r {
|
||||||
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
void _extract_layer_heights_profile_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||||
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
void _extract_layer_config_ranges_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||||
void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
void _extract_sla_support_points_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||||
|
void _extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||||
|
|
||||||
void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
void _extract_custom_gcode_per_print_z_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat);
|
||||||
|
|
||||||
|
|
@ -629,6 +633,11 @@ namespace Slic3r {
|
||||||
// extract sla support points file
|
// extract sla support points file
|
||||||
_extract_sla_support_points_from_archive(archive, stat);
|
_extract_sla_support_points_from_archive(archive, stat);
|
||||||
}
|
}
|
||||||
|
else if (boost::algorithm::iequals(name, SLA_DRAIN_HOLES_FILE))
|
||||||
|
{
|
||||||
|
// extract sla support points file
|
||||||
|
_extract_sla_drain_holes_from_archive(archive, stat);
|
||||||
|
}
|
||||||
else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE))
|
else if (boost::algorithm::iequals(name, PRINT_CONFIG_FILE))
|
||||||
{
|
{
|
||||||
// extract slic3r print config file
|
// extract slic3r print config file
|
||||||
|
|
@ -684,6 +693,11 @@ namespace Slic3r {
|
||||||
model_object->sla_points_status = sla::PointsStatus::UserModified;
|
model_object->sla_points_status = sla::PointsStatus::UserModified;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IdToSlaDrainHolesMap::iterator obj_drain_holes = m_sla_drain_holes.find(object.second + 1);
|
||||||
|
if (obj_drain_holes != m_sla_drain_holes.end() && !obj_drain_holes->second.empty()) {
|
||||||
|
model_object->sla_drain_holes = obj_drain_holes->second;
|
||||||
|
}
|
||||||
|
|
||||||
IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first);
|
IdToMetadataMap::iterator obj_metadata = m_objects_metadata.find(object.first);
|
||||||
if (obj_metadata != m_objects_metadata.end())
|
if (obj_metadata != m_objects_metadata.end())
|
||||||
{
|
{
|
||||||
|
|
@ -955,8 +969,9 @@ namespace Slic3r {
|
||||||
|
|
||||||
// Info on format versioning - see 3mf.hpp
|
// Info on format versioning - see 3mf.hpp
|
||||||
int version = 0;
|
int version = 0;
|
||||||
if (!objects.empty() && objects[0].find("support_points_format_version=") != std::string::npos) {
|
std::string key("support_points_format_version=");
|
||||||
objects[0].erase(objects[0].begin(), objects[0].begin() + 30); // removes the string
|
if (!objects.empty() && objects[0].find(key) != std::string::npos) {
|
||||||
|
objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string
|
||||||
version = std::stoi(objects[0]);
|
version = std::stoi(objects[0]);
|
||||||
objects.erase(objects.begin()); // pop the header
|
objects.erase(objects.begin()); // pop the header
|
||||||
}
|
}
|
||||||
|
|
@ -1023,6 +1038,90 @@ namespace Slic3r {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _3MF_Importer::_extract_sla_drain_holes_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat)
|
||||||
|
{
|
||||||
|
if (stat.m_uncomp_size > 0)
|
||||||
|
{
|
||||||
|
std::string buffer(size_t(stat.m_uncomp_size), 0);
|
||||||
|
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
|
||||||
|
if (res == 0)
|
||||||
|
{
|
||||||
|
add_error("Error while reading sla support points data to buffer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer.back() == '\n')
|
||||||
|
buffer.pop_back();
|
||||||
|
|
||||||
|
std::vector<std::string> objects;
|
||||||
|
boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off);
|
||||||
|
|
||||||
|
// Info on format versioning - see 3mf.hpp
|
||||||
|
int version = 0;
|
||||||
|
std::string key("drain_holes_format_version=");
|
||||||
|
if (!objects.empty() && objects[0].find(key) != std::string::npos) {
|
||||||
|
objects[0].erase(objects[0].begin(), objects[0].begin() + long(key.size())); // removes the string
|
||||||
|
version = std::stoi(objects[0]);
|
||||||
|
objects.erase(objects.begin()); // pop the header
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const std::string& object : objects)
|
||||||
|
{
|
||||||
|
std::vector<std::string> object_data;
|
||||||
|
boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off);
|
||||||
|
|
||||||
|
if (object_data.size() != 2)
|
||||||
|
{
|
||||||
|
add_error("Error while reading object data");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> object_data_id;
|
||||||
|
boost::split(object_data_id, object_data[0], boost::is_any_of("="), boost::token_compress_off);
|
||||||
|
if (object_data_id.size() != 2)
|
||||||
|
{
|
||||||
|
add_error("Error while reading object id");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int object_id = std::atoi(object_data_id[1].c_str());
|
||||||
|
if (object_id == 0)
|
||||||
|
{
|
||||||
|
add_error("Found invalid object id");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
IdToSlaDrainHolesMap::iterator object_item = m_sla_drain_holes.find(object_id);
|
||||||
|
if (object_item != m_sla_drain_holes.end())
|
||||||
|
{
|
||||||
|
add_error("Found duplicated SLA drain holes");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> object_data_points;
|
||||||
|
boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off);
|
||||||
|
|
||||||
|
sla::DrainHoles sla_drain_holes;
|
||||||
|
|
||||||
|
if (version == 1) {
|
||||||
|
for (unsigned int i=0; i<object_data_points.size(); i+=8)
|
||||||
|
sla_drain_holes.emplace_back(Vec3f{float(std::atof(object_data_points[i+0].c_str())),
|
||||||
|
float(std::atof(object_data_points[i+1].c_str())),
|
||||||
|
float(std::atof(object_data_points[i+2].c_str()))},
|
||||||
|
Vec3f{float(std::atof(object_data_points[i+3].c_str())),
|
||||||
|
float(std::atof(object_data_points[i+4].c_str())),
|
||||||
|
float(std::atof(object_data_points[i+5].c_str()))},
|
||||||
|
float(std::atof(object_data_points[i+6].c_str())),
|
||||||
|
float(std::atof(object_data_points[i+7].c_str())));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sla_drain_holes.empty())
|
||||||
|
m_sla_drain_holes.insert(IdToSlaDrainHolesMap::value_type(object_id, sla_drain_holes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool _3MF_Importer::_extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model)
|
bool _3MF_Importer::_extract_model_config_from_archive(mz_zip_archive& archive, const mz_zip_archive_file_stat& stat, Model& model)
|
||||||
{
|
{
|
||||||
|
|
@ -1904,6 +2003,7 @@ namespace Slic3r {
|
||||||
bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model);
|
bool _add_layer_height_profile_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||||
bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model);
|
bool _add_layer_config_ranges_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||||
bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model);
|
bool _add_sla_support_points_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||||
|
bool _add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||||
bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config);
|
bool _add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config);
|
||||||
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data);
|
bool _add_model_config_file_to_archive(mz_zip_archive& archive, const Model& model, const IdToObjectDataMap &objects_data);
|
||||||
bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model);
|
bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model);
|
||||||
|
|
@ -2026,6 +2126,14 @@ namespace Slic3r {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_add_sla_drain_holes_file_to_archive(archive, model))
|
||||||
|
{
|
||||||
|
close_zip_writer(&archive);
|
||||||
|
boost::filesystem::remove(filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml").
|
// Adds custom gcode per height file ("Metadata/Prusa_Slicer_custom_gcode_per_print_z.xml").
|
||||||
// All custom gcode per height of whole Model are stored here
|
// All custom gcode per height of whole Model are stored here
|
||||||
if (!_add_custom_gcode_per_print_z_file_to_archive(archive, model))
|
if (!_add_custom_gcode_per_print_z_file_to_archive(archive, model))
|
||||||
|
|
@ -2483,6 +2591,50 @@ namespace Slic3r {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _3MF_Exporter::_add_sla_drain_holes_file_to_archive(mz_zip_archive& archive, Model& model)
|
||||||
|
{
|
||||||
|
const char *const fmt = "object_id=%d|";
|
||||||
|
std::string out;
|
||||||
|
|
||||||
|
unsigned int count = 0;
|
||||||
|
for (const ModelObject* object : model.objects)
|
||||||
|
{
|
||||||
|
++count;
|
||||||
|
auto& drain_holes = object->sla_drain_holes;
|
||||||
|
if (!drain_holes.empty())
|
||||||
|
{
|
||||||
|
out += string_printf(fmt, count);
|
||||||
|
|
||||||
|
// Store the layer height profile as a single space separated list.
|
||||||
|
for (size_t i = 0; i < drain_holes.size(); ++i)
|
||||||
|
out += string_printf((i == 0 ? "%f %f %f %f %f %f %f %f" : " %f %f %f %f %f %f %f %f"),
|
||||||
|
drain_holes[i].pos(0),
|
||||||
|
drain_holes[i].pos(1),
|
||||||
|
drain_holes[i].pos(2),
|
||||||
|
drain_holes[i].normal(0),
|
||||||
|
drain_holes[i].normal(1),
|
||||||
|
drain_holes[i].normal(2),
|
||||||
|
drain_holes[i].radius,
|
||||||
|
drain_holes[i].height);
|
||||||
|
|
||||||
|
out += "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!out.empty())
|
||||||
|
{
|
||||||
|
// Adds version header at the beginning:
|
||||||
|
out = std::string("drain_holes_format_version=") + std::to_string(drain_holes_format_version) + std::string("\n") + out;
|
||||||
|
|
||||||
|
if (!mz_zip_writer_add_mem(&archive, SLA_DRAIN_HOLES_FILE.c_str(), static_cast<const void*>(out.data()), out.length(), mz_uint(MZ_DEFAULT_COMPRESSION)))
|
||||||
|
{
|
||||||
|
add_error("Unable to add sla support points file to archive");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config)
|
bool _3MF_Exporter::_add_print_config_file_to_archive(mz_zip_archive& archive, const DynamicPrintConfig &config)
|
||||||
{
|
{
|
||||||
char buffer[1024];
|
char buffer[1024];
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,10 @@ namespace Slic3r {
|
||||||
support_points_format_version = 1
|
support_points_format_version = 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum {
|
||||||
|
drain_holes_format_version = 1
|
||||||
|
};
|
||||||
|
|
||||||
class Model;
|
class Model;
|
||||||
class DynamicPrintConfig;
|
class DynamicPrintConfig;
|
||||||
#if ENABLE_THUMBNAIL_GENERATOR
|
#if ENABLE_THUMBNAIL_GENERATOR
|
||||||
|
|
|
||||||
|
|
@ -355,6 +355,35 @@ bool objparse(const char *path, ObjData &data)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool objparse(std::istream &stream, ObjData &data)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
char buf[65536 * 2];
|
||||||
|
size_t len = 0;
|
||||||
|
size_t lenPrev = 0;
|
||||||
|
while ((len = size_t(stream.read(buf + lenPrev, 65536).gcount())) != 0) {
|
||||||
|
len += lenPrev;
|
||||||
|
size_t lastLine = 0;
|
||||||
|
for (size_t i = 0; i < len; ++ i)
|
||||||
|
if (buf[i] == '\r' || buf[i] == '\n') {
|
||||||
|
buf[i] = 0;
|
||||||
|
char *c = buf + lastLine;
|
||||||
|
while (*c == ' ' || *c == '\t')
|
||||||
|
++ c;
|
||||||
|
obj_parseline(c, data);
|
||||||
|
lastLine = i + 1;
|
||||||
|
}
|
||||||
|
lenPrev = len - lastLine;
|
||||||
|
memmove(buf, buf + lastLine, lenPrev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (std::bad_alloc&) {
|
||||||
|
printf("Out of memory\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
bool savevector(FILE *pFile, const std::vector<T> &v)
|
bool savevector(FILE *pFile, const std::vector<T> &v)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <istream>
|
||||||
|
|
||||||
namespace ObjParser {
|
namespace ObjParser {
|
||||||
|
|
||||||
|
|
@ -97,6 +98,7 @@ struct ObjData {
|
||||||
};
|
};
|
||||||
|
|
||||||
extern bool objparse(const char *path, ObjData &data);
|
extern bool objparse(const char *path, ObjData &data);
|
||||||
|
extern bool objparse(std::istream &stream, ObjData &data);
|
||||||
|
|
||||||
extern bool objbinsave(const char *path, const ObjData &data);
|
extern bool objbinsave(const char *path, const ObjData &data);
|
||||||
|
|
||||||
|
|
|
||||||
170
src/libslic3r/MeshBoolean.cpp
Normal file
170
src/libslic3r/MeshBoolean.cpp
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
#include "MeshBoolean.hpp"
|
||||||
|
#include "libslic3r/TriangleMesh.hpp"
|
||||||
|
#undef PI
|
||||||
|
|
||||||
|
// Include igl first. It defines "L" macro which then clashes with our localization
|
||||||
|
#include <igl/copyleft/cgal/mesh_boolean.h>
|
||||||
|
#undef L
|
||||||
|
|
||||||
|
// CGAL headers
|
||||||
|
#include <CGAL/Polygon_mesh_processing/corefinement.h>
|
||||||
|
#include <CGAL/Exact_integer.h>
|
||||||
|
#include <CGAL/Surface_mesh.h>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace MeshBoolean {
|
||||||
|
|
||||||
|
typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
|
||||||
|
typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
|
||||||
|
|
||||||
|
typedef std::pair<Eigen::MatrixXd, Eigen::MatrixXi> EigenMesh;
|
||||||
|
|
||||||
|
static TriangleMesh eigen_to_triangle_mesh(const Eigen::MatrixXd& VC, const Eigen::MatrixXi& FC)
|
||||||
|
{
|
||||||
|
Pointf3s points(size_t(VC.rows()));
|
||||||
|
std::vector<Vec3crd> facets(size_t(FC.rows()));
|
||||||
|
|
||||||
|
for (Eigen::Index i = 0; i < VC.rows(); ++i)
|
||||||
|
points[size_t(i)] = VC.row(i);
|
||||||
|
|
||||||
|
for (Eigen::Index i = 0; i < FC.rows(); ++i)
|
||||||
|
facets[size_t(i)] = FC.row(i);
|
||||||
|
|
||||||
|
TriangleMesh out{points, facets};
|
||||||
|
out.require_shared_vertices();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static EigenMesh triangle_mesh_to_eigen_mesh(const TriangleMesh &mesh)
|
||||||
|
{
|
||||||
|
EigenMesh emesh;
|
||||||
|
emesh.first = MapMatrixXfUnaligned(mesh.its.vertices.front().data(),
|
||||||
|
Eigen::Index(mesh.its.vertices.size()),
|
||||||
|
3).cast<double>();
|
||||||
|
|
||||||
|
emesh.second = MapMatrixXiUnaligned(mesh.its.indices.front().data(),
|
||||||
|
Eigen::Index(mesh.its.indices.size()),
|
||||||
|
3);
|
||||||
|
return emesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
void minus(TriangleMesh& A, const TriangleMesh& B)
|
||||||
|
{
|
||||||
|
auto [VA, FA] = triangle_mesh_to_eigen_mesh(A);
|
||||||
|
auto [VB, FB] = triangle_mesh_to_eigen_mesh(B);
|
||||||
|
|
||||||
|
Eigen::MatrixXd VC;
|
||||||
|
Eigen::MatrixXi FC;
|
||||||
|
igl::MeshBooleanType boolean_type(igl::MESH_BOOLEAN_TYPE_MINUS);
|
||||||
|
igl::copyleft::cgal::mesh_boolean(VA, FA, VB, FB, boolean_type, VC, FC);
|
||||||
|
|
||||||
|
A = eigen_to_triangle_mesh(VC, FC);
|
||||||
|
}
|
||||||
|
|
||||||
|
void self_union(TriangleMesh& mesh)
|
||||||
|
{
|
||||||
|
auto [V, F] = triangle_mesh_to_eigen_mesh(mesh);
|
||||||
|
|
||||||
|
Eigen::MatrixXd VC;
|
||||||
|
Eigen::MatrixXi FC;
|
||||||
|
|
||||||
|
igl::MeshBooleanType boolean_type(igl::MESH_BOOLEAN_TYPE_UNION);
|
||||||
|
igl::copyleft::cgal::mesh_boolean(V, F, Eigen::MatrixXd(), Eigen::MatrixXi(), boolean_type, VC, FC);
|
||||||
|
|
||||||
|
mesh = eigen_to_triangle_mesh(VC, FC);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace cgal {
|
||||||
|
|
||||||
|
namespace CGALProc = CGAL::Polygon_mesh_processing;
|
||||||
|
namespace CGALParams = CGAL::Polygon_mesh_processing::parameters;
|
||||||
|
|
||||||
|
using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel;
|
||||||
|
using _CGALMesh = CGAL::Surface_mesh<Kernel::Point_3>;
|
||||||
|
|
||||||
|
struct CGALMesh { _CGALMesh m; };
|
||||||
|
|
||||||
|
static void triangle_mesh_to_cgal(const TriangleMesh &M, _CGALMesh &out)
|
||||||
|
{
|
||||||
|
for (const Vec3f &v : M.its.vertices)
|
||||||
|
out.add_vertex(_CGALMesh::Point(v.x(), v.y(), v.z()));
|
||||||
|
|
||||||
|
for (const Vec3crd &face : M.its.indices) {
|
||||||
|
auto f = face.cast<CGAL::SM_Vertex_index>();
|
||||||
|
out.add_face(f(0), f(1), f(2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static TriangleMesh cgal_to_triangle_mesh(const _CGALMesh &cgalmesh)
|
||||||
|
{
|
||||||
|
Pointf3s points;
|
||||||
|
std::vector<Vec3crd> facets;
|
||||||
|
points.reserve(cgalmesh.num_vertices());
|
||||||
|
facets.reserve(cgalmesh.num_faces());
|
||||||
|
|
||||||
|
for (auto &vi : cgalmesh.vertices()) {
|
||||||
|
auto &v = cgalmesh.point(vi); // Don't ask...
|
||||||
|
points.emplace_back(v.x(), v.y(), v.z());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &face : cgalmesh.faces()) {
|
||||||
|
auto vtc = cgalmesh.vertices_around_face(cgalmesh.halfedge(face));
|
||||||
|
int i = 0;
|
||||||
|
Vec3crd trface;
|
||||||
|
for (auto v : vtc) trface(i++) = int(v.idx());
|
||||||
|
facets.emplace_back(trface);
|
||||||
|
}
|
||||||
|
|
||||||
|
TriangleMesh out{points, facets};
|
||||||
|
out.require_shared_vertices();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<CGALMesh> triangle_mesh_to_cgal(const TriangleMesh &M)
|
||||||
|
{
|
||||||
|
auto out = std::make_unique<CGALMesh>();
|
||||||
|
triangle_mesh_to_cgal(M, out->m);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cgal_to_triangle_mesh(const CGALMesh &cgalmesh, TriangleMesh &out)
|
||||||
|
{
|
||||||
|
out = cgal_to_triangle_mesh(cgalmesh.m);
|
||||||
|
}
|
||||||
|
|
||||||
|
void minus(CGALMesh &A, CGALMesh &B)
|
||||||
|
{
|
||||||
|
CGALProc::corefine_and_compute_difference(A.m, B.m, A.m);
|
||||||
|
}
|
||||||
|
|
||||||
|
void self_union(CGALMesh &A)
|
||||||
|
{
|
||||||
|
CGALProc::corefine(A.m, A.m);
|
||||||
|
}
|
||||||
|
|
||||||
|
void minus(TriangleMesh &A, const TriangleMesh &B)
|
||||||
|
{
|
||||||
|
CGALMesh meshA;
|
||||||
|
CGALMesh meshB;
|
||||||
|
triangle_mesh_to_cgal(A, meshA.m);
|
||||||
|
triangle_mesh_to_cgal(B, meshB.m);
|
||||||
|
|
||||||
|
CGALMesh meshResult;
|
||||||
|
CGALProc::corefine_and_compute_difference(meshA.m, meshB.m, meshResult.m);
|
||||||
|
|
||||||
|
A = cgal_to_triangle_mesh(meshResult.m);
|
||||||
|
}
|
||||||
|
|
||||||
|
void self_union(TriangleMesh &m)
|
||||||
|
{
|
||||||
|
_CGALMesh cgalmesh;
|
||||||
|
triangle_mesh_to_cgal(m, cgalmesh);
|
||||||
|
CGALProc::corefine(cgalmesh, cgalmesh);
|
||||||
|
|
||||||
|
m = cgal_to_triangle_mesh(cgalmesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace cgal
|
||||||
|
|
||||||
|
} // namespace MeshBoolean
|
||||||
|
} // namespace Slic3r
|
||||||
38
src/libslic3r/MeshBoolean.hpp
Normal file
38
src/libslic3r/MeshBoolean.hpp
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
#ifndef libslic3r_MeshBoolean_hpp_
|
||||||
|
#define libslic3r_MeshBoolean_hpp_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class TriangleMesh;
|
||||||
|
|
||||||
|
namespace MeshBoolean {
|
||||||
|
|
||||||
|
void minus(TriangleMesh& A, const TriangleMesh& B);
|
||||||
|
void self_union(TriangleMesh& mesh);
|
||||||
|
|
||||||
|
namespace cgal {
|
||||||
|
|
||||||
|
struct CGALMesh;
|
||||||
|
|
||||||
|
std::unique_ptr<CGALMesh> triangle_mesh_to_cgal(const TriangleMesh &M);
|
||||||
|
void cgal_to_triangle_mesh(const CGALMesh &cgalmesh, TriangleMesh &out);
|
||||||
|
|
||||||
|
// Do boolean mesh difference with CGAL bypassing igl.
|
||||||
|
void minus(TriangleMesh &A, const TriangleMesh &B);
|
||||||
|
|
||||||
|
// Do self union only with CGAL.
|
||||||
|
void self_union(TriangleMesh& mesh);
|
||||||
|
|
||||||
|
// does A = A - B
|
||||||
|
// CGAL takes non-const objects as arguments. I suppose it doesn't change B but
|
||||||
|
// there is no official garantee.
|
||||||
|
void minus(CGALMesh &A, CGALMesh &B);
|
||||||
|
void self_union(CGALMesh &A);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace MeshBoolean
|
||||||
|
} // namespace Slic3r
|
||||||
|
#endif // libslic3r_MeshBoolean_hpp_
|
||||||
|
|
@ -620,6 +620,7 @@ ModelObject& ModelObject::assign_copy(const ModelObject &rhs)
|
||||||
assert(this->config.id() == rhs.config.id());
|
assert(this->config.id() == rhs.config.id());
|
||||||
this->sla_support_points = rhs.sla_support_points;
|
this->sla_support_points = rhs.sla_support_points;
|
||||||
this->sla_points_status = rhs.sla_points_status;
|
this->sla_points_status = rhs.sla_points_status;
|
||||||
|
this->sla_drain_holes = rhs.sla_drain_holes;
|
||||||
this->layer_config_ranges = rhs.layer_config_ranges; // #ys_FIXME_experiment
|
this->layer_config_ranges = rhs.layer_config_ranges; // #ys_FIXME_experiment
|
||||||
this->layer_height_profile = rhs.layer_height_profile;
|
this->layer_height_profile = rhs.layer_height_profile;
|
||||||
this->printable = rhs.printable;
|
this->printable = rhs.printable;
|
||||||
|
|
@ -660,6 +661,7 @@ ModelObject& ModelObject::assign_copy(ModelObject &&rhs)
|
||||||
assert(this->config.id() == rhs.config.id());
|
assert(this->config.id() == rhs.config.id());
|
||||||
this->sla_support_points = std::move(rhs.sla_support_points);
|
this->sla_support_points = std::move(rhs.sla_support_points);
|
||||||
this->sla_points_status = std::move(rhs.sla_points_status);
|
this->sla_points_status = std::move(rhs.sla_points_status);
|
||||||
|
this->sla_drain_holes = std::move(rhs.sla_drain_holes);
|
||||||
this->layer_config_ranges = std::move(rhs.layer_config_ranges); // #ys_FIXME_experiment
|
this->layer_config_ranges = std::move(rhs.layer_config_ranges); // #ys_FIXME_experiment
|
||||||
this->layer_height_profile = std::move(rhs.layer_height_profile);
|
this->layer_height_profile = std::move(rhs.layer_height_profile);
|
||||||
this->origin_translation = std::move(rhs.origin_translation);
|
this->origin_translation = std::move(rhs.origin_translation);
|
||||||
|
|
@ -1113,6 +1115,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
||||||
if (keep_upper) {
|
if (keep_upper) {
|
||||||
upper->set_model(nullptr);
|
upper->set_model(nullptr);
|
||||||
upper->sla_support_points.clear();
|
upper->sla_support_points.clear();
|
||||||
|
lower->sla_drain_holes.clear();
|
||||||
upper->sla_points_status = sla::PointsStatus::NoPoints;
|
upper->sla_points_status = sla::PointsStatus::NoPoints;
|
||||||
upper->clear_volumes();
|
upper->clear_volumes();
|
||||||
upper->input_file = "";
|
upper->input_file = "";
|
||||||
|
|
@ -1121,6 +1124,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
||||||
if (keep_lower) {
|
if (keep_lower) {
|
||||||
lower->set_model(nullptr);
|
lower->set_model(nullptr);
|
||||||
lower->sla_support_points.clear();
|
lower->sla_support_points.clear();
|
||||||
|
lower->sla_drain_holes.clear();
|
||||||
lower->sla_points_status = sla::PointsStatus::NoPoints;
|
lower->sla_points_status = sla::PointsStatus::NoPoints;
|
||||||
lower->clear_volumes();
|
lower->clear_volumes();
|
||||||
lower->input_file = "";
|
lower->input_file = "";
|
||||||
|
|
@ -1156,7 +1160,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
||||||
if (keep_upper) { upper->add_volume(*volume); }
|
if (keep_upper) { upper->add_volume(*volume); }
|
||||||
if (keep_lower) { lower->add_volume(*volume); }
|
if (keep_lower) { lower->add_volume(*volume); }
|
||||||
}
|
}
|
||||||
else {
|
else if (! volume->mesh().empty()) {
|
||||||
|
|
||||||
TriangleMesh upper_mesh, lower_mesh;
|
TriangleMesh upper_mesh, lower_mesh;
|
||||||
|
|
||||||
// Transform the mesh by the combined transformation matrix.
|
// Transform the mesh by the combined transformation matrix.
|
||||||
|
|
@ -1165,6 +1170,8 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
|
||||||
mesh.transform(instance_matrix * volume_matrix, true);
|
mesh.transform(instance_matrix * volume_matrix, true);
|
||||||
volume->reset_mesh();
|
volume->reset_mesh();
|
||||||
|
|
||||||
|
mesh.require_shared_vertices();
|
||||||
|
|
||||||
// Perform cut
|
// Perform cut
|
||||||
TriangleMeshSlicer tms(&mesh);
|
TriangleMeshSlicer tms(&mesh);
|
||||||
tms.cut(float(z), &upper_mesh, &lower_mesh);
|
tms.cut(float(z), &upper_mesh, &lower_mesh);
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@
|
||||||
#include "Point.hpp"
|
#include "Point.hpp"
|
||||||
#include "PrintConfig.hpp"
|
#include "PrintConfig.hpp"
|
||||||
#include "Slicing.hpp"
|
#include "Slicing.hpp"
|
||||||
#include "SLA/SLACommon.hpp"
|
#include "SLA/SupportPoint.hpp"
|
||||||
|
#include "SLA/Hollowing.hpp"
|
||||||
#include "TriangleMesh.hpp"
|
#include "TriangleMesh.hpp"
|
||||||
#include "Arrange.hpp"
|
#include "Arrange.hpp"
|
||||||
#include "CustomGCode.hpp"
|
#include "CustomGCode.hpp"
|
||||||
|
|
@ -199,11 +200,14 @@ public:
|
||||||
// This vector holds position of selected support points for SLA. The data are
|
// This vector holds position of selected support points for SLA. The data are
|
||||||
// saved in mesh coordinates to allow using them for several instances.
|
// saved in mesh coordinates to allow using them for several instances.
|
||||||
// The format is (x, y, z, point_size, supports_island)
|
// The format is (x, y, z, point_size, supports_island)
|
||||||
std::vector<sla::SupportPoint> sla_support_points;
|
sla::SupportPoints sla_support_points;
|
||||||
// To keep track of where the points came from (used for synchronization between
|
// To keep track of where the points came from (used for synchronization between
|
||||||
// the SLA gizmo and the backend).
|
// the SLA gizmo and the backend).
|
||||||
sla::PointsStatus sla_points_status = sla::PointsStatus::NoPoints;
|
sla::PointsStatus sla_points_status = sla::PointsStatus::NoPoints;
|
||||||
|
|
||||||
|
// Holes to be drilled into the object so resin can flow out
|
||||||
|
sla::DrainHoles sla_drain_holes;
|
||||||
|
|
||||||
/* This vector accumulates the total translation applied to the object by the
|
/* This vector accumulates the total translation applied to the object by the
|
||||||
center_around_origin() method. Callers might want to apply the same translation
|
center_around_origin() method. Callers might want to apply the same translation
|
||||||
to new volumes before adding them to this object in order to preserve alignment
|
to new volumes before adding them to this object in order to preserve alignment
|
||||||
|
|
@ -373,7 +377,7 @@ private:
|
||||||
template<class Archive> void serialize(Archive &ar) {
|
template<class Archive> void serialize(Archive &ar) {
|
||||||
ar(cereal::base_class<ObjectBase>(this));
|
ar(cereal::base_class<ObjectBase>(this));
|
||||||
Internal::StaticSerializationWrapper<ModelConfig> config_wrapper(config);
|
Internal::StaticSerializationWrapper<ModelConfig> config_wrapper(config);
|
||||||
ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, printable, origin_translation,
|
ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation,
|
||||||
m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid);
|
m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
135
src/libslic3r/OpenVDBUtils.cpp
Normal file
135
src/libslic3r/OpenVDBUtils.cpp
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
#define NOMINMAX
|
||||||
|
#include "OpenVDBUtils.hpp"
|
||||||
|
#include <openvdb/tools/MeshToVolume.h>
|
||||||
|
#include <openvdb/tools/VolumeToMesh.h>
|
||||||
|
#include <openvdb/tools/LevelSetRebuild.h>
|
||||||
|
|
||||||
|
//#include "MTUtils.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class TriangleMeshDataAdapter {
|
||||||
|
public:
|
||||||
|
const TriangleMesh &mesh;
|
||||||
|
|
||||||
|
size_t polygonCount() const { return mesh.its.indices.size(); }
|
||||||
|
size_t pointCount() const { return mesh.its.vertices.size(); }
|
||||||
|
size_t vertexCount(size_t) const { return 3; }
|
||||||
|
|
||||||
|
// Return position pos in local grid index space for polygon n and vertex v
|
||||||
|
void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Contour3DDataAdapter {
|
||||||
|
public:
|
||||||
|
const sla::Contour3D &mesh;
|
||||||
|
|
||||||
|
size_t polygonCount() const { return mesh.faces3.size() + mesh.faces4.size(); }
|
||||||
|
size_t pointCount() const { return mesh.points.size(); }
|
||||||
|
size_t vertexCount(size_t n) const { return n < mesh.faces3.size() ? 3 : 4; }
|
||||||
|
|
||||||
|
// Return position pos in local grid index space for polygon n and vertex v
|
||||||
|
void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
void TriangleMeshDataAdapter::getIndexSpacePoint(size_t n,
|
||||||
|
size_t v,
|
||||||
|
openvdb::Vec3d &pos) const
|
||||||
|
{
|
||||||
|
auto vidx = size_t(mesh.its.indices[n](Eigen::Index(v)));
|
||||||
|
Slic3r::Vec3d p = mesh.its.vertices[vidx].cast<double>();
|
||||||
|
pos = {p.x(), p.y(), p.z()};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Contour3DDataAdapter::getIndexSpacePoint(size_t n,
|
||||||
|
size_t v,
|
||||||
|
openvdb::Vec3d &pos) const
|
||||||
|
{
|
||||||
|
size_t vidx = 0;
|
||||||
|
if (n < mesh.faces3.size()) vidx = size_t(mesh.faces3[n](Eigen::Index(v)));
|
||||||
|
else vidx = size_t(mesh.faces4[n - mesh.faces3.size()](Eigen::Index(v)));
|
||||||
|
|
||||||
|
Slic3r::Vec3d p = mesh.points[vidx];
|
||||||
|
pos = {p.x(), p.y(), p.z()};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Do I need to call initialize? Seems to work without it as well but the
|
||||||
|
// docs say it should be called ones. It does a mutex lock-unlock sequence all
|
||||||
|
// even if was called previously.
|
||||||
|
|
||||||
|
openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh,
|
||||||
|
const openvdb::math::Transform &tr,
|
||||||
|
float exteriorBandWidth,
|
||||||
|
float interiorBandWidth,
|
||||||
|
int flags)
|
||||||
|
{
|
||||||
|
openvdb::initialize();
|
||||||
|
return openvdb::tools::meshToVolume<openvdb::FloatGrid>(
|
||||||
|
TriangleMeshDataAdapter{mesh}, tr, exteriorBandWidth,
|
||||||
|
interiorBandWidth, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &mesh,
|
||||||
|
const openvdb::math::Transform &tr,
|
||||||
|
float exteriorBandWidth,
|
||||||
|
float interiorBandWidth,
|
||||||
|
int flags)
|
||||||
|
{
|
||||||
|
openvdb::initialize();
|
||||||
|
return openvdb::tools::meshToVolume<openvdb::FloatGrid>(
|
||||||
|
Contour3DDataAdapter{mesh}, tr, exteriorBandWidth, interiorBandWidth,
|
||||||
|
flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Grid>
|
||||||
|
sla::Contour3D _volumeToMesh(const Grid &grid,
|
||||||
|
double isovalue,
|
||||||
|
double adaptivity,
|
||||||
|
bool relaxDisorientedTriangles)
|
||||||
|
{
|
||||||
|
openvdb::initialize();
|
||||||
|
|
||||||
|
std::vector<openvdb::Vec3s> points;
|
||||||
|
std::vector<openvdb::Vec3I> triangles;
|
||||||
|
std::vector<openvdb::Vec4I> quads;
|
||||||
|
|
||||||
|
openvdb::tools::volumeToMesh(grid, points, triangles, quads, isovalue,
|
||||||
|
adaptivity, relaxDisorientedTriangles);
|
||||||
|
|
||||||
|
sla::Contour3D ret;
|
||||||
|
ret.points.reserve(points.size());
|
||||||
|
ret.faces3.reserve(triangles.size());
|
||||||
|
ret.faces4.reserve(quads.size());
|
||||||
|
|
||||||
|
for (auto &v : points) ret.points.emplace_back(to_vec3d(v));
|
||||||
|
for (auto &v : triangles) ret.faces3.emplace_back(to_vec3i(v));
|
||||||
|
for (auto &v : quads) ret.faces4.emplace_back(to_vec4i(v));
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
TriangleMesh grid_to_mesh(const openvdb::FloatGrid &grid,
|
||||||
|
double isovalue,
|
||||||
|
double adaptivity,
|
||||||
|
bool relaxDisorientedTriangles)
|
||||||
|
{
|
||||||
|
return to_triangle_mesh(
|
||||||
|
_volumeToMesh(grid, isovalue, adaptivity, relaxDisorientedTriangles));
|
||||||
|
}
|
||||||
|
|
||||||
|
sla::Contour3D grid_to_contour3d(const openvdb::FloatGrid &grid,
|
||||||
|
double isovalue,
|
||||||
|
double adaptivity,
|
||||||
|
bool relaxDisorientedTriangles)
|
||||||
|
{
|
||||||
|
return _volumeToMesh(grid, isovalue, adaptivity,
|
||||||
|
relaxDisorientedTriangles);
|
||||||
|
}
|
||||||
|
|
||||||
|
openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, double iso, double er, double ir)
|
||||||
|
{
|
||||||
|
return openvdb::tools::levelSetRebuild(grid, float(iso), float(er), float(ir));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
45
src/libslic3r/OpenVDBUtils.hpp
Normal file
45
src/libslic3r/OpenVDBUtils.hpp
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#ifndef OPENVDBUTILS_HPP
|
||||||
|
#define OPENVDBUTILS_HPP
|
||||||
|
|
||||||
|
#include <libslic3r/TriangleMesh.hpp>
|
||||||
|
#include <libslic3r/SLA/Common.hpp>
|
||||||
|
#include <libslic3r/SLA/Contour3D.hpp>
|
||||||
|
#include <openvdb/openvdb.h>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
inline Vec3f to_vec3f(const openvdb::Vec3s &v) { return Vec3f{v.x(), v.y(), v.z()}; }
|
||||||
|
inline Vec3d to_vec3d(const openvdb::Vec3s &v) { return to_vec3f(v).cast<double>(); }
|
||||||
|
inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[0]), int(v[1]), int(v[2])}; }
|
||||||
|
inline Vec4i to_vec4i(const openvdb::Vec4I &v) { return Vec4i{int(v[0]), int(v[1]), int(v[2]), int(v[3])}; }
|
||||||
|
|
||||||
|
openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh & mesh,
|
||||||
|
const openvdb::math::Transform &tr = {},
|
||||||
|
float exteriorBandWidth = 3.0f,
|
||||||
|
float interiorBandWidth = 3.0f,
|
||||||
|
int flags = 0);
|
||||||
|
|
||||||
|
openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D & mesh,
|
||||||
|
const openvdb::math::Transform &tr = {},
|
||||||
|
float exteriorBandWidth = 3.0f,
|
||||||
|
float interiorBandWidth = 3.0f,
|
||||||
|
int flags = 0);
|
||||||
|
|
||||||
|
sla::Contour3D grid_to_contour3d(const openvdb::FloatGrid &grid,
|
||||||
|
double isovalue,
|
||||||
|
double adaptivity,
|
||||||
|
bool relaxDisorientedTriangles = true);
|
||||||
|
|
||||||
|
TriangleMesh grid_to_mesh(const openvdb::FloatGrid &grid,
|
||||||
|
double isovalue = 0.0,
|
||||||
|
double adaptivity = 0.0,
|
||||||
|
bool relaxDisorientedTriangles = true);
|
||||||
|
|
||||||
|
openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid,
|
||||||
|
double iso,
|
||||||
|
double ext_range = 3.,
|
||||||
|
double int_range = 3.);
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif // OPENVDBUTILS_HPP
|
||||||
|
|
@ -2873,6 +2873,41 @@ void PrintConfigDef::init_sla_params()
|
||||||
def->min = 0;
|
def->min = 0;
|
||||||
def->mode = comExpert;
|
def->mode = comExpert;
|
||||||
def->set_default_value(new ConfigOptionFloat(0.3));
|
def->set_default_value(new ConfigOptionFloat(0.3));
|
||||||
|
|
||||||
|
def = this->add("hollowing_enable", coBool);
|
||||||
|
def->label = L("Enable hollowing");
|
||||||
|
def->category = L("Hollowing");
|
||||||
|
def->tooltip = L("Hollow out a model to have an empty interior");
|
||||||
|
def->mode = comSimple;
|
||||||
|
def->set_default_value(new ConfigOptionBool(false));
|
||||||
|
|
||||||
|
def = this->add("hollowing_min_thickness", coFloat);
|
||||||
|
def->label = L("Hollowing thickness");
|
||||||
|
def->category = L("Hollowing");
|
||||||
|
def->tooltip = L("Minimum wall thickness of a hollowed model.");
|
||||||
|
def->sidetext = L("mm");
|
||||||
|
def->min = 1;
|
||||||
|
def->max = 10;
|
||||||
|
def->mode = comSimple;
|
||||||
|
def->set_default_value(new ConfigOptionFloat(3.));
|
||||||
|
|
||||||
|
def = this->add("hollowing_quality", coFloat);
|
||||||
|
def->label = L("Hollowing accuracy");
|
||||||
|
def->category = L("Hollowing");
|
||||||
|
def->tooltip = L("Performance vs accuracy of calculation. Lower values may produce unwanted artifacts.");
|
||||||
|
def->min = 0;
|
||||||
|
def->max = 1;
|
||||||
|
def->mode = comExpert;
|
||||||
|
def->set_default_value(new ConfigOptionFloat(0.5));
|
||||||
|
|
||||||
|
def = this->add("hollowing_closing_distance", coFloat);
|
||||||
|
def->label = L("Hollowing closing distance");
|
||||||
|
def->category = L("Hollowing");
|
||||||
|
def->tooltip = L("");
|
||||||
|
def->min = 0;
|
||||||
|
def->max = 10;
|
||||||
|
def->mode = comExpert;
|
||||||
|
def->set_default_value(new ConfigOptionFloat(2.0));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value)
|
void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value)
|
||||||
|
|
|
||||||
|
|
@ -1017,7 +1017,7 @@ public:
|
||||||
ConfigOptionFloat support_base_height /*= 1.0*/;
|
ConfigOptionFloat support_base_height /*= 1.0*/;
|
||||||
|
|
||||||
// The minimum distance of the pillar base from the model in mm.
|
// The minimum distance of the pillar base from the model in mm.
|
||||||
ConfigOptionFloat support_base_safety_distance; /*= 1.0*/;
|
ConfigOptionFloat support_base_safety_distance; /*= 1.0*/
|
||||||
|
|
||||||
// The default angle for connecting support sticks and junctions.
|
// The default angle for connecting support sticks and junctions.
|
||||||
ConfigOptionFloat support_critical_angle /*= 45*/;
|
ConfigOptionFloat support_critical_angle /*= 45*/;
|
||||||
|
|
@ -1062,7 +1062,7 @@ public:
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////////////
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
// Zero elevation mode parameters:
|
// Zero elevation mode parameters:
|
||||||
// - The object pad will be derived from the the model geometry.
|
// - The object pad will be derived from the model geometry.
|
||||||
// - There will be a gap between the object pad and the generated pad
|
// - There will be a gap between the object pad and the generated pad
|
||||||
// according to the support_base_safety_distance parameter.
|
// according to the support_base_safety_distance parameter.
|
||||||
// - The two pads will be connected with tiny connector sticks
|
// - The two pads will be connected with tiny connector sticks
|
||||||
|
|
@ -1085,6 +1085,28 @@ public:
|
||||||
// How much should the tiny connectors penetrate into the model body
|
// How much should the tiny connectors penetrate into the model body
|
||||||
ConfigOptionFloat pad_object_connector_penetration;
|
ConfigOptionFloat pad_object_connector_penetration;
|
||||||
|
|
||||||
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
// Model hollowing parameters:
|
||||||
|
// - Models can be hollowed out as part of the SLA print process
|
||||||
|
// - Thickness of the hollowed model walls can be adjusted
|
||||||
|
// -
|
||||||
|
// - Additional holes will be drilled into the hollow model to allow for
|
||||||
|
// - resin removal.
|
||||||
|
// /////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
ConfigOptionBool hollowing_enable;
|
||||||
|
|
||||||
|
// The minimum thickness of the model walls to maintain. Note that the
|
||||||
|
// resulting walls may be thicker due to smoothing out fine cavities where
|
||||||
|
// resin could stuck.
|
||||||
|
ConfigOptionFloat hollowing_min_thickness;
|
||||||
|
|
||||||
|
// Indirectly controls the voxel size (resolution) used by openvdb
|
||||||
|
ConfigOptionFloat hollowing_quality;
|
||||||
|
|
||||||
|
// Indirectly controls the minimum size of created cavities.
|
||||||
|
ConfigOptionFloat hollowing_closing_distance;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void initialize(StaticCacheBase &cache, const char *base_ptr)
|
void initialize(StaticCacheBase &cache, const char *base_ptr)
|
||||||
{
|
{
|
||||||
|
|
@ -1121,6 +1143,10 @@ protected:
|
||||||
OPT_PTR(pad_object_connector_stride);
|
OPT_PTR(pad_object_connector_stride);
|
||||||
OPT_PTR(pad_object_connector_width);
|
OPT_PTR(pad_object_connector_width);
|
||||||
OPT_PTR(pad_object_connector_penetration);
|
OPT_PTR(pad_object_connector_penetration);
|
||||||
|
OPT_PTR(hollowing_enable);
|
||||||
|
OPT_PTR(hollowing_min_thickness);
|
||||||
|
OPT_PTR(hollowing_quality);
|
||||||
|
OPT_PTR(hollowing_closing_distance);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#ifndef SLABOOSTADAPTER_HPP
|
#ifndef SLA_BOOSTADAPTER_HPP
|
||||||
#define SLABOOSTADAPTER_HPP
|
#define SLA_BOOSTADAPTER_HPP
|
||||||
|
|
||||||
#include "SLA/SLABoilerPlate.hpp"
|
#include <libslic3r/SLA/Common.hpp>
|
||||||
#include <boost/geometry.hpp>
|
#include <boost/geometry.hpp>
|
||||||
|
|
||||||
namespace boost {
|
namespace boost {
|
||||||
30
src/libslic3r/SLA/Clustering.hpp
Normal file
30
src/libslic3r/SLA/Clustering.hpp
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef SLA_CLUSTERING_HPP
|
||||||
|
#define SLA_CLUSTERING_HPP
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <libslic3r/SLA/Common.hpp>
|
||||||
|
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||||
|
|
||||||
|
namespace Slic3r { namespace sla {
|
||||||
|
|
||||||
|
using ClusterEl = std::vector<unsigned>;
|
||||||
|
using ClusteredPoints = std::vector<ClusterEl>;
|
||||||
|
|
||||||
|
// Clustering a set of points by the given distance.
|
||||||
|
ClusteredPoints cluster(const std::vector<unsigned>& indices,
|
||||||
|
std::function<Vec3d(unsigned)> pointfn,
|
||||||
|
double dist,
|
||||||
|
unsigned max_points);
|
||||||
|
|
||||||
|
ClusteredPoints cluster(const PointSet& points,
|
||||||
|
double dist,
|
||||||
|
unsigned max_points);
|
||||||
|
|
||||||
|
ClusteredPoints cluster(
|
||||||
|
const std::vector<unsigned>& indices,
|
||||||
|
std::function<Vec3d(unsigned)> pointfn,
|
||||||
|
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
|
||||||
|
unsigned max_points);
|
||||||
|
|
||||||
|
}}
|
||||||
|
#endif // CLUSTERING_HPP
|
||||||
741
src/libslic3r/SLA/Common.cpp
Normal file
741
src/libslic3r/SLA/Common.cpp
Normal file
|
|
@ -0,0 +1,741 @@
|
||||||
|
#include <cmath>
|
||||||
|
#include <libslic3r/SLA/Common.hpp>
|
||||||
|
#include <libslic3r/SLA/Concurrency.hpp>
|
||||||
|
#include <libslic3r/SLA/SupportTree.hpp>
|
||||||
|
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||||
|
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||||
|
#include <libslic3r/SLA/Contour3D.hpp>
|
||||||
|
#include <libslic3r/SLA/Clustering.hpp>
|
||||||
|
#include <libslic3r/SLA/Hollowing.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
// Workaround: IGL signed_distance.h will define PI in the igl namespace.
|
||||||
|
#undef PI
|
||||||
|
|
||||||
|
// HEAVY headers... takes eternity to compile
|
||||||
|
|
||||||
|
// for concave hull merging decisions
|
||||||
|
#include <libslic3r/SLA/BoostAdapter.hpp>
|
||||||
|
#include "boost/geometry/index/rtree.hpp"
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(push)
|
||||||
|
#pragma warning(disable: 4244)
|
||||||
|
#pragma warning(disable: 4267)
|
||||||
|
#endif
|
||||||
|
#include <igl/ray_mesh_intersect.h>
|
||||||
|
#include <igl/point_mesh_squared_distance.h>
|
||||||
|
#include <igl/remove_duplicate_vertices.h>
|
||||||
|
#include <igl/signed_distance.h>
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma warning(pop)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <tbb/parallel_for.h>
|
||||||
|
|
||||||
|
#include "ClipperUtils.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace sla {
|
||||||
|
|
||||||
|
// Bring back PI from the igl namespace
|
||||||
|
using igl::PI;
|
||||||
|
|
||||||
|
/* **************************************************************************
|
||||||
|
* PointIndex implementation
|
||||||
|
* ************************************************************************** */
|
||||||
|
|
||||||
|
class PointIndex::Impl {
|
||||||
|
public:
|
||||||
|
using BoostIndex = boost::geometry::index::rtree< PointIndexEl,
|
||||||
|
boost::geometry::index::rstar<16, 4> /* ? */ >;
|
||||||
|
|
||||||
|
BoostIndex m_store;
|
||||||
|
};
|
||||||
|
|
||||||
|
PointIndex::PointIndex(): m_impl(new Impl()) {}
|
||||||
|
PointIndex::~PointIndex() {}
|
||||||
|
|
||||||
|
PointIndex::PointIndex(const PointIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
|
||||||
|
PointIndex::PointIndex(PointIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
|
||||||
|
|
||||||
|
PointIndex& PointIndex::operator=(const PointIndex &cpy)
|
||||||
|
{
|
||||||
|
m_impl.reset(new Impl(*cpy.m_impl));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
PointIndex& PointIndex::operator=(PointIndex &&cpy)
|
||||||
|
{
|
||||||
|
m_impl.swap(cpy.m_impl);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PointIndex::insert(const PointIndexEl &el)
|
||||||
|
{
|
||||||
|
m_impl->m_store.insert(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PointIndex::remove(const PointIndexEl& el)
|
||||||
|
{
|
||||||
|
return m_impl->m_store.remove(el) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<PointIndexEl>
|
||||||
|
PointIndex::query(std::function<bool(const PointIndexEl &)> fn) const
|
||||||
|
{
|
||||||
|
namespace bgi = boost::geometry::index;
|
||||||
|
|
||||||
|
std::vector<PointIndexEl> ret;
|
||||||
|
m_impl->m_store.query(bgi::satisfies(fn), std::back_inserter(ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<PointIndexEl> PointIndex::nearest(const Vec3d &el, unsigned k = 1) const
|
||||||
|
{
|
||||||
|
namespace bgi = boost::geometry::index;
|
||||||
|
std::vector<PointIndexEl> ret; ret.reserve(k);
|
||||||
|
m_impl->m_store.query(bgi::nearest(el, k), std::back_inserter(ret));
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t PointIndex::size() const
|
||||||
|
{
|
||||||
|
return m_impl->m_store.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn)
|
||||||
|
{
|
||||||
|
for(auto& el : m_impl->m_store) fn(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PointIndex::foreach(std::function<void (const PointIndexEl &)> fn) const
|
||||||
|
{
|
||||||
|
for(const auto &el : m_impl->m_store) fn(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* **************************************************************************
|
||||||
|
* BoxIndex implementation
|
||||||
|
* ************************************************************************** */
|
||||||
|
|
||||||
|
class BoxIndex::Impl {
|
||||||
|
public:
|
||||||
|
using BoostIndex = boost::geometry::index::
|
||||||
|
rtree<BoxIndexEl, boost::geometry::index::rstar<16, 4> /* ? */>;
|
||||||
|
|
||||||
|
BoostIndex m_store;
|
||||||
|
};
|
||||||
|
|
||||||
|
BoxIndex::BoxIndex(): m_impl(new Impl()) {}
|
||||||
|
BoxIndex::~BoxIndex() {}
|
||||||
|
|
||||||
|
BoxIndex::BoxIndex(const BoxIndex &cpy): m_impl(new Impl(*cpy.m_impl)) {}
|
||||||
|
BoxIndex::BoxIndex(BoxIndex&& cpy): m_impl(std::move(cpy.m_impl)) {}
|
||||||
|
|
||||||
|
BoxIndex& BoxIndex::operator=(const BoxIndex &cpy)
|
||||||
|
{
|
||||||
|
m_impl.reset(new Impl(*cpy.m_impl));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
BoxIndex& BoxIndex::operator=(BoxIndex &&cpy)
|
||||||
|
{
|
||||||
|
m_impl.swap(cpy.m_impl);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BoxIndex::insert(const BoxIndexEl &el)
|
||||||
|
{
|
||||||
|
m_impl->m_store.insert(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BoxIndex::remove(const BoxIndexEl& el)
|
||||||
|
{
|
||||||
|
return m_impl->m_store.remove(el) == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BoxIndexEl> BoxIndex::query(const BoundingBox &qrbb,
|
||||||
|
BoxIndex::QueryType qt)
|
||||||
|
{
|
||||||
|
namespace bgi = boost::geometry::index;
|
||||||
|
|
||||||
|
std::vector<BoxIndexEl> ret; ret.reserve(m_impl->m_store.size());
|
||||||
|
|
||||||
|
switch (qt) {
|
||||||
|
case qtIntersects:
|
||||||
|
m_impl->m_store.query(bgi::intersects(qrbb), std::back_inserter(ret));
|
||||||
|
break;
|
||||||
|
case qtWithin:
|
||||||
|
m_impl->m_store.query(bgi::within(qrbb), std::back_inserter(ret));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t BoxIndex::size() const
|
||||||
|
{
|
||||||
|
return m_impl->m_store.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void BoxIndex::foreach(std::function<void (const BoxIndexEl &)> fn)
|
||||||
|
{
|
||||||
|
for(auto& el : m_impl->m_store) fn(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ****************************************************************************
|
||||||
|
* EigenMesh3D implementation
|
||||||
|
* ****************************************************************************/
|
||||||
|
|
||||||
|
class EigenMesh3D::AABBImpl: public igl::AABB<Eigen::MatrixXd, 3> {
|
||||||
|
public:
|
||||||
|
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
||||||
|
igl::WindingNumberAABB<Vec3d, Eigen::MatrixXd, Eigen::MatrixXi> windtree;
|
||||||
|
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
||||||
|
};
|
||||||
|
|
||||||
|
EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) {
|
||||||
|
static const double dEPS = 1e-6;
|
||||||
|
|
||||||
|
const stl_file& stl = tmesh.stl;
|
||||||
|
|
||||||
|
auto&& bb = tmesh.bounding_box();
|
||||||
|
m_ground_level += bb.min(Z);
|
||||||
|
|
||||||
|
Eigen::MatrixXd V;
|
||||||
|
Eigen::MatrixXi F;
|
||||||
|
|
||||||
|
V.resize(3*stl.stats.number_of_facets, 3);
|
||||||
|
F.resize(stl.stats.number_of_facets, 3);
|
||||||
|
for (unsigned int i = 0; i < stl.stats.number_of_facets; ++i) {
|
||||||
|
const stl_facet &facet = stl.facet_start[i];
|
||||||
|
V.block<1, 3>(3 * i + 0, 0) = facet.vertex[0].cast<double>();
|
||||||
|
V.block<1, 3>(3 * i + 1, 0) = facet.vertex[1].cast<double>();
|
||||||
|
V.block<1, 3>(3 * i + 2, 0) = facet.vertex[2].cast<double>();
|
||||||
|
F(i, 0) = int(3*i+0);
|
||||||
|
F(i, 1) = int(3*i+1);
|
||||||
|
F(i, 2) = int(3*i+2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We will convert this to a proper 3d mesh with no duplicate points.
|
||||||
|
Eigen::VectorXi SVI, SVJ;
|
||||||
|
igl::remove_duplicate_vertices(V, F, dEPS, m_V, SVI, SVJ, m_F);
|
||||||
|
|
||||||
|
// Build the AABB accelaration tree
|
||||||
|
m_aabb->init(m_V, m_F);
|
||||||
|
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
||||||
|
m_aabb->windtree.set_mesh(m_V, m_F);
|
||||||
|
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
||||||
|
}
|
||||||
|
|
||||||
|
EigenMesh3D::~EigenMesh3D() {}
|
||||||
|
|
||||||
|
EigenMesh3D::EigenMesh3D(const EigenMesh3D &other):
|
||||||
|
m_V(other.m_V), m_F(other.m_F), m_ground_level(other.m_ground_level),
|
||||||
|
m_aabb( new AABBImpl(*other.m_aabb) ) {}
|
||||||
|
|
||||||
|
EigenMesh3D::EigenMesh3D(const Contour3D &other)
|
||||||
|
{
|
||||||
|
m_V.resize(Eigen::Index(other.points.size()), 3);
|
||||||
|
m_F.resize(Eigen::Index(other.faces3.size() + 2 * other.faces4.size()), 3);
|
||||||
|
|
||||||
|
for (Eigen::Index i = 0; i < Eigen::Index(other.points.size()); ++i)
|
||||||
|
m_V.row(i) = other.points[size_t(i)];
|
||||||
|
|
||||||
|
for (Eigen::Index i = 0; i < Eigen::Index(other.faces3.size()); ++i)
|
||||||
|
m_F.row(i) = other.faces3[size_t(i)];
|
||||||
|
|
||||||
|
size_t N = other.faces3.size() + 2 * other.faces4.size();
|
||||||
|
for (size_t i = other.faces3.size(); i < N; i += 2) {
|
||||||
|
size_t quad_idx = (i - other.faces3.size()) / 2;
|
||||||
|
auto & quad = other.faces4[quad_idx];
|
||||||
|
m_F.row(Eigen::Index(i)) = Vec3i{quad(0), quad(1), quad(2)};
|
||||||
|
m_F.row(Eigen::Index(i + 1)) = Vec3i{quad(2), quad(3), quad(0)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other)
|
||||||
|
{
|
||||||
|
m_V = other.m_V;
|
||||||
|
m_F = other.m_F;
|
||||||
|
m_ground_level = other.m_ground_level;
|
||||||
|
m_aabb.reset(new AABBImpl(*other.m_aabb)); return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
EigenMesh3D::hit_result
|
||||||
|
EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
|
||||||
|
{
|
||||||
|
assert(is_approx(dir.norm(), 1.));
|
||||||
|
igl::Hit hit;
|
||||||
|
hit.t = std::numeric_limits<float>::infinity();
|
||||||
|
|
||||||
|
if (m_holes.empty()) {
|
||||||
|
m_aabb->intersect_ray(m_V, m_F, s, dir, hit);
|
||||||
|
hit_result ret(*this);
|
||||||
|
ret.m_t = double(hit.t);
|
||||||
|
ret.m_dir = dir;
|
||||||
|
ret.m_source = s;
|
||||||
|
if(!std::isinf(hit.t) && !std::isnan(hit.t))
|
||||||
|
ret.m_normal = this->normal_by_face_id(hit.id);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If there are holes, the hit_results will be made by
|
||||||
|
// query_ray_hits (object) and filter_hits (holes):
|
||||||
|
return filter_hits(query_ray_hits(s, dir));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<EigenMesh3D::hit_result>
|
||||||
|
EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
|
||||||
|
{
|
||||||
|
std::vector<EigenMesh3D::hit_result> outs;
|
||||||
|
std::vector<igl::Hit> hits;
|
||||||
|
m_aabb->intersect_ray(m_V, m_F, s, dir, hits);
|
||||||
|
|
||||||
|
// The sort is necessary, the hits are not always sorted.
|
||||||
|
std::sort(hits.begin(), hits.end(),
|
||||||
|
[](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
|
||||||
|
|
||||||
|
// Remove duplicates. They sometimes appear, for example when the ray is cast
|
||||||
|
// along an axis of a cube due to floating-point approximations in igl (?)
|
||||||
|
hits.erase(std::unique(hits.begin(), hits.end(),
|
||||||
|
[](const igl::Hit& a, const igl::Hit& b)
|
||||||
|
{ return a.t == b.t; }),
|
||||||
|
hits.end());
|
||||||
|
|
||||||
|
// Convert the igl::Hit into hit_result
|
||||||
|
outs.reserve(hits.size());
|
||||||
|
for (const igl::Hit& hit : hits) {
|
||||||
|
outs.emplace_back(EigenMesh3D::hit_result(*this));
|
||||||
|
outs.back().m_t = double(hit.t);
|
||||||
|
outs.back().m_dir = dir;
|
||||||
|
outs.back().m_source = s;
|
||||||
|
if(!std::isinf(hit.t) && !std::isnan(hit.t))
|
||||||
|
outs.back().m_normal = this->normal_by_face_id(hit.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return outs;
|
||||||
|
}
|
||||||
|
|
||||||
|
EigenMesh3D::hit_result EigenMesh3D::filter_hits(
|
||||||
|
const std::vector<EigenMesh3D::hit_result>& object_hits) const
|
||||||
|
{
|
||||||
|
assert(! m_holes.empty());
|
||||||
|
hit_result out(*this);
|
||||||
|
|
||||||
|
if (object_hits.empty())
|
||||||
|
return out;
|
||||||
|
|
||||||
|
const Vec3d& s = object_hits.front().source();
|
||||||
|
const Vec3d& dir = object_hits.front().direction();
|
||||||
|
|
||||||
|
// A helper struct to save an intersetion with a hole
|
||||||
|
struct HoleHit {
|
||||||
|
HoleHit(float t_p, const Vec3d& normal_p, bool entry_p) :
|
||||||
|
t(t_p), normal(normal_p), entry(entry_p) {}
|
||||||
|
float t;
|
||||||
|
Vec3d normal;
|
||||||
|
bool entry;
|
||||||
|
};
|
||||||
|
std::vector<HoleHit> hole_isects;
|
||||||
|
hole_isects.reserve(m_holes.size());
|
||||||
|
|
||||||
|
auto sf = s.cast<float>();
|
||||||
|
auto dirf = dir.cast<float>();
|
||||||
|
|
||||||
|
// Collect hits on all holes, preserve information about entry/exit
|
||||||
|
for (const sla::DrainHole& hole : m_holes) {
|
||||||
|
std::array<std::pair<float, Vec3d>, 2> isects;
|
||||||
|
if (hole.get_intersections(sf, dirf, isects)) {
|
||||||
|
// Ignore hole hits behind the source
|
||||||
|
if (isects[0].first > 0.f) hole_isects.emplace_back(isects[0].first, isects[0].second, true);
|
||||||
|
if (isects[1].first > 0.f) hole_isects.emplace_back(isects[1].first, isects[1].second, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Holes can intersect each other, sort the hits by t
|
||||||
|
std::sort(hole_isects.begin(), hole_isects.end(),
|
||||||
|
[](const HoleHit& a, const HoleHit& b) { return a.t < b.t; });
|
||||||
|
|
||||||
|
// Now inspect the intersections with object and holes, in the order of
|
||||||
|
// increasing distance. Keep track how deep are we nested in mesh/holes and
|
||||||
|
// pick the correct intersection.
|
||||||
|
// This needs to be done twice - first to find out how deep in the structure
|
||||||
|
// the source is, then to pick the correct intersection.
|
||||||
|
int hole_nested = 0;
|
||||||
|
int object_nested = 0;
|
||||||
|
for (int dry_run=1; dry_run>=0; --dry_run) {
|
||||||
|
hole_nested = -hole_nested;
|
||||||
|
object_nested = -object_nested;
|
||||||
|
|
||||||
|
bool is_hole = false;
|
||||||
|
bool is_entry = false;
|
||||||
|
const HoleHit* next_hole_hit = hole_isects.empty() ? nullptr : &hole_isects.front();
|
||||||
|
const hit_result* next_mesh_hit = &object_hits.front();
|
||||||
|
|
||||||
|
while (next_hole_hit || next_mesh_hit) {
|
||||||
|
if (next_hole_hit && next_mesh_hit) // still have hole and obj hits
|
||||||
|
is_hole = (next_hole_hit->t < next_mesh_hit->m_t);
|
||||||
|
else
|
||||||
|
is_hole = next_hole_hit; // one or the other ran out
|
||||||
|
|
||||||
|
// Is this entry or exit hit?
|
||||||
|
is_entry = is_hole ? next_hole_hit->entry : ! next_mesh_hit->is_inside();
|
||||||
|
|
||||||
|
if (! dry_run) {
|
||||||
|
if (! is_hole && hole_nested == 0) {
|
||||||
|
// This is a valid object hit
|
||||||
|
return *next_mesh_hit;
|
||||||
|
}
|
||||||
|
if (is_hole && ! is_entry && object_nested != 0) {
|
||||||
|
// This holehit is the one we seek
|
||||||
|
out.m_t = next_hole_hit->t;
|
||||||
|
out.m_normal = next_hole_hit->normal;
|
||||||
|
out.m_source = s;
|
||||||
|
out.m_dir = dir;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increase/decrease the counter
|
||||||
|
(is_hole ? hole_nested : object_nested) += (is_entry ? 1 : -1);
|
||||||
|
|
||||||
|
// Advance the respective pointer
|
||||||
|
if (is_hole && next_hole_hit++ == &hole_isects.back())
|
||||||
|
next_hole_hit = nullptr;
|
||||||
|
if (! is_hole && next_mesh_hit++ == &object_hits.back())
|
||||||
|
next_mesh_hit = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we got here, the ray ended up in infinity
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
||||||
|
EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const {
|
||||||
|
double sign = 0; double sqdst = 0; int i = 0; Vec3d c;
|
||||||
|
igl::signed_distance_winding_number(*m_aabb, m_V, m_F, m_aabb->windtree,
|
||||||
|
p, sign, sqdst, i, c);
|
||||||
|
|
||||||
|
return si_result(sign * std::sqrt(sqdst), i, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EigenMesh3D::inside(const Vec3d &p) const {
|
||||||
|
return m_aabb->windtree.inside(p);
|
||||||
|
}
|
||||||
|
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
||||||
|
|
||||||
|
double EigenMesh3D::squared_distance(const Vec3d &p, int& i, Vec3d& c) const {
|
||||||
|
double sqdst = 0;
|
||||||
|
Eigen::Matrix<double, 1, 3> pp = p;
|
||||||
|
Eigen::Matrix<double, 1, 3> cc;
|
||||||
|
sqdst = m_aabb->squared_distance(m_V, m_F, pp, i, cc);
|
||||||
|
c = cc;
|
||||||
|
return sqdst;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ****************************************************************************
|
||||||
|
* Misc functions
|
||||||
|
* ****************************************************************************/
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool point_on_edge(const Vec3d& p, const Vec3d& e1, const Vec3d& e2,
|
||||||
|
double eps = 0.05)
|
||||||
|
{
|
||||||
|
using Line3D = Eigen::ParametrizedLine<double, 3>;
|
||||||
|
|
||||||
|
auto line = Line3D::Through(e1, e2);
|
||||||
|
double d = line.distance(p);
|
||||||
|
return std::abs(d) < eps;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
|
||||||
|
auto p = pp2 - pp1;
|
||||||
|
return std::sqrt(p.transpose() * p);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
PointSet normals(const PointSet& points,
|
||||||
|
const EigenMesh3D& mesh,
|
||||||
|
double eps,
|
||||||
|
std::function<void()> thr, // throw on cancel
|
||||||
|
const std::vector<unsigned>& pt_indices)
|
||||||
|
{
|
||||||
|
if (points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::vector<unsigned> range = pt_indices;
|
||||||
|
if (range.empty()) {
|
||||||
|
range.resize(size_t(points.rows()), 0);
|
||||||
|
std::iota(range.begin(), range.end(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
PointSet ret(range.size(), 3);
|
||||||
|
|
||||||
|
// for (size_t ridx = 0; ridx < range.size(); ++ridx)
|
||||||
|
ccr::enumerate(
|
||||||
|
range.begin(), range.end(),
|
||||||
|
[&ret, &mesh, &points, thr, eps](unsigned el, size_t ridx) {
|
||||||
|
thr();
|
||||||
|
auto eidx = Eigen::Index(el);
|
||||||
|
int faceid = 0;
|
||||||
|
Vec3d p;
|
||||||
|
|
||||||
|
mesh.squared_distance(points.row(eidx), faceid, p);
|
||||||
|
|
||||||
|
auto trindex = mesh.F().row(faceid);
|
||||||
|
|
||||||
|
const Vec3d &p1 = mesh.V().row(trindex(0));
|
||||||
|
const Vec3d &p2 = mesh.V().row(trindex(1));
|
||||||
|
const Vec3d &p3 = mesh.V().row(trindex(2));
|
||||||
|
|
||||||
|
// We should check if the point lies on an edge of the hosting
|
||||||
|
// triangle. If it does then all the other triangles using the
|
||||||
|
// same two points have to be searched and the final normal should
|
||||||
|
// be some kind of aggregation of the participating triangle
|
||||||
|
// normals. We should also consider the cases where the support
|
||||||
|
// point lies right on a vertex of its triangle. The procedure is
|
||||||
|
// the same, get the neighbor triangles and calculate an average
|
||||||
|
// normal.
|
||||||
|
|
||||||
|
// mark the vertex indices of the edge. ia and ib marks and edge
|
||||||
|
// ic will mark a single vertex.
|
||||||
|
int ia = -1, ib = -1, ic = -1;
|
||||||
|
|
||||||
|
if (std::abs(distance(p, p1)) < eps) {
|
||||||
|
ic = trindex(0);
|
||||||
|
} else if (std::abs(distance(p, p2)) < eps) {
|
||||||
|
ic = trindex(1);
|
||||||
|
} else if (std::abs(distance(p, p3)) < eps) {
|
||||||
|
ic = trindex(2);
|
||||||
|
} else if (point_on_edge(p, p1, p2, eps)) {
|
||||||
|
ia = trindex(0);
|
||||||
|
ib = trindex(1);
|
||||||
|
} else if (point_on_edge(p, p2, p3, eps)) {
|
||||||
|
ia = trindex(1);
|
||||||
|
ib = trindex(2);
|
||||||
|
} else if (point_on_edge(p, p1, p3, eps)) {
|
||||||
|
ia = trindex(0);
|
||||||
|
ib = trindex(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// vector for the neigboring triangles including the detected one.
|
||||||
|
std::vector<Vec3i> neigh;
|
||||||
|
if (ic >= 0) { // The point is right on a vertex of the triangle
|
||||||
|
for (int n = 0; n < mesh.F().rows(); ++n) {
|
||||||
|
thr();
|
||||||
|
Vec3i ni = mesh.F().row(n);
|
||||||
|
if ((ni(X) == ic || ni(Y) == ic || ni(Z) == ic))
|
||||||
|
neigh.emplace_back(ni);
|
||||||
|
}
|
||||||
|
} else if (ia >= 0 && ib >= 0) { // the point is on and edge
|
||||||
|
// now get all the neigboring triangles
|
||||||
|
for (int n = 0; n < mesh.F().rows(); ++n) {
|
||||||
|
thr();
|
||||||
|
Vec3i ni = mesh.F().row(n);
|
||||||
|
if ((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) &&
|
||||||
|
(ni(X) == ib || ni(Y) == ib || ni(Z) == ib))
|
||||||
|
neigh.emplace_back(ni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the normals for the neighboring triangles
|
||||||
|
std::vector<Vec3d> neighnorms;
|
||||||
|
neighnorms.reserve(neigh.size());
|
||||||
|
for (const Vec3i &tri : neigh) {
|
||||||
|
const Vec3d & pt1 = mesh.V().row(tri(0));
|
||||||
|
const Vec3d & pt2 = mesh.V().row(tri(1));
|
||||||
|
const Vec3d & pt3 = mesh.V().row(tri(2));
|
||||||
|
Eigen::Vector3d U = pt2 - pt1;
|
||||||
|
Eigen::Vector3d V = pt3 - pt1;
|
||||||
|
neighnorms.emplace_back(U.cross(V).normalized());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw out duplicates. They would cause trouble with summing. We
|
||||||
|
// will use std::unique which works on sorted ranges. We will sort
|
||||||
|
// by the coefficient-wise sum of the normals. It should force the
|
||||||
|
// same elements to be consecutive.
|
||||||
|
std::sort(neighnorms.begin(), neighnorms.end(),
|
||||||
|
[](const Vec3d &v1, const Vec3d &v2) {
|
||||||
|
return v1.sum() < v2.sum();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto lend = std::unique(neighnorms.begin(), neighnorms.end(),
|
||||||
|
[](const Vec3d &n1, const Vec3d &n2) {
|
||||||
|
// Compare normals for equivalence.
|
||||||
|
// This is controvers stuff.
|
||||||
|
auto deq = [](double a, double b) {
|
||||||
|
return std::abs(a - b) < 1e-3;
|
||||||
|
};
|
||||||
|
return deq(n1(X), n2(X)) &&
|
||||||
|
deq(n1(Y), n2(Y)) &&
|
||||||
|
deq(n1(Z), n2(Z));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!neighnorms.empty()) { // there were neighbors to count with
|
||||||
|
// sum up the normals and then normalize the result again.
|
||||||
|
// This unification seems to be enough.
|
||||||
|
Vec3d sumnorm(0, 0, 0);
|
||||||
|
sumnorm = std::accumulate(neighnorms.begin(), lend, sumnorm);
|
||||||
|
sumnorm.normalize();
|
||||||
|
ret.row(long(ridx)) = sumnorm;
|
||||||
|
} else { // point lies safely within its triangle
|
||||||
|
Eigen::Vector3d U = p2 - p1;
|
||||||
|
Eigen::Vector3d V = p3 - p1;
|
||||||
|
ret.row(long(ridx)) = U.cross(V).normalized();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace bgi = boost::geometry::index;
|
||||||
|
using Index3D = bgi::rtree< PointIndexEl, bgi::rstar<16, 4> /* ? */ >;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool cmp_ptidx_elements(const PointIndexEl& e1, const PointIndexEl& e2)
|
||||||
|
{
|
||||||
|
return e1.second < e2.second;
|
||||||
|
};
|
||||||
|
|
||||||
|
ClusteredPoints cluster(Index3D &sindex,
|
||||||
|
unsigned max_points,
|
||||||
|
std::function<std::vector<PointIndexEl>(
|
||||||
|
const Index3D &, const PointIndexEl &)> qfn)
|
||||||
|
{
|
||||||
|
using Elems = std::vector<PointIndexEl>;
|
||||||
|
|
||||||
|
// Recursive function for visiting all the points in a given distance to
|
||||||
|
// each other
|
||||||
|
std::function<void(Elems&, Elems&)> group =
|
||||||
|
[&sindex, &group, max_points, qfn](Elems& pts, Elems& cluster)
|
||||||
|
{
|
||||||
|
for(auto& p : pts) {
|
||||||
|
std::vector<PointIndexEl> tmp = qfn(sindex, p);
|
||||||
|
|
||||||
|
std::sort(tmp.begin(), tmp.end(), cmp_ptidx_elements);
|
||||||
|
|
||||||
|
Elems newpts;
|
||||||
|
std::set_difference(tmp.begin(), tmp.end(),
|
||||||
|
cluster.begin(), cluster.end(),
|
||||||
|
std::back_inserter(newpts), cmp_ptidx_elements);
|
||||||
|
|
||||||
|
int c = max_points && newpts.size() + cluster.size() > max_points?
|
||||||
|
int(max_points - cluster.size()) : int(newpts.size());
|
||||||
|
|
||||||
|
cluster.insert(cluster.end(), newpts.begin(), newpts.begin() + c);
|
||||||
|
std::sort(cluster.begin(), cluster.end(), cmp_ptidx_elements);
|
||||||
|
|
||||||
|
if(!newpts.empty() && (!max_points || cluster.size() < max_points))
|
||||||
|
group(newpts, cluster);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Elems> clusters;
|
||||||
|
for(auto it = sindex.begin(); it != sindex.end();) {
|
||||||
|
Elems cluster = {};
|
||||||
|
Elems pts = {*it};
|
||||||
|
group(pts, cluster);
|
||||||
|
|
||||||
|
for(auto& c : cluster) sindex.remove(c);
|
||||||
|
it = sindex.begin();
|
||||||
|
|
||||||
|
clusters.emplace_back(cluster);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClusteredPoints result;
|
||||||
|
for(auto& cluster : clusters) {
|
||||||
|
result.emplace_back();
|
||||||
|
for(auto c : cluster) result.back().emplace_back(c.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<PointIndexEl> distance_queryfn(const Index3D& sindex,
|
||||||
|
const PointIndexEl& p,
|
||||||
|
double dist,
|
||||||
|
unsigned max_points)
|
||||||
|
{
|
||||||
|
std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
|
||||||
|
sindex.query(
|
||||||
|
bgi::nearest(p.first, max_points),
|
||||||
|
std::back_inserter(tmp)
|
||||||
|
);
|
||||||
|
|
||||||
|
for(auto it = tmp.begin(); it < tmp.end(); ++it)
|
||||||
|
if(distance(p.first, it->first) > dist) it = tmp.erase(it);
|
||||||
|
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// Clustering a set of points by the given criteria
|
||||||
|
ClusteredPoints cluster(
|
||||||
|
const std::vector<unsigned>& indices,
|
||||||
|
std::function<Vec3d(unsigned)> pointfn,
|
||||||
|
double dist,
|
||||||
|
unsigned max_points)
|
||||||
|
{
|
||||||
|
// A spatial index for querying the nearest points
|
||||||
|
Index3D sindex;
|
||||||
|
|
||||||
|
// Build the index
|
||||||
|
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
|
||||||
|
|
||||||
|
return cluster(sindex, max_points,
|
||||||
|
[dist, max_points](const Index3D& sidx, const PointIndexEl& p)
|
||||||
|
{
|
||||||
|
return distance_queryfn(sidx, p, dist, max_points);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clustering a set of points by the given criteria
|
||||||
|
ClusteredPoints cluster(
|
||||||
|
const std::vector<unsigned>& indices,
|
||||||
|
std::function<Vec3d(unsigned)> pointfn,
|
||||||
|
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
|
||||||
|
unsigned max_points)
|
||||||
|
{
|
||||||
|
// A spatial index for querying the nearest points
|
||||||
|
Index3D sindex;
|
||||||
|
|
||||||
|
// Build the index
|
||||||
|
for(auto idx : indices) sindex.insert( std::make_pair(pointfn(idx), idx));
|
||||||
|
|
||||||
|
return cluster(sindex, max_points,
|
||||||
|
[max_points, predicate](const Index3D& sidx, const PointIndexEl& p)
|
||||||
|
{
|
||||||
|
std::vector<PointIndexEl> tmp; tmp.reserve(max_points);
|
||||||
|
sidx.query(bgi::satisfies([p, predicate](const PointIndexEl& e){
|
||||||
|
return predicate(p, e);
|
||||||
|
}), std::back_inserter(tmp));
|
||||||
|
return tmp;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ClusteredPoints cluster(const PointSet& pts, double dist, unsigned max_points)
|
||||||
|
{
|
||||||
|
// A spatial index for querying the nearest points
|
||||||
|
Index3D sindex;
|
||||||
|
|
||||||
|
// Build the index
|
||||||
|
for(Eigen::Index i = 0; i < pts.rows(); i++)
|
||||||
|
sindex.insert(std::make_pair(Vec3d(pts.row(i)), unsigned(i)));
|
||||||
|
|
||||||
|
return cluster(sindex, max_points,
|
||||||
|
[dist, max_points](const Index3D& sidx, const PointIndexEl& p)
|
||||||
|
{
|
||||||
|
return distance_queryfn(sidx, p, dist, max_points);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sla
|
||||||
|
} // namespace Slic3r
|
||||||
32
src/libslic3r/SLA/Common.hpp
Normal file
32
src/libslic3r/SLA/Common.hpp
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#ifndef SLA_COMMON_HPP
|
||||||
|
#define SLA_COMMON_HPP
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
#include <numeric>
|
||||||
|
#include <functional>
|
||||||
|
#include <Eigen/Geometry>
|
||||||
|
|
||||||
|
//#include "SLASpatIndex.hpp"
|
||||||
|
|
||||||
|
//#include <libslic3r/ExPolygon.hpp>
|
||||||
|
//#include <libslic3r/TriangleMesh.hpp>
|
||||||
|
|
||||||
|
// #define SLIC3R_SLA_NEEDS_WINDTREE
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
// Typedefs from Point.hpp
|
||||||
|
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f;
|
||||||
|
typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d;
|
||||||
|
typedef Eigen::Matrix<int, 4, 1, Eigen::DontAlign> Vec4i;
|
||||||
|
|
||||||
|
namespace sla {
|
||||||
|
|
||||||
|
using PointSet = Eigen::MatrixXd;
|
||||||
|
|
||||||
|
} // namespace sla
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
|
||||||
|
#endif // SLASUPPORTTREE_HPP
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
#include "ConcaveHull.hpp"
|
#include <libslic3r/SLA/ConcaveHull.hpp>
|
||||||
|
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||||
|
|
||||||
#include <libslic3r/MTUtils.hpp>
|
#include <libslic3r/MTUtils.hpp>
|
||||||
#include <libslic3r/ClipperUtils.hpp>
|
#include <libslic3r/ClipperUtils.hpp>
|
||||||
#include "SLASpatIndex.hpp"
|
|
||||||
#include <boost/log/trivial.hpp>
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
@ -40,7 +42,7 @@ Point ConcaveHull::centroid(const Points &pp)
|
||||||
|
|
||||||
// As it shows, the current offset_ex in ClipperUtils hangs if used in jtRound
|
// As it shows, the current offset_ex in ClipperUtils hangs if used in jtRound
|
||||||
// mode
|
// mode
|
||||||
ClipperLib::Paths fast_offset(const ClipperLib::Paths &paths,
|
static ClipperLib::Paths fast_offset(const ClipperLib::Paths &paths,
|
||||||
coord_t delta,
|
coord_t delta,
|
||||||
ClipperLib::JoinType jointype)
|
ClipperLib::JoinType jointype)
|
||||||
{
|
{
|
||||||
|
|
@ -73,7 +75,7 @@ Points ConcaveHull::calculate_centroids() const
|
||||||
Points centroids = reserve_vector<Point>(m_polys.size());
|
Points centroids = reserve_vector<Point>(m_polys.size());
|
||||||
std::transform(m_polys.begin(), m_polys.end(),
|
std::transform(m_polys.begin(), m_polys.end(),
|
||||||
std::back_inserter(centroids),
|
std::back_inserter(centroids),
|
||||||
[this](const Polygon &poly) { return centroid(poly); });
|
[](const Polygon &poly) { return centroid(poly); });
|
||||||
|
|
||||||
return centroids;
|
return centroids;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef CONCAVEHULL_HPP
|
#ifndef SLA_CONCAVEHULL_HPP
|
||||||
#define CONCAVEHULL_HPP
|
#define SLA_CONCAVEHULL_HPP
|
||||||
|
|
||||||
#include <libslic3r/ExPolygon.hpp>
|
#include <libslic3r/ExPolygon.hpp>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef SLACONCURRENCY_H
|
#ifndef SLA_CONCURRENCY_H
|
||||||
#define SLACONCURRENCY_H
|
#define SLA_CONCURRENCY_H
|
||||||
|
|
||||||
#include <tbb/spin_mutex.h>
|
#include <tbb/spin_mutex.h>
|
||||||
#include <tbb/mutex.h>
|
#include <tbb/mutex.h>
|
||||||
149
src/libslic3r/SLA/Contour3D.cpp
Normal file
149
src/libslic3r/SLA/Contour3D.cpp
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
#include <libslic3r/SLA/Contour3D.hpp>
|
||||||
|
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||||
|
|
||||||
|
#include <libslic3r/Format/objparser.hpp>
|
||||||
|
|
||||||
|
namespace Slic3r { namespace sla {
|
||||||
|
|
||||||
|
Contour3D::Contour3D(const TriangleMesh &trmesh)
|
||||||
|
{
|
||||||
|
points.reserve(trmesh.its.vertices.size());
|
||||||
|
faces3.reserve(trmesh.its.indices.size());
|
||||||
|
|
||||||
|
for (auto &v : trmesh.its.vertices)
|
||||||
|
points.emplace_back(v.cast<double>());
|
||||||
|
|
||||||
|
std::copy(trmesh.its.indices.begin(), trmesh.its.indices.end(),
|
||||||
|
std::back_inserter(faces3));
|
||||||
|
}
|
||||||
|
|
||||||
|
Contour3D::Contour3D(TriangleMesh &&trmesh)
|
||||||
|
{
|
||||||
|
points.reserve(trmesh.its.vertices.size());
|
||||||
|
|
||||||
|
for (auto &v : trmesh.its.vertices)
|
||||||
|
points.emplace_back(v.cast<double>());
|
||||||
|
|
||||||
|
faces3.swap(trmesh.its.indices);
|
||||||
|
}
|
||||||
|
|
||||||
|
Contour3D::Contour3D(const EigenMesh3D &emesh) {
|
||||||
|
points.reserve(size_t(emesh.V().rows()));
|
||||||
|
faces3.reserve(size_t(emesh.F().rows()));
|
||||||
|
|
||||||
|
for (int r = 0; r < emesh.V().rows(); r++)
|
||||||
|
points.emplace_back(emesh.V().row(r).cast<double>());
|
||||||
|
|
||||||
|
for (int i = 0; i < emesh.F().rows(); i++)
|
||||||
|
faces3.emplace_back(emesh.F().row(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
Contour3D &Contour3D::merge(const Contour3D &ctr)
|
||||||
|
{
|
||||||
|
auto N = coord_t(points.size());
|
||||||
|
auto N_f3 = faces3.size();
|
||||||
|
auto N_f4 = faces4.size();
|
||||||
|
|
||||||
|
points.insert(points.end(), ctr.points.begin(), ctr.points.end());
|
||||||
|
faces3.insert(faces3.end(), ctr.faces3.begin(), ctr.faces3.end());
|
||||||
|
faces4.insert(faces4.end(), ctr.faces4.begin(), ctr.faces4.end());
|
||||||
|
|
||||||
|
for(size_t n = N_f3; n < faces3.size(); n++) {
|
||||||
|
auto& idx = faces3[n]; idx.x() += N; idx.y() += N; idx.z() += N;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(size_t n = N_f4; n < faces4.size(); n++) {
|
||||||
|
auto& idx = faces4[n]; for (int k = 0; k < 4; k++) idx(k) += N;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Contour3D &Contour3D::merge(const Pointf3s &triangles)
|
||||||
|
{
|
||||||
|
const size_t offs = points.size();
|
||||||
|
points.insert(points.end(), triangles.begin(), triangles.end());
|
||||||
|
faces3.reserve(faces3.size() + points.size() / 3);
|
||||||
|
|
||||||
|
for(int i = int(offs); i < int(points.size()); i += 3)
|
||||||
|
faces3.emplace_back(i, i + 1, i + 2);
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Contour3D::to_obj(std::ostream &stream)
|
||||||
|
{
|
||||||
|
for(auto& p : points)
|
||||||
|
stream << "v " << p.transpose() << "\n";
|
||||||
|
|
||||||
|
for(auto& f : faces3)
|
||||||
|
stream << "f " << (f + Vec3i(1, 1, 1)).transpose() << "\n";
|
||||||
|
|
||||||
|
for(auto& f : faces4)
|
||||||
|
stream << "f " << (f + Vec4i(1, 1, 1, 1)).transpose() << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void Contour3D::from_obj(std::istream &stream)
|
||||||
|
{
|
||||||
|
ObjParser::ObjData data;
|
||||||
|
ObjParser::objparse(stream, data);
|
||||||
|
|
||||||
|
points.reserve(data.coordinates.size() / 4 + 1);
|
||||||
|
auto &coords = data.coordinates;
|
||||||
|
for (size_t i = 0; i < coords.size(); i += 4)
|
||||||
|
points.emplace_back(coords[i], coords[i + 1], coords[i + 2]);
|
||||||
|
|
||||||
|
Vec3i triangle;
|
||||||
|
Vec4i quad;
|
||||||
|
size_t v = 0;
|
||||||
|
while(v < data.vertices.size()) {
|
||||||
|
size_t N = 0;
|
||||||
|
size_t i = v;
|
||||||
|
while (data.vertices[v++].coordIdx != -1) ++N;
|
||||||
|
|
||||||
|
std::function<void(int, int)> setfn;
|
||||||
|
if (N < 3 || N > 4) continue;
|
||||||
|
else if (N == 3) setfn = [&triangle](int k, int f) { triangle(k) = f; };
|
||||||
|
else setfn = [&quad](int k, int f) { quad(k) = f; };
|
||||||
|
|
||||||
|
for (size_t j = 0; j < N; ++j)
|
||||||
|
setfn(int(j), data.vertices[i + j].coordIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TriangleMesh to_triangle_mesh(const Contour3D &ctour) {
|
||||||
|
if (ctour.faces4.empty()) return {ctour.points, ctour.faces3};
|
||||||
|
|
||||||
|
std::vector<Vec3i> triangles;
|
||||||
|
|
||||||
|
triangles.reserve(ctour.faces3.size() + 2 * ctour.faces4.size());
|
||||||
|
std::copy(ctour.faces3.begin(), ctour.faces3.end(),
|
||||||
|
std::back_inserter(triangles));
|
||||||
|
|
||||||
|
for (auto &quad : ctour.faces4) {
|
||||||
|
triangles.emplace_back(quad(0), quad(1), quad(2));
|
||||||
|
triangles.emplace_back(quad(2), quad(3), quad(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {ctour.points, std::move(triangles)};
|
||||||
|
}
|
||||||
|
|
||||||
|
TriangleMesh to_triangle_mesh(Contour3D &&ctour) {
|
||||||
|
if (ctour.faces4.empty())
|
||||||
|
return {std::move(ctour.points), std::move(ctour.faces3)};
|
||||||
|
|
||||||
|
std::vector<Vec3i> triangles;
|
||||||
|
|
||||||
|
triangles.reserve(ctour.faces3.size() + 2 * ctour.faces4.size());
|
||||||
|
std::copy(ctour.faces3.begin(), ctour.faces3.end(),
|
||||||
|
std::back_inserter(triangles));
|
||||||
|
|
||||||
|
for (auto &quad : ctour.faces4) {
|
||||||
|
triangles.emplace_back(quad(0), quad(1), quad(2));
|
||||||
|
triangles.emplace_back(quad(2), quad(3), quad(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return {std::move(ctour.points), std::move(triangles)};
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // namespace Slic3r::sla
|
||||||
45
src/libslic3r/SLA/Contour3D.hpp
Normal file
45
src/libslic3r/SLA/Contour3D.hpp
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#ifndef SLA_CONTOUR3D_HPP
|
||||||
|
#define SLA_CONTOUR3D_HPP
|
||||||
|
|
||||||
|
#include <libslic3r/SLA/Common.hpp>
|
||||||
|
|
||||||
|
#include <libslic3r/TriangleMesh.hpp>
|
||||||
|
|
||||||
|
namespace Slic3r { namespace sla {
|
||||||
|
|
||||||
|
class EigenMesh3D;
|
||||||
|
|
||||||
|
/// Dumb vertex mesh consisting of triangles (or) quads. Capable of merging with
|
||||||
|
/// other meshes of this type and converting to and from other mesh formats.
|
||||||
|
struct Contour3D {
|
||||||
|
std::vector<Vec3d> points;
|
||||||
|
std::vector<Vec3i> faces3;
|
||||||
|
std::vector<Vec4i> faces4;
|
||||||
|
|
||||||
|
Contour3D() = default;
|
||||||
|
Contour3D(const TriangleMesh &trmesh);
|
||||||
|
Contour3D(TriangleMesh &&trmesh);
|
||||||
|
Contour3D(const EigenMesh3D &emesh);
|
||||||
|
|
||||||
|
Contour3D& merge(const Contour3D& ctr);
|
||||||
|
Contour3D& merge(const Pointf3s& triangles);
|
||||||
|
|
||||||
|
// Write the index triangle structure to OBJ file for debugging purposes.
|
||||||
|
void to_obj(std::ostream& stream);
|
||||||
|
void from_obj(std::istream &stream);
|
||||||
|
|
||||||
|
inline bool empty() const
|
||||||
|
{
|
||||||
|
return points.empty() || (faces4.empty() && faces3.empty());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Mesh from an existing contour.
|
||||||
|
TriangleMesh to_triangle_mesh(const Contour3D& ctour);
|
||||||
|
|
||||||
|
/// Mesh from an evaporating 3D contour
|
||||||
|
TriangleMesh to_triangle_mesh(Contour3D&& ctour);
|
||||||
|
|
||||||
|
}} // namespace Slic3r::sla
|
||||||
|
|
||||||
|
#endif // CONTOUR3D_HPP
|
||||||
156
src/libslic3r/SLA/EigenMesh3D.hpp
Normal file
156
src/libslic3r/SLA/EigenMesh3D.hpp
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
#ifndef SLA_EIGENMESH3D_H
|
||||||
|
#define SLA_EIGENMESH3D_H
|
||||||
|
|
||||||
|
#include <libslic3r/SLA/Common.hpp>
|
||||||
|
#include "libslic3r/SLA/Hollowing.hpp"
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class TriangleMesh;
|
||||||
|
|
||||||
|
namespace sla {
|
||||||
|
|
||||||
|
struct Contour3D;
|
||||||
|
|
||||||
|
/// An index-triangle structure for libIGL functions. Also serves as an
|
||||||
|
/// alternative (raw) input format for the SLASupportTree.
|
||||||
|
// Implemented in libslic3r/SLA/Common.cpp
|
||||||
|
class EigenMesh3D {
|
||||||
|
class AABBImpl;
|
||||||
|
|
||||||
|
Eigen::MatrixXd m_V;
|
||||||
|
Eigen::MatrixXi m_F;
|
||||||
|
double m_ground_level = 0, m_gnd_offset = 0;
|
||||||
|
|
||||||
|
std::unique_ptr<AABBImpl> m_aabb;
|
||||||
|
|
||||||
|
// This holds a copy of holes in the mesh. Initialized externally
|
||||||
|
// by load_mesh setter.
|
||||||
|
std::vector<DrainHole> m_holes;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
EigenMesh3D(const TriangleMesh&);
|
||||||
|
EigenMesh3D(const EigenMesh3D& other);
|
||||||
|
EigenMesh3D(const Contour3D &other);
|
||||||
|
EigenMesh3D& operator=(const EigenMesh3D&);
|
||||||
|
|
||||||
|
~EigenMesh3D();
|
||||||
|
|
||||||
|
inline double ground_level() const { return m_ground_level + m_gnd_offset; }
|
||||||
|
inline void ground_level_offset(double o) { m_gnd_offset = o; }
|
||||||
|
inline double ground_level_offset() const { return m_gnd_offset; }
|
||||||
|
|
||||||
|
inline const Eigen::MatrixXd& V() const { return m_V; }
|
||||||
|
inline const Eigen::MatrixXi& F() const { return m_F; }
|
||||||
|
|
||||||
|
// Result of a raycast
|
||||||
|
class hit_result {
|
||||||
|
// m_t holds a distance from m_source to the intersection.
|
||||||
|
double m_t = infty();
|
||||||
|
const EigenMesh3D *m_mesh = nullptr;
|
||||||
|
Vec3d m_dir;
|
||||||
|
Vec3d m_source;
|
||||||
|
Vec3d m_normal;
|
||||||
|
friend class EigenMesh3D;
|
||||||
|
|
||||||
|
// A valid object of this class can only be obtained from
|
||||||
|
// EigenMesh3D::query_ray_hit method.
|
||||||
|
explicit inline hit_result(const EigenMesh3D& em): m_mesh(&em) {}
|
||||||
|
public:
|
||||||
|
// This denotes no hit on the mesh.
|
||||||
|
static inline constexpr double infty() { return std::numeric_limits<double>::infinity(); }
|
||||||
|
|
||||||
|
explicit inline hit_result(double val = infty()) : m_t(val) {}
|
||||||
|
|
||||||
|
inline double distance() const { return m_t; }
|
||||||
|
inline const Vec3d& direction() const { return m_dir; }
|
||||||
|
inline const Vec3d& source() const { return m_source; }
|
||||||
|
inline Vec3d position() const { return m_source + m_dir * m_t; }
|
||||||
|
inline bool is_valid() const { return m_mesh != nullptr; }
|
||||||
|
inline bool is_hit() const { return !std::isinf(m_t); }
|
||||||
|
|
||||||
|
// Hit_result can decay into a double as the hit distance.
|
||||||
|
inline operator double() const { return distance(); }
|
||||||
|
|
||||||
|
inline const Vec3d& normal() const {
|
||||||
|
assert(is_valid());
|
||||||
|
return m_normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool is_inside() const {
|
||||||
|
return is_hit() && normal().dot(m_dir) > 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Inform the object about location of holes
|
||||||
|
// creates internal copy of the vector
|
||||||
|
void load_holes(const std::vector<DrainHole>& holes) {
|
||||||
|
m_holes = holes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Casting a ray on the mesh, returns the distance where the hit occures.
|
||||||
|
hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const;
|
||||||
|
|
||||||
|
// Casts a ray on the mesh and returns all hits
|
||||||
|
std::vector<hit_result> query_ray_hits(const Vec3d &s, const Vec3d &dir) const;
|
||||||
|
|
||||||
|
// Iterates over hits and holes and returns the true hit, possibly
|
||||||
|
// on the inside of a hole.
|
||||||
|
hit_result filter_hits(const std::vector<EigenMesh3D::hit_result>& obj_hits) const;
|
||||||
|
|
||||||
|
class si_result {
|
||||||
|
double m_value;
|
||||||
|
int m_fidx;
|
||||||
|
Vec3d m_p;
|
||||||
|
si_result(double val, int i, const Vec3d& c):
|
||||||
|
m_value(val), m_fidx(i), m_p(c) {}
|
||||||
|
friend class EigenMesh3D;
|
||||||
|
public:
|
||||||
|
|
||||||
|
si_result() = delete;
|
||||||
|
|
||||||
|
double value() const { return m_value; }
|
||||||
|
operator double() const { return m_value; }
|
||||||
|
const Vec3d& point_on_mesh() const { return m_p; }
|
||||||
|
int F_idx() const { return m_fidx; }
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
||||||
|
// The signed distance from a point to the mesh. Outputs the distance,
|
||||||
|
// the index of the triangle and the closest point in mesh coordinate space.
|
||||||
|
si_result signed_distance(const Vec3d& p) const;
|
||||||
|
|
||||||
|
bool inside(const Vec3d& p) const;
|
||||||
|
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
||||||
|
|
||||||
|
double squared_distance(const Vec3d& p, int& i, Vec3d& c) const;
|
||||||
|
inline double squared_distance(const Vec3d &p) const
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
Vec3d c;
|
||||||
|
return squared_distance(p, i, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3d normal_by_face_id(int face_id) const {
|
||||||
|
auto trindex = F().row(face_id);
|
||||||
|
const Vec3d& p1 = V().row(trindex(0));
|
||||||
|
const Vec3d& p2 = V().row(trindex(1));
|
||||||
|
const Vec3d& p3 = V().row(trindex(2));
|
||||||
|
Eigen::Vector3d U = p2 - p1;
|
||||||
|
Eigen::Vector3d V = p3 - p1;
|
||||||
|
return U.cross(V).normalized();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate the normals for the selected points (from 'points' set) on the
|
||||||
|
// mesh. This will call squared distance for each point.
|
||||||
|
PointSet normals(const PointSet& points,
|
||||||
|
const EigenMesh3D& convert_mesh,
|
||||||
|
double eps = 0.05, // min distance from edges
|
||||||
|
std::function<void()> throw_on_cancel = [](){},
|
||||||
|
const std::vector<unsigned>& selected_points = {});
|
||||||
|
|
||||||
|
}} // namespace Slic3r::sla
|
||||||
|
|
||||||
|
#endif // EIGENMESH3D_H
|
||||||
283
src/libslic3r/SLA/Hollowing.cpp
Normal file
283
src/libslic3r/SLA/Hollowing.cpp
Normal file
|
|
@ -0,0 +1,283 @@
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include <libslic3r/OpenVDBUtils.hpp>
|
||||||
|
#include <libslic3r/TriangleMesh.hpp>
|
||||||
|
#include <libslic3r/SLA/Hollowing.hpp>
|
||||||
|
#include <libslic3r/SLA/Contour3D.hpp>
|
||||||
|
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||||
|
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||||
|
#include <libslic3r/ClipperUtils.hpp>
|
||||||
|
#include <libslic3r/SimplifyMesh.hpp>
|
||||||
|
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
|
#include <libslic3r/MTUtils.hpp>
|
||||||
|
#include <libslic3r/I18N.hpp>
|
||||||
|
|
||||||
|
//! macro used to mark string used at localization,
|
||||||
|
//! return same string
|
||||||
|
#define L(s) Slic3r::I18N::translate(s)
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace sla {
|
||||||
|
|
||||||
|
template<class S, class = FloatingOnly<S>>
|
||||||
|
inline void _scale(S s, TriangleMesh &m) { m.scale(float(s)); }
|
||||||
|
|
||||||
|
template<class S, class = FloatingOnly<S>>
|
||||||
|
inline void _scale(S s, Contour3D &m) { for (auto &p : m.points) p *= s; }
|
||||||
|
|
||||||
|
static TriangleMesh _generate_interior(const TriangleMesh &mesh,
|
||||||
|
const JobController &ctl,
|
||||||
|
double min_thickness,
|
||||||
|
double voxel_scale,
|
||||||
|
double closing_dist)
|
||||||
|
{
|
||||||
|
TriangleMesh imesh{mesh};
|
||||||
|
|
||||||
|
_scale(voxel_scale, imesh);
|
||||||
|
|
||||||
|
double offset = voxel_scale * min_thickness;
|
||||||
|
double D = voxel_scale * closing_dist;
|
||||||
|
float out_range = 0.1f * float(offset);
|
||||||
|
float in_range = 1.1f * float(offset + D);
|
||||||
|
|
||||||
|
if (ctl.stopcondition()) return {};
|
||||||
|
else ctl.statuscb(0, L("Hollowing"));
|
||||||
|
|
||||||
|
auto gridptr = mesh_to_grid(imesh, {}, out_range, in_range);
|
||||||
|
|
||||||
|
assert(gridptr);
|
||||||
|
|
||||||
|
if (!gridptr) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Returned OpenVDB grid is NULL";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctl.stopcondition()) return {};
|
||||||
|
else ctl.statuscb(30, L("Hollowing"));
|
||||||
|
|
||||||
|
if (closing_dist > .0) {
|
||||||
|
gridptr = redistance_grid(*gridptr, -(offset + D), double(in_range));
|
||||||
|
} else {
|
||||||
|
D = -offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctl.stopcondition()) return {};
|
||||||
|
else ctl.statuscb(70, L("Hollowing"));
|
||||||
|
|
||||||
|
double iso_surface = D;
|
||||||
|
double adaptivity = 0.;
|
||||||
|
auto omesh = grid_to_mesh(*gridptr, iso_surface, adaptivity);
|
||||||
|
|
||||||
|
_scale(1. / voxel_scale, omesh);
|
||||||
|
|
||||||
|
if (ctl.stopcondition()) return {};
|
||||||
|
else ctl.statuscb(100, L("Hollowing"));
|
||||||
|
|
||||||
|
return omesh;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh & mesh,
|
||||||
|
const HollowingConfig &hc,
|
||||||
|
const JobController & ctl)
|
||||||
|
{
|
||||||
|
static const double MIN_OVERSAMPL = 3.;
|
||||||
|
static const double MAX_OVERSAMPL = 8.;
|
||||||
|
|
||||||
|
// I can't figure out how to increase the grid resolution through openvdb
|
||||||
|
// API so the model will be scaled up before conversion and the result
|
||||||
|
// scaled down. Voxels have a unit size. If I set voxelSize smaller, it
|
||||||
|
// scales the whole geometry down, and doesn't increase the number of
|
||||||
|
// voxels.
|
||||||
|
//
|
||||||
|
// max 8x upscale, min is native voxel size
|
||||||
|
auto voxel_scale = MIN_OVERSAMPL + (MAX_OVERSAMPL - MIN_OVERSAMPL) * hc.quality;
|
||||||
|
auto meshptr = std::make_unique<TriangleMesh>(
|
||||||
|
_generate_interior(mesh, ctl, hc.min_thickness, voxel_scale,
|
||||||
|
hc.closing_distance));
|
||||||
|
|
||||||
|
if (meshptr) {
|
||||||
|
|
||||||
|
// This flips the normals to be outward facing...
|
||||||
|
meshptr->require_shared_vertices();
|
||||||
|
indexed_triangle_set its = std::move(meshptr->its);
|
||||||
|
|
||||||
|
Slic3r::simplify_mesh(its);
|
||||||
|
|
||||||
|
// flip normals back...
|
||||||
|
for (stl_triangle_vertex_indices &ind : its.indices)
|
||||||
|
std::swap(ind(0), ind(2));
|
||||||
|
|
||||||
|
*meshptr = Slic3r::TriangleMesh{its};
|
||||||
|
}
|
||||||
|
|
||||||
|
return meshptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Contour3D DrainHole::to_mesh() const
|
||||||
|
{
|
||||||
|
auto r = double(radius);
|
||||||
|
auto h = double(height);
|
||||||
|
sla::Contour3D hole = sla::cylinder(r, h);
|
||||||
|
Eigen::Quaterniond q;
|
||||||
|
q.setFromTwoVectors(Vec3d{0., 0., 1.}, normal.cast<double>());
|
||||||
|
for(auto& p : hole.points) p = q * p + pos.cast<double>();
|
||||||
|
return hole;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DrainHole::operator==(const DrainHole &sp) const
|
||||||
|
{
|
||||||
|
return (pos == sp.pos) && (normal == sp.normal) &&
|
||||||
|
is_approx(radius, sp.radius) &&
|
||||||
|
is_approx(height, sp.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DrainHole::is_inside(const Vec3f& pt) const
|
||||||
|
{
|
||||||
|
Eigen::Hyperplane<float, 3> plane(normal, pos);
|
||||||
|
float dist = plane.signedDistance(pt);
|
||||||
|
if (dist < float(EPSILON) || dist > height)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Eigen::ParametrizedLine<float, 3> axis(pos, normal);
|
||||||
|
if ( axis.squaredDistance(pt) < pow(radius, 2.f))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Given a line s+dir*t, find parameter t of intersections with the hole
|
||||||
|
// and the normal (points inside the hole). Outputs through out reference,
|
||||||
|
// returns true if two intersections were found.
|
||||||
|
bool DrainHole::get_intersections(const Vec3f& s, const Vec3f& dir,
|
||||||
|
std::array<std::pair<float, Vec3d>, 2>& out)
|
||||||
|
const
|
||||||
|
{
|
||||||
|
assert(is_approx(normal.norm(), 1.f));
|
||||||
|
const Eigen::ParametrizedLine<float, 3> ray(s, dir.normalized());
|
||||||
|
|
||||||
|
for (size_t i=0; i<2; ++i)
|
||||||
|
out[i] = std::make_pair(sla::EigenMesh3D::hit_result::infty(), Vec3d::Zero());
|
||||||
|
|
||||||
|
const float sqr_radius = pow(radius, 2.f);
|
||||||
|
|
||||||
|
// first check a bounding sphere of the hole:
|
||||||
|
Vec3f center = pos+normal*height/2.f;
|
||||||
|
float sqr_dist_limit = pow(height/2.f, 2.f) + sqr_radius ;
|
||||||
|
if (ray.squaredDistance(center) > sqr_dist_limit)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// The line intersects the bounding sphere, look for intersections with
|
||||||
|
// bases of the cylinder.
|
||||||
|
|
||||||
|
size_t found = 0; // counts how many intersections were found
|
||||||
|
Eigen::Hyperplane<float, 3> base;
|
||||||
|
if (! is_approx(ray.direction().dot(normal), 0.f)) {
|
||||||
|
for (size_t i=1; i<=1; --i) {
|
||||||
|
Vec3f cylinder_center = pos+i*height*normal;
|
||||||
|
if (i == 0) {
|
||||||
|
// The hole base can be identical to mesh surface if it is flat
|
||||||
|
// let's better move the base outward a bit
|
||||||
|
cylinder_center -= EPSILON*normal;
|
||||||
|
}
|
||||||
|
base = Eigen::Hyperplane<float, 3>(normal, cylinder_center);
|
||||||
|
Vec3f intersection = ray.intersectionPoint(base);
|
||||||
|
// Only accept the point if it is inside the cylinder base.
|
||||||
|
if ((cylinder_center-intersection).squaredNorm() < sqr_radius) {
|
||||||
|
out[found].first = ray.intersectionParameter(base);
|
||||||
|
out[found].second = (i==0 ? 1. : -1.) * normal.cast<double>();
|
||||||
|
++found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// In case the line was perpendicular to the cylinder axis, previous
|
||||||
|
// block was skipped, but base will later be assumed to be valid.
|
||||||
|
base = Eigen::Hyperplane<float, 3>(normal, pos-EPSILON*normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In case there is still an intersection to be found, check the wall
|
||||||
|
if (found != 2 && ! is_approx(std::abs(ray.direction().dot(normal)), 1.f)) {
|
||||||
|
// Project the ray onto the base plane
|
||||||
|
Vec3f proj_origin = base.projection(ray.origin());
|
||||||
|
Vec3f proj_dir = base.projection(ray.origin()+ray.direction())-proj_origin;
|
||||||
|
// save how the parameter scales and normalize the projected direction
|
||||||
|
float par_scale = proj_dir.norm();
|
||||||
|
proj_dir = proj_dir/par_scale;
|
||||||
|
Eigen::ParametrizedLine<float, 3> projected_ray(proj_origin, proj_dir);
|
||||||
|
// Calculate point on the secant that's closest to the center
|
||||||
|
// and its distance to the circle along the projected line
|
||||||
|
Vec3f closest = projected_ray.projection(pos);
|
||||||
|
float dist = sqrt((sqr_radius - (closest-pos).squaredNorm()));
|
||||||
|
// Unproject both intersections on the original line and check
|
||||||
|
// they are on the cylinder and not past it:
|
||||||
|
for (int i=-1; i<=1 && found !=2; i+=2) {
|
||||||
|
Vec3f isect = closest + i*dist * projected_ray.direction();
|
||||||
|
Vec3f to_isect = isect-proj_origin;
|
||||||
|
float par = to_isect.norm() / par_scale;
|
||||||
|
if (to_isect.normalized().dot(proj_dir.normalized()) < 0.f)
|
||||||
|
par *= -1.f;
|
||||||
|
Vec3d hit_normal = (pos-isect).normalized().cast<double>();
|
||||||
|
isect = ray.pointAt(par);
|
||||||
|
// check that the intersection is between the base planes:
|
||||||
|
float vert_dist = base.signedDistance(isect);
|
||||||
|
if (vert_dist > 0.f && vert_dist < height) {
|
||||||
|
out[found].first = par;
|
||||||
|
out[found].second = hit_normal;
|
||||||
|
++found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If only one intersection was found, it is some corner case,
|
||||||
|
// no intersection will be returned:
|
||||||
|
if (found != 2)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Sort the intersections:
|
||||||
|
if (out[0].first > out[1].first)
|
||||||
|
std::swap(out[0], out[1]);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cut_drainholes(std::vector<ExPolygons> & obj_slices,
|
||||||
|
const std::vector<float> &slicegrid,
|
||||||
|
float closing_radius,
|
||||||
|
const sla::DrainHoles & holes,
|
||||||
|
std::function<void(void)> thr)
|
||||||
|
{
|
||||||
|
TriangleMesh mesh;
|
||||||
|
for (const sla::DrainHole &holept : holes) {
|
||||||
|
auto r = double(holept.radius);
|
||||||
|
auto h = double(holept.height);
|
||||||
|
sla::Contour3D hole = sla::cylinder(r, h);
|
||||||
|
Eigen::Quaterniond q;
|
||||||
|
q.setFromTwoVectors(Vec3d{0., 0., 1.}, holept.normal.cast<double>());
|
||||||
|
for(auto& p : hole.points) p = q * p + holept.pos.cast<double>();
|
||||||
|
mesh.merge(sla::to_triangle_mesh(hole));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mesh.empty()) return;
|
||||||
|
|
||||||
|
mesh.require_shared_vertices();
|
||||||
|
|
||||||
|
TriangleMeshSlicer slicer(&mesh);
|
||||||
|
|
||||||
|
std::vector<ExPolygons> hole_slices;
|
||||||
|
slicer.slice(slicegrid, closing_radius, &hole_slices, thr);
|
||||||
|
|
||||||
|
if (obj_slices.size() != hole_slices.size())
|
||||||
|
BOOST_LOG_TRIVIAL(warning)
|
||||||
|
<< "Sliced object and drain-holes layer count does not match!";
|
||||||
|
|
||||||
|
size_t until = std::min(obj_slices.size(), hole_slices.size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < until; ++i)
|
||||||
|
obj_slices[i] = diff_ex(obj_slices[i], hole_slices[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}} // namespace Slic3r::sla
|
||||||
70
src/libslic3r/SLA/Hollowing.hpp
Normal file
70
src/libslic3r/SLA/Hollowing.hpp
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
#ifndef SLA_HOLLOWING_HPP
|
||||||
|
#define SLA_HOLLOWING_HPP
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <libslic3r/SLA/Common.hpp>
|
||||||
|
#include <libslic3r/SLA/Contour3D.hpp>
|
||||||
|
#include <libslic3r/SLA/JobController.hpp>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class TriangleMesh;
|
||||||
|
|
||||||
|
namespace sla {
|
||||||
|
|
||||||
|
struct HollowingConfig
|
||||||
|
{
|
||||||
|
double min_thickness = 2.;
|
||||||
|
double quality = 0.5;
|
||||||
|
double closing_distance = 0.5;
|
||||||
|
bool enabled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DrainHole
|
||||||
|
{
|
||||||
|
Vec3f pos;
|
||||||
|
Vec3f normal;
|
||||||
|
float radius;
|
||||||
|
float height;
|
||||||
|
|
||||||
|
DrainHole()
|
||||||
|
: pos(Vec3f::Zero()), normal(Vec3f::UnitZ()), radius(5.f), height(10.f)
|
||||||
|
{}
|
||||||
|
|
||||||
|
DrainHole(Vec3f p, Vec3f n, float r, float h)
|
||||||
|
: pos(p), normal(n), radius(r), height(h)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool operator==(const DrainHole &sp) const;
|
||||||
|
|
||||||
|
bool operator!=(const DrainHole &sp) const { return !(sp == (*this)); }
|
||||||
|
|
||||||
|
bool is_inside(const Vec3f& pt) const;
|
||||||
|
|
||||||
|
bool get_intersections(const Vec3f& s, const Vec3f& dir,
|
||||||
|
std::array<std::pair<float, Vec3d>, 2>& out) const;
|
||||||
|
|
||||||
|
Contour3D to_mesh() const;
|
||||||
|
|
||||||
|
template<class Archive> inline void serialize(Archive &ar)
|
||||||
|
{
|
||||||
|
ar(pos, normal, radius, height);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using DrainHoles = std::vector<DrainHole>;
|
||||||
|
|
||||||
|
std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh &mesh,
|
||||||
|
const HollowingConfig & = {},
|
||||||
|
const JobController &ctl = {});
|
||||||
|
|
||||||
|
void cut_drainholes(std::vector<ExPolygons> & obj_slices,
|
||||||
|
const std::vector<float> &slicegrid,
|
||||||
|
float closing_radius,
|
||||||
|
const sla::DrainHoles & holes,
|
||||||
|
std::function<void(void)> thr);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // HOLLOWINGFILTER_H
|
||||||
31
src/libslic3r/SLA/JobController.hpp
Normal file
31
src/libslic3r/SLA/JobController.hpp
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef SLA_JOBCONTROLLER_HPP
|
||||||
|
#define SLA_JOBCONTROLLER_HPP
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
namespace Slic3r { namespace sla {
|
||||||
|
|
||||||
|
/// A Control structure for the support calculation. Consists of the status
|
||||||
|
/// indicator callback and the stop condition predicate.
|
||||||
|
struct JobController
|
||||||
|
{
|
||||||
|
using StatusFn = std::function<void(unsigned, const std::string&)>;
|
||||||
|
using StopCond = std::function<bool(void)>;
|
||||||
|
using CancelFn = std::function<void(void)>;
|
||||||
|
|
||||||
|
// This will signal the status of the calculation to the front-end
|
||||||
|
StatusFn statuscb = [](unsigned, const std::string&){};
|
||||||
|
|
||||||
|
// Returns true if the calculation should be aborted.
|
||||||
|
StopCond stopcondition = [](){ return false; };
|
||||||
|
|
||||||
|
// Similar to cancel callback. This should check the stop condition and
|
||||||
|
// if true, throw an appropriate exception. (TriangleMeshSlicer needs this)
|
||||||
|
// consider it a hard abort. stopcondition is permits the algorithm to
|
||||||
|
// terminate itself
|
||||||
|
CancelFn cancelfn = [](){};
|
||||||
|
};
|
||||||
|
|
||||||
|
}} // namespace Slic3r::sla
|
||||||
|
|
||||||
|
#endif // JOBCONTROLLER_HPP
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
#include "SLAPad.hpp"
|
#include <libslic3r/SLA/Pad.hpp>
|
||||||
#include "SLABoilerPlate.hpp"
|
#include <libslic3r/SLA/Common.hpp>
|
||||||
#include "SLASpatIndex.hpp"
|
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||||
|
#include <libslic3r/SLA/BoostAdapter.hpp>
|
||||||
|
#include <libslic3r/SLA/Contour3D.hpp>
|
||||||
|
|
||||||
#include "ConcaveHull.hpp"
|
#include "ConcaveHull.hpp"
|
||||||
|
|
||||||
#include "boost/log/trivial.hpp"
|
#include "boost/log/trivial.hpp"
|
||||||
#include "SLABoostAdapter.hpp"
|
|
||||||
#include "ClipperUtils.hpp"
|
#include "ClipperUtils.hpp"
|
||||||
#include "Tesselate.hpp"
|
#include "Tesselate.hpp"
|
||||||
#include "MTUtils.hpp"
|
#include "MTUtils.hpp"
|
||||||
|
|
@ -69,7 +71,7 @@ Contour3D walls(
|
||||||
|
|
||||||
// Shorthand for the vertex arrays
|
// Shorthand for the vertex arrays
|
||||||
auto& upts = upper.points, &lpts = lower.points;
|
auto& upts = upper.points, &lpts = lower.points;
|
||||||
auto& rpts = ret.points; auto& ind = ret.indices;
|
auto& rpts = ret.points; auto& ind = ret.faces3;
|
||||||
|
|
||||||
// If the Z levels are flipped, or the offset difference is negative, we
|
// If the Z levels are flipped, or the offset difference is negative, we
|
||||||
// will interpret that as the triangles normals should be inverted.
|
// will interpret that as the triangles normals should be inverted.
|
||||||
|
|
@ -676,7 +678,7 @@ void create_pad(const ExPolygons &sup_blueprint,
|
||||||
ThrowOnCancel thr)
|
ThrowOnCancel thr)
|
||||||
{
|
{
|
||||||
Contour3D t = create_pad_geometry(sup_blueprint, model_blueprint, cfg, thr);
|
Contour3D t = create_pad_geometry(sup_blueprint, model_blueprint, cfg, thr);
|
||||||
out.merge(mesh(std::move(t)));
|
out.merge(to_triangle_mesh(std::move(t)));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string PadConfig::validate() const
|
std::string PadConfig::validate() const
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef SLABASEPOOL_HPP
|
#ifndef SLA_PAD_HPP
|
||||||
#define SLABASEPOOL_HPP
|
#define SLA_PAD_HPP
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include "SLARaster.hpp"
|
#include <libslic3r/SLA/Raster.hpp>
|
||||||
#include "libslic3r/ExPolygon.hpp"
|
#include "libslic3r/ExPolygon.hpp"
|
||||||
#include "libslic3r/MTUtils.hpp"
|
#include "libslic3r/MTUtils.hpp"
|
||||||
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef SLARASTER_HPP
|
#ifndef SLA_RASTER_HPP
|
||||||
#define SLARASTER_HPP
|
#define SLA_RASTER_HPP
|
||||||
|
|
||||||
#include <ostream>
|
#include <ostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#include "SLARasterWriter.hpp"
|
#include <libslic3r/SLA/RasterWriter.hpp>
|
||||||
#include "libslic3r/Zipper.hpp"
|
|
||||||
#include "libslic3r/Time.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
#include <libslic3r/Zipper.hpp>
|
||||||
|
#include <libslic3r/Time.hpp>
|
||||||
|
|
||||||
#include "ExPolygon.hpp"
|
#include "ExPolygon.hpp"
|
||||||
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef SLARASTERWRITER_HPP
|
#ifndef SLA_RASTERWRITER_HPP
|
||||||
#define SLARASTERWRITER_HPP
|
#define SLA_RASTERWRITER_HPP
|
||||||
|
|
||||||
// For png export of the sliced model
|
// For png export of the sliced model
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
@ -9,12 +9,14 @@
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
#include <libslic3r/SLA/Raster.hpp>
|
||||||
|
#include <libslic3r/Zipper.hpp>
|
||||||
|
|
||||||
#include "SLARaster.hpp"
|
namespace Slic3r {
|
||||||
#include "libslic3r/Zipper.hpp"
|
|
||||||
|
|
||||||
namespace Slic3r { namespace sla {
|
class DynamicPrintConfig;
|
||||||
|
|
||||||
|
namespace sla {
|
||||||
|
|
||||||
// API to write the zipped sla output layers and metadata.
|
// API to write the zipped sla output layers and metadata.
|
||||||
// Implementation uses PNG raster output.
|
// Implementation uses PNG raster output.
|
||||||
|
|
@ -2,9 +2,9 @@
|
||||||
#include <exception>
|
#include <exception>
|
||||||
|
|
||||||
#include <libnest2d/optimizers/nlopt/genetic.hpp>
|
#include <libnest2d/optimizers/nlopt/genetic.hpp>
|
||||||
#include "SLABoilerPlate.hpp"
|
#include <libslic3r/SLA/Common.hpp>
|
||||||
#include "SLARotfinder.hpp"
|
#include <libslic3r/SLA/Rotfinder.hpp>
|
||||||
#include "SLASupportTree.hpp"
|
#include <libslic3r/SLA/SupportTree.hpp>
|
||||||
#include "Model.hpp"
|
#include "Model.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef SLAROTFINDER_HPP
|
#ifndef SLA_ROTFINDER_HPP
|
||||||
#define SLAROTFINDER_HPP
|
#define SLA_ROTFINDER_HPP
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
#ifndef SLABOILERPLATE_HPP
|
|
||||||
#define SLABOILERPLATE_HPP
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <functional>
|
|
||||||
#include <numeric>
|
|
||||||
|
|
||||||
#include <libslic3r/ExPolygon.hpp>
|
|
||||||
#include <libslic3r/TriangleMesh.hpp>
|
|
||||||
|
|
||||||
#include "SLACommon.hpp"
|
|
||||||
#include "SLASpatIndex.hpp"
|
|
||||||
|
|
||||||
namespace Slic3r {
|
|
||||||
namespace sla {
|
|
||||||
|
|
||||||
/// Intermediate struct for a 3D mesh
|
|
||||||
struct Contour3D {
|
|
||||||
Pointf3s points;
|
|
||||||
std::vector<Vec3i> indices;
|
|
||||||
|
|
||||||
Contour3D& merge(const Contour3D& ctr)
|
|
||||||
{
|
|
||||||
auto s3 = coord_t(points.size());
|
|
||||||
auto s = indices.size();
|
|
||||||
|
|
||||||
points.insert(points.end(), ctr.points.begin(), ctr.points.end());
|
|
||||||
indices.insert(indices.end(), ctr.indices.begin(), ctr.indices.end());
|
|
||||||
|
|
||||||
for(size_t n = s; n < indices.size(); n++) {
|
|
||||||
auto& idx = indices[n]; idx.x() += s3; idx.y() += s3; idx.z() += s3;
|
|
||||||
}
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
Contour3D& merge(const Pointf3s& triangles)
|
|
||||||
{
|
|
||||||
const size_t offs = points.size();
|
|
||||||
points.insert(points.end(), triangles.begin(), triangles.end());
|
|
||||||
indices.reserve(indices.size() + points.size() / 3);
|
|
||||||
|
|
||||||
for(int i = int(offs); i < int(points.size()); i += 3)
|
|
||||||
indices.emplace_back(i, i + 1, i + 2);
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the index triangle structure to OBJ file for debugging purposes.
|
|
||||||
void to_obj(std::ostream& stream)
|
|
||||||
{
|
|
||||||
for(auto& p : points) {
|
|
||||||
stream << "v " << p.transpose() << "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
for(auto& f : indices) {
|
|
||||||
stream << "f " << (f + Vec3i(1, 1, 1)).transpose() << "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using ClusterEl = std::vector<unsigned>;
|
|
||||||
using ClusteredPoints = std::vector<ClusterEl>;
|
|
||||||
|
|
||||||
// Clustering a set of points by the given distance.
|
|
||||||
ClusteredPoints cluster(const std::vector<unsigned>& indices,
|
|
||||||
std::function<Vec3d(unsigned)> pointfn,
|
|
||||||
double dist,
|
|
||||||
unsigned max_points);
|
|
||||||
|
|
||||||
ClusteredPoints cluster(const PointSet& points,
|
|
||||||
double dist,
|
|
||||||
unsigned max_points);
|
|
||||||
|
|
||||||
ClusteredPoints cluster(
|
|
||||||
const std::vector<unsigned>& indices,
|
|
||||||
std::function<Vec3d(unsigned)> pointfn,
|
|
||||||
std::function<bool(const PointIndexEl&, const PointIndexEl&)> predicate,
|
|
||||||
unsigned max_points);
|
|
||||||
|
|
||||||
|
|
||||||
// Calculate the normals for the selected points (from 'points' set) on the
|
|
||||||
// mesh. This will call squared distance for each point.
|
|
||||||
PointSet normals(const PointSet& points,
|
|
||||||
const EigenMesh3D& mesh,
|
|
||||||
double eps = 0.05, // min distance from edges
|
|
||||||
std::function<void()> throw_on_cancel = [](){},
|
|
||||||
const std::vector<unsigned>& selected_points = {});
|
|
||||||
|
|
||||||
/// Mesh from an existing contour.
|
|
||||||
inline TriangleMesh mesh(const Contour3D& ctour) {
|
|
||||||
return {ctour.points, ctour.indices};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mesh from an evaporating 3D contour
|
|
||||||
inline TriangleMesh mesh(Contour3D&& ctour) {
|
|
||||||
return {std::move(ctour.points), std::move(ctour.indices)};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // SLABOILERPLATE_HPP
|
|
||||||
|
|
@ -1,187 +0,0 @@
|
||||||
#ifndef SLACOMMON_HPP
|
|
||||||
#define SLACOMMON_HPP
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <vector>
|
|
||||||
#include <Eigen/Geometry>
|
|
||||||
|
|
||||||
// #define SLIC3R_SLA_NEEDS_WINDTREE
|
|
||||||
|
|
||||||
namespace Slic3r {
|
|
||||||
|
|
||||||
// Typedefs from Point.hpp
|
|
||||||
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f;
|
|
||||||
typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d;
|
|
||||||
|
|
||||||
class TriangleMesh;
|
|
||||||
|
|
||||||
namespace sla {
|
|
||||||
|
|
||||||
// An enum to keep track of where the current points on the ModelObject came from.
|
|
||||||
enum class PointsStatus {
|
|
||||||
NoPoints, // No points were generated so far.
|
|
||||||
Generating, // The autogeneration algorithm triggered, but not yet finished.
|
|
||||||
AutoGenerated, // Points were autogenerated (i.e. copied from the backend).
|
|
||||||
UserModified // User has done some edits.
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SupportPoint
|
|
||||||
{
|
|
||||||
Vec3f pos;
|
|
||||||
float head_front_radius;
|
|
||||||
bool is_new_island;
|
|
||||||
|
|
||||||
SupportPoint()
|
|
||||||
: pos(Vec3f::Zero()), head_front_radius(0.f), is_new_island(false)
|
|
||||||
{}
|
|
||||||
|
|
||||||
SupportPoint(float pos_x,
|
|
||||||
float pos_y,
|
|
||||||
float pos_z,
|
|
||||||
float head_radius,
|
|
||||||
bool new_island)
|
|
||||||
: pos(pos_x, pos_y, pos_z)
|
|
||||||
, head_front_radius(head_radius)
|
|
||||||
, is_new_island(new_island)
|
|
||||||
{}
|
|
||||||
|
|
||||||
SupportPoint(Vec3f position, float head_radius, bool new_island)
|
|
||||||
: pos(position)
|
|
||||||
, head_front_radius(head_radius)
|
|
||||||
, is_new_island(new_island)
|
|
||||||
{}
|
|
||||||
|
|
||||||
SupportPoint(Eigen::Matrix<float, 5, 1, Eigen::DontAlign> data)
|
|
||||||
: pos(data(0), data(1), data(2))
|
|
||||||
, head_front_radius(data(3))
|
|
||||||
, is_new_island(data(4) != 0.f)
|
|
||||||
{}
|
|
||||||
|
|
||||||
bool operator==(const SupportPoint &sp) const
|
|
||||||
{
|
|
||||||
return (pos == sp.pos) && head_front_radius == sp.head_front_radius &&
|
|
||||||
is_new_island == sp.is_new_island;
|
|
||||||
}
|
|
||||||
bool operator!=(const SupportPoint &sp) const { return !(sp == (*this)); }
|
|
||||||
|
|
||||||
template<class Archive> void serialize(Archive &ar)
|
|
||||||
{
|
|
||||||
ar(pos, head_front_radius, is_new_island);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using SupportPoints = std::vector<SupportPoint>;
|
|
||||||
|
|
||||||
/// An index-triangle structure for libIGL functions. Also serves as an
|
|
||||||
/// alternative (raw) input format for the SLASupportTree
|
|
||||||
class EigenMesh3D {
|
|
||||||
class AABBImpl;
|
|
||||||
|
|
||||||
Eigen::MatrixXd m_V;
|
|
||||||
Eigen::MatrixXi m_F;
|
|
||||||
double m_ground_level = 0, m_gnd_offset = 0;
|
|
||||||
|
|
||||||
std::unique_ptr<AABBImpl> m_aabb;
|
|
||||||
public:
|
|
||||||
|
|
||||||
EigenMesh3D(const TriangleMesh&);
|
|
||||||
EigenMesh3D(const EigenMesh3D& other);
|
|
||||||
EigenMesh3D& operator=(const EigenMesh3D&);
|
|
||||||
|
|
||||||
~EigenMesh3D();
|
|
||||||
|
|
||||||
inline double ground_level() const { return m_ground_level + m_gnd_offset; }
|
|
||||||
inline void ground_level_offset(double o) { m_gnd_offset = o; }
|
|
||||||
inline double ground_level_offset() const { return m_gnd_offset; }
|
|
||||||
|
|
||||||
inline const Eigen::MatrixXd& V() const { return m_V; }
|
|
||||||
inline const Eigen::MatrixXi& F() const { return m_F; }
|
|
||||||
|
|
||||||
// Result of a raycast
|
|
||||||
class hit_result {
|
|
||||||
double m_t = std::nan("");
|
|
||||||
int m_face_id = -1;
|
|
||||||
const EigenMesh3D *m_mesh = nullptr;
|
|
||||||
Vec3d m_dir;
|
|
||||||
Vec3d m_source;
|
|
||||||
friend class EigenMesh3D;
|
|
||||||
|
|
||||||
// A valid object of this class can only be obtained from
|
|
||||||
// EigenMesh3D::query_ray_hit method.
|
|
||||||
explicit inline hit_result(const EigenMesh3D& em): m_mesh(&em) {}
|
|
||||||
public:
|
|
||||||
|
|
||||||
// This can create a placeholder object which is invalid (not created
|
|
||||||
// by a query_ray_hit call) but the distance can be preset to
|
|
||||||
// a specific value for distinguishing the placeholder.
|
|
||||||
inline hit_result(double val = std::nan("")): m_t(val) {}
|
|
||||||
|
|
||||||
inline double distance() const { return m_t; }
|
|
||||||
inline const Vec3d& direction() const { return m_dir; }
|
|
||||||
inline Vec3d position() const { return m_source + m_dir * m_t; }
|
|
||||||
inline int face() const { return m_face_id; }
|
|
||||||
inline bool is_valid() const { return m_mesh != nullptr; }
|
|
||||||
|
|
||||||
// Hit_result can decay into a double as the hit distance.
|
|
||||||
inline operator double() const { return distance(); }
|
|
||||||
|
|
||||||
inline Vec3d normal() const {
|
|
||||||
if(m_face_id < 0 || !is_valid()) return {};
|
|
||||||
auto trindex = m_mesh->m_F.row(m_face_id);
|
|
||||||
const Vec3d& p1 = m_mesh->V().row(trindex(0));
|
|
||||||
const Vec3d& p2 = m_mesh->V().row(trindex(1));
|
|
||||||
const Vec3d& p3 = m_mesh->V().row(trindex(2));
|
|
||||||
Eigen::Vector3d U = p2 - p1;
|
|
||||||
Eigen::Vector3d V = p3 - p1;
|
|
||||||
return U.cross(V).normalized();
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool is_inside() {
|
|
||||||
return m_face_id >= 0 && normal().dot(m_dir) > 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Casting a ray on the mesh, returns the distance where the hit occures.
|
|
||||||
hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const;
|
|
||||||
|
|
||||||
class si_result {
|
|
||||||
double m_value;
|
|
||||||
int m_fidx;
|
|
||||||
Vec3d m_p;
|
|
||||||
si_result(double val, int i, const Vec3d& c):
|
|
||||||
m_value(val), m_fidx(i), m_p(c) {}
|
|
||||||
friend class EigenMesh3D;
|
|
||||||
public:
|
|
||||||
|
|
||||||
si_result() = delete;
|
|
||||||
|
|
||||||
double value() const { return m_value; }
|
|
||||||
operator double() const { return m_value; }
|
|
||||||
const Vec3d& point_on_mesh() const { return m_p; }
|
|
||||||
int F_idx() const { return m_fidx; }
|
|
||||||
};
|
|
||||||
|
|
||||||
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
|
||||||
// The signed distance from a point to the mesh. Outputs the distance,
|
|
||||||
// the index of the triangle and the closest point in mesh coordinate space.
|
|
||||||
si_result signed_distance(const Vec3d& p) const;
|
|
||||||
|
|
||||||
bool inside(const Vec3d& p) const;
|
|
||||||
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
|
|
||||||
|
|
||||||
double squared_distance(const Vec3d& p, int& i, Vec3d& c) const;
|
|
||||||
inline double squared_distance(const Vec3d &p) const
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
Vec3d c;
|
|
||||||
return squared_distance(p, i, c);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
using PointSet = Eigen::MatrixXd;
|
|
||||||
|
|
||||||
} // namespace sla
|
|
||||||
} // namespace Slic3r
|
|
||||||
|
|
||||||
|
|
||||||
#endif // SLASUPPORTTREE_HPP
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef SPATINDEX_HPP
|
#ifndef SLA_SPATINDEX_HPP
|
||||||
#define SPATINDEX_HPP
|
#define SLA_SPATINDEX_HPP
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
69
src/libslic3r/SLA/SupportPoint.hpp
Normal file
69
src/libslic3r/SLA/SupportPoint.hpp
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
#ifndef SLA_SUPPORTPOINT_HPP
|
||||||
|
#define SLA_SUPPORTPOINT_HPP
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <libslic3r/SLA/Common.hpp>
|
||||||
|
#include <libslic3r/ExPolygon.hpp>
|
||||||
|
|
||||||
|
namespace Slic3r { namespace sla {
|
||||||
|
|
||||||
|
// An enum to keep track of where the current points on the ModelObject came from.
|
||||||
|
enum class PointsStatus {
|
||||||
|
NoPoints, // No points were generated so far.
|
||||||
|
Generating, // The autogeneration algorithm triggered, but not yet finished.
|
||||||
|
AutoGenerated, // Points were autogenerated (i.e. copied from the backend).
|
||||||
|
UserModified // User has done some edits.
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SupportPoint
|
||||||
|
{
|
||||||
|
Vec3f pos;
|
||||||
|
float head_front_radius;
|
||||||
|
bool is_new_island;
|
||||||
|
|
||||||
|
SupportPoint()
|
||||||
|
: pos(Vec3f::Zero()), head_front_radius(0.f), is_new_island(false)
|
||||||
|
{}
|
||||||
|
|
||||||
|
SupportPoint(float pos_x,
|
||||||
|
float pos_y,
|
||||||
|
float pos_z,
|
||||||
|
float head_radius,
|
||||||
|
bool new_island)
|
||||||
|
: pos(pos_x, pos_y, pos_z)
|
||||||
|
, head_front_radius(head_radius)
|
||||||
|
, is_new_island(new_island)
|
||||||
|
{}
|
||||||
|
|
||||||
|
SupportPoint(Vec3f position, float head_radius, bool new_island)
|
||||||
|
: pos(position)
|
||||||
|
, head_front_radius(head_radius)
|
||||||
|
, is_new_island(new_island)
|
||||||
|
{}
|
||||||
|
|
||||||
|
SupportPoint(Eigen::Matrix<float, 5, 1, Eigen::DontAlign> data)
|
||||||
|
: pos(data(0), data(1), data(2))
|
||||||
|
, head_front_radius(data(3))
|
||||||
|
, is_new_island(data(4) != 0.f)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool operator==(const SupportPoint &sp) const
|
||||||
|
{
|
||||||
|
float rdiff = std::abs(head_front_radius - sp.head_front_radius);
|
||||||
|
return (pos == sp.pos) && rdiff < float(EPSILON) &&
|
||||||
|
is_new_island == sp.is_new_island;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const SupportPoint &sp) const { return !(sp == (*this)); }
|
||||||
|
|
||||||
|
template<class Archive> void serialize(Archive &ar)
|
||||||
|
{
|
||||||
|
ar(pos, head_front_radius, is_new_island);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using SupportPoints = std::vector<SupportPoint>;
|
||||||
|
|
||||||
|
}} // namespace Slic3r::sla
|
||||||
|
|
||||||
|
#endif // SUPPORTPOINT_HPP
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
#include <tbb/parallel_for.h>
|
#include <tbb/parallel_for.h>
|
||||||
|
|
||||||
#include "SLAAutoSupports.hpp"
|
#include "SupportPointGenerator.hpp"
|
||||||
#include "Model.hpp"
|
#include "Model.hpp"
|
||||||
#include "ExPolygon.hpp"
|
#include "ExPolygon.hpp"
|
||||||
#include "SVG.hpp"
|
#include "SVG.hpp"
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace sla {
|
namespace sla {
|
||||||
|
|
||||||
/*float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
|
/*float SupportPointGenerator::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
|
||||||
{
|
{
|
||||||
n1.normalize();
|
n1.normalize();
|
||||||
n2.normalize();
|
n2.normalize();
|
||||||
|
|
@ -36,7 +36,7 @@ namespace sla {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
float SLAAutoSupports::get_required_density(float angle) const
|
float SupportPointGenerator::get_required_density(float angle) const
|
||||||
{
|
{
|
||||||
// calculation would be density_0 * cos(angle). To provide one more degree of freedom, we will scale the angle
|
// calculation would be density_0 * cos(angle). To provide one more degree of freedom, we will scale the angle
|
||||||
// to get the user-set density for 45 deg. So it ends up as density_0 * cos(K * angle).
|
// to get the user-set density for 45 deg. So it ends up as density_0 * cos(K * angle).
|
||||||
|
|
@ -44,27 +44,45 @@ float SLAAutoSupports::get_required_density(float angle) const
|
||||||
return std::max(0.f, float(m_config.density_at_horizontal * cos(K*angle)));
|
return std::max(0.f, float(m_config.density_at_horizontal * cos(K*angle)));
|
||||||
}
|
}
|
||||||
|
|
||||||
float SLAAutoSupports::distance_limit(float angle) const
|
float SupportPointGenerator::distance_limit(float angle) const
|
||||||
{
|
{
|
||||||
return 1./(2.4*get_required_density(angle));
|
return 1./(2.4*get_required_density(angle));
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
SLAAutoSupports::SLAAutoSupports(const sla::EigenMesh3D & emesh,
|
SupportPointGenerator::SupportPointGenerator(
|
||||||
|
const sla::EigenMesh3D &emesh,
|
||||||
const std::vector<ExPolygons> &slices,
|
const std::vector<ExPolygons> &slices,
|
||||||
const std::vector<float> & heights,
|
const std::vector<float> & heights,
|
||||||
const Config & config,
|
const Config & config,
|
||||||
std::function<void(void)> throw_on_cancel,
|
std::function<void(void)> throw_on_cancel,
|
||||||
std::function<void(int)> statusfn)
|
std::function<void(int)> statusfn)
|
||||||
|
: SupportPointGenerator(emesh, config, throw_on_cancel, statusfn)
|
||||||
|
{
|
||||||
|
std::random_device rd;
|
||||||
|
m_rng.seed(rd());
|
||||||
|
execute(slices, heights);
|
||||||
|
}
|
||||||
|
|
||||||
|
SupportPointGenerator::SupportPointGenerator(
|
||||||
|
const EigenMesh3D &emesh,
|
||||||
|
const SupportPointGenerator::Config &config,
|
||||||
|
std::function<void ()> throw_on_cancel,
|
||||||
|
std::function<void (int)> statusfn)
|
||||||
: m_config(config)
|
: m_config(config)
|
||||||
, m_emesh(emesh)
|
, m_emesh(emesh)
|
||||||
, m_throw_on_cancel(throw_on_cancel)
|
, m_throw_on_cancel(throw_on_cancel)
|
||||||
, m_statusfn(statusfn)
|
, m_statusfn(statusfn)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void SupportPointGenerator::execute(const std::vector<ExPolygons> &slices,
|
||||||
|
const std::vector<float> & heights)
|
||||||
{
|
{
|
||||||
process(slices, heights);
|
process(slices, heights);
|
||||||
project_onto_mesh(m_output);
|
project_onto_mesh(m_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SLAAutoSupports::project_onto_mesh(std::vector<sla::SupportPoint>& points) const
|
void SupportPointGenerator::project_onto_mesh(std::vector<sla::SupportPoint>& points) const
|
||||||
{
|
{
|
||||||
// The function makes sure that all the points are really exactly placed on the mesh.
|
// The function makes sure that all the points are really exactly placed on the mesh.
|
||||||
|
|
||||||
|
|
@ -77,36 +95,29 @@ void SLAAutoSupports::project_onto_mesh(std::vector<sla::SupportPoint>& points)
|
||||||
m_throw_on_cancel();
|
m_throw_on_cancel();
|
||||||
Vec3f& p = points[point_id].pos;
|
Vec3f& p = points[point_id].pos;
|
||||||
// Project the point upward and downward and choose the closer intersection with the mesh.
|
// Project the point upward and downward and choose the closer intersection with the mesh.
|
||||||
//bool up = igl::ray_mesh_intersect(p.cast<float>(), Vec3f(0., 0., 1.), m_V, m_F, hit_up);
|
|
||||||
//bool down = igl::ray_mesh_intersect(p.cast<float>(), Vec3f(0., 0., -1.), m_V, m_F, hit_down);
|
|
||||||
|
|
||||||
sla::EigenMesh3D::hit_result hit_up = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., 1.));
|
sla::EigenMesh3D::hit_result hit_up = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., 1.));
|
||||||
sla::EigenMesh3D::hit_result hit_down = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., -1.));
|
sla::EigenMesh3D::hit_result hit_down = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., -1.));
|
||||||
|
|
||||||
bool up = hit_up.face() != -1;
|
bool up = hit_up.is_hit();
|
||||||
bool down = hit_down.face() != -1;
|
bool down = hit_down.is_hit();
|
||||||
|
|
||||||
if (!up && !down)
|
if (!up && !down)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
sla::EigenMesh3D::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down;
|
sla::EigenMesh3D::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down;
|
||||||
//int fid = hit.face();
|
|
||||||
//Vec3f bc(1-hit.u-hit.v, hit.u, hit.v);
|
|
||||||
//p = (bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2))).cast<float>();
|
|
||||||
|
|
||||||
p = p + (hit.distance() * hit.direction()).cast<float>();
|
p = p + (hit.distance() * hit.direction()).cast<float>();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector<SLAAutoSupports::MyLayer> make_layers(
|
static std::vector<SupportPointGenerator::MyLayer> make_layers(
|
||||||
const std::vector<ExPolygons>& slices, const std::vector<float>& heights,
|
const std::vector<ExPolygons>& slices, const std::vector<float>& heights,
|
||||||
std::function<void(void)> throw_on_cancel)
|
std::function<void(void)> throw_on_cancel)
|
||||||
{
|
{
|
||||||
assert(slices.size() == heights.size());
|
assert(slices.size() == heights.size());
|
||||||
|
|
||||||
// Allocate empty layers.
|
// Allocate empty layers.
|
||||||
std::vector<SLAAutoSupports::MyLayer> layers;
|
std::vector<SupportPointGenerator::MyLayer> layers;
|
||||||
layers.reserve(slices.size());
|
layers.reserve(slices.size());
|
||||||
for (size_t i = 0; i < slices.size(); ++ i)
|
for (size_t i = 0; i < slices.size(); ++ i)
|
||||||
layers.emplace_back(i, heights[i]);
|
layers.emplace_back(i, heights[i]);
|
||||||
|
|
@ -122,7 +133,7 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers(
|
||||||
if ((layer_id % 8) == 0)
|
if ((layer_id % 8) == 0)
|
||||||
// Don't call the following function too often as it flushes CPU write caches due to synchronization primitves.
|
// Don't call the following function too often as it flushes CPU write caches due to synchronization primitves.
|
||||||
throw_on_cancel();
|
throw_on_cancel();
|
||||||
SLAAutoSupports::MyLayer &layer = layers[layer_id];
|
SupportPointGenerator::MyLayer &layer = layers[layer_id];
|
||||||
const ExPolygons &islands = slices[layer_id];
|
const ExPolygons &islands = slices[layer_id];
|
||||||
//FIXME WTF?
|
//FIXME WTF?
|
||||||
const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0]));
|
const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0]));
|
||||||
|
|
@ -143,8 +154,8 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers(
|
||||||
if ((layer_id % 2) == 0)
|
if ((layer_id % 2) == 0)
|
||||||
// Don't call the following function too often as it flushes CPU write caches due to synchronization primitves.
|
// Don't call the following function too often as it flushes CPU write caches due to synchronization primitves.
|
||||||
throw_on_cancel();
|
throw_on_cancel();
|
||||||
SLAAutoSupports::MyLayer &layer_above = layers[layer_id];
|
SupportPointGenerator::MyLayer &layer_above = layers[layer_id];
|
||||||
SLAAutoSupports::MyLayer &layer_below = layers[layer_id - 1];
|
SupportPointGenerator::MyLayer &layer_below = layers[layer_id - 1];
|
||||||
//FIXME WTF?
|
//FIXME WTF?
|
||||||
const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]);
|
const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]);
|
||||||
const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports
|
const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports
|
||||||
|
|
@ -152,8 +163,8 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers(
|
||||||
const float slope_angle = 75.f * (float(M_PI)/180.f); // smaller number - less supports
|
const float slope_angle = 75.f * (float(M_PI)/180.f); // smaller number - less supports
|
||||||
const float slope_offset = float(scale_(layer_height / std::tan(slope_angle)));
|
const float slope_offset = float(scale_(layer_height / std::tan(slope_angle)));
|
||||||
//FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands.
|
//FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands.
|
||||||
for (SLAAutoSupports::Structure &top : layer_above.islands) {
|
for (SupportPointGenerator::Structure &top : layer_above.islands) {
|
||||||
for (SLAAutoSupports::Structure &bottom : layer_below.islands) {
|
for (SupportPointGenerator::Structure &bottom : layer_below.islands) {
|
||||||
float overlap_area = top.overlap_area(bottom);
|
float overlap_area = top.overlap_area(bottom);
|
||||||
if (overlap_area > 0) {
|
if (overlap_area > 0) {
|
||||||
top.islands_below.emplace_back(&bottom, overlap_area);
|
top.islands_below.emplace_back(&bottom, overlap_area);
|
||||||
|
|
@ -191,13 +202,13 @@ static std::vector<SLAAutoSupports::MyLayer> make_layers(
|
||||||
return layers;
|
return layers;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights)
|
void SupportPointGenerator::process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights)
|
||||||
{
|
{
|
||||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||||
std::vector<std::pair<ExPolygon, coord_t>> islands;
|
std::vector<std::pair<ExPolygon, coord_t>> islands;
|
||||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
|
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
|
||||||
|
|
||||||
std::vector<SLAAutoSupports::MyLayer> layers = make_layers(slices, heights, m_throw_on_cancel);
|
std::vector<SupportPointGenerator::MyLayer> layers = make_layers(slices, heights, m_throw_on_cancel);
|
||||||
|
|
||||||
PointGrid3D point_grid;
|
PointGrid3D point_grid;
|
||||||
point_grid.cell_size = Vec3f(10.f, 10.f, 10.f);
|
point_grid.cell_size = Vec3f(10.f, 10.f, 10.f);
|
||||||
|
|
@ -206,8 +217,8 @@ void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::
|
||||||
double status = 0;
|
double status = 0;
|
||||||
|
|
||||||
for (unsigned int layer_id = 0; layer_id < layers.size(); ++ layer_id) {
|
for (unsigned int layer_id = 0; layer_id < layers.size(); ++ layer_id) {
|
||||||
SLAAutoSupports::MyLayer *layer_top = &layers[layer_id];
|
SupportPointGenerator::MyLayer *layer_top = &layers[layer_id];
|
||||||
SLAAutoSupports::MyLayer *layer_bottom = (layer_id > 0) ? &layers[layer_id - 1] : nullptr;
|
SupportPointGenerator::MyLayer *layer_bottom = (layer_id > 0) ? &layers[layer_id - 1] : nullptr;
|
||||||
std::vector<float> support_force_bottom;
|
std::vector<float> support_force_bottom;
|
||||||
if (layer_bottom != nullptr) {
|
if (layer_bottom != nullptr) {
|
||||||
support_force_bottom.assign(layer_bottom->islands.size(), 0.f);
|
support_force_bottom.assign(layer_bottom->islands.size(), 0.f);
|
||||||
|
|
@ -263,13 +274,13 @@ void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::
|
||||||
status += increment;
|
status += increment;
|
||||||
m_statusfn(int(std::round(status)));
|
m_statusfn(int(std::round(status)));
|
||||||
|
|
||||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||||
/*std::string layer_num_str = std::string((i<10 ? "0" : "")) + std::string((i<100 ? "0" : "")) + std::to_string(i);
|
/*std::string layer_num_str = std::string((i<10 ? "0" : "")) + std::string((i<100 ? "0" : "")) + std::to_string(i);
|
||||||
output_expolygons(expolys_top, "top" + layer_num_str + ".svg");
|
output_expolygons(expolys_top, "top" + layer_num_str + ".svg");
|
||||||
output_expolygons(diff, "diff" + layer_num_str + ".svg");
|
output_expolygons(diff, "diff" + layer_num_str + ".svg");
|
||||||
if (!islands.empty())
|
if (!islands.empty())
|
||||||
output_expolygons(islands, "islands" + layer_num_str + ".svg");*/
|
output_expolygons(islands, "islands" + layer_num_str + ".svg");*/
|
||||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
|
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -449,7 +460,7 @@ static inline std::vector<Vec2f> poisson_disk_from_samples(const std::vector<Vec
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island, bool just_one)
|
void SupportPointGenerator::uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island, bool just_one)
|
||||||
{
|
{
|
||||||
//int num_of_points = std::max(1, (int)((island.area()*pow(SCALING_FACTOR, 2) * m_config.tear_pressure)/m_config.support_force));
|
//int num_of_points = std::max(1, (int)((island.area()*pow(SCALING_FACTOR, 2) * m_config.tear_pressure)/m_config.support_force));
|
||||||
|
|
||||||
|
|
@ -470,9 +481,8 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru
|
||||||
float min_spacing = poisson_radius;
|
float min_spacing = poisson_radius;
|
||||||
|
|
||||||
//FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon.
|
//FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon.
|
||||||
std::random_device rd;
|
|
||||||
std::mt19937 rng(rd());
|
std::vector<Vec2f> raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, m_rng);
|
||||||
std::vector<Vec2f> raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, rng);
|
|
||||||
std::vector<Vec2f> poisson_samples;
|
std::vector<Vec2f> poisson_samples;
|
||||||
for (size_t iter = 0; iter < 4; ++ iter) {
|
for (size_t iter = 0; iter < 4; ++ iter) {
|
||||||
poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius,
|
poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius,
|
||||||
|
|
@ -488,7 +498,7 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru
|
||||||
min_spacing = std::max(m_config.minimal_distance, min_spacing * coeff);
|
min_spacing = std::max(m_config.minimal_distance, min_spacing * coeff);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||||
{
|
{
|
||||||
static int irun = 0;
|
static int irun = 0;
|
||||||
Slic3r::SVG svg(debug_out_path("SLA_supports-uniformly_cover-%d.svg", irun ++), get_extents(islands));
|
Slic3r::SVG svg(debug_out_path("SLA_supports-uniformly_cover-%d.svg", irun ++), get_extents(islands));
|
||||||
|
|
@ -503,7 +513,7 @@ void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& stru
|
||||||
|
|
||||||
// assert(! poisson_samples.empty());
|
// assert(! poisson_samples.empty());
|
||||||
if (poisson_samples_target < poisson_samples.size()) {
|
if (poisson_samples_target < poisson_samples.size()) {
|
||||||
std::shuffle(poisson_samples.begin(), poisson_samples.end(), rng);
|
std::shuffle(poisson_samples.begin(), poisson_samples.end(), m_rng);
|
||||||
poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end());
|
poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end());
|
||||||
}
|
}
|
||||||
for (const Vec2f &pt : poisson_samples) {
|
for (const Vec2f &pt : poisson_samples) {
|
||||||
|
|
@ -528,8 +538,8 @@ void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double
|
||||||
pts.erase(endit, pts.end());
|
pts.erase(endit, pts.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||||
void SLAAutoSupports::output_structures(const std::vector<Structure>& structures)
|
void SupportPointGenerator::output_structures(const std::vector<Structure>& structures)
|
||||||
{
|
{
|
||||||
for (unsigned int i=0 ; i<structures.size(); ++i) {
|
for (unsigned int i=0 ; i<structures.size(); ++i) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
|
|
@ -538,7 +548,7 @@ void SLAAutoSupports::output_structures(const std::vector<Structure>& structures
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, const std::string &filename)
|
void SupportPointGenerator::output_expolygons(const ExPolygons& expolys, const std::string &filename)
|
||||||
{
|
{
|
||||||
BoundingBox bb(Point(-30000000, -30000000), Point(30000000, 30000000));
|
BoundingBox bb(Point(-30000000, -30000000), Point(30000000, 30000000));
|
||||||
Slic3r::SVG svg_cummulative(filename, bb);
|
Slic3r::SVG svg_cummulative(filename, bb);
|
||||||
|
|
@ -1,19 +1,23 @@
|
||||||
#ifndef SLAAUTOSUPPORTS_HPP_
|
#ifndef SLA_SUPPORTPOINTGENERATOR_HPP
|
||||||
#define SLAAUTOSUPPORTS_HPP_
|
#define SLA_SUPPORTPOINTGENERATOR_HPP
|
||||||
|
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
#include <libslic3r/SLA/Common.hpp>
|
||||||
|
#include <libslic3r/SLA/SupportPoint.hpp>
|
||||||
|
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||||
|
|
||||||
#include <libslic3r/ClipperUtils.hpp>
|
#include <libslic3r/ClipperUtils.hpp>
|
||||||
#include <libslic3r/Point.hpp>
|
#include <libslic3r/Point.hpp>
|
||||||
#include <libslic3r/TriangleMesh.hpp>
|
#include <libslic3r/TriangleMesh.hpp>
|
||||||
#include <libslic3r/SLA/SLACommon.hpp>
|
|
||||||
|
|
||||||
#include <boost/container/small_vector.hpp>
|
#include <boost/container/small_vector.hpp>
|
||||||
|
|
||||||
// #define SLA_AUTOSUPPORTS_DEBUG
|
// #define SLA_SUPPORTPOINTGEN_DEBUG
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r { namespace sla {
|
||||||
namespace sla {
|
|
||||||
|
|
||||||
class SLAAutoSupports {
|
class SupportPointGenerator {
|
||||||
public:
|
public:
|
||||||
struct Config {
|
struct Config {
|
||||||
float density_relative {1.f};
|
float density_relative {1.f};
|
||||||
|
|
@ -24,19 +28,22 @@ public:
|
||||||
inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2)
|
inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2)
|
||||||
};
|
};
|
||||||
|
|
||||||
SLAAutoSupports(const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
|
SupportPointGenerator(const EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
|
||||||
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
||||||
|
|
||||||
const std::vector<sla::SupportPoint>& output() { return m_output; }
|
SupportPointGenerator(const EigenMesh3D& emesh, const Config& config, std::function<void(void)> throw_on_cancel, std::function<void(int)> statusfn);
|
||||||
|
|
||||||
|
const std::vector<SupportPoint>& output() const { return m_output; }
|
||||||
|
std::vector<SupportPoint>& output() { return m_output; }
|
||||||
|
|
||||||
struct MyLayer;
|
struct MyLayer;
|
||||||
|
|
||||||
struct Structure {
|
struct Structure {
|
||||||
Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f ¢roid, float area, float h) :
|
Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f ¢roid, float area, float h) :
|
||||||
layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), height(h)
|
layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), height(h)
|
||||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||||
, unique_id(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()))
|
, unique_id(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()))
|
||||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
|
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
|
||||||
{}
|
{}
|
||||||
MyLayer *layer;
|
MyLayer *layer;
|
||||||
const ExPolygon* polygon = nullptr;
|
const ExPolygon* polygon = nullptr;
|
||||||
|
|
@ -49,9 +56,9 @@ public:
|
||||||
float supports_force_this_layer = 0.f;
|
float supports_force_this_layer = 0.f;
|
||||||
float supports_force_inherited = 0.f;
|
float supports_force_inherited = 0.f;
|
||||||
float supports_force_total() const { return this->supports_force_this_layer + this->supports_force_inherited; }
|
float supports_force_total() const { return this->supports_force_this_layer + this->supports_force_inherited; }
|
||||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||||
std::chrono::milliseconds unique_id;
|
std::chrono::milliseconds unique_id;
|
||||||
#endif /* SLA_AUTOSUPPORTS_DEBUG */
|
#endif /* SLA_SUPPORTPOINTGEN_DEBUG */
|
||||||
|
|
||||||
struct Link {
|
struct Link {
|
||||||
Link(Structure *island, float overlap_area) : island(island), overlap_area(overlap_area) {}
|
Link(Structure *island, float overlap_area) : island(island), overlap_area(overlap_area) {}
|
||||||
|
|
@ -74,7 +81,7 @@ public:
|
||||||
ExPolygons overhangs;
|
ExPolygons overhangs;
|
||||||
// Overhangs, where the surface must slope.
|
// Overhangs, where the surface must slope.
|
||||||
ExPolygons overhangs_slopes;
|
ExPolygons overhangs_slopes;
|
||||||
float overhangs_area;
|
float overhangs_area = 0.f;
|
||||||
|
|
||||||
bool overlaps(const Structure &rhs) const {
|
bool overlaps(const Structure &rhs) const {
|
||||||
return this->bbox.overlap(rhs.bbox) && (this->polygon->overlaps(*rhs.polygon) || rhs.polygon->overlaps(*this->polygon));
|
return this->bbox.overlap(rhs.bbox) && (this->polygon->overlaps(*rhs.polygon) || rhs.polygon->overlaps(*this->polygon));
|
||||||
|
|
@ -182,29 +189,33 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
void execute(const std::vector<ExPolygons> &slices,
|
||||||
std::vector<sla::SupportPoint> m_output;
|
const std::vector<float> & heights);
|
||||||
|
|
||||||
SLAAutoSupports::Config m_config;
|
void seed(std::mt19937::result_type s) { m_rng.seed(s); }
|
||||||
|
private:
|
||||||
|
std::vector<SupportPoint> m_output;
|
||||||
|
|
||||||
|
SupportPointGenerator::Config m_config;
|
||||||
|
|
||||||
void process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights);
|
void process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights);
|
||||||
void uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island = false, bool just_one = false);
|
void uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island = false, bool just_one = false);
|
||||||
void project_onto_mesh(std::vector<sla::SupportPoint>& points) const;
|
void project_onto_mesh(std::vector<SupportPoint>& points) const;
|
||||||
|
|
||||||
#ifdef SLA_AUTOSUPPORTS_DEBUG
|
#ifdef SLA_SUPPORTPOINTGEN_DEBUG
|
||||||
static void output_expolygons(const ExPolygons& expolys, const std::string &filename);
|
static void output_expolygons(const ExPolygons& expolys, const std::string &filename);
|
||||||
static void output_structures(const std::vector<Structure> &structures);
|
static void output_structures(const std::vector<Structure> &structures);
|
||||||
#endif // SLA_AUTOSUPPORTS_DEBUG
|
#endif // SLA_SUPPORTPOINTGEN_DEBUG
|
||||||
|
|
||||||
const sla::EigenMesh3D& m_emesh;
|
const EigenMesh3D& m_emesh;
|
||||||
std::function<void(void)> m_throw_on_cancel;
|
std::function<void(void)> m_throw_on_cancel;
|
||||||
std::function<void(int)> m_statusfn;
|
std::function<void(int)> m_statusfn;
|
||||||
|
|
||||||
|
std::mt19937 m_rng;
|
||||||
};
|
};
|
||||||
|
|
||||||
void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance);
|
void remove_bottom_points(std::vector<SupportPoint> &pts, double gnd_lvl, double tolerance);
|
||||||
|
|
||||||
} // namespace sla
|
}} // namespace Slic3r::sla
|
||||||
} // namespace Slic3r
|
|
||||||
|
|
||||||
|
#endif // SUPPORTPOINTGENERATOR_HPP
|
||||||
#endif // SLAAUTOSUPPORTS_HPP_
|
|
||||||
|
|
@ -4,10 +4,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include "SLASupportTree.hpp"
|
#include <libslic3r/SLA/SupportTree.hpp>
|
||||||
#include "SLABoilerPlate.hpp"
|
#include <libslic3r/SLA/Common.hpp>
|
||||||
#include "SLASpatIndex.hpp"
|
#include <libslic3r/SLA/SpatIndex.hpp>
|
||||||
#include "SLASupportTreeBuilder.hpp"
|
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||||
|
|
||||||
#include <libslic3r/MTUtils.hpp>
|
#include <libslic3r/MTUtils.hpp>
|
||||||
#include <libslic3r/ClipperUtils.hpp>
|
#include <libslic3r/ClipperUtils.hpp>
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
#ifndef SLASUPPORTTREE_HPP
|
#ifndef SLA_SUPPORTTREE_HPP
|
||||||
#define SLASUPPORTTREE_HPP
|
#define SLA_SUPPORTTREE_HPP
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <Eigen/Geometry>
|
#include <Eigen/Geometry>
|
||||||
|
|
||||||
#include "SLACommon.hpp"
|
#include <libslic3r/SLA/Common.hpp>
|
||||||
#include "SLAPad.hpp"
|
#include <libslic3r/SLA/Pad.hpp>
|
||||||
|
#include <libslic3r/SLA/EigenMesh3D.hpp>
|
||||||
|
#include <libslic3r/SLA/SupportPoint.hpp>
|
||||||
|
#include <libslic3r/SLA/JobController.hpp>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
|
|
@ -105,27 +108,6 @@ struct SupportConfig
|
||||||
|
|
||||||
enum class MeshType { Support, Pad };
|
enum class MeshType { Support, Pad };
|
||||||
|
|
||||||
/// A Control structure for the support calculation. Consists of the status
|
|
||||||
/// indicator callback and the stop condition predicate.
|
|
||||||
struct JobController
|
|
||||||
{
|
|
||||||
using StatusFn = std::function<void(unsigned, const std::string&)>;
|
|
||||||
using StopCond = std::function<bool(void)>;
|
|
||||||
using CancelFn = std::function<void(void)>;
|
|
||||||
|
|
||||||
// This will signal the status of the calculation to the front-end
|
|
||||||
StatusFn statuscb = [](unsigned, const std::string&){};
|
|
||||||
|
|
||||||
// Returns true if the calculation should be aborted.
|
|
||||||
StopCond stopcondition = [](){ return false; };
|
|
||||||
|
|
||||||
// Similar to cancel callback. This should check the stop condition and
|
|
||||||
// if true, throw an appropriate exception. (TriangleMeshSlicer needs this)
|
|
||||||
// consider it a hard abort. stopcondition is permits the algorithm to
|
|
||||||
// terminate itself
|
|
||||||
CancelFn cancelfn = [](){};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SupportableMesh
|
struct SupportableMesh
|
||||||
{
|
{
|
||||||
EigenMesh3D emesh;
|
EigenMesh3D emesh;
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include "SLASupportTreeBuilder.hpp"
|
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||||
#include "SLASupportTreeBuildsteps.hpp"
|
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
|
||||||
|
#include <libslic3r/SLA/Contour3D.hpp>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace sla {
|
namespace sla {
|
||||||
|
|
@ -12,7 +13,7 @@ Contour3D sphere(double rho, Portion portion, double fa) {
|
||||||
if(rho <= 1e-6 && rho >= -1e-6) return ret;
|
if(rho <= 1e-6 && rho >= -1e-6) return ret;
|
||||||
|
|
||||||
auto& vertices = ret.points;
|
auto& vertices = ret.points;
|
||||||
auto& facets = ret.indices;
|
auto& facets = ret.faces3;
|
||||||
|
|
||||||
// Algorithm:
|
// Algorithm:
|
||||||
// Add points one-by-one to the sphere grid and form facets using relative
|
// Add points one-by-one to the sphere grid and form facets using relative
|
||||||
|
|
@ -102,7 +103,7 @@ Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp)
|
||||||
|
|
||||||
auto steps = int(ssteps);
|
auto steps = int(ssteps);
|
||||||
auto& points = ret.points;
|
auto& points = ret.points;
|
||||||
auto& indices = ret.indices;
|
auto& indices = ret.faces3;
|
||||||
points.reserve(2*ssteps);
|
points.reserve(2*ssteps);
|
||||||
double a = 2*PI/steps;
|
double a = 2*PI/steps;
|
||||||
|
|
||||||
|
|
@ -211,8 +212,8 @@ Head::Head(double r_big_mm,
|
||||||
coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2);
|
coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2);
|
||||||
coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1;
|
coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1;
|
||||||
|
|
||||||
mesh.indices.emplace_back(i1s1, i2s1, i2s2);
|
mesh.faces3.emplace_back(i1s1, i2s1, i2s2);
|
||||||
mesh.indices.emplace_back(i1s1, i2s2, i1s2);
|
mesh.faces3.emplace_back(i1s1, i2s2, i1s2);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto i1s1 = coord_t(s1.points.size()) - coord_t(steps);
|
auto i1s1 = coord_t(s1.points.size()) - coord_t(steps);
|
||||||
|
|
@ -220,8 +221,8 @@ Head::Head(double r_big_mm,
|
||||||
auto i1s2 = coord_t(s1.points.size());
|
auto i1s2 = coord_t(s1.points.size());
|
||||||
auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1;
|
auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1;
|
||||||
|
|
||||||
mesh.indices.emplace_back(i2s2, i2s1, i1s1);
|
mesh.faces3.emplace_back(i2s2, i2s1, i1s1);
|
||||||
mesh.indices.emplace_back(i1s2, i2s2, i1s1);
|
mesh.faces3.emplace_back(i1s2, i2s2, i1s1);
|
||||||
|
|
||||||
// To simplify further processing, we translate the mesh so that the
|
// To simplify further processing, we translate the mesh so that the
|
||||||
// last vertex of the pointing sphere (the pinpoint) will be at (0,0,0)
|
// last vertex of the pointing sphere (the pinpoint) will be at (0,0,0)
|
||||||
|
|
@ -240,7 +241,7 @@ Pillar::Pillar(const Vec3d &jp, const Vec3d &endp, double radius, size_t st):
|
||||||
// move the data.
|
// move the data.
|
||||||
Contour3D body = cylinder(radius, height, st, endp);
|
Contour3D body = cylinder(radius, height, st, endp);
|
||||||
mesh.points.swap(body.points);
|
mesh.points.swap(body.points);
|
||||||
mesh.indices.swap(body.indices);
|
mesh.faces3.swap(body.faces3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -275,7 +276,7 @@ Pillar &Pillar::add_base(double baseheight, double radius)
|
||||||
base.points.emplace_back(endpt);
|
base.points.emplace_back(endpt);
|
||||||
base.points.emplace_back(ep);
|
base.points.emplace_back(ep);
|
||||||
|
|
||||||
auto& indices = base.indices;
|
auto& indices = base.faces3;
|
||||||
auto hcenter = int(base.points.size() - 1);
|
auto hcenter = int(base.points.size() - 1);
|
||||||
auto lcenter = int(base.points.size() - 2);
|
auto lcenter = int(base.points.size() - 2);
|
||||||
auto offs = int(steps);
|
auto offs = int(steps);
|
||||||
|
|
@ -466,7 +467,7 @@ const TriangleMesh &SupportTreeBuilder::merged_mesh() const
|
||||||
return m_meshcache;
|
return m_meshcache;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_meshcache = mesh(merged);
|
m_meshcache = to_triangle_mesh(merged);
|
||||||
|
|
||||||
// The mesh will be passed by const-pointer to TriangleMeshSlicer,
|
// The mesh will be passed by const-pointer to TriangleMeshSlicer,
|
||||||
// which will need this.
|
// which will need this.
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
#ifndef SUPPORTTREEBUILDER_HPP
|
#ifndef SLA_SUPPORTTREEBUILDER_HPP
|
||||||
#define SUPPORTTREEBUILDER_HPP
|
#define SLA_SUPPORTTREEBUILDER_HPP
|
||||||
|
|
||||||
#include "SLAConcurrency.hpp"
|
#include <libslic3r/SLA/Concurrency.hpp>
|
||||||
#include "SLABoilerPlate.hpp"
|
#include <libslic3r/SLA/Common.hpp>
|
||||||
#include "SLASupportTree.hpp"
|
#include <libslic3r/SLA/SupportTree.hpp>
|
||||||
#include "SLAPad.hpp"
|
#include <libslic3r/SLA/Contour3D.hpp>
|
||||||
|
#include <libslic3r/SLA/Pad.hpp>
|
||||||
#include <libslic3r/MTUtils.hpp>
|
#include <libslic3r/MTUtils.hpp>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
@ -73,7 +74,7 @@ Contour3D sphere(double rho, Portion portion = make_portion(0.0, 2.0*PI),
|
||||||
// h: Height
|
// h: Height
|
||||||
// ssteps: how many edges will create the base circle
|
// ssteps: how many edges will create the base circle
|
||||||
// sp: starting point
|
// sp: starting point
|
||||||
Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp = {0,0,0});
|
Contour3D cylinder(double r, double h, size_t ssteps = 45, const Vec3d &sp = {0,0,0});
|
||||||
|
|
||||||
const constexpr long ID_UNSET = -1;
|
const constexpr long ID_UNSET = -1;
|
||||||
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include "SLASupportTreeBuildsteps.hpp"
|
#include <libslic3r/SLA/SupportTreeBuildsteps.hpp>
|
||||||
|
|
||||||
#include <libnest2d/optimizers/nlopt/genetic.hpp>
|
#include <libnest2d/optimizers/nlopt/genetic.hpp>
|
||||||
#include <libnest2d/optimizers/nlopt/subplex.hpp>
|
#include <libnest2d/optimizers/nlopt/subplex.hpp>
|
||||||
|
|
@ -7,6 +7,14 @@
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace sla {
|
namespace sla {
|
||||||
|
|
||||||
|
static const Vec3d DOWN = {0.0, 0.0, -1.0};
|
||||||
|
|
||||||
|
using libnest2d::opt::initvals;
|
||||||
|
using libnest2d::opt::bound;
|
||||||
|
using libnest2d::opt::StopCriteria;
|
||||||
|
using libnest2d::opt::GeneticOptimizer;
|
||||||
|
using libnest2d::opt::SubplexOptimizer;
|
||||||
|
|
||||||
SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder,
|
SupportTreeBuildsteps::SupportTreeBuildsteps(SupportTreeBuilder & builder,
|
||||||
const SupportableMesh &sm)
|
const SupportableMesh &sm)
|
||||||
: m_cfg(sm.cfg)
|
: m_cfg(sm.cfg)
|
||||||
|
|
@ -560,9 +568,7 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
||||||
double radius,
|
double radius,
|
||||||
long head_id)
|
long head_id)
|
||||||
{
|
{
|
||||||
// People were killed for this number (seriously)
|
const double SLOPE = 1. / std::cos(m_cfg.bridge_slope);
|
||||||
static const double SQR2 = std::sqrt(2.0);
|
|
||||||
static const Vec3d DOWN = {0.0, 0.0, -1.0};
|
|
||||||
|
|
||||||
double gndlvl = m_builder.ground_level;
|
double gndlvl = m_builder.ground_level;
|
||||||
Vec3d endp = {jp(X), jp(Y), gndlvl};
|
Vec3d endp = {jp(X), jp(Y), gndlvl};
|
||||||
|
|
@ -573,6 +579,11 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
||||||
bool can_add_base = true;
|
bool can_add_base = true;
|
||||||
bool normal_mode = true;
|
bool normal_mode = true;
|
||||||
|
|
||||||
|
// If in zero elevation mode and the pillar is too close to the model body,
|
||||||
|
// the support pillar can not be placed in the gap between the model and
|
||||||
|
// the pad, and the pillar bases must not touch the model body either.
|
||||||
|
// To solve this, a corrector bridge is inserted between the starting point
|
||||||
|
// (jp) and the new pillar.
|
||||||
if (m_cfg.object_elevation_mm < EPSILON
|
if (m_cfg.object_elevation_mm < EPSILON
|
||||||
&& (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) {
|
&& (dist = std::sqrt(m_mesh.squared_distance(endp))) < min_dist) {
|
||||||
// Get the distance from the mesh. This can be later optimized
|
// Get the distance from the mesh. This can be later optimized
|
||||||
|
|
@ -580,31 +591,35 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
||||||
// the ground level only.
|
// the ground level only.
|
||||||
|
|
||||||
normal_mode = false;
|
normal_mode = false;
|
||||||
double mind = min_dist - dist;
|
|
||||||
double azimuth = std::atan2(sourcedir(Y), sourcedir(X));
|
|
||||||
double sinpolar = std::sin(PI - m_cfg.bridge_slope);
|
|
||||||
double cospolar = std::cos(PI - m_cfg.bridge_slope);
|
|
||||||
double cosazm = std::cos(azimuth);
|
|
||||||
double sinazm = std::sin(azimuth);
|
|
||||||
|
|
||||||
auto dir = Vec3d(cosazm * sinpolar, sinazm * sinpolar, cospolar)
|
// The min distance needed to move away from the model in XY plane.
|
||||||
.normalized();
|
double current_d = min_dist - dist;
|
||||||
|
double current_bride_d = SLOPE * current_d;
|
||||||
|
|
||||||
|
// get a suitable direction for the corrector bridge. It is the
|
||||||
|
// original sourcedir's azimuth but the polar angle is saturated to the
|
||||||
|
// configured bridge slope.
|
||||||
|
auto [polar, azimuth] = dir_to_spheric(sourcedir);
|
||||||
|
polar = PI - m_cfg.bridge_slope;
|
||||||
|
auto dir = spheric_to_dir(polar, azimuth).normalized();
|
||||||
|
|
||||||
using namespace libnest2d::opt;
|
|
||||||
StopCriteria scr;
|
StopCriteria scr;
|
||||||
scr.stop_score = min_dist;
|
scr.stop_score = min_dist;
|
||||||
SubplexOptimizer solver(scr);
|
SubplexOptimizer solver(scr);
|
||||||
|
|
||||||
|
// Search for a distance along the corrector bridge to move the endpoint
|
||||||
|
// sufficiently away form the model body. The first few optimization
|
||||||
|
// cycles should succeed here.
|
||||||
auto result = solver.optimize_max(
|
auto result = solver.optimize_max(
|
||||||
[this, dir, jp, gndlvl](double mv) {
|
[this, dir, jp, gndlvl](double mv) {
|
||||||
Vec3d endpt = jp + SQR2 * mv * dir;
|
Vec3d endpt = jp + mv * dir;
|
||||||
endpt(Z) = gndlvl;
|
endpt(Z) = gndlvl;
|
||||||
return std::sqrt(m_mesh.squared_distance(endpt));
|
return std::sqrt(m_mesh.squared_distance(endpt));
|
||||||
},
|
},
|
||||||
initvals(mind), bound(0.0, 2 * min_dist));
|
initvals(current_bride_d),
|
||||||
|
bound(0.0, m_cfg.max_bridge_length_mm - current_bride_d));
|
||||||
|
|
||||||
mind = std::get<0>(result.optimum);
|
endp = jp + std::get<0>(result.optimum) * dir;
|
||||||
endp = jp + SQR2 * mind * dir;
|
|
||||||
Vec3d pgnd = {endp(X), endp(Y), gndlvl};
|
Vec3d pgnd = {endp(X), endp(Y), gndlvl};
|
||||||
can_add_base = result.score > min_dist;
|
can_add_base = result.score > min_dist;
|
||||||
|
|
||||||
|
|
@ -623,7 +638,7 @@ void SupportTreeBuildsteps::create_ground_pillar(const Vec3d &jp,
|
||||||
else {
|
else {
|
||||||
// If the new endpoint is below ground, do not make a pillar
|
// If the new endpoint is below ground, do not make a pillar
|
||||||
if (endp(Z) < gndlvl)
|
if (endp(Z) < gndlvl)
|
||||||
endp = endp - SQR2 * (gndlvl - endp(Z)) * dir; // back off
|
endp = endp - SLOPE * (gndlvl - endp(Z)) * dir; // back off
|
||||||
else {
|
else {
|
||||||
|
|
||||||
auto hit = bridge_mesh_intersect(endp, DOWN, radius);
|
auto hit = bridge_mesh_intersect(endp, DOWN, radius);
|
||||||
|
|
@ -685,11 +700,6 @@ void SupportTreeBuildsteps::filter()
|
||||||
// not be enough space for the pinhead. Filtering is applied for
|
// not be enough space for the pinhead. Filtering is applied for
|
||||||
// these reasons.
|
// these reasons.
|
||||||
|
|
||||||
using libnest2d::opt::bound;
|
|
||||||
using libnest2d::opt::initvals;
|
|
||||||
using libnest2d::opt::GeneticOptimizer;
|
|
||||||
using libnest2d::opt::StopCriteria;
|
|
||||||
|
|
||||||
ccr::SpinningMutex mutex;
|
ccr::SpinningMutex mutex;
|
||||||
auto addfn = [&mutex](PtIndices &container, unsigned val) {
|
auto addfn = [&mutex](PtIndices &container, unsigned val) {
|
||||||
std::lock_guard<ccr::SpinningMutex> lk(mutex);
|
std::lock_guard<ccr::SpinningMutex> lk(mutex);
|
||||||
|
|
@ -708,10 +718,7 @@ void SupportTreeBuildsteps::filter()
|
||||||
// (Quaternion::FromTwoVectors) and apply the rotation to the
|
// (Quaternion::FromTwoVectors) and apply the rotation to the
|
||||||
// arrow head.
|
// arrow head.
|
||||||
|
|
||||||
double z = n(2);
|
auto [polar, azimuth] = dir_to_spheric(n);
|
||||||
double r = 1.0; // for normalized vector
|
|
||||||
double polar = std::acos(z / r);
|
|
||||||
double azimuth = std::atan2(n(1), n(0));
|
|
||||||
|
|
||||||
// skip if the tilt is not sane
|
// skip if the tilt is not sane
|
||||||
if(polar >= PI - m_cfg.normal_cutoff_angle) {
|
if(polar >= PI - m_cfg.normal_cutoff_angle) {
|
||||||
|
|
@ -729,9 +736,7 @@ void SupportTreeBuildsteps::filter()
|
||||||
double pin_r = double(m_support_pts[fidx].head_front_radius);
|
double pin_r = double(m_support_pts[fidx].head_front_radius);
|
||||||
|
|
||||||
// Reassemble the now corrected normal
|
// Reassemble the now corrected normal
|
||||||
auto nn = Vec3d(std::cos(azimuth) * std::sin(polar),
|
auto nn = spheric_to_dir(polar, azimuth).normalized();
|
||||||
std::sin(azimuth) * std::sin(polar),
|
|
||||||
std::cos(polar)).normalized();
|
|
||||||
|
|
||||||
// check available distance
|
// check available distance
|
||||||
EigenMesh3D::hit_result t
|
EigenMesh3D::hit_result t
|
||||||
|
|
@ -757,9 +762,7 @@ void SupportTreeBuildsteps::filter()
|
||||||
auto oresult = solver.optimize_max(
|
auto oresult = solver.optimize_max(
|
||||||
[this, pin_r, w, hp](double plr, double azm)
|
[this, pin_r, w, hp](double plr, double azm)
|
||||||
{
|
{
|
||||||
auto dir = Vec3d(std::cos(azm) * std::sin(plr),
|
auto dir = spheric_to_dir(plr, azm).normalized();
|
||||||
std::sin(azm) * std::sin(plr),
|
|
||||||
std::cos(plr)).normalized();
|
|
||||||
|
|
||||||
double score = pinhead_mesh_intersect(
|
double score = pinhead_mesh_intersect(
|
||||||
hp, dir, pin_r, m_cfg.head_back_radius_mm, w);
|
hp, dir, pin_r, m_cfg.head_back_radius_mm, w);
|
||||||
|
|
@ -767,18 +770,15 @@ void SupportTreeBuildsteps::filter()
|
||||||
return score;
|
return score;
|
||||||
},
|
},
|
||||||
initvals(polar, azimuth), // start with what we have
|
initvals(polar, azimuth), // start with what we have
|
||||||
bound(3 * PI / 4,
|
bound(3 * PI / 4, PI), // Must not exceed the tilt limit
|
||||||
PI), // Must not exceed the tilt limit
|
|
||||||
bound(-PI, PI) // azimuth can be a full search
|
bound(-PI, PI) // azimuth can be a full search
|
||||||
);
|
);
|
||||||
|
|
||||||
if(oresult.score > w) {
|
if(oresult.score > w) {
|
||||||
polar = std::get<0>(oresult.optimum);
|
polar = std::get<0>(oresult.optimum);
|
||||||
azimuth = std::get<1>(oresult.optimum);
|
azimuth = std::get<1>(oresult.optimum);
|
||||||
nn = Vec3d(std::cos(azimuth) * std::sin(polar),
|
nn = spheric_to_dir(polar, azimuth).normalized();
|
||||||
std::sin(azimuth) * std::sin(polar),
|
t = EigenMesh3D::hit_result(oresult.score);
|
||||||
std::cos(polar)).normalized();
|
|
||||||
t = oresult.score;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -837,16 +837,17 @@ void SupportTreeBuildsteps::classify()
|
||||||
m_thr();
|
m_thr();
|
||||||
|
|
||||||
auto& head = m_builder.head(i);
|
auto& head = m_builder.head(i);
|
||||||
Vec3d n(0, 0, -1);
|
|
||||||
double r = head.r_back_mm;
|
double r = head.r_back_mm;
|
||||||
Vec3d headjp = head.junction_point();
|
Vec3d headjp = head.junction_point();
|
||||||
|
|
||||||
// collision check
|
// collision check
|
||||||
auto hit = bridge_mesh_intersect(headjp, n, r);
|
auto hit = bridge_mesh_intersect(headjp, DOWN, r);
|
||||||
|
|
||||||
if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i);
|
if(std::isinf(hit.distance())) ground_head_indices.emplace_back(i);
|
||||||
else if(m_cfg.ground_facing_only) head.invalidate();
|
else if(m_cfg.ground_facing_only) head.invalidate();
|
||||||
else m_iheads_onmodel.emplace_back(std::make_pair(i, hit));
|
else m_iheads_onmodel.emplace_back(i);
|
||||||
|
|
||||||
|
m_head_to_ground_scans[i] = hit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We want to search for clusters of points that are far enough
|
// We want to search for clusters of points that are far enough
|
||||||
|
|
@ -893,6 +894,7 @@ void SupportTreeBuildsteps::routing_to_ground()
|
||||||
// get the current cluster centroid
|
// get the current cluster centroid
|
||||||
auto & thr = m_thr;
|
auto & thr = m_thr;
|
||||||
const auto &points = m_points;
|
const auto &points = m_points;
|
||||||
|
|
||||||
long lcid = cluster_centroid(
|
long lcid = cluster_centroid(
|
||||||
cl, [&points](size_t idx) { return points.row(long(idx)); },
|
cl, [&points](size_t idx) { return points.row(long(idx)); },
|
||||||
[thr](const Vec3d &p1, const Vec3d &p2) {
|
[thr](const Vec3d &p1, const Vec3d &p2) {
|
||||||
|
|
@ -944,86 +946,36 @@ void SupportTreeBuildsteps::routing_to_ground()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SupportTreeBuildsteps::routing_to_model()
|
bool SupportTreeBuildsteps::connect_to_ground(Head &head, const Vec3d &dir)
|
||||||
{
|
{
|
||||||
// We need to check if there is an easy way out to the bed surface.
|
auto hjp = head.junction_point();
|
||||||
// If it can be routed there with a bridge shorter than
|
double r = head.r_back_mm;
|
||||||
// min_bridge_distance.
|
double t = bridge_mesh_intersect(hjp, dir, head.r_back_mm);
|
||||||
|
double d = 0, tdown = 0;
|
||||||
|
t = std::min(t, m_cfg.max_bridge_length_mm);
|
||||||
|
|
||||||
// First we want to index the available pillars. The best is to connect
|
while (d < t && !std::isinf(tdown = bridge_mesh_intersect(hjp + d * dir, DOWN, r)))
|
||||||
// these points to the available pillars
|
d += r;
|
||||||
|
|
||||||
auto routedown = [this](Head& head, const Vec3d& dir, double dist)
|
if(!std::isinf(tdown)) return false;
|
||||||
{
|
|
||||||
head.transform();
|
Vec3d endp = hjp + d * dir;
|
||||||
Vec3d endp = head.junction_point() + dist * dir;
|
|
||||||
m_builder.add_bridge(head.id, endp);
|
m_builder.add_bridge(head.id, endp);
|
||||||
m_builder.add_junction(endp, head.r_back_mm);
|
m_builder.add_junction(endp, head.r_back_mm);
|
||||||
|
|
||||||
this->create_ground_pillar(endp, dir, head.r_back_mm);
|
this->create_ground_pillar(endp, dir, head.r_back_mm);
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<unsigned> modelpillars;
|
return true;
|
||||||
ccr::SpinningMutex mutex;
|
}
|
||||||
|
|
||||||
auto onmodelfn =
|
bool SupportTreeBuildsteps::connect_to_ground(Head &head)
|
||||||
[this, routedown, &modelpillars, &mutex]
|
|
||||||
(const std::pair<unsigned, EigenMesh3D::hit_result> &el, size_t)
|
|
||||||
{
|
{
|
||||||
m_thr();
|
if (connect_to_ground(head, head.dir)) return true;
|
||||||
unsigned idx = el.first;
|
|
||||||
EigenMesh3D::hit_result hit = el.second;
|
|
||||||
|
|
||||||
auto& head = m_builder.head(idx);
|
|
||||||
Vec3d hjp = head.junction_point();
|
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////
|
|
||||||
// Search nearby pillar
|
|
||||||
// /////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
if(search_pillar_and_connect(head)) { head.transform(); return; }
|
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////
|
|
||||||
// Try straight path
|
|
||||||
// /////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
// Cannot connect to nearby pillar. We will try to search for
|
|
||||||
// a route to the ground.
|
|
||||||
|
|
||||||
double t = bridge_mesh_intersect(hjp, head.dir, head.r_back_mm);
|
|
||||||
double d = 0, tdown = 0;
|
|
||||||
Vec3d dirdown(0.0, 0.0, -1.0);
|
|
||||||
|
|
||||||
t = std::min(t, m_cfg.max_bridge_length_mm);
|
|
||||||
|
|
||||||
while(d < t && !std::isinf(tdown = bridge_mesh_intersect(
|
|
||||||
hjp + d*head.dir,
|
|
||||||
dirdown, head.r_back_mm))) {
|
|
||||||
d += head.r_back_mm;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(std::isinf(tdown)) { // we heave found a route to the ground
|
|
||||||
routedown(head, head.dir, d); return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////
|
|
||||||
// Optimize bridge direction
|
|
||||||
// /////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
|
// Optimize bridge direction:
|
||||||
// Straight path failed so we will try to search for a suitable
|
// Straight path failed so we will try to search for a suitable
|
||||||
// direction out of the cavity.
|
// direction out of the cavity.
|
||||||
|
auto [polar, azimuth] = dir_to_spheric(head.dir);
|
||||||
// Get the spherical representation of the normal. its easier to
|
|
||||||
// work with.
|
|
||||||
double z = head.dir(Z);
|
|
||||||
double r = 1.0; // for normalized vector
|
|
||||||
double polar = std::acos(z / r);
|
|
||||||
double azimuth = std::atan2(head.dir(Y), head.dir(X));
|
|
||||||
|
|
||||||
using libnest2d::opt::bound;
|
|
||||||
using libnest2d::opt::initvals;
|
|
||||||
using libnest2d::opt::GeneticOptimizer;
|
|
||||||
using libnest2d::opt::StopCriteria;
|
|
||||||
|
|
||||||
StopCriteria stc;
|
StopCriteria stc;
|
||||||
stc.max_iterations = m_cfg.optimizer_max_iterations;
|
stc.max_iterations = m_cfg.optimizer_max_iterations;
|
||||||
|
|
@ -1033,13 +985,10 @@ void SupportTreeBuildsteps::routing_to_model()
|
||||||
solver.seed(0); // we want deterministic behavior
|
solver.seed(0); // we want deterministic behavior
|
||||||
|
|
||||||
double r_back = head.r_back_mm;
|
double r_back = head.r_back_mm;
|
||||||
|
Vec3d hjp = head.junction_point();
|
||||||
auto oresult = solver.optimize_max(
|
auto oresult = solver.optimize_max(
|
||||||
[this, hjp, r_back](double plr, double azm)
|
[this, hjp, r_back](double plr, double azm) {
|
||||||
{
|
Vec3d n = spheric_to_dir(plr, azm).normalized();
|
||||||
Vec3d n = Vec3d(std::cos(azm) * std::sin(plr),
|
|
||||||
std::sin(azm) * std::sin(plr),
|
|
||||||
std::cos(plr)).normalized();
|
|
||||||
return bridge_mesh_intersect(hjp, n, r_back);
|
return bridge_mesh_intersect(hjp, n, r_back);
|
||||||
},
|
},
|
||||||
initvals(polar, azimuth), // let's start with what we have
|
initvals(polar, azimuth), // let's start with what we have
|
||||||
|
|
@ -1047,31 +996,19 @@ void SupportTreeBuildsteps::routing_to_model()
|
||||||
bound(-PI, PI) // azimuth can be a full range search
|
bound(-PI, PI) // azimuth can be a full range search
|
||||||
);
|
);
|
||||||
|
|
||||||
d = 0; t = oresult.score;
|
Vec3d bridgedir = spheric_to_dir(oresult.optimum).normalized();
|
||||||
|
return connect_to_ground(head, bridgedir);
|
||||||
polar = std::get<0>(oresult.optimum);
|
|
||||||
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
|
bool SupportTreeBuildsteps::connect_to_model_body(Head &head)
|
||||||
routedown(head, bridgedir, d); return;
|
{
|
||||||
}
|
if (head.id <= ID_UNSET) return false;
|
||||||
|
|
||||||
// /////////////////////////////////////////////////////////////////
|
auto it = m_head_to_ground_scans.find(unsigned(head.id));
|
||||||
// Route to model body
|
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));
|
double zangle = std::asin(hit.direction()(Z));
|
||||||
zangle = std::max(zangle, PI/4);
|
zangle = std::max(zangle, PI/4);
|
||||||
double h = std::sin(zangle) * head.fullwidth();
|
double h = std::sin(zangle) * head.fullwidth();
|
||||||
|
|
@ -1079,9 +1016,10 @@ void SupportTreeBuildsteps::routing_to_model()
|
||||||
// The width of the tail head that we would like to have...
|
// The width of the tail head that we would like to have...
|
||||||
h = std::min(hit.distance() - head.r_back_mm, h);
|
h = std::min(hit.distance() - head.r_back_mm, h);
|
||||||
|
|
||||||
if(h > 0) {
|
if(h <= 0.) return false;
|
||||||
|
|
||||||
Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h};
|
Vec3d endp{hjp(X), hjp(Y), hjp(Z) - hit.distance() + h};
|
||||||
auto center_hit = m_mesh.query_ray_hit(hjp, dirdown);
|
auto center_hit = m_mesh.query_ray_hit(hjp, DOWN);
|
||||||
|
|
||||||
double hitdiff = center_hit.distance() - hit.distance();
|
double hitdiff = center_hit.distance() - hit.distance();
|
||||||
Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm?
|
Vec3d hitp = std::abs(hitdiff) < 2*head.r_back_mm?
|
||||||
|
|
@ -1101,35 +1039,45 @@ void SupportTreeBuildsteps::routing_to_model()
|
||||||
w = 0.;
|
w = 0.;
|
||||||
}
|
}
|
||||||
|
|
||||||
Head tailhead(head.r_back_mm,
|
Head tailhead(head.r_back_mm, head.r_pin_mm, w,
|
||||||
head.r_pin_mm,
|
m_cfg.head_penetration_mm, taildir, hitp);
|
||||||
w,
|
|
||||||
m_cfg.head_penetration_mm,
|
|
||||||
taildir,
|
|
||||||
hitp);
|
|
||||||
|
|
||||||
tailhead.transform();
|
tailhead.transform();
|
||||||
pill.base = tailhead.mesh;
|
pill.base = tailhead.mesh;
|
||||||
|
|
||||||
// Experimental: add the pillar to the index for cascading
|
m_pillar_index.guarded_insert(pill.endpoint(), pill.id);
|
||||||
std::lock_guard<ccr::SpinningMutex> lk(mutex);
|
|
||||||
modelpillars.emplace_back(unsigned(pill.id));
|
return true;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SupportTreeBuildsteps::routing_to_model()
|
||||||
|
{
|
||||||
|
// We need to check if there is an easy way out to the bed surface.
|
||||||
|
// If it can be routed there with a bridge shorter than
|
||||||
|
// min_bridge_distance.
|
||||||
|
|
||||||
|
ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(),
|
||||||
|
[this] (const unsigned idx, size_t) {
|
||||||
|
m_thr();
|
||||||
|
|
||||||
|
auto& head = m_builder.head(idx);
|
||||||
|
|
||||||
|
// Search nearby pillar
|
||||||
|
if(search_pillar_and_connect(head)) { head.transform(); return; }
|
||||||
|
|
||||||
|
// Cannot connect to nearby pillar. We will try to search for
|
||||||
|
// a route to the ground.
|
||||||
|
if(connect_to_ground(head)) { head.transform(); return; }
|
||||||
|
|
||||||
|
// No route to the ground, so connect to the model body as a last resort
|
||||||
|
if (connect_to_model_body(head)) { return; }
|
||||||
|
|
||||||
// We have failed to route this head.
|
// We have failed to route this head.
|
||||||
BOOST_LOG_TRIVIAL(warning)
|
BOOST_LOG_TRIVIAL(warning)
|
||||||
<< "Failed to route model facing support point."
|
<< "Failed to route model facing support point. ID: " << idx;
|
||||||
<< " ID: " << idx;
|
|
||||||
head.invalidate();
|
head.invalidate();
|
||||||
};
|
});
|
||||||
|
|
||||||
ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(), onmodelfn);
|
|
||||||
|
|
||||||
for(auto pillid : modelpillars) {
|
|
||||||
auto& pillar = m_builder.pillar(pillid);
|
|
||||||
m_pillar_index.insert(pillar.endpoint(), pillid);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SupportTreeBuildsteps::interconnect_pillars()
|
void SupportTreeBuildsteps::interconnect_pillars()
|
||||||
|
|
@ -1280,7 +1228,8 @@ void SupportTreeBuildsteps::interconnect_pillars()
|
||||||
spts[n] = s;
|
spts[n] = s;
|
||||||
|
|
||||||
// Check the path vertically down
|
// Check the path vertically down
|
||||||
auto hr = bridge_mesh_intersect(s, {0, 0, -1}, pillar().r);
|
Vec3d check_from = s + Vec3d{0., 0., pillar().r};
|
||||||
|
auto hr = bridge_mesh_intersect(check_from, DOWN, pillar().r);
|
||||||
Vec3d gndsp{s(X), s(Y), gnd};
|
Vec3d gndsp{s(X), s(Y), gnd};
|
||||||
|
|
||||||
// If the path is clear, check for pillar base collisions
|
// If the path is clear, check for pillar base collisions
|
||||||
|
|
@ -1360,12 +1309,11 @@ void SupportTreeBuildsteps::routing_headless()
|
||||||
Vec3d n = m_support_nmls.row(i); // mesh outward normal
|
Vec3d n = m_support_nmls.row(i); // mesh outward normal
|
||||||
Vec3d sp = sph - n * HWIDTH_MM; // stick head start point
|
Vec3d sp = sph - n * HWIDTH_MM; // stick head start point
|
||||||
|
|
||||||
Vec3d dir = {0, 0, -1};
|
|
||||||
Vec3d sj = sp + R * n; // stick start point
|
Vec3d sj = sp + R * n; // stick start point
|
||||||
|
|
||||||
// This is only for checking
|
// This is only for checking
|
||||||
double idist = bridge_mesh_intersect(sph, dir, R, true);
|
double idist = bridge_mesh_intersect(sph, DOWN, R, true);
|
||||||
double realdist = ray_mesh_intersect(sj, dir);
|
double realdist = ray_mesh_intersect(sj, DOWN);
|
||||||
double dist = realdist;
|
double dist = realdist;
|
||||||
|
|
||||||
if (std::isinf(dist)) dist = sph(Z) - m_builder.ground_level;
|
if (std::isinf(dist)) dist = sph(Z) - m_builder.ground_level;
|
||||||
|
|
@ -1378,7 +1326,7 @@ void SupportTreeBuildsteps::routing_headless()
|
||||||
}
|
}
|
||||||
|
|
||||||
bool use_endball = !std::isinf(realdist);
|
bool use_endball = !std::isinf(realdist);
|
||||||
Vec3d ej = sj + (dist + HWIDTH_MM) * dir;
|
Vec3d ej = sj + (dist + HWIDTH_MM) * DOWN ;
|
||||||
m_builder.add_compact_bridge(sp, ej, n, R, use_endball);
|
m_builder.add_compact_bridge(sp, ej, n, R, use_endball);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,8 @@
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
#include "SLASupportTreeBuilder.hpp"
|
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||||
|
#include <libslic3r/SLA/Clustering.hpp>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace sla {
|
namespace sla {
|
||||||
|
|
@ -19,6 +20,32 @@ inline Vec2d to_vec2(const Vec3d& v3) {
|
||||||
return {v3(X), v3(Y)};
|
return {v3(X), v3(Y)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline std::pair<double, double> dir_to_spheric(const Vec3d &n, double norm = 1.)
|
||||||
|
{
|
||||||
|
double z = n.z();
|
||||||
|
double r = norm;
|
||||||
|
double polar = std::acos(z / r);
|
||||||
|
double azimuth = std::atan2(n(1), n(0));
|
||||||
|
return {polar, azimuth};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vec3d spheric_to_dir(double polar, double azimuth)
|
||||||
|
{
|
||||||
|
return {std::cos(azimuth) * std::sin(polar),
|
||||||
|
std::sin(azimuth) * std::sin(polar), std::cos(polar)};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vec3d spheric_to_dir(const std::tuple<double, double> &v)
|
||||||
|
{
|
||||||
|
auto [plr, azm] = v;
|
||||||
|
return spheric_to_dir(plr, azm);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vec3d spheric_to_dir(const std::pair<double, double> &v)
|
||||||
|
{
|
||||||
|
return spheric_to_dir(v.first, v.second);
|
||||||
|
}
|
||||||
|
|
||||||
// This function returns the position of the centroid in the input 'clust'
|
// This function returns the position of the centroid in the input 'clust'
|
||||||
// vector of point indices.
|
// vector of point indices.
|
||||||
template<class DistFn>
|
template<class DistFn>
|
||||||
|
|
@ -150,10 +177,10 @@ class SupportTreeBuildsteps {
|
||||||
using PtIndices = std::vector<unsigned>;
|
using PtIndices = std::vector<unsigned>;
|
||||||
|
|
||||||
PtIndices m_iheads; // support points with pinhead
|
PtIndices m_iheads; // support points with pinhead
|
||||||
|
PtIndices m_iheads_onmodel;
|
||||||
PtIndices m_iheadless; // headless support points
|
PtIndices m_iheadless; // headless support points
|
||||||
|
|
||||||
// supp. pts. connecting to model: point index and the ray hit data
|
std::map<unsigned, EigenMesh3D::hit_result> m_head_to_ground_scans;
|
||||||
std::vector<std::pair<unsigned, EigenMesh3D::hit_result>> m_iheads_onmodel;
|
|
||||||
|
|
||||||
// normals for support points from model faces.
|
// normals for support points from model faces.
|
||||||
PointSet m_support_nmls;
|
PointSet m_support_nmls;
|
||||||
|
|
@ -223,14 +250,28 @@ class SupportTreeBuildsteps {
|
||||||
// For connecting a head to a nearby pillar.
|
// For connecting a head to a nearby pillar.
|
||||||
bool connect_to_nearpillar(const Head& head, long nearpillar_id);
|
bool connect_to_nearpillar(const Head& head, long nearpillar_id);
|
||||||
|
|
||||||
|
// Find route for a head to the ground. Inserts additional bridge from the
|
||||||
|
// head to the pillar if cannot create pillar directly.
|
||||||
|
// The optional dir parameter is the direction of the bridge which is the
|
||||||
|
// direction of the pinhead if omitted.
|
||||||
|
bool connect_to_ground(Head& head, const Vec3d &dir);
|
||||||
|
inline bool connect_to_ground(Head& head);
|
||||||
|
|
||||||
|
bool connect_to_model_body(Head &head);
|
||||||
|
|
||||||
bool search_pillar_and_connect(const Head& head);
|
bool search_pillar_and_connect(const Head& head);
|
||||||
|
|
||||||
// This is a proxy function for pillar creation which will mind the gap
|
// This is a proxy function for pillar creation which will mind the gap
|
||||||
// between the pad and the model bottom in zero elevation mode.
|
// between the pad and the model bottom in zero elevation mode.
|
||||||
|
// jp is the starting junction point which needs to be routed down.
|
||||||
|
// sourcedir is the allowed direction of an optional bridge between the
|
||||||
|
// jp junction and the final pillar.
|
||||||
void create_ground_pillar(const Vec3d &jp,
|
void create_ground_pillar(const Vec3d &jp,
|
||||||
const Vec3d &sourcedir,
|
const Vec3d &sourcedir,
|
||||||
double radius,
|
double radius,
|
||||||
long head_id = ID_UNSET);
|
long head_id = ID_UNSET);
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
SupportTreeBuildsteps(SupportTreeBuilder & builder, const SupportableMesh &sm);
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include "SLA/SLASupportTree.hpp"
|
#include "SLA/SLASupportTree.hpp"
|
||||||
#include "SLA/SLABoilerPlate.hpp"
|
#include "SLA/SLACommon.hpp"
|
||||||
#include "SLA/SLASpatIndex.hpp"
|
#include "SLA/SLASpatIndex.hpp"
|
||||||
|
|
||||||
// Workaround: IGL signed_distance.h will define PI in the igl namespace.
|
// Workaround: IGL signed_distance.h will define PI in the igl namespace.
|
||||||
|
|
@ -228,6 +228,26 @@ EigenMesh3D::EigenMesh3D(const EigenMesh3D &other):
|
||||||
m_V(other.m_V), m_F(other.m_F), m_ground_level(other.m_ground_level),
|
m_V(other.m_V), m_F(other.m_F), m_ground_level(other.m_ground_level),
|
||||||
m_aabb( new AABBImpl(*other.m_aabb) ) {}
|
m_aabb( new AABBImpl(*other.m_aabb) ) {}
|
||||||
|
|
||||||
|
EigenMesh3D::EigenMesh3D(const Contour3D &other)
|
||||||
|
{
|
||||||
|
m_V.resize(Eigen::Index(other.points.size()), 3);
|
||||||
|
m_F.resize(Eigen::Index(other.faces3.size() + 2 * other.faces4.size()), 3);
|
||||||
|
|
||||||
|
for (Eigen::Index i = 0; i < Eigen::Index(other.points.size()); ++i)
|
||||||
|
m_V.row(i) = other.points[size_t(i)];
|
||||||
|
|
||||||
|
for (Eigen::Index i = 0; i < Eigen::Index(other.faces3.size()); ++i)
|
||||||
|
m_F.row(i) = other.faces3[size_t(i)];
|
||||||
|
|
||||||
|
size_t N = other.faces3.size() + 2 * other.faces4.size();
|
||||||
|
for (size_t i = other.faces3.size(); i < N; i += 2) {
|
||||||
|
size_t quad_idx = (i - other.faces3.size()) / 2;
|
||||||
|
auto & quad = other.faces4[quad_idx];
|
||||||
|
m_F.row(Eigen::Index(i)) = Vec3i{quad(0), quad(1), quad(2)};
|
||||||
|
m_F.row(Eigen::Index(i + 1)) = Vec3i{quad(2), quad(3), quad(0)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other)
|
EigenMesh3D &EigenMesh3D::operator=(const EigenMesh3D &other)
|
||||||
{
|
{
|
||||||
m_V = other.m_V;
|
m_V = other.m_V;
|
||||||
|
|
@ -252,6 +272,31 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<EigenMesh3D::hit_result>
|
||||||
|
EigenMesh3D::query_ray_hits(const Vec3d &s, const Vec3d &dir) const
|
||||||
|
{
|
||||||
|
std::vector<EigenMesh3D::hit_result> outs;
|
||||||
|
std::vector<igl::Hit> hits;
|
||||||
|
m_aabb->intersect_ray(m_V, m_F, s, dir, hits);
|
||||||
|
|
||||||
|
// The sort is necessary, the hits are not always sorted.
|
||||||
|
std::sort(hits.begin(), hits.end(),
|
||||||
|
[](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
|
||||||
|
|
||||||
|
// Convert the igl::Hit into hit_result
|
||||||
|
outs.reserve(hits.size());
|
||||||
|
for (const igl::Hit& hit : hits) {
|
||||||
|
outs.emplace_back(EigenMesh3D::hit_result(*this));
|
||||||
|
outs.back().m_t = double(hit.t);
|
||||||
|
outs.back().m_dir = dir;
|
||||||
|
outs.back().m_source = s;
|
||||||
|
if(!std::isinf(hit.t) && !std::isnan(hit.t))
|
||||||
|
outs.back().m_face_id = hit.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return outs;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
|
||||||
EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const {
|
EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const {
|
||||||
double sign = 0; double sqdst = 0; int i = 0; Vec3d c;
|
double sign = 0; double sqdst = 0; int i = 0; Vec3d c;
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include "PrintBase.hpp"
|
#include "PrintBase.hpp"
|
||||||
//#include "PrintExport.hpp"
|
#include "SLA/RasterWriter.hpp"
|
||||||
#include "SLA/SLARasterWriter.hpp"
|
#include "SLA/SupportTree.hpp"
|
||||||
#include "Point.hpp"
|
#include "Point.hpp"
|
||||||
#include "MTUtils.hpp"
|
#include "MTUtils.hpp"
|
||||||
#include "Zipper.hpp"
|
#include "Zipper.hpp"
|
||||||
|
|
@ -19,7 +19,9 @@ enum SLAPrintStep : unsigned int {
|
||||||
};
|
};
|
||||||
|
|
||||||
enum SLAPrintObjectStep : unsigned int {
|
enum SLAPrintObjectStep : unsigned int {
|
||||||
|
slaposHollowing,
|
||||||
slaposObjectSlice,
|
slaposObjectSlice,
|
||||||
|
slaposDrillHolesIfHollowed,
|
||||||
slaposSupportPoints,
|
slaposSupportPoints,
|
||||||
slaposSupportTree,
|
slaposSupportTree,
|
||||||
slaposPad,
|
slaposPad,
|
||||||
|
|
@ -73,13 +75,17 @@ public:
|
||||||
// Support mesh is only valid if this->is_step_done(slaposSupportTree) is true.
|
// Support mesh is only valid if this->is_step_done(slaposSupportTree) is true.
|
||||||
const TriangleMesh& support_mesh() const;
|
const TriangleMesh& support_mesh() const;
|
||||||
// Get a pad mesh centered around origin in XY, and with zero rotation around Z applied.
|
// Get a pad mesh centered around origin in XY, and with zero rotation around Z applied.
|
||||||
// Support mesh is only valid if this->is_step_done(slaposBasePool) is true.
|
// Support mesh is only valid if this->is_step_done(slaposPad) is true.
|
||||||
const TriangleMesh& pad_mesh() const;
|
const TriangleMesh& pad_mesh() const;
|
||||||
|
|
||||||
|
// Ready after this->is_step_done(slaposHollowing) is true
|
||||||
|
const TriangleMesh& hollowed_interior_mesh() const;
|
||||||
|
|
||||||
// This will return the transformed mesh which is cached
|
// This will return the transformed mesh which is cached
|
||||||
const TriangleMesh& transformed_mesh() const;
|
const TriangleMesh& transformed_mesh() const;
|
||||||
|
|
||||||
std::vector<sla::SupportPoint> transformed_support_points() const;
|
sla::SupportPoints transformed_support_points() const;
|
||||||
|
sla::DrainHoles transformed_drainhole_points() const;
|
||||||
|
|
||||||
// Get the needed Z elevation for the model geometry if supports should be
|
// Get the needed Z elevation for the model geometry if supports should be
|
||||||
// displayed. This Z offset should also be applied to the support
|
// displayed. This Z offset should also be applied to the support
|
||||||
|
|
@ -288,8 +294,34 @@ private:
|
||||||
// Caching the transformed (m_trafo) raw mesh of the object
|
// Caching the transformed (m_trafo) raw mesh of the object
|
||||||
mutable CachedObject<TriangleMesh> m_transformed_rmesh;
|
mutable CachedObject<TriangleMesh> m_transformed_rmesh;
|
||||||
|
|
||||||
class SupportData;
|
class SupportData : public sla::SupportableMesh
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
sla::SupportTree::UPtr support_tree_ptr; // the supports
|
||||||
|
std::vector<ExPolygons> support_slices; // sliced supports
|
||||||
|
|
||||||
|
inline SupportData(const TriangleMesh &t)
|
||||||
|
: sla::SupportableMesh{t, {}, {}}
|
||||||
|
{}
|
||||||
|
|
||||||
|
sla::SupportTree::UPtr &create_support_tree(const sla::JobController &ctl)
|
||||||
|
{
|
||||||
|
support_tree_ptr = sla::SupportTree::create(*this, ctl);
|
||||||
|
return support_tree_ptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
std::unique_ptr<SupportData> m_supportdata;
|
std::unique_ptr<SupportData> m_supportdata;
|
||||||
|
|
||||||
|
class HollowingData
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
TriangleMesh interior;
|
||||||
|
// std::vector<drillpoints>
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<HollowingData> m_hollowing_data;
|
||||||
};
|
};
|
||||||
|
|
||||||
using PrintObjects = std::vector<SLAPrintObject*>;
|
using PrintObjects = std::vector<SLAPrintObject*>;
|
||||||
|
|
@ -340,6 +372,8 @@ class SLAPrint : public PrintBaseWithState<SLAPrintStep, slapsCount>
|
||||||
private: // Prevents erroneous use by other classes.
|
private: // Prevents erroneous use by other classes.
|
||||||
typedef PrintBaseWithState<SLAPrintStep, slapsCount> Inherited;
|
typedef PrintBaseWithState<SLAPrintStep, slapsCount> Inherited;
|
||||||
|
|
||||||
|
class Steps; // See SLAPrintSteps.cpp
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
SLAPrint(): m_stepmask(slapsCount, true) {}
|
SLAPrint(): m_stepmask(slapsCount, true) {}
|
||||||
|
|
@ -402,7 +436,7 @@ public:
|
||||||
m_transformed_slices = std::forward<Container>(c);
|
m_transformed_slices = std::forward<Container>(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
friend void SLAPrint::process();
|
friend class SLAPrint::Steps;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
@ -478,6 +512,19 @@ private:
|
||||||
friend SLAPrintObject;
|
friend SLAPrintObject;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper functions:
|
||||||
|
|
||||||
|
bool is_zero_elevation(const SLAPrintObjectConfig &c);
|
||||||
|
|
||||||
|
sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c);
|
||||||
|
|
||||||
|
sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c);
|
||||||
|
|
||||||
|
sla::PadConfig make_pad_cfg(const SLAPrintObjectConfig& c);
|
||||||
|
|
||||||
|
bool validate_pad(const TriangleMesh &pad, const sla::PadConfig &pcfg);
|
||||||
|
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
#endif /* slic3r_SLAPrint_hpp_ */
|
#endif /* slic3r_SLAPrint_hpp_ */
|
||||||
|
|
|
||||||
862
src/libslic3r/SLAPrintSteps.cpp
Normal file
862
src/libslic3r/SLAPrintSteps.cpp
Normal file
|
|
@ -0,0 +1,862 @@
|
||||||
|
#include <libslic3r/SLAPrintSteps.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
// Need the cylinder method for the the drainholes in hollowing step
|
||||||
|
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
|
||||||
|
|
||||||
|
#include <libslic3r/SLA/Concurrency.hpp>
|
||||||
|
#include <libslic3r/SLA/Pad.hpp>
|
||||||
|
#include <libslic3r/SLA/SupportPointGenerator.hpp>
|
||||||
|
|
||||||
|
#include <libslic3r/ClipperUtils.hpp>
|
||||||
|
|
||||||
|
// For geometry algorithms with native Clipper types (no copies and conversions)
|
||||||
|
#include <libnest2d/backends/clipper/geometries.hpp>
|
||||||
|
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
|
||||||
|
#include "I18N.hpp"
|
||||||
|
|
||||||
|
//! macro used to mark string used at localization,
|
||||||
|
//! return same string
|
||||||
|
#define L(s) Slic3r::I18N::translate(s)
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS = {
|
||||||
|
5, // slaposHollowing,
|
||||||
|
20, // slaposObjectSlice,
|
||||||
|
5, // slaposDrillHolesIfHollowed
|
||||||
|
20, // slaposSupportPoints,
|
||||||
|
10, // slaposSupportTree,
|
||||||
|
10, // slaposPad,
|
||||||
|
30, // slaposSliceSupports,
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string OBJ_STEP_LABELS(size_t idx)
|
||||||
|
{
|
||||||
|
switch (idx) {
|
||||||
|
case slaposHollowing: return L("Hollowing out the model");
|
||||||
|
case slaposObjectSlice: return L("Slicing model");
|
||||||
|
case slaposDrillHolesIfHollowed: return L("Drilling holes into hollowed model.");
|
||||||
|
case slaposSupportPoints: return L("Generating support points");
|
||||||
|
case slaposSupportTree: return L("Generating support tree");
|
||||||
|
case slaposPad: return L("Generating pad");
|
||||||
|
case slaposSliceSupports: return L("Slicing supports");
|
||||||
|
default:;
|
||||||
|
}
|
||||||
|
assert(false);
|
||||||
|
return "Out of bounds!";
|
||||||
|
};
|
||||||
|
|
||||||
|
const std::array<unsigned, slapsCount> PRINT_STEP_LEVELS = {
|
||||||
|
10, // slapsMergeSlicesAndEval
|
||||||
|
90, // slapsRasterize
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string PRINT_STEP_LABELS(size_t idx)
|
||||||
|
{
|
||||||
|
switch (idx) {
|
||||||
|
case slapsMergeSlicesAndEval: return L("Merging slices and calculating statistics");
|
||||||
|
case slapsRasterize: return L("Rasterizing layers");
|
||||||
|
default:;
|
||||||
|
}
|
||||||
|
assert(false); return "Out of bounds!";
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
SLAPrint::Steps::Steps(SLAPrint *print)
|
||||||
|
: m_print{print}
|
||||||
|
, objcount{m_print->m_objects.size()}
|
||||||
|
, ilhd{m_print->m_material_config.initial_layer_height.getFloat()}
|
||||||
|
, ilh{float(ilhd)}
|
||||||
|
, ilhs{scaled(ilhd)}
|
||||||
|
, objectstep_scale{(max_objstatus - min_objstatus) / (objcount * 100.0)}
|
||||||
|
{}
|
||||||
|
|
||||||
|
void SLAPrint::Steps::hollow_model(SLAPrintObject &po)
|
||||||
|
{
|
||||||
|
if (!po.m_config.hollowing_enable.getBool()) {
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!";
|
||||||
|
po.m_hollowing_data.reset();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!po.m_hollowing_data)
|
||||||
|
po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
|
||||||
|
|
||||||
|
double thickness = po.m_config.hollowing_min_thickness.getFloat();
|
||||||
|
double quality = po.m_config.hollowing_quality.getFloat();
|
||||||
|
double closing_d = po.m_config.hollowing_closing_distance.getFloat();
|
||||||
|
sla::HollowingConfig hlwcfg{thickness, quality, closing_d};
|
||||||
|
auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg);
|
||||||
|
if (meshptr) po.m_hollowing_data->interior = *meshptr;
|
||||||
|
|
||||||
|
if (po.m_hollowing_data->interior.empty())
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!";
|
||||||
|
}
|
||||||
|
|
||||||
|
// The slicing will be performed on an imaginary 1D grid which starts from
|
||||||
|
// the bottom of the bounding box created around the supported model. So
|
||||||
|
// the first layer which is usually thicker will be part of the supports
|
||||||
|
// not the model geometry. Exception is when the model is not in the air
|
||||||
|
// (elevation is zero) and no pad creation was requested. In this case the
|
||||||
|
// model geometry starts on the ground level and the initial layer is part
|
||||||
|
// of it. In any case, the model and the supports have to be sliced in the
|
||||||
|
// same imaginary grid (the height vector argument to TriangleMeshSlicer).
|
||||||
|
void SLAPrint::Steps::slice_model(SLAPrintObject &po)
|
||||||
|
{
|
||||||
|
TriangleMesh hollowed_mesh;
|
||||||
|
|
||||||
|
bool is_hollowing = po.m_config.hollowing_enable.getBool() && po.m_hollowing_data;
|
||||||
|
|
||||||
|
if (is_hollowing) {
|
||||||
|
hollowed_mesh = po.transformed_mesh();
|
||||||
|
hollowed_mesh.merge(po.m_hollowing_data->interior);
|
||||||
|
hollowed_mesh.require_shared_vertices();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TriangleMesh &mesh = is_hollowing ? hollowed_mesh : po.transformed_mesh();
|
||||||
|
|
||||||
|
// We need to prepare the slice index...
|
||||||
|
|
||||||
|
double lhd = m_print->m_objects.front()->m_config.layer_height.getFloat();
|
||||||
|
float lh = float(lhd);
|
||||||
|
coord_t lhs = scaled(lhd);
|
||||||
|
auto && bb3d = mesh.bounding_box();
|
||||||
|
double minZ = bb3d.min(Z) - po.get_elevation();
|
||||||
|
double maxZ = bb3d.max(Z);
|
||||||
|
auto minZf = float(minZ);
|
||||||
|
coord_t minZs = scaled(minZ);
|
||||||
|
coord_t maxZs = scaled(maxZ);
|
||||||
|
|
||||||
|
po.m_slice_index.clear();
|
||||||
|
|
||||||
|
size_t cap = size_t(1 + (maxZs - minZs - ilhs) / lhs);
|
||||||
|
po.m_slice_index.reserve(cap);
|
||||||
|
|
||||||
|
po.m_slice_index.emplace_back(minZs + ilhs, minZf + ilh / 2.f, ilh);
|
||||||
|
|
||||||
|
for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs)
|
||||||
|
po.m_slice_index.emplace_back(h, unscaled<float>(h) - lh / 2.f, lh);
|
||||||
|
|
||||||
|
// Just get the first record that is from the model:
|
||||||
|
auto slindex_it =
|
||||||
|
po.closest_slice_record(po.m_slice_index, float(bb3d.min(Z)));
|
||||||
|
|
||||||
|
if(slindex_it == po.m_slice_index.end())
|
||||||
|
//TRN To be shown at the status bar on SLA slicing error.
|
||||||
|
throw std::runtime_error(
|
||||||
|
L("Slicing had to be stopped due to an internal error: "
|
||||||
|
"Inconsistent slice index."));
|
||||||
|
|
||||||
|
po.m_model_height_levels.clear();
|
||||||
|
po.m_model_height_levels.reserve(po.m_slice_index.size());
|
||||||
|
for(auto it = slindex_it; it != po.m_slice_index.end(); ++it)
|
||||||
|
po.m_model_height_levels.emplace_back(it->slice_level());
|
||||||
|
|
||||||
|
TriangleMeshSlicer slicer(&mesh);
|
||||||
|
|
||||||
|
po.m_model_slices.clear();
|
||||||
|
float closing_r = float(po.config().slice_closing_radius.value);
|
||||||
|
auto thr = [this]() { m_print->throw_if_canceled(); };
|
||||||
|
auto &slice_grid = po.m_model_height_levels;
|
||||||
|
slicer.slice(slice_grid, closing_r, &po.m_model_slices, thr);
|
||||||
|
|
||||||
|
sla::DrainHoles drainholes = po.transformed_drainhole_points();
|
||||||
|
cut_drainholes(po.m_model_slices, slice_grid, closing_r, drainholes, thr);
|
||||||
|
|
||||||
|
auto mit = slindex_it;
|
||||||
|
double doffs = m_print->m_printer_config.absolute_correction.getFloat();
|
||||||
|
coord_t clpr_offs = scaled(doffs);
|
||||||
|
for(size_t id = 0;
|
||||||
|
id < po.m_model_slices.size() && mit != po.m_slice_index.end();
|
||||||
|
id++)
|
||||||
|
{
|
||||||
|
// We apply the printer correction offset here.
|
||||||
|
if(clpr_offs != 0)
|
||||||
|
po.m_model_slices[id] =
|
||||||
|
offset_ex(po.m_model_slices[id], float(clpr_offs));
|
||||||
|
|
||||||
|
mit->set_model_slice_idx(po, id); ++mit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(po.m_config.supports_enable.getBool() || po.m_config.pad_enable.getBool())
|
||||||
|
{
|
||||||
|
po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// In this step we check the slices, identify island and cover them with
|
||||||
|
// support points. Then we sprinkle the rest of the mesh.
|
||||||
|
void SLAPrint::Steps::support_points(SLAPrintObject &po)
|
||||||
|
{
|
||||||
|
// If supports are disabled, we can skip the model scan.
|
||||||
|
if(!po.m_config.supports_enable.getBool()) return;
|
||||||
|
|
||||||
|
bool is_hollowing = po.m_config.hollowing_enable.getBool() && po.m_hollowing_data;
|
||||||
|
|
||||||
|
TriangleMesh hollowed_mesh;
|
||||||
|
if (is_hollowing) {
|
||||||
|
hollowed_mesh = po.transformed_mesh();
|
||||||
|
hollowed_mesh.merge(po.m_hollowing_data->interior);
|
||||||
|
hollowed_mesh.require_shared_vertices();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TriangleMesh &mesh = is_hollowing ? hollowed_mesh : po.transformed_mesh();
|
||||||
|
|
||||||
|
if (!po.m_supportdata)
|
||||||
|
po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh));
|
||||||
|
|
||||||
|
const ModelObject& mo = *po.m_model_object;
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << "Support point count "
|
||||||
|
<< mo.sla_support_points.size();
|
||||||
|
|
||||||
|
// Unless the user modified the points or we already did the calculation,
|
||||||
|
// we will do the autoplacement. Otherwise we will just blindly copy the
|
||||||
|
// frontend data into the backend cache.
|
||||||
|
if (mo.sla_points_status != sla::PointsStatus::UserModified) {
|
||||||
|
|
||||||
|
// calculate heights of slices (slices are calculated already)
|
||||||
|
const std::vector<float>& heights = po.m_model_height_levels;
|
||||||
|
|
||||||
|
// Tell the mesh where drain holes are. Although the points are
|
||||||
|
// calculated on slices, the algorithm then raycasts the points
|
||||||
|
// so they actually lie on the mesh.
|
||||||
|
po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
|
||||||
|
|
||||||
|
throw_if_canceled();
|
||||||
|
sla::SupportPointGenerator::Config config;
|
||||||
|
const SLAPrintObjectConfig& cfg = po.config();
|
||||||
|
|
||||||
|
// the density config value is in percents:
|
||||||
|
config.density_relative = float(cfg.support_points_density_relative / 100.f);
|
||||||
|
config.minimal_distance = float(cfg.support_points_minimal_distance);
|
||||||
|
config.head_diameter = float(cfg.support_head_front_diameter);
|
||||||
|
|
||||||
|
// scaling for the sub operations
|
||||||
|
double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0;
|
||||||
|
double init = current_status();
|
||||||
|
|
||||||
|
auto statuscb = [this, d, init](unsigned st)
|
||||||
|
{
|
||||||
|
double current = init + st * d;
|
||||||
|
if(std::round(current_status()) < std::round(current))
|
||||||
|
report_status(current, OBJ_STEP_LABELS(slaposSupportPoints));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Construction of this object does the calculation.
|
||||||
|
throw_if_canceled();
|
||||||
|
sla::SupportPointGenerator auto_supports(
|
||||||
|
po.m_supportdata->emesh, po.get_model_slices(), heights, config,
|
||||||
|
[this]() { throw_if_canceled(); }, statuscb);
|
||||||
|
|
||||||
|
// Now let's extract the result.
|
||||||
|
const std::vector<sla::SupportPoint>& points = auto_supports.output();
|
||||||
|
throw_if_canceled();
|
||||||
|
po.m_supportdata->pts = points;
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << "Automatic support points: "
|
||||||
|
<< po.m_supportdata->pts.size();
|
||||||
|
|
||||||
|
// Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
|
||||||
|
// the update status to GLGizmoSlaSupports
|
||||||
|
report_status(-1, L("Generating support points"),
|
||||||
|
SlicingStatus::RELOAD_SLA_SUPPORT_POINTS);
|
||||||
|
} else {
|
||||||
|
// There are either some points on the front-end, or the user
|
||||||
|
// removed them on purpose. No calculation will be done.
|
||||||
|
po.m_supportdata->pts = po.transformed_support_points();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the zero elevation mode is engaged, we have to filter out all the
|
||||||
|
// points that are on the bottom of the object
|
||||||
|
if (is_zero_elevation(po.config())) {
|
||||||
|
double tolerance = po.config().pad_enable.getBool() ?
|
||||||
|
po.m_config.pad_wall_thickness.getFloat() :
|
||||||
|
po.m_config.support_base_height.getFloat();
|
||||||
|
|
||||||
|
remove_bottom_points(po.m_supportdata->pts,
|
||||||
|
po.m_supportdata->emesh.ground_level(),
|
||||||
|
tolerance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SLAPrint::Steps::support_tree(SLAPrintObject &po)
|
||||||
|
{
|
||||||
|
if(!po.m_supportdata) return;
|
||||||
|
|
||||||
|
sla::PadConfig pcfg = make_pad_cfg(po.m_config);
|
||||||
|
|
||||||
|
if (pcfg.embed_object)
|
||||||
|
po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm);
|
||||||
|
|
||||||
|
po.m_supportdata->cfg = make_support_cfg(po.m_config);
|
||||||
|
po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
|
||||||
|
|
||||||
|
// scaling for the sub operations
|
||||||
|
double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0;
|
||||||
|
double init = current_status();
|
||||||
|
sla::JobController ctl;
|
||||||
|
|
||||||
|
ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) {
|
||||||
|
double current = init + st * d;
|
||||||
|
if (std::round(current_status()) < std::round(current))
|
||||||
|
report_status(current, OBJ_STEP_LABELS(slaposSupportTree),
|
||||||
|
SlicingStatus::DEFAULT, logmsg);
|
||||||
|
};
|
||||||
|
ctl.stopcondition = [this]() { return canceled(); };
|
||||||
|
ctl.cancelfn = [this]() { throw_if_canceled(); };
|
||||||
|
|
||||||
|
po.m_supportdata->create_support_tree(ctl);
|
||||||
|
|
||||||
|
if (!po.m_config.supports_enable.getBool()) return;
|
||||||
|
|
||||||
|
throw_if_canceled();
|
||||||
|
|
||||||
|
// Create the unified mesh
|
||||||
|
auto rc = SlicingStatus::RELOAD_SCENE;
|
||||||
|
|
||||||
|
// This is to prevent "Done." being displayed during merged_mesh()
|
||||||
|
report_status(-1, L("Visualizing supports"));
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << "Processed support point count "
|
||||||
|
<< po.m_supportdata->pts.size();
|
||||||
|
|
||||||
|
// Check the mesh for later troubleshooting.
|
||||||
|
if(po.support_mesh().empty())
|
||||||
|
BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty";
|
||||||
|
|
||||||
|
report_status(-1, L("Visualizing supports"), rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SLAPrint::Steps::generate_pad(SLAPrintObject &po) {
|
||||||
|
// this step can only go after the support tree has been created
|
||||||
|
// and before the supports had been sliced. (or the slicing has to be
|
||||||
|
// repeated)
|
||||||
|
|
||||||
|
if(po.m_config.pad_enable.getBool()) {
|
||||||
|
// Get the distilled pad configuration from the config
|
||||||
|
sla::PadConfig pcfg = make_pad_cfg(po.m_config);
|
||||||
|
|
||||||
|
ExPolygons bp; // This will store the base plate of the pad.
|
||||||
|
double pad_h = pcfg.full_height();
|
||||||
|
const TriangleMesh &trmesh = po.transformed_mesh();
|
||||||
|
|
||||||
|
if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) {
|
||||||
|
// No support (thus no elevation) or zero elevation mode
|
||||||
|
// we sometimes call it "builtin pad" is enabled so we will
|
||||||
|
// get a sample from the bottom of the mesh and use it for pad
|
||||||
|
// creation.
|
||||||
|
sla::pad_blueprint(trmesh, bp, float(pad_h),
|
||||||
|
float(po.m_config.layer_height.getFloat()),
|
||||||
|
[this](){ throw_if_canceled(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg);
|
||||||
|
auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad);
|
||||||
|
|
||||||
|
if (!validate_pad(pad_mesh, pcfg))
|
||||||
|
throw std::runtime_error(
|
||||||
|
L("No pad can be generated for this model with the "
|
||||||
|
"current configuration"));
|
||||||
|
|
||||||
|
} else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) {
|
||||||
|
po.m_supportdata->support_tree_ptr->remove_pad();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw_if_canceled();
|
||||||
|
report_status(-1, L("Visualizing supports"), SlicingStatus::RELOAD_SCENE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slicing the support geometries similarly to the model slicing procedure.
|
||||||
|
// If the pad had been added previously (see step "base_pool" than it will
|
||||||
|
// be part of the slices)
|
||||||
|
void SLAPrint::Steps::slice_supports(SLAPrintObject &po) {
|
||||||
|
auto& sd = po.m_supportdata;
|
||||||
|
|
||||||
|
if(sd) sd->support_slices.clear();
|
||||||
|
|
||||||
|
// Don't bother if no supports and no pad is present.
|
||||||
|
if (!po.m_config.supports_enable.getBool() && !po.m_config.pad_enable.getBool())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(sd && sd->support_tree_ptr) {
|
||||||
|
auto heights = reserve_vector<float>(po.m_slice_index.size());
|
||||||
|
|
||||||
|
for(auto& rec : po.m_slice_index) heights.emplace_back(rec.slice_level());
|
||||||
|
|
||||||
|
sd->support_slices = sd->support_tree_ptr->slice(
|
||||||
|
heights, float(po.config().slice_closing_radius.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
double doffs = m_print->m_printer_config.absolute_correction.getFloat();
|
||||||
|
coord_t clpr_offs = scaled(doffs);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i) {
|
||||||
|
// We apply the printer correction offset here.
|
||||||
|
if (clpr_offs != 0)
|
||||||
|
sd->support_slices[i] = offset_ex(sd->support_slices[i], float(clpr_offs));
|
||||||
|
|
||||||
|
po.m_slice_index[i].set_support_slice_idx(po, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update
|
||||||
|
// status to the 3D preview to load the SLA slices.
|
||||||
|
report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
|
||||||
|
}
|
||||||
|
|
||||||
|
using ClipperPoint = ClipperLib::IntPoint;
|
||||||
|
using ClipperPolygon = ClipperLib::Polygon; // see clipper_polygon.hpp in libnest2d
|
||||||
|
using ClipperPolygons = std::vector<ClipperPolygon>;
|
||||||
|
|
||||||
|
static ClipperPolygons polyunion(const ClipperPolygons &subjects)
|
||||||
|
{
|
||||||
|
ClipperLib::Clipper clipper;
|
||||||
|
|
||||||
|
bool closed = true;
|
||||||
|
|
||||||
|
for(auto& path : subjects) {
|
||||||
|
clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
|
||||||
|
clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mode = ClipperLib::pftPositive;
|
||||||
|
|
||||||
|
return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPolygons& clips)
|
||||||
|
{
|
||||||
|
ClipperLib::Clipper clipper;
|
||||||
|
|
||||||
|
bool closed = true;
|
||||||
|
|
||||||
|
for(auto& path : subjects) {
|
||||||
|
clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
|
||||||
|
clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto& path : clips) {
|
||||||
|
clipper.AddPath(path.Contour, ClipperLib::ptClip, closed);
|
||||||
|
clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mode = ClipperLib::pftPositive;
|
||||||
|
|
||||||
|
return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get polygons for all instances in the object
|
||||||
|
static ClipperPolygons get_all_polygons(
|
||||||
|
const ExPolygons & input_polygons,
|
||||||
|
const std::vector<SLAPrintObject::Instance> &instances,
|
||||||
|
bool is_lefthanded)
|
||||||
|
{
|
||||||
|
namespace sl = libnest2d::sl;
|
||||||
|
|
||||||
|
ClipperPolygons polygons;
|
||||||
|
polygons.reserve(input_polygons.size() * instances.size());
|
||||||
|
|
||||||
|
for (const ExPolygon& polygon : input_polygons) {
|
||||||
|
if(polygon.contour.empty()) continue;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < instances.size(); ++i)
|
||||||
|
{
|
||||||
|
ClipperPolygon poly;
|
||||||
|
|
||||||
|
// We need to reverse if is_lefthanded is true but
|
||||||
|
bool needreverse = is_lefthanded;
|
||||||
|
|
||||||
|
// should be a move
|
||||||
|
poly.Contour.reserve(polygon.contour.size() + 1);
|
||||||
|
|
||||||
|
auto& cntr = polygon.contour.points;
|
||||||
|
if(needreverse)
|
||||||
|
for(auto it = cntr.rbegin(); it != cntr.rend(); ++it)
|
||||||
|
poly.Contour.emplace_back(it->x(), it->y());
|
||||||
|
else
|
||||||
|
for(auto& p : cntr)
|
||||||
|
poly.Contour.emplace_back(p.x(), p.y());
|
||||||
|
|
||||||
|
for(auto& h : polygon.holes) {
|
||||||
|
poly.Holes.emplace_back();
|
||||||
|
auto& hole = poly.Holes.back();
|
||||||
|
hole.reserve(h.points.size() + 1);
|
||||||
|
|
||||||
|
if(needreverse)
|
||||||
|
for(auto it = h.points.rbegin(); it != h.points.rend(); ++it)
|
||||||
|
hole.emplace_back(it->x(), it->y());
|
||||||
|
else
|
||||||
|
for(auto& p : h.points)
|
||||||
|
hole.emplace_back(p.x(), p.y());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(is_lefthanded) {
|
||||||
|
for(auto& p : poly.Contour) p.X = -p.X;
|
||||||
|
for(auto& h : poly.Holes) for(auto& p : h) p.X = -p.X;
|
||||||
|
}
|
||||||
|
|
||||||
|
sl::rotate(poly, double(instances[i].rotation));
|
||||||
|
sl::translate(poly, ClipperPoint{instances[i].shift(X),
|
||||||
|
instances[i].shift(Y)});
|
||||||
|
|
||||||
|
polygons.emplace_back(std::move(poly));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return polygons;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SLAPrint::Steps::initialize_printer_input()
|
||||||
|
{
|
||||||
|
auto &printer_input = m_print->m_printer_input;
|
||||||
|
|
||||||
|
// clear the rasterizer input
|
||||||
|
printer_input.clear();
|
||||||
|
|
||||||
|
size_t mx = 0;
|
||||||
|
for(SLAPrintObject * o : m_print->m_objects) {
|
||||||
|
if(auto m = o->get_slice_index().size() > mx) mx = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
printer_input.reserve(mx);
|
||||||
|
|
||||||
|
auto eps = coord_t(SCALED_EPSILON);
|
||||||
|
|
||||||
|
for(SLAPrintObject * o : m_print->m_objects) {
|
||||||
|
coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs;
|
||||||
|
|
||||||
|
for(const SliceRecord& slicerecord : o->get_slice_index()) {
|
||||||
|
coord_t lvlid = slicerecord.print_level() - gndlvl;
|
||||||
|
|
||||||
|
// Neat trick to round the layer levels to the grid.
|
||||||
|
lvlid = eps * (lvlid / eps);
|
||||||
|
|
||||||
|
auto it = std::lower_bound(printer_input.begin(),
|
||||||
|
printer_input.end(),
|
||||||
|
PrintLayer(lvlid));
|
||||||
|
|
||||||
|
if(it == printer_input.end() || it->level() != lvlid)
|
||||||
|
it = printer_input.insert(it, PrintLayer(lvlid));
|
||||||
|
|
||||||
|
|
||||||
|
it->add(slicerecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merging the slices from all the print objects into one slice grid and
|
||||||
|
// calculating print statistics from the merge result.
|
||||||
|
void SLAPrint::Steps::merge_slices_and_eval_stats() {
|
||||||
|
|
||||||
|
initialize_printer_input();
|
||||||
|
|
||||||
|
auto &print_statistics = m_print->m_print_statistics;
|
||||||
|
auto &printer_config = m_print->m_printer_config;
|
||||||
|
auto &material_config = m_print->m_material_config;
|
||||||
|
auto &printer_input = m_print->m_printer_input;
|
||||||
|
|
||||||
|
print_statistics.clear();
|
||||||
|
|
||||||
|
// libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise
|
||||||
|
auto areafn = [](const ClipperPolygon& poly) { return - libnest2d::sl::area(poly); };
|
||||||
|
|
||||||
|
const double area_fill = printer_config.area_fill.getFloat()*0.01;// 0.5 (50%);
|
||||||
|
const double fast_tilt = printer_config.fast_tilt_time.getFloat();// 5.0;
|
||||||
|
const double slow_tilt = printer_config.slow_tilt_time.getFloat();// 8.0;
|
||||||
|
|
||||||
|
const double init_exp_time = material_config.initial_exposure_time.getFloat();
|
||||||
|
const double exp_time = material_config.exposure_time.getFloat();
|
||||||
|
|
||||||
|
const int fade_layers_cnt = m_print->m_default_object_config.faded_layers.getInt();// 10 // [3;20]
|
||||||
|
|
||||||
|
const auto width = scaled<double>(printer_config.display_width.getFloat());
|
||||||
|
const auto height = scaled<double>(printer_config.display_height.getFloat());
|
||||||
|
const double display_area = width*height;
|
||||||
|
|
||||||
|
double supports_volume(0.0);
|
||||||
|
double models_volume(0.0);
|
||||||
|
|
||||||
|
double estim_time(0.0);
|
||||||
|
|
||||||
|
size_t slow_layers = 0;
|
||||||
|
size_t fast_layers = 0;
|
||||||
|
|
||||||
|
const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1);
|
||||||
|
double fade_layer_time = init_exp_time;
|
||||||
|
|
||||||
|
sla::ccr::SpinningMutex mutex;
|
||||||
|
using Lock = std::lock_guard<sla::ccr::SpinningMutex>;
|
||||||
|
|
||||||
|
// Going to parallel:
|
||||||
|
auto printlayerfn = [
|
||||||
|
// functions and read only vars
|
||||||
|
areafn, area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time,
|
||||||
|
|
||||||
|
// write vars
|
||||||
|
&mutex, &models_volume, &supports_volume, &estim_time, &slow_layers,
|
||||||
|
&fast_layers, &fade_layer_time](PrintLayer& layer, size_t sliced_layer_cnt)
|
||||||
|
{
|
||||||
|
// vector of slice record references
|
||||||
|
auto& slicerecord_references = layer.slices();
|
||||||
|
|
||||||
|
if(slicerecord_references.empty()) return;
|
||||||
|
|
||||||
|
// Layer height should match for all object slices for a given level.
|
||||||
|
const auto l_height = double(slicerecord_references.front().get().layer_height());
|
||||||
|
|
||||||
|
// Calculation of the consumed material
|
||||||
|
|
||||||
|
ClipperPolygons model_polygons;
|
||||||
|
ClipperPolygons supports_polygons;
|
||||||
|
|
||||||
|
size_t c = std::accumulate(layer.slices().begin(),
|
||||||
|
layer.slices().end(),
|
||||||
|
size_t(0),
|
||||||
|
[](size_t a, const SliceRecord &sr) {
|
||||||
|
return a + sr.get_slice(soModel).size();
|
||||||
|
});
|
||||||
|
|
||||||
|
model_polygons.reserve(c);
|
||||||
|
|
||||||
|
c = std::accumulate(layer.slices().begin(),
|
||||||
|
layer.slices().end(),
|
||||||
|
size_t(0),
|
||||||
|
[](size_t a, const SliceRecord &sr) {
|
||||||
|
return a + sr.get_slice(soModel).size();
|
||||||
|
});
|
||||||
|
|
||||||
|
supports_polygons.reserve(c);
|
||||||
|
|
||||||
|
for(const SliceRecord& record : layer.slices()) {
|
||||||
|
const SLAPrintObject *po = record.print_obj();
|
||||||
|
|
||||||
|
const ExPolygons &modelslices = record.get_slice(soModel);
|
||||||
|
|
||||||
|
bool is_lefth = record.print_obj()->is_left_handed();
|
||||||
|
if (!modelslices.empty()) {
|
||||||
|
ClipperPolygons v = get_all_polygons(modelslices, po->instances(), is_lefth);
|
||||||
|
for(ClipperPolygon& p_tmp : v) model_polygons.emplace_back(std::move(p_tmp));
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExPolygons &supportslices = record.get_slice(soSupport);
|
||||||
|
|
||||||
|
if (!supportslices.empty()) {
|
||||||
|
ClipperPolygons v = get_all_polygons(supportslices, po->instances(), is_lefth);
|
||||||
|
for(ClipperPolygon& p_tmp : v) supports_polygons.emplace_back(std::move(p_tmp));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
model_polygons = polyunion(model_polygons);
|
||||||
|
double layer_model_area = 0;
|
||||||
|
for (const ClipperPolygon& polygon : model_polygons)
|
||||||
|
layer_model_area += areafn(polygon);
|
||||||
|
|
||||||
|
if (layer_model_area < 0 || layer_model_area > 0) {
|
||||||
|
Lock lck(mutex); models_volume += layer_model_area * l_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!supports_polygons.empty()) {
|
||||||
|
if(model_polygons.empty()) supports_polygons = polyunion(supports_polygons);
|
||||||
|
else supports_polygons = polydiff(supports_polygons, model_polygons);
|
||||||
|
// allegedly, union of subject is done withing the diff according to the pftPositive polyFillType
|
||||||
|
}
|
||||||
|
|
||||||
|
double layer_support_area = 0;
|
||||||
|
for (const ClipperPolygon& polygon : supports_polygons)
|
||||||
|
layer_support_area += areafn(polygon);
|
||||||
|
|
||||||
|
if (layer_support_area < 0 || layer_support_area > 0) {
|
||||||
|
Lock lck(mutex); supports_volume += layer_support_area * l_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here we can save the expensively calculated polygons for printing
|
||||||
|
ClipperPolygons trslices;
|
||||||
|
trslices.reserve(model_polygons.size() + supports_polygons.size());
|
||||||
|
for(ClipperPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly));
|
||||||
|
for(ClipperPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly));
|
||||||
|
|
||||||
|
layer.transformed_slices(polyunion(trslices));
|
||||||
|
|
||||||
|
// Calculation of the slow and fast layers to the future controlling those values on FW
|
||||||
|
|
||||||
|
const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill;
|
||||||
|
const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt;
|
||||||
|
|
||||||
|
{ Lock lck(mutex);
|
||||||
|
if (is_fast_layer)
|
||||||
|
fast_layers++;
|
||||||
|
else
|
||||||
|
slow_layers++;
|
||||||
|
|
||||||
|
|
||||||
|
// Calculation of the printing time
|
||||||
|
|
||||||
|
if (sliced_layer_cnt < 3)
|
||||||
|
estim_time += init_exp_time;
|
||||||
|
else if (fade_layer_time > exp_time)
|
||||||
|
{
|
||||||
|
fade_layer_time -= delta_fade_time;
|
||||||
|
estim_time += fade_layer_time;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
estim_time += exp_time;
|
||||||
|
|
||||||
|
estim_time += tilt_time;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// sequential version for debugging:
|
||||||
|
// for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i);
|
||||||
|
sla::ccr::enumerate(printer_input.begin(), printer_input.end(), printlayerfn);
|
||||||
|
|
||||||
|
auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR;
|
||||||
|
print_statistics.support_used_material = supports_volume * SCALING2;
|
||||||
|
print_statistics.objects_used_material = models_volume * SCALING2;
|
||||||
|
|
||||||
|
// Estimated printing time
|
||||||
|
// A layers count o the highest object
|
||||||
|
if (printer_input.size() == 0)
|
||||||
|
print_statistics.estimated_print_time = std::nan("");
|
||||||
|
else
|
||||||
|
print_statistics.estimated_print_time = estim_time;
|
||||||
|
|
||||||
|
print_statistics.fast_layers_count = fast_layers;
|
||||||
|
print_statistics.slow_layers_count = slow_layers;
|
||||||
|
|
||||||
|
report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rasterizing the model objects, and their supports
|
||||||
|
void SLAPrint::Steps::rasterize()
|
||||||
|
{
|
||||||
|
if(canceled()) return;
|
||||||
|
|
||||||
|
auto &print_statistics = m_print->m_print_statistics;
|
||||||
|
auto &printer_input = m_print->m_printer_input;
|
||||||
|
|
||||||
|
// Set up the printer, allocate space for all the layers
|
||||||
|
sla::RasterWriter &printer = m_print->init_printer();
|
||||||
|
|
||||||
|
auto lvlcnt = unsigned(printer_input.size());
|
||||||
|
printer.layers(lvlcnt);
|
||||||
|
|
||||||
|
// coefficient to map the rasterization state (0-99) to the allocated
|
||||||
|
// portion (slot) of the process state
|
||||||
|
double sd = (100 - max_objstatus) / 100.0;
|
||||||
|
|
||||||
|
// slot is the portion of 100% that is realted to rasterization
|
||||||
|
unsigned slot = PRINT_STEP_LEVELS[slapsRasterize];
|
||||||
|
|
||||||
|
// pst: previous state
|
||||||
|
double pst = current_status();
|
||||||
|
|
||||||
|
double increment = (slot * sd) / printer_input.size();
|
||||||
|
double dstatus = current_status();
|
||||||
|
|
||||||
|
sla::ccr::SpinningMutex slck;
|
||||||
|
using Lock = std::lock_guard<sla::ccr::SpinningMutex>;
|
||||||
|
|
||||||
|
// procedure to process one height level. This will run in parallel
|
||||||
|
auto lvlfn =
|
||||||
|
[this, &slck, &printer, increment, &dstatus, &pst]
|
||||||
|
(PrintLayer& printlayer, size_t idx)
|
||||||
|
{
|
||||||
|
if(canceled()) return;
|
||||||
|
auto level_id = unsigned(idx);
|
||||||
|
|
||||||
|
// Switch to the appropriate layer in the printer
|
||||||
|
printer.begin_layer(level_id);
|
||||||
|
|
||||||
|
for(const ClipperLib::Polygon& poly : printlayer.transformed_slices())
|
||||||
|
printer.draw_polygon(poly, level_id);
|
||||||
|
|
||||||
|
// Finish the layer for later saving it.
|
||||||
|
printer.finish_layer(level_id);
|
||||||
|
|
||||||
|
// Status indication guarded with the spinlock
|
||||||
|
{
|
||||||
|
Lock lck(slck);
|
||||||
|
dstatus += increment;
|
||||||
|
double st = std::round(dstatus);
|
||||||
|
if(st > pst) {
|
||||||
|
report_status(st, PRINT_STEP_LABELS(slapsRasterize));
|
||||||
|
pst = st;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// last minute escape
|
||||||
|
if(canceled()) return;
|
||||||
|
|
||||||
|
// Sequential version (for testing)
|
||||||
|
// for(unsigned l = 0; l < lvlcnt; ++l) lvlfn(l);
|
||||||
|
|
||||||
|
// Print all the layers in parallel
|
||||||
|
sla::ccr::enumerate(printer_input.begin(), printer_input.end(), lvlfn);
|
||||||
|
|
||||||
|
// Set statistics values to the printer
|
||||||
|
sla::RasterWriter::PrintStatistics stats;
|
||||||
|
stats.used_material = (print_statistics.objects_used_material +
|
||||||
|
print_statistics.support_used_material) / 1000;
|
||||||
|
|
||||||
|
int num_fade = m_print->m_default_object_config.faded_layers.getInt();
|
||||||
|
stats.num_fade = num_fade >= 0 ? size_t(num_fade) : size_t(0);
|
||||||
|
stats.num_fast = print_statistics.fast_layers_count;
|
||||||
|
stats.num_slow = print_statistics.slow_layers_count;
|
||||||
|
stats.estimated_print_time_s = print_statistics.estimated_print_time;
|
||||||
|
|
||||||
|
printer.set_statistics(stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SLAPrint::Steps::label(SLAPrintObjectStep step)
|
||||||
|
{
|
||||||
|
return OBJ_STEP_LABELS(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string SLAPrint::Steps::label(SLAPrintStep step)
|
||||||
|
{
|
||||||
|
return PRINT_STEP_LABELS(step);
|
||||||
|
}
|
||||||
|
|
||||||
|
double SLAPrint::Steps::progressrange(SLAPrintObjectStep step) const
|
||||||
|
{
|
||||||
|
return OBJ_STEP_LEVELS[step] * objectstep_scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
double SLAPrint::Steps::progressrange(SLAPrintStep step) const
|
||||||
|
{
|
||||||
|
return PRINT_STEP_LEVELS[step] * (100 - max_objstatus) / 100.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SLAPrint::Steps::execute(SLAPrintObjectStep step, SLAPrintObject &obj)
|
||||||
|
{
|
||||||
|
switch(step) {
|
||||||
|
case slaposHollowing: hollow_model(obj); break;
|
||||||
|
case slaposObjectSlice: slice_model(obj); break;
|
||||||
|
case slaposDrillHolesIfHollowed: break;
|
||||||
|
case slaposSupportPoints: support_points(obj); break;
|
||||||
|
case slaposSupportTree: support_tree(obj); break;
|
||||||
|
case slaposPad: generate_pad(obj); break;
|
||||||
|
case slaposSliceSupports: slice_supports(obj); break;
|
||||||
|
case slaposCount: assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SLAPrint::Steps::execute(SLAPrintStep step)
|
||||||
|
{
|
||||||
|
switch (step) {
|
||||||
|
case slapsMergeSlicesAndEval: merge_slices_and_eval_stats(); break;
|
||||||
|
case slapsRasterize: rasterize(); break;
|
||||||
|
case slapsCount: assert(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
68
src/libslic3r/SLAPrintSteps.hpp
Normal file
68
src/libslic3r/SLAPrintSteps.hpp
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
#ifndef SLAPRINTSTEPS_HPP
|
||||||
|
#define SLAPRINTSTEPS_HPP
|
||||||
|
|
||||||
|
#include <libslic3r/SLAPrint.hpp>
|
||||||
|
|
||||||
|
#include <libslic3r/SLA/Hollowing.hpp>
|
||||||
|
#include <libslic3r/SLA/SupportTree.hpp>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class SLAPrint::Steps
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
SLAPrint *m_print = nullptr;
|
||||||
|
|
||||||
|
public:
|
||||||
|
// where the per object operations start and end
|
||||||
|
static const constexpr unsigned min_objstatus = 0;
|
||||||
|
static const constexpr unsigned max_objstatus = 50;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const size_t objcount;
|
||||||
|
|
||||||
|
// shortcut to initial layer height
|
||||||
|
const double ilhd;
|
||||||
|
const float ilh;
|
||||||
|
const coord_t ilhs;
|
||||||
|
|
||||||
|
// the coefficient that multiplies the per object status values which
|
||||||
|
// are set up for <0, 100>. They need to be scaled into the whole process
|
||||||
|
const double objectstep_scale;
|
||||||
|
|
||||||
|
template<class...Args> void report_status(Args&&...args)
|
||||||
|
{
|
||||||
|
m_print->m_report_status(*m_print, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
double current_status() const { return m_print->m_report_status.status(); }
|
||||||
|
void throw_if_canceled() const { m_print->throw_if_canceled(); }
|
||||||
|
bool canceled() const { return m_print->canceled(); }
|
||||||
|
void initialize_printer_input();
|
||||||
|
|
||||||
|
public:
|
||||||
|
Steps(SLAPrint *print);
|
||||||
|
|
||||||
|
void hollow_model(SLAPrintObject &po);
|
||||||
|
void slice_model(SLAPrintObject& po);
|
||||||
|
void support_points(SLAPrintObject& po);
|
||||||
|
void support_tree(SLAPrintObject& po);
|
||||||
|
void generate_pad(SLAPrintObject& po);
|
||||||
|
void slice_supports(SLAPrintObject& po);
|
||||||
|
|
||||||
|
void merge_slices_and_eval_stats();
|
||||||
|
void rasterize();
|
||||||
|
|
||||||
|
void execute(SLAPrintObjectStep step, SLAPrintObject &obj);
|
||||||
|
void execute(SLAPrintStep step);
|
||||||
|
|
||||||
|
static std::string label(SLAPrintObjectStep step);
|
||||||
|
static std::string label(SLAPrintStep step);
|
||||||
|
|
||||||
|
double progressrange(SLAPrintObjectStep step) const;
|
||||||
|
double progressrange(SLAPrintStep step) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SLAPRINTSTEPS_HPP
|
||||||
66
src/libslic3r/SimplifyMesh.cpp
Normal file
66
src/libslic3r/SimplifyMesh.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
#include "SimplifyMesh.hpp"
|
||||||
|
#include "SimplifyMeshImpl.hpp"
|
||||||
|
|
||||||
|
namespace SimplifyMesh {
|
||||||
|
|
||||||
|
template<> struct vertex_traits<stl_vertex> {
|
||||||
|
using coord_type = float;
|
||||||
|
using compute_type = double;
|
||||||
|
|
||||||
|
static inline float x(const stl_vertex &v) { return v.x(); }
|
||||||
|
static inline float& x(stl_vertex &v) { return v.x(); }
|
||||||
|
|
||||||
|
static inline float y(const stl_vertex &v) { return v.y(); }
|
||||||
|
static inline float& y(stl_vertex &v) { return v.y(); }
|
||||||
|
|
||||||
|
static inline float z(const stl_vertex &v) { return v.z(); }
|
||||||
|
static inline float& z(stl_vertex &v) { return v.z(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
template<> struct mesh_traits<indexed_triangle_set> {
|
||||||
|
using vertex_t = stl_vertex;
|
||||||
|
static size_t face_count(const indexed_triangle_set &m)
|
||||||
|
{
|
||||||
|
return m.indices.size();
|
||||||
|
}
|
||||||
|
static size_t vertex_count(const indexed_triangle_set &m)
|
||||||
|
{
|
||||||
|
return m.vertices.size();
|
||||||
|
}
|
||||||
|
static vertex_t vertex(const indexed_triangle_set &m, size_t idx)
|
||||||
|
{
|
||||||
|
return m.vertices[idx];
|
||||||
|
}
|
||||||
|
static void vertex(indexed_triangle_set &m, size_t idx, const vertex_t &v)
|
||||||
|
{
|
||||||
|
m.vertices[idx] = v;
|
||||||
|
}
|
||||||
|
static Index3 triangle(const indexed_triangle_set &m, size_t idx)
|
||||||
|
{
|
||||||
|
std::array<size_t, 3> t;
|
||||||
|
for (size_t i = 0; i < 3; ++i) t[i] = size_t(m.indices[idx](int(i)));
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
static void triangle(indexed_triangle_set &m, size_t fidx, const Index3 &t)
|
||||||
|
{
|
||||||
|
auto &face = m.indices[fidx];
|
||||||
|
face(0) = int(t[0]); face(1) = int(t[1]); face(2) = int(t[2]);
|
||||||
|
}
|
||||||
|
static void update(indexed_triangle_set &m, size_t vc, size_t fc)
|
||||||
|
{
|
||||||
|
m.vertices.resize(vc);
|
||||||
|
m.indices.resize(fc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace SimplifyMesh
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
void simplify_mesh(indexed_triangle_set &m)
|
||||||
|
{
|
||||||
|
SimplifyMesh::implementation::SimplifiableMesh sm{&m};
|
||||||
|
sm.simplify_mesh_lossless();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
25
src/libslic3r/SimplifyMesh.hpp
Normal file
25
src/libslic3r/SimplifyMesh.hpp
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#ifndef MESHSIMPLIFY_HPP
|
||||||
|
#define MESHSIMPLIFY_HPP
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <libslic3r/TriangleMesh.hpp>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
void simplify_mesh(indexed_triangle_set &);
|
||||||
|
|
||||||
|
// TODO: (but this can be done with IGL as well)
|
||||||
|
// void simplify_mesh(indexed_triangle_set &, int face_count, float agressiveness = 0.5f);
|
||||||
|
|
||||||
|
template<class...Args> void simplify_mesh(TriangleMesh &m, Args &&...a)
|
||||||
|
{
|
||||||
|
m.require_shared_vertices();
|
||||||
|
simplify_mesh(m.its, std::forward<Args>(a)...);
|
||||||
|
m = TriangleMesh{m.its};
|
||||||
|
m.require_shared_vertices();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif // MESHSIMPLIFY_H
|
||||||
699
src/libslic3r/SimplifyMeshImpl.hpp
Normal file
699
src/libslic3r/SimplifyMeshImpl.hpp
Normal file
|
|
@ -0,0 +1,699 @@
|
||||||
|
// ///////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Mesh Simplification Tutorial
|
||||||
|
//
|
||||||
|
// (C) by Sven Forstmann in 2014
|
||||||
|
//
|
||||||
|
// License : MIT
|
||||||
|
// http://opensource.org/licenses/MIT
|
||||||
|
//
|
||||||
|
// https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification
|
||||||
|
//
|
||||||
|
// 5/2016: Chris Rorden created minimal version for OSX/Linux/Windows compile
|
||||||
|
// https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification/
|
||||||
|
//
|
||||||
|
// libslic3r refactor by tamasmeszaros
|
||||||
|
|
||||||
|
#ifndef SIMPLIFYMESHIMPL_HPP
|
||||||
|
#define SIMPLIFYMESHIMPL_HPP
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <array>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
#include <iostream>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace SimplifyMesh {
|
||||||
|
|
||||||
|
using Bary = std::array<double, 3>;
|
||||||
|
using Index3 = std::array<size_t, 3>;
|
||||||
|
|
||||||
|
template<class Vertex> struct vertex_traits {
|
||||||
|
using coord_type = typename Vertex::coord_type;
|
||||||
|
using compute_type = coord_type;
|
||||||
|
|
||||||
|
static coord_type x(const Vertex &v);
|
||||||
|
static coord_type& x(Vertex &v);
|
||||||
|
|
||||||
|
static coord_type y(const Vertex &v);
|
||||||
|
static coord_type& y(Vertex &v);
|
||||||
|
|
||||||
|
static coord_type z(const Vertex &v);
|
||||||
|
static coord_type& z(Vertex &v);
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class Mesh> struct mesh_traits {
|
||||||
|
using vertex_t = typename Mesh::vertex_t;
|
||||||
|
|
||||||
|
static size_t face_count(const Mesh &m);
|
||||||
|
static size_t vertex_count(const Mesh &m);
|
||||||
|
static vertex_t vertex(const Mesh &m, size_t vertex_idx);
|
||||||
|
static void vertex(Mesh &m, size_t vertex_idx, const vertex_t &v);
|
||||||
|
static Index3 triangle(const Mesh &m, size_t face_idx);
|
||||||
|
static void triangle(Mesh &m, size_t face_idx, const Index3 &t);
|
||||||
|
static void update(Mesh &m, size_t vertex_count, size_t face_count);
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace implementation {
|
||||||
|
|
||||||
|
// A shorter C++14 style form of the enable_if metafunction
|
||||||
|
template<bool B, class T>
|
||||||
|
using enable_if_t = typename std::enable_if<B, T>::type;
|
||||||
|
|
||||||
|
// Meta predicates for floating, 'scaled coord' and generic arithmetic types
|
||||||
|
template<class T, class O = T>
|
||||||
|
using FloatingOnly = enable_if_t<std::is_floating_point<T>::value, O>;
|
||||||
|
|
||||||
|
template<class T, class O = T>
|
||||||
|
using IntegerOnly = enable_if_t<std::is_integral<T>::value, O>;
|
||||||
|
|
||||||
|
template<class T, class O = T>
|
||||||
|
using ArithmeticOnly = enable_if_t<std::is_arithmetic<T>::value, O>;
|
||||||
|
|
||||||
|
template< class T >
|
||||||
|
struct remove_cvref {
|
||||||
|
using type = typename std::remove_cv<
|
||||||
|
typename std::remove_reference<T>::type>::type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template< class T >
|
||||||
|
using remove_cvref_t = typename remove_cvref<T>::type;
|
||||||
|
|
||||||
|
struct DOut {
|
||||||
|
#ifndef NDEBUG
|
||||||
|
std::ostream& out = std::cout;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
inline DOut&& operator<<( DOut&& out, T&& d) {
|
||||||
|
#ifndef NDEBUG
|
||||||
|
out.out << d;
|
||||||
|
#endif
|
||||||
|
return std::move(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline DOut dout() { return DOut(); }
|
||||||
|
|
||||||
|
template<class T> FloatingOnly<T, bool> is_approx(T val, T ref) { return std::abs(val - ref) < 1e-8; }
|
||||||
|
template<class T> IntegerOnly <T, bool> is_approx(T val, T ref) { val == ref; }
|
||||||
|
|
||||||
|
template<class T, size_t N = 10> class SymetricMatrix {
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit SymetricMatrix(ArithmeticOnly<T> c = T()) { std::fill(m, m + N, c); }
|
||||||
|
|
||||||
|
SymetricMatrix(T m11, T m12, T m13, T m14,
|
||||||
|
T m22, T m23, T m24,
|
||||||
|
T m33, T m34,
|
||||||
|
T m44)
|
||||||
|
{
|
||||||
|
m[0] = m11; m[1] = m12; m[2] = m13; m[3] = m14;
|
||||||
|
m[4] = m22; m[5] = m23; m[6] = m24;
|
||||||
|
m[7] = m33; m[8] = m34;
|
||||||
|
m[9] = m44;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make plane
|
||||||
|
SymetricMatrix(T a, T b, T c, T d)
|
||||||
|
{
|
||||||
|
m[0] = a * a; m[1] = a * b; m[2] = a * c; m[3] = a * d;
|
||||||
|
m[4] = b * b; m[5] = b * c; m[6] = b * d;
|
||||||
|
m[7] = c * c; m[8] = c * d;
|
||||||
|
m[9] = d * d;
|
||||||
|
}
|
||||||
|
|
||||||
|
T operator[](int c) const { return m[c]; }
|
||||||
|
|
||||||
|
// Determinant
|
||||||
|
T det(int a11, int a12, int a13,
|
||||||
|
int a21, int a22, int a23,
|
||||||
|
int a31, int a32, int a33)
|
||||||
|
{
|
||||||
|
T det = m[a11] * m[a22] * m[a33] + m[a13] * m[a21] * m[a32] +
|
||||||
|
m[a12] * m[a23] * m[a31] - m[a13] * m[a22] * m[a31] -
|
||||||
|
m[a11] * m[a23] * m[a32] - m[a12] * m[a21] * m[a33];
|
||||||
|
|
||||||
|
return det;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SymetricMatrix operator+(const SymetricMatrix& n) const
|
||||||
|
{
|
||||||
|
return SymetricMatrix(m[0] + n[0], m[1] + n[1], m[2] + n[2], m[3]+n[3],
|
||||||
|
m[4] + n[4], m[5] + n[5], m[6] + n[6],
|
||||||
|
m[7] + n[7], m[8] + n[8],
|
||||||
|
m[9] + n[9]);
|
||||||
|
}
|
||||||
|
|
||||||
|
SymetricMatrix& operator+=(const SymetricMatrix& n)
|
||||||
|
{
|
||||||
|
m[0]+=n[0]; m[1]+=n[1]; m[2]+=n[2]; m[3]+=n[3];
|
||||||
|
m[4]+=n[4]; m[5]+=n[5]; m[6]+=n[6]; m[7]+=n[7];
|
||||||
|
m[8]+=n[8]; m[9]+=n[9];
|
||||||
|
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
T m[N];
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class V> using TCoord = typename vertex_traits<remove_cvref_t<V>>::coord_type;
|
||||||
|
template<class V> using TCompute = typename vertex_traits<remove_cvref_t<V>>::compute_type;
|
||||||
|
template<class V> inline TCoord<V> x(const V &v) { return vertex_traits<remove_cvref_t<V>>::x(v); }
|
||||||
|
template<class V> inline TCoord<V> y(const V &v) { return vertex_traits<remove_cvref_t<V>>::y(v); }
|
||||||
|
template<class V> inline TCoord<V> z(const V &v) { return vertex_traits<remove_cvref_t<V>>::z(v); }
|
||||||
|
template<class V> inline TCoord<V>& x(V &v) { return vertex_traits<remove_cvref_t<V>>::x(v); }
|
||||||
|
template<class V> inline TCoord<V>& y(V &v) { return vertex_traits<remove_cvref_t<V>>::y(v); }
|
||||||
|
template<class V> inline TCoord<V>& z(V &v) { return vertex_traits<remove_cvref_t<V>>::z(v); }
|
||||||
|
template<class M> using TVertex = typename mesh_traits<remove_cvref_t<M>>::vertex_t;
|
||||||
|
template<class Mesh> using TMeshCoord = TCoord<TVertex<Mesh>>;
|
||||||
|
|
||||||
|
template<class Vertex> TCompute<Vertex> dot(const Vertex &v1, const Vertex &v2)
|
||||||
|
{
|
||||||
|
return TCompute<Vertex>(x(v1)) * x(v2) +
|
||||||
|
TCompute<Vertex>(y(v1)) * y(v2) +
|
||||||
|
TCompute<Vertex>(z(v1)) * z(v2);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Vertex> Vertex cross(const Vertex &a, const Vertex &b)
|
||||||
|
{
|
||||||
|
return Vertex{y(a) * z(b) - z(a) * y(b),
|
||||||
|
z(a) * x(b) - x(a) * z(b),
|
||||||
|
x(a) * y(b) - y(a) * x(b)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Vertex> TCompute<Vertex> lengthsq(const Vertex &v)
|
||||||
|
{
|
||||||
|
return TCompute<Vertex>(x(v)) * x(v) + TCompute<Vertex>(y(v)) * y(v) +
|
||||||
|
TCompute<Vertex>(z(v)) * z(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Vertex> void normalize(Vertex &v)
|
||||||
|
{
|
||||||
|
double square = std::sqrt(lengthsq(v));
|
||||||
|
x(v) /= square; y(v) /= square; z(v) /= square;
|
||||||
|
}
|
||||||
|
|
||||||
|
using Bary = std::array<double, 3>;
|
||||||
|
|
||||||
|
template<class Vertex>
|
||||||
|
Bary barycentric(const Vertex &p, const Vertex &a, const Vertex &b, const Vertex &c)
|
||||||
|
{
|
||||||
|
Vertex v0 = (b - a);
|
||||||
|
Vertex v1 = (c - a);
|
||||||
|
Vertex v2 = (p - a);
|
||||||
|
|
||||||
|
double d00 = dot(v0, v0);
|
||||||
|
double d01 = dot(v0, v1);
|
||||||
|
double d11 = dot(v1, v1);
|
||||||
|
double d20 = dot(v2, v0);
|
||||||
|
double d21 = dot(v2, v1);
|
||||||
|
double denom = d00 * d11 - d01 * d01;
|
||||||
|
double v = (d11 * d20 - d01 * d21) / denom;
|
||||||
|
double w = (d00 * d21 - d01 * d20) / denom;
|
||||||
|
double u = 1.0 - v - w;
|
||||||
|
|
||||||
|
return {u, v, w};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Mesh> class SimplifiableMesh {
|
||||||
|
Mesh *m_mesh;
|
||||||
|
|
||||||
|
using Vertex = TVertex<Mesh>;
|
||||||
|
using Coord = TMeshCoord<Mesh>;
|
||||||
|
using HiPrecison = TCompute<TVertex<Mesh>>;
|
||||||
|
using SymMat = SymetricMatrix<HiPrecison>;
|
||||||
|
|
||||||
|
struct FaceInfo {
|
||||||
|
size_t idx;
|
||||||
|
double err[4] = {0.};
|
||||||
|
bool deleted = false, dirty = false;
|
||||||
|
Vertex n;
|
||||||
|
explicit FaceInfo(size_t id): idx(id) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VertexInfo {
|
||||||
|
size_t idx;
|
||||||
|
size_t tstart = 0, tcount = 0;
|
||||||
|
bool border = false;
|
||||||
|
SymMat q;
|
||||||
|
explicit VertexInfo(size_t id): idx(id) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Ref { size_t face; size_t vertex; };
|
||||||
|
|
||||||
|
std::vector<Ref> m_refs;
|
||||||
|
std::vector<FaceInfo> m_faceinfo;
|
||||||
|
std::vector<VertexInfo> m_vertexinfo;
|
||||||
|
|
||||||
|
void compact_faces();
|
||||||
|
void compact();
|
||||||
|
|
||||||
|
size_t mesh_vcount() const { return mesh_traits<Mesh>::vertex_count(*m_mesh); }
|
||||||
|
size_t mesh_facecount() const { return mesh_traits<Mesh>::face_count(*m_mesh); }
|
||||||
|
|
||||||
|
size_t vcount() const { return m_vertexinfo.size(); }
|
||||||
|
|
||||||
|
inline Vertex read_vertex(size_t vi) const
|
||||||
|
{
|
||||||
|
return mesh_traits<Mesh>::vertex(*m_mesh, vi);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Vertex read_vertex(const VertexInfo &vinf) const
|
||||||
|
{
|
||||||
|
return read_vertex(vinf.idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void write_vertex(size_t idx, const Vertex &v) const
|
||||||
|
{
|
||||||
|
mesh_traits<Mesh>::vertex(*m_mesh, idx, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void write_vertex(const VertexInfo &vinf, const Vertex &v) const
|
||||||
|
{
|
||||||
|
write_vertex(vinf.idx, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Index3 read_triangle(size_t fi) const
|
||||||
|
{
|
||||||
|
return mesh_traits<Mesh>::triangle(*m_mesh, fi);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Index3 read_triangle(const FaceInfo &finf) const
|
||||||
|
{
|
||||||
|
return read_triangle(finf.idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void write_triangle(size_t idx, const Index3 &t)
|
||||||
|
{
|
||||||
|
return mesh_traits<Mesh>::triangle(*m_mesh, idx, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void write_triangle(const FaceInfo &finf, const Index3 &t)
|
||||||
|
{
|
||||||
|
return write_triangle(finf.idx, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::array<Vertex, 3> triangle_vertices(const Index3 &f) const
|
||||||
|
{
|
||||||
|
std::array<Vertex, 3> p;
|
||||||
|
for (size_t i = 0; i < 3; ++i) p[i] = read_vertex(f[i]);
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error between vertex and Quadric
|
||||||
|
static double vertex_error(const SymMat &q, const Vertex &v)
|
||||||
|
{
|
||||||
|
Coord _x = x(v) , _y = y(v), _z = z(v);
|
||||||
|
return q[0] * _x * _x + 2 * q[1] * _x * _y + 2 * q[2] * _x * _z +
|
||||||
|
2 * q[3] * _x + q[4] * _y * _y + 2 * q[5] * _y * _z +
|
||||||
|
2 * q[6] * _y + q[7] * _z * _z + 2 * q[8] * _z + q[9];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error for one edge
|
||||||
|
double calculate_error(size_t id_v1, size_t id_v2, Vertex &p_result);
|
||||||
|
|
||||||
|
void calculate_error(FaceInfo &fi)
|
||||||
|
{
|
||||||
|
Vertex p;
|
||||||
|
Index3 t = read_triangle(fi);
|
||||||
|
for (size_t j = 0; j < 3; ++j)
|
||||||
|
fi.err[j] = calculate_error(t[j], t[(j + 1) % 3], p);
|
||||||
|
|
||||||
|
fi.err[3] = std::min(fi.err[0], std::min(fi.err[1], fi.err[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_mesh(int iteration);
|
||||||
|
|
||||||
|
// Update triangle connections and edge error after a edge is collapsed
|
||||||
|
void update_triangles(size_t i, VertexInfo &vi, std::vector<bool> &deleted, int &deleted_triangles);
|
||||||
|
|
||||||
|
// Check if a triangle flips when this edge is removed
|
||||||
|
bool flipped(const Vertex &p, size_t i0, size_t i1, VertexInfo &v0, VertexInfo &v1, std::vector<bool> &deleted);
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
explicit SimplifiableMesh(Mesh *m) : m_mesh{m}
|
||||||
|
{
|
||||||
|
static_assert(
|
||||||
|
std::is_arithmetic<Coord>::value,
|
||||||
|
"Coordinate type of mesh has to be an arithmetic type!");
|
||||||
|
|
||||||
|
m_faceinfo.reserve(mesh_traits<Mesh>::face_count(*m));
|
||||||
|
m_vertexinfo.reserve(mesh_traits<Mesh>::vertex_count(*m));
|
||||||
|
for (size_t i = 0; i < mesh_facecount(); ++i) m_faceinfo.emplace_back(i);
|
||||||
|
for (size_t i = 0; i < mesh_vcount(); ++i) m_vertexinfo.emplace_back(i);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void simplify_mesh_lossless();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template<class Mesh> void SimplifiableMesh<Mesh>::compact_faces()
|
||||||
|
{
|
||||||
|
auto it = std::remove_if(m_faceinfo.begin(), m_faceinfo.end(),
|
||||||
|
[](const FaceInfo &inf) { return inf.deleted; });
|
||||||
|
|
||||||
|
m_faceinfo.erase(it, m_faceinfo.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class M> void SimplifiableMesh<M>::compact()
|
||||||
|
{
|
||||||
|
for (auto &vi : m_vertexinfo) vi.tcount = 0;
|
||||||
|
|
||||||
|
compact_faces();
|
||||||
|
|
||||||
|
for (FaceInfo &fi : m_faceinfo)
|
||||||
|
for (size_t vidx : read_triangle(fi)) m_vertexinfo[vidx].tcount = 1;
|
||||||
|
|
||||||
|
size_t dst = 0;
|
||||||
|
for (VertexInfo &vi : m_vertexinfo) {
|
||||||
|
if (vi.tcount) {
|
||||||
|
vi.tstart = dst;
|
||||||
|
write_vertex(dst++, read_vertex(vi));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t vertex_count = dst;
|
||||||
|
|
||||||
|
dst = 0;
|
||||||
|
for (const FaceInfo &fi : m_faceinfo) {
|
||||||
|
Index3 t = read_triangle(fi);
|
||||||
|
for (size_t &idx : t) idx = m_vertexinfo[idx].tstart;
|
||||||
|
write_triangle(dst++, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh_traits<M>::update(*m_mesh, vertex_count, m_faceinfo.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Mesh>
|
||||||
|
double SimplifiableMesh<Mesh>::calculate_error(size_t id_v1, size_t id_v2, Vertex &p_result)
|
||||||
|
{
|
||||||
|
// compute interpolated vertex
|
||||||
|
|
||||||
|
SymMat q = m_vertexinfo[id_v1].q + m_vertexinfo[id_v2].q;
|
||||||
|
|
||||||
|
bool border = m_vertexinfo[id_v1].border & m_vertexinfo[id_v2].border;
|
||||||
|
double error = 0;
|
||||||
|
HiPrecison det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7);
|
||||||
|
|
||||||
|
if (!is_approx(det, HiPrecison(0)) && !border)
|
||||||
|
{
|
||||||
|
// q_delta is invertible
|
||||||
|
x(p_result) = Coord(-1) / det * q.det(1, 2, 3, 4, 5, 6, 5, 7, 8); // vx = A41/det(q_delta)
|
||||||
|
y(p_result) = Coord( 1) / det * q.det(0, 2, 3, 1, 5, 6, 2, 7, 8); // vy = A42/det(q_delta)
|
||||||
|
z(p_result) = Coord(-1) / det * q.det(0, 1, 3, 1, 4, 6, 2, 5, 8); // vz = A43/det(q_delta)
|
||||||
|
|
||||||
|
error = vertex_error(q, p_result);
|
||||||
|
} else {
|
||||||
|
// det = 0 -> try to find best result
|
||||||
|
Vertex p1 = read_vertex(id_v1);
|
||||||
|
Vertex p2 = read_vertex(id_v2);
|
||||||
|
Vertex p3 = (p1 + p2) / 2;
|
||||||
|
double error1 = vertex_error(q, p1);
|
||||||
|
double error2 = vertex_error(q, p2);
|
||||||
|
double error3 = vertex_error(q, p3);
|
||||||
|
error = std::min(error1, std::min(error2, error3));
|
||||||
|
|
||||||
|
if (is_approx(error1, error)) p_result = p1;
|
||||||
|
if (is_approx(error2, error)) p_result = p2;
|
||||||
|
if (is_approx(error3, error)) p_result = p3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Mesh> void SimplifiableMesh<Mesh>::update_mesh(int iteration)
|
||||||
|
{
|
||||||
|
if (iteration > 0) compact_faces();
|
||||||
|
|
||||||
|
assert(mesh_vcount() == m_vertexinfo.size());
|
||||||
|
|
||||||
|
//
|
||||||
|
// Init Quadrics by Plane & Edge Errors
|
||||||
|
//
|
||||||
|
// required at the beginning ( iteration == 0 )
|
||||||
|
// recomputing during the simplification is not required,
|
||||||
|
// but mostly improves the result for closed meshes
|
||||||
|
//
|
||||||
|
if (iteration == 0) {
|
||||||
|
|
||||||
|
for (VertexInfo &vinf : m_vertexinfo) vinf.q = SymMat{};
|
||||||
|
for (FaceInfo &finf : m_faceinfo) {
|
||||||
|
Index3 t = read_triangle(finf);
|
||||||
|
std::array<Vertex, 3> p = triangle_vertices(t);
|
||||||
|
Vertex n = cross(Vertex(p[1] - p[0]), Vertex(p[2] - p[0]));
|
||||||
|
normalize(n);
|
||||||
|
finf.n = n;
|
||||||
|
|
||||||
|
for (size_t fi : t)
|
||||||
|
m_vertexinfo[fi].q += SymMat(x(n), y(n), z(n), -dot(n, p[0]));
|
||||||
|
|
||||||
|
calculate_error(finf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init Reference ID list
|
||||||
|
for (VertexInfo &vi : m_vertexinfo) { vi.tstart = 0; vi.tcount = 0; }
|
||||||
|
|
||||||
|
for (FaceInfo &fi : m_faceinfo)
|
||||||
|
for (size_t vidx : read_triangle(fi))
|
||||||
|
m_vertexinfo[vidx].tcount++;
|
||||||
|
|
||||||
|
size_t tstart = 0;
|
||||||
|
for (VertexInfo &vi : m_vertexinfo) {
|
||||||
|
vi.tstart = tstart;
|
||||||
|
tstart += vi.tcount;
|
||||||
|
vi.tcount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write References
|
||||||
|
m_refs.resize(m_faceinfo.size() * 3);
|
||||||
|
for (size_t i = 0; i < m_faceinfo.size(); ++i) {
|
||||||
|
const FaceInfo &fi = m_faceinfo[i];
|
||||||
|
Index3 t = read_triangle(fi);
|
||||||
|
for (size_t j = 0; j < 3; ++j) {
|
||||||
|
VertexInfo &vi = m_vertexinfo[t[j]];
|
||||||
|
|
||||||
|
assert(vi.tstart + vi.tcount < m_refs.size());
|
||||||
|
|
||||||
|
Ref &ref = m_refs[vi.tstart + vi.tcount];
|
||||||
|
ref.face = i;
|
||||||
|
ref.vertex = j;
|
||||||
|
vi.tcount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identify boundary : vertices[].border=0,1
|
||||||
|
if (iteration == 0) {
|
||||||
|
for (VertexInfo &vi: m_vertexinfo) vi.border = false;
|
||||||
|
|
||||||
|
std::vector<size_t> vcount, vids;
|
||||||
|
|
||||||
|
for (VertexInfo &vi: m_vertexinfo) {
|
||||||
|
vcount.clear();
|
||||||
|
vids.clear();
|
||||||
|
|
||||||
|
for(size_t j = 0; j < vi.tcount; ++j) {
|
||||||
|
assert(vi.tstart + j < m_refs.size());
|
||||||
|
FaceInfo &fi = m_faceinfo[m_refs[vi.tstart + j].face];
|
||||||
|
Index3 t = read_triangle(fi);
|
||||||
|
|
||||||
|
for (size_t fid : t) {
|
||||||
|
size_t ofs=0;
|
||||||
|
while (ofs < vcount.size())
|
||||||
|
{
|
||||||
|
if (vids[ofs] == fid) break;
|
||||||
|
ofs++;
|
||||||
|
}
|
||||||
|
if (ofs == vcount.size())
|
||||||
|
{
|
||||||
|
vcount.emplace_back(1);
|
||||||
|
vids.emplace_back(fid);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
vcount[ofs]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t j = 0; j < vcount.size(); ++j)
|
||||||
|
if(vcount[j] == 1) m_vertexinfo[vids[j]].border = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Mesh>
|
||||||
|
void SimplifiableMesh<Mesh>::update_triangles(size_t i0,
|
||||||
|
VertexInfo & vi,
|
||||||
|
std::vector<bool> &deleted,
|
||||||
|
int &deleted_triangles)
|
||||||
|
{
|
||||||
|
Vertex p;
|
||||||
|
for (size_t k = 0; k < vi.tcount; ++k) {
|
||||||
|
assert(vi.tstart + k < m_refs.size());
|
||||||
|
|
||||||
|
Ref &r = m_refs[vi.tstart + k];
|
||||||
|
FaceInfo &fi = m_faceinfo[r.face];
|
||||||
|
|
||||||
|
if (fi.deleted) continue;
|
||||||
|
|
||||||
|
if (deleted[k]) {
|
||||||
|
fi.deleted = true;
|
||||||
|
deleted_triangles++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Index3 t = read_triangle(fi);
|
||||||
|
t[r.vertex] = i0;
|
||||||
|
write_triangle(fi, t);
|
||||||
|
|
||||||
|
fi.dirty = true;
|
||||||
|
fi.err[0] = calculate_error(t[0], t[1], p);
|
||||||
|
fi.err[1] = calculate_error(t[1], t[2], p);
|
||||||
|
fi.err[2] = calculate_error(t[2], t[0], p);
|
||||||
|
fi.err[3] = std::min(fi.err[0], std::min(fi.err[1], fi.err[2]));
|
||||||
|
m_refs.emplace_back(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Mesh>
|
||||||
|
bool SimplifiableMesh<Mesh>::flipped(const Vertex & p,
|
||||||
|
size_t /*i0*/,
|
||||||
|
size_t i1,
|
||||||
|
VertexInfo & v0,
|
||||||
|
VertexInfo & /*v1*/,
|
||||||
|
std::vector<bool> &deleted)
|
||||||
|
{
|
||||||
|
for (size_t k = 0; k < v0.tcount; ++k) {
|
||||||
|
size_t ridx = v0.tstart + k;
|
||||||
|
assert(ridx < m_refs.size());
|
||||||
|
|
||||||
|
FaceInfo &fi = m_faceinfo[m_refs[ridx].face];
|
||||||
|
if (fi.deleted) continue;
|
||||||
|
|
||||||
|
Index3 t = read_triangle(fi);
|
||||||
|
int s = m_refs[ridx].vertex;
|
||||||
|
size_t id1 = t[(s+1) % 3];
|
||||||
|
size_t id2 = t[(s+2) % 3];
|
||||||
|
|
||||||
|
if(id1 == i1 || id2 == i1) // delete ?
|
||||||
|
{
|
||||||
|
deleted[k] = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vertex d1 = read_vertex(id1) - p;
|
||||||
|
normalize(d1);
|
||||||
|
Vertex d2 = read_vertex(id2) - p;
|
||||||
|
normalize(d2);
|
||||||
|
|
||||||
|
if (std::abs(dot(d1, d2)) > 0.999) return true;
|
||||||
|
|
||||||
|
Vertex n = cross(d1, d2);
|
||||||
|
normalize(n);
|
||||||
|
|
||||||
|
deleted[k] = false;
|
||||||
|
if (dot(n, fi.n) < 0.2) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<class Mesh>
|
||||||
|
void SimplifiableMesh<Mesh>::simplify_mesh_lossless()
|
||||||
|
{
|
||||||
|
// init
|
||||||
|
for (FaceInfo &fi : m_faceinfo) fi.deleted = false;
|
||||||
|
|
||||||
|
// main iteration loop
|
||||||
|
int deleted_triangles=0;
|
||||||
|
std::vector<bool> deleted0, deleted1;
|
||||||
|
|
||||||
|
for (int iteration = 0; iteration < 9999; iteration ++) {
|
||||||
|
// update mesh constantly
|
||||||
|
update_mesh(iteration);
|
||||||
|
|
||||||
|
// clear dirty flag
|
||||||
|
for (FaceInfo &fi : m_faceinfo) fi.dirty = false;
|
||||||
|
|
||||||
|
//
|
||||||
|
// All triangles with edges below the threshold will be removed
|
||||||
|
//
|
||||||
|
// The following numbers works well for most models.
|
||||||
|
// If it does not, try to adjust the 3 parameters
|
||||||
|
//
|
||||||
|
double threshold = std::numeric_limits<double>::epsilon(); //1.0E-3 EPS; // Really? (tm)
|
||||||
|
|
||||||
|
dout() << "lossless iteration " << iteration << "\n";
|
||||||
|
|
||||||
|
for (FaceInfo &fi : m_faceinfo) {
|
||||||
|
if (fi.err[3] > threshold || fi.deleted || fi.dirty) continue;
|
||||||
|
|
||||||
|
for (size_t j = 0; j < 3; ++j) {
|
||||||
|
if (fi.err[j] > threshold) continue;
|
||||||
|
|
||||||
|
Index3 t = read_triangle(fi);
|
||||||
|
size_t i0 = t[j];
|
||||||
|
VertexInfo &v0 = m_vertexinfo[i0];
|
||||||
|
|
||||||
|
size_t i1 = t[(j + 1) % 3];
|
||||||
|
VertexInfo &v1 = m_vertexinfo[i1];
|
||||||
|
|
||||||
|
// Border check
|
||||||
|
if(v0.border != v1.border) continue;
|
||||||
|
|
||||||
|
// Compute vertex to collapse to
|
||||||
|
Vertex p;
|
||||||
|
calculate_error(i0, i1, p);
|
||||||
|
|
||||||
|
deleted0.resize(v0.tcount); // normals temporarily
|
||||||
|
deleted1.resize(v1.tcount); // normals temporarily
|
||||||
|
|
||||||
|
// don't remove if flipped
|
||||||
|
if (flipped(p, i0, i1, v0, v1, deleted0)) continue;
|
||||||
|
if (flipped(p, i1, i0, v1, v0, deleted1)) continue;
|
||||||
|
|
||||||
|
// not flipped, so remove edge
|
||||||
|
write_vertex(v0, p);
|
||||||
|
v0.q = v1.q + v0.q;
|
||||||
|
size_t tstart = m_refs.size();
|
||||||
|
|
||||||
|
update_triangles(i0, v0, deleted0, deleted_triangles);
|
||||||
|
update_triangles(i0, v1, deleted1, deleted_triangles);
|
||||||
|
|
||||||
|
assert(m_refs.size() >= tstart);
|
||||||
|
|
||||||
|
size_t tcount = m_refs.size() - tstart;
|
||||||
|
|
||||||
|
if(tcount <= v0.tcount)
|
||||||
|
{
|
||||||
|
// save ram
|
||||||
|
if (tcount) {
|
||||||
|
auto from = m_refs.begin() + tstart, to = from + tcount;
|
||||||
|
std::copy(from, to, m_refs.begin() + v0.tstart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// append
|
||||||
|
v0.tstart = tstart;
|
||||||
|
|
||||||
|
v0.tcount = tcount;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deleted_triangles <= 0) break;
|
||||||
|
deleted_triangles = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
compact();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace implementation
|
||||||
|
} // namespace SimplifyMesh
|
||||||
|
|
||||||
|
#endif // SIMPLIFYMESHIMPL_HPP
|
||||||
|
|
@ -70,6 +70,34 @@ TriangleMesh::TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd>& f
|
||||||
stl_get_size(&stl);
|
stl_get_size(&stl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TriangleMesh::TriangleMesh(const indexed_triangle_set &M)
|
||||||
|
{
|
||||||
|
stl.stats.type = inmemory;
|
||||||
|
|
||||||
|
// count facets and allocate memory
|
||||||
|
stl.stats.number_of_facets = uint32_t(M.indices.size());
|
||||||
|
stl.stats.original_num_facets = int(stl.stats.number_of_facets);
|
||||||
|
stl_allocate(&stl);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < stl.stats.number_of_facets; ++ i) {
|
||||||
|
stl_facet facet;
|
||||||
|
facet.vertex[0] = M.vertices[size_t(M.indices[i](0))];
|
||||||
|
facet.vertex[1] = M.vertices[size_t(M.indices[i](1))];
|
||||||
|
facet.vertex[2] = M.vertices[size_t(M.indices[i](2))];
|
||||||
|
facet.extra[0] = 0;
|
||||||
|
facet.extra[1] = 0;
|
||||||
|
|
||||||
|
stl_normal normal;
|
||||||
|
stl_calculate_normal(normal, &facet);
|
||||||
|
stl_normalize_vector(normal);
|
||||||
|
facet.normal = normal;
|
||||||
|
|
||||||
|
stl.facet_start[i] = facet;
|
||||||
|
}
|
||||||
|
|
||||||
|
stl_get_size(&stl);
|
||||||
|
}
|
||||||
|
|
||||||
// #define SLIC3R_TRACE_REPAIR
|
// #define SLIC3R_TRACE_REPAIR
|
||||||
|
|
||||||
void TriangleMesh::repair(bool update_shared_vertices)
|
void TriangleMesh::repair(bool update_shared_vertices)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ class TriangleMesh
|
||||||
public:
|
public:
|
||||||
TriangleMesh() : repaired(false) {}
|
TriangleMesh() : repaired(false) {}
|
||||||
TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd> &facets);
|
TriangleMesh(const Pointf3s &points, const std::vector<Vec3crd> &facets);
|
||||||
|
explicit TriangleMesh(const indexed_triangle_set &M);
|
||||||
void clear() { this->stl.clear(); this->its.clear(); this->repaired = false; }
|
void clear() { this->stl.clear(); this->its.clear(); this->repaired = false; }
|
||||||
bool ReadSTLFile(const char* input_file) { return stl_open(&stl, input_file); }
|
bool ReadSTLFile(const char* input_file) { return stl_open(&stl, input_file); }
|
||||||
bool write_ascii(const char* output_file) { return stl_write_ascii(&this->stl, output_file, ""); }
|
bool write_ascii(const char* output_file) { return stl_write_ascii(&this->stl, output_file, ""); }
|
||||||
|
|
|
||||||
|
|
@ -230,6 +230,22 @@ static inline bool is_approx(Number value, Number test_value)
|
||||||
return std::fabs(double(value) - double(test_value)) < double(EPSILON);
|
return std::fabs(double(value) - double(test_value)) < double(EPSILON);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class...Args>
|
||||||
|
std::string string_printf(const char *const fmt, Args &&...args)
|
||||||
|
{
|
||||||
|
static const size_t INITIAL_LEN = 1024;
|
||||||
|
std::vector<char> buffer(INITIAL_LEN, '\0');
|
||||||
|
|
||||||
|
int bufflen = snprintf(buffer.data(), INITIAL_LEN - 1, fmt, std::forward<Args>(args)...);
|
||||||
|
|
||||||
|
if (bufflen >= int(INITIAL_LEN)) {
|
||||||
|
buffer.resize(size_t(bufflen) + 1);
|
||||||
|
snprintf(buffer.data(), buffer.size(), fmt, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::string(buffer.begin(), buffer.begin() + bufflen);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,8 @@ set(SLIC3R_GUI_SOURCES
|
||||||
GUI/Gizmos/GLGizmoFlatten.hpp
|
GUI/Gizmos/GLGizmoFlatten.hpp
|
||||||
GUI/Gizmos/GLGizmoCut.cpp
|
GUI/Gizmos/GLGizmoCut.cpp
|
||||||
GUI/Gizmos/GLGizmoCut.hpp
|
GUI/Gizmos/GLGizmoCut.hpp
|
||||||
|
GUI/Gizmos/GLGizmoHollow.cpp
|
||||||
|
GUI/Gizmos/GLGizmoHollow.hpp
|
||||||
GUI/GLSelectionRectangle.cpp
|
GUI/GLSelectionRectangle.cpp
|
||||||
GUI/GLSelectionRectangle.hpp
|
GUI/GLSelectionRectangle.hpp
|
||||||
GUI/GLTexture.hpp
|
GUI/GLTexture.hpp
|
||||||
|
|
@ -140,6 +142,7 @@ set(SLIC3R_GUI_SOURCES
|
||||||
GUI/ProgressStatusBar.cpp
|
GUI/ProgressStatusBar.cpp
|
||||||
GUI/PrintHostDialogs.cpp
|
GUI/PrintHostDialogs.cpp
|
||||||
GUI/PrintHostDialogs.hpp
|
GUI/PrintHostDialogs.hpp
|
||||||
|
GUI/Job.hpp
|
||||||
GUI/Mouse3DController.cpp
|
GUI/Mouse3DController.cpp
|
||||||
GUI/Mouse3DController.hpp
|
GUI/Mouse3DController.hpp
|
||||||
GUI/DoubleSlider.cpp
|
GUI/DoubleSlider.cpp
|
||||||
|
|
|
||||||
|
|
@ -1438,14 +1438,14 @@ int GLCanvas3D::check_volumes_outside_state() const
|
||||||
|
|
||||||
void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo, int instance_idx)
|
void GLCanvas3D::toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo, int instance_idx)
|
||||||
{
|
{
|
||||||
|
m_render_sla_auxiliaries = visible;
|
||||||
|
|
||||||
for (GLVolume* vol : m_volumes.volumes) {
|
for (GLVolume* vol : m_volumes.volumes) {
|
||||||
if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
|
if ((mo == nullptr || m_model->objects[vol->composite_id.object_id] == mo)
|
||||||
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)
|
&& (instance_idx == -1 || vol->composite_id.instance_id == instance_idx)
|
||||||
&& vol->composite_id.volume_id < 0)
|
&& vol->composite_id.volume_id < 0)
|
||||||
vol->is_active = visible;
|
vol->is_active = visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_render_sla_auxiliaries = visible;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx)
|
void GLCanvas3D::toggle_model_objects_visibility(bool visible, const ModelObject* mo, int instance_idx)
|
||||||
|
|
@ -6061,6 +6061,8 @@ void GLCanvas3D::_load_sla_shells()
|
||||||
unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size();
|
unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size();
|
||||||
for (const SLAPrintObject::Instance& instance : obj->instances()) {
|
for (const SLAPrintObject::Instance& instance : obj->instances()) {
|
||||||
add_volume(*obj, 0, instance, obj->transformed_mesh(), GLVolume::MODEL_COLOR[0], true);
|
add_volume(*obj, 0, instance, obj->transformed_mesh(), GLVolume::MODEL_COLOR[0], true);
|
||||||
|
if (! obj->hollowed_interior_mesh().empty())
|
||||||
|
add_volume(*obj, -int(slaposHollowing), instance, obj->hollowed_interior_mesh(), GLVolume::MODEL_COLOR[0], false);
|
||||||
// Set the extruder_id and volume_id to achieve the same color as in the 3D scene when
|
// Set the extruder_id and volume_id to achieve the same color as in the 3D scene when
|
||||||
// through the update_volumes_colors_by_extruder() call.
|
// through the update_volumes_colors_by_extruder() call.
|
||||||
m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id();
|
m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id();
|
||||||
|
|
|
||||||
|
|
@ -499,6 +499,7 @@ public:
|
||||||
void set_color_by(const std::string& value);
|
void set_color_by(const std::string& value);
|
||||||
|
|
||||||
const Camera& get_camera() const { return m_camera; }
|
const Camera& get_camera() const { return m_camera; }
|
||||||
|
const Shader& get_shader() const { return m_shader; }
|
||||||
Camera& get_camera() { return m_camera; }
|
Camera& get_camera() { return m_camera; }
|
||||||
|
|
||||||
BoundingBoxf3 volumes_bounding_box() const;
|
BoundingBoxf3 volumes_bounding_box() const;
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,7 @@ ObjectList::ObjectList(wxWindow* parent) :
|
||||||
// ptSLA
|
// ptSLA
|
||||||
CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(nullptr, "support"/*"sla_supports"*/);
|
CATEGORY_ICON[L("Supports")] = create_scaled_bitmap(nullptr, "support"/*"sla_supports"*/);
|
||||||
CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(nullptr, "pad");
|
CATEGORY_ICON[L("Pad")] = create_scaled_bitmap(nullptr, "pad");
|
||||||
|
CATEGORY_ICON[L("Hollowing")] = create_scaled_bitmap(nullptr, "hollowing");
|
||||||
}
|
}
|
||||||
|
|
||||||
// create control
|
// create control
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ void GLGizmoBase::Grabber::render_face(float half_size) const
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
|
GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, CommonGizmosData* common_data_ptr)
|
||||||
: m_parent(parent)
|
: m_parent(parent)
|
||||||
, m_group_id(-1)
|
, m_group_id(-1)
|
||||||
, m_state(Off)
|
, m_state(Off)
|
||||||
|
|
@ -146,6 +146,7 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, u
|
||||||
, m_dragging(false)
|
, m_dragging(false)
|
||||||
, m_imgui(wxGetApp().imgui())
|
, m_imgui(wxGetApp().imgui())
|
||||||
, m_first_input_window_render(true)
|
, m_first_input_window_render(true)
|
||||||
|
, m_c(common_data_ptr)
|
||||||
{
|
{
|
||||||
::memcpy((void*)m_base_color, (const void*)DEFAULT_BASE_COLOR, 4 * sizeof(float));
|
::memcpy((void*)m_base_color, (const void*)DEFAULT_BASE_COLOR, 4 * sizeof(float));
|
||||||
::memcpy((void*)m_drag_color, (const void*)DEFAULT_DRAG_COLOR, 4 * sizeof(float));
|
::memcpy((void*)m_drag_color, (const void*)DEFAULT_DRAG_COLOR, 4 * sizeof(float));
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ static const float CONSTRAINED_COLOR[4] = { 0.5f, 0.5f, 0.5f, 1.0f };
|
||||||
|
|
||||||
|
|
||||||
class ImGuiWrapper;
|
class ImGuiWrapper;
|
||||||
|
class CommonGizmosData;
|
||||||
|
|
||||||
class GLGizmoBase
|
class GLGizmoBase
|
||||||
{
|
{
|
||||||
|
|
@ -99,9 +99,13 @@ protected:
|
||||||
mutable std::vector<Grabber> m_grabbers;
|
mutable std::vector<Grabber> m_grabbers;
|
||||||
ImGuiWrapper* m_imgui;
|
ImGuiWrapper* m_imgui;
|
||||||
bool m_first_input_window_render;
|
bool m_first_input_window_render;
|
||||||
|
CommonGizmosData* m_c = nullptr;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GLGizmoBase(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
GLGizmoBase(GLCanvas3D& parent,
|
||||||
|
const std::string& icon_filename,
|
||||||
|
unsigned int sprite_id,
|
||||||
|
CommonGizmosData* common_data = nullptr);
|
||||||
virtual ~GLGizmoBase() {}
|
virtual ~GLGizmoBase() {}
|
||||||
|
|
||||||
bool init() { return on_init(); }
|
bool init() { return on_init(); }
|
||||||
|
|
@ -179,6 +183,34 @@ protected:
|
||||||
// were not interpolated by alpha blending or multi sampling.
|
// were not interpolated by alpha blending or multi sampling.
|
||||||
extern unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue);
|
extern unsigned char picking_checksum_alpha_channel(unsigned char red, unsigned char green, unsigned char blue);
|
||||||
|
|
||||||
|
class MeshRaycaster;
|
||||||
|
class MeshClipper;
|
||||||
|
|
||||||
|
class CommonGizmosData {
|
||||||
|
public:
|
||||||
|
const TriangleMesh* mesh() const {
|
||||||
|
return (! m_mesh ? nullptr : (m_cavity_mesh ? m_cavity_mesh.get() : m_mesh));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ModelObject* m_model_object = nullptr;
|
||||||
|
const TriangleMesh* m_mesh;
|
||||||
|
std::unique_ptr<MeshRaycaster> m_mesh_raycaster;
|
||||||
|
std::unique_ptr<MeshClipper> m_object_clipper;
|
||||||
|
std::unique_ptr<MeshClipper> m_supports_clipper;
|
||||||
|
|
||||||
|
std::unique_ptr<TriangleMesh> m_cavity_mesh;
|
||||||
|
std::unique_ptr<GLVolume> m_volume_with_cavity;
|
||||||
|
|
||||||
|
int m_active_instance = -1;
|
||||||
|
float m_active_instance_bb_radius = 0;
|
||||||
|
ObjectID m_model_object_id = 0;
|
||||||
|
int m_print_object_idx = -1;
|
||||||
|
int m_print_objects_count = -1;
|
||||||
|
int m_old_timestamp = -1;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace GUI
|
} // namespace GUI
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
|
|
||||||
1140
src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp
Normal file
1140
src/slic3r/GUI/Gizmos/GLGizmoHollow.cpp
Normal file
File diff suppressed because it is too large
Load diff
131
src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp
Normal file
131
src/slic3r/GUI/Gizmos/GLGizmoHollow.hpp
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
#ifndef slic3r_GLGizmoHollow_hpp_
|
||||||
|
#define slic3r_GLGizmoHollow_hpp_
|
||||||
|
|
||||||
|
#include "GLGizmoBase.hpp"
|
||||||
|
#include "slic3r/GUI/GLSelectionRectangle.hpp"
|
||||||
|
|
||||||
|
#include <libslic3r/SLA/Hollowing.hpp>
|
||||||
|
#include <wx/dialog.h>
|
||||||
|
|
||||||
|
#include <cereal/types/vector.hpp>
|
||||||
|
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace GUI {
|
||||||
|
|
||||||
|
class ClippingPlane;
|
||||||
|
class MeshClipper;
|
||||||
|
class MeshRaycaster;
|
||||||
|
enum class SLAGizmoEventType : unsigned char;
|
||||||
|
|
||||||
|
class GLGizmoHollow : public GLGizmoBase
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
mutable double m_z_shift = 0.;
|
||||||
|
bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal);
|
||||||
|
|
||||||
|
const float HoleStickOutLength = 1.f;
|
||||||
|
|
||||||
|
GLUquadricObj* m_quadric;
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
GLGizmoHollow(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, CommonGizmosData* cd);
|
||||||
|
~GLGizmoHollow() override;
|
||||||
|
void set_sla_support_data(ModelObject* model_object, const Selection& selection);
|
||||||
|
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||||
|
void delete_selected_points();
|
||||||
|
ClippingPlane get_sla_clipping_plane() const;
|
||||||
|
|
||||||
|
|
||||||
|
std::pair<const TriangleMesh *, sla::HollowingConfig> get_hollowing_parameters() const;
|
||||||
|
void update_mesh_raycaster(std::unique_ptr<MeshRaycaster> &&rc);
|
||||||
|
void update_hollowed_mesh(std::unique_ptr<TriangleMesh> &&mesh);
|
||||||
|
|
||||||
|
bool is_selection_rectangle_dragging() const { return m_selection_rectangle.is_dragging(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool on_init() override;
|
||||||
|
void on_update(const UpdateData& data) override;
|
||||||
|
void on_render() const override;
|
||||||
|
void on_render_for_picking() const override;
|
||||||
|
|
||||||
|
void render_points(const Selection& selection, bool picking = false) const;
|
||||||
|
void render_clipping_plane(const Selection& selection) const;
|
||||||
|
void render_hollowed_mesh() const;
|
||||||
|
bool is_mesh_update_necessary() const;
|
||||||
|
void update_mesh();
|
||||||
|
void hollow_mesh();
|
||||||
|
bool unsaved_changes() const;
|
||||||
|
|
||||||
|
bool m_show_supports = true;
|
||||||
|
float m_new_hole_radius = 2.f; // Size of a new hole.
|
||||||
|
float m_new_hole_height = 5.f;
|
||||||
|
mutable std::vector<bool> m_selected; // which holes are currently selected
|
||||||
|
|
||||||
|
bool m_enable_hollowing = true;
|
||||||
|
|
||||||
|
// Stashes to keep data for undo redo. Is taken after the editing
|
||||||
|
// is done, the data are updated continuously.
|
||||||
|
float m_offset_stash = 3.0f;
|
||||||
|
float m_quality_stash = 0.5f;
|
||||||
|
float m_closing_d_stash = 2.f;
|
||||||
|
Vec3f m_hole_before_drag = Vec3f::Zero();
|
||||||
|
|
||||||
|
|
||||||
|
sla::DrainHoles m_holes_stash;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
float m_clipping_plane_distance = 0.f;
|
||||||
|
std::unique_ptr<ClippingPlane> m_clipping_plane;
|
||||||
|
|
||||||
|
// This map holds all translated description texts, so they can be easily referenced during layout calculations
|
||||||
|
// etc. When language changes, GUI is recreated and this class constructed again, so the change takes effect.
|
||||||
|
std::map<std::string, wxString> m_desc;
|
||||||
|
|
||||||
|
GLSelectionRectangle m_selection_rectangle;
|
||||||
|
|
||||||
|
bool m_wait_for_up_event = false;
|
||||||
|
bool m_selection_empty = true;
|
||||||
|
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
|
||||||
|
|
||||||
|
std::vector<std::pair<const ConfigOption*, const ConfigOptionDef*>> get_config_options(const std::vector<std::string>& keys) const;
|
||||||
|
bool is_mesh_point_clipped(const Vec3d& point) const;
|
||||||
|
|
||||||
|
// Methods that do the model_object and editing cache synchronization,
|
||||||
|
// editing mode selection, etc:
|
||||||
|
enum {
|
||||||
|
AllPoints = -2,
|
||||||
|
NoPoints,
|
||||||
|
};
|
||||||
|
void select_point(int i);
|
||||||
|
void unselect_point(int i);
|
||||||
|
void reload_cache();
|
||||||
|
void update_clipping_plane(bool keep_normal = false) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void on_set_state() override;
|
||||||
|
void on_set_hover_id() override
|
||||||
|
|
||||||
|
{
|
||||||
|
if (int(m_c->m_model_object->sla_drain_holes.size()) <= m_hover_id)
|
||||||
|
m_hover_id = -1;
|
||||||
|
}
|
||||||
|
void on_start_dragging() override;
|
||||||
|
void on_stop_dragging() override;
|
||||||
|
void on_render_input_window(float x, float y, float bottom_limit) override;
|
||||||
|
|
||||||
|
std::string on_get_name() const override;
|
||||||
|
bool on_is_activable() const override;
|
||||||
|
bool on_is_selectable() const override;
|
||||||
|
void on_load(cereal::BinaryInputArchive& ar) override;
|
||||||
|
void on_save(cereal::BinaryOutputArchive& ar) const override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace GUI
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif // slic3r_GLGizmoHollow_hpp_
|
||||||
|
|
@ -22,8 +22,8 @@
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
namespace GUI {
|
namespace GUI {
|
||||||
|
|
||||||
GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
|
GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, CommonGizmosData* cd)
|
||||||
: GLGizmoBase(parent, icon_filename, sprite_id)
|
: GLGizmoBase(parent, icon_filename, sprite_id, cd)
|
||||||
, m_quadric(nullptr)
|
, m_quadric(nullptr)
|
||||||
, m_its(nullptr)
|
, m_its(nullptr)
|
||||||
{
|
{
|
||||||
|
|
@ -64,23 +64,35 @@ bool GLGizmoSlaSupports::on_init()
|
||||||
void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const Selection& selection)
|
void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const Selection& selection)
|
||||||
{
|
{
|
||||||
if (! model_object || selection.is_empty()) {
|
if (! model_object || selection.is_empty()) {
|
||||||
m_model_object = nullptr;
|
m_c->m_model_object = nullptr;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_model_object != model_object || m_model_object_id != model_object->id()) {
|
bool something_changed = false;
|
||||||
m_model_object = model_object;
|
|
||||||
m_print_object_idx = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_active_instance = selection.get_instance_idx();
|
if (m_c->m_model_object != model_object
|
||||||
|
|| m_c->m_model_object_id != model_object->id()
|
||||||
|
|| m_c->m_active_instance != selection.get_instance_idx()) {
|
||||||
|
m_c->m_model_object = model_object;
|
||||||
|
m_c->m_print_object_idx = -1;
|
||||||
|
m_c->m_active_instance = selection.get_instance_idx();
|
||||||
|
something_changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (model_object && selection.is_from_single_instance())
|
if (model_object && selection.is_from_single_instance())
|
||||||
{
|
{
|
||||||
// Cache the bb - it's needed for dealing with the clipping plane quite often
|
// Cache the bb - it's needed for dealing with the clipping plane quite often
|
||||||
// It could be done inside update_mesh but one has to account for scaling of the instance.
|
// It could be done inside update_mesh but one has to account for scaling of the instance.
|
||||||
//FIXME calling ModelObject::instance_bounding_box() is expensive!
|
if (something_changed) {
|
||||||
m_active_instance_bb_radius = m_model_object->instance_bounding_box(m_active_instance).radius();
|
m_c->m_active_instance_bb_radius = m_c->m_model_object->instance_bounding_box(m_c->m_active_instance).radius();
|
||||||
|
if (m_state == On) {
|
||||||
|
m_parent.toggle_model_objects_visibility(false);
|
||||||
|
m_parent.toggle_model_objects_visibility(! m_c->m_cavity_mesh, m_c->m_model_object, m_c->m_active_instance);
|
||||||
|
m_parent.toggle_sla_auxiliaries_visibility(! m_editing_mode, m_c->m_model_object, m_c->m_active_instance);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_parent.toggle_model_objects_visibility(true, nullptr, -1);
|
||||||
|
}
|
||||||
|
|
||||||
if (is_mesh_update_necessary()) {
|
if (is_mesh_update_necessary()) {
|
||||||
update_mesh();
|
update_mesh();
|
||||||
|
|
@ -88,15 +100,8 @@ void GLGizmoSlaSupports::set_sla_support_data(ModelObject* model_object, const S
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we triggered autogeneration before, check backend and fetch results if they are there
|
// If we triggered autogeneration before, check backend and fetch results if they are there
|
||||||
if (m_model_object->sla_points_status == sla::PointsStatus::Generating)
|
if (m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating)
|
||||||
get_data_from_backend();
|
get_data_from_backend();
|
||||||
|
|
||||||
if (m_state == On) {
|
|
||||||
m_parent.toggle_model_objects_visibility(false);
|
|
||||||
m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
m_parent.toggle_model_objects_visibility(true, nullptr, -1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,16 +111,16 @@ void GLGizmoSlaSupports::on_render() const
|
||||||
{
|
{
|
||||||
const Selection& selection = m_parent.get_selection();
|
const Selection& selection = m_parent.get_selection();
|
||||||
|
|
||||||
// If current m_model_object does not match selection, ask GLCanvas3D to turn us off
|
// If current m_c->m_model_object does not match selection, ask GLCanvas3D to turn us off
|
||||||
if (m_state == On
|
if (m_state == On
|
||||||
&& (m_model_object != selection.get_model()->objects[selection.get_object_idx()]
|
&& (m_c->m_model_object != selection.get_model()->objects[selection.get_object_idx()]
|
||||||
|| m_active_instance != selection.get_instance_idx()
|
|| m_c->m_active_instance != selection.get_instance_idx()
|
||||||
|| m_model_object_id != m_model_object->id())) {
|
|| m_c->m_model_object_id != m_c->m_model_object->id())) {
|
||||||
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS));
|
m_parent.post_event(SimpleEvent(EVT_GLCANVAS_RESETGIZMOS));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! m_its || ! m_mesh)
|
if (! m_its || ! m_c->m_mesh)
|
||||||
const_cast<GLGizmoSlaSupports*>(this)->update_mesh();
|
const_cast<GLGizmoSlaSupports*>(this)->update_mesh();
|
||||||
|
|
||||||
glsafe(::glEnable(GL_BLEND));
|
glsafe(::glEnable(GL_BLEND));
|
||||||
|
|
@ -123,6 +128,8 @@ void GLGizmoSlaSupports::on_render() const
|
||||||
|
|
||||||
m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z();
|
m_z_shift = selection.get_volume(*selection.get_volume_idxs().begin())->get_sla_shift_z();
|
||||||
|
|
||||||
|
render_hollowed_mesh();
|
||||||
|
|
||||||
if (m_quadric != nullptr && selection.is_from_single_instance())
|
if (m_quadric != nullptr && selection.is_from_single_instance())
|
||||||
render_points(selection, false);
|
render_points(selection, false);
|
||||||
|
|
||||||
|
|
@ -134,9 +141,32 @@ void GLGizmoSlaSupports::on_render() const
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void GLGizmoSlaSupports::render_hollowed_mesh() const
|
||||||
|
{
|
||||||
|
if (m_c->m_volume_with_cavity) {
|
||||||
|
m_c->m_volume_with_cavity->set_sla_shift_z(m_z_shift);
|
||||||
|
m_parent.get_shader().start_using();
|
||||||
|
|
||||||
|
GLint current_program_id;
|
||||||
|
glsafe(::glGetIntegerv(GL_CURRENT_PROGRAM, ¤t_program_id));
|
||||||
|
GLint color_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "uniform_color") : -1;
|
||||||
|
GLint print_box_detection_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.volume_detection") : -1;
|
||||||
|
GLint print_box_worldmatrix_id = (current_program_id > 0) ? ::glGetUniformLocation(current_program_id, "print_box.volume_world_matrix") : -1;
|
||||||
|
glcheck();
|
||||||
|
m_c->m_volume_with_cavity->set_render_color();
|
||||||
|
const Geometry::Transformation& volume_trafo = m_c->m_model_object->volumes.front()->get_transformation();
|
||||||
|
m_c->m_volume_with_cavity->set_volume_transformation(volume_trafo);
|
||||||
|
m_c->m_volume_with_cavity->set_instance_transformation(m_c->m_model_object->instances[size_t(m_c->m_active_instance)]->get_transformation());
|
||||||
|
m_c->m_volume_with_cavity->render(color_id, print_box_detection_id, print_box_worldmatrix_id);
|
||||||
|
m_parent.get_shader().stop_using();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const
|
void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const
|
||||||
{
|
{
|
||||||
if (m_clipping_plane_distance == 0.f)
|
if (m_clipping_plane_distance == 0.f || m_c->m_mesh->empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Get transformation of the instance
|
// Get transformation of the instance
|
||||||
|
|
@ -154,66 +184,66 @@ void GLGizmoSlaSupports::render_clipping_plane(const Selection& selection) const
|
||||||
1.));
|
1.));
|
||||||
|
|
||||||
// Now initialize the TMS for the object, perform the cut and save the result.
|
// Now initialize the TMS for the object, perform the cut and save the result.
|
||||||
if (! m_object_clipper) {
|
if (! m_c->m_object_clipper) {
|
||||||
m_object_clipper.reset(new MeshClipper);
|
m_c->m_object_clipper.reset(new MeshClipper);
|
||||||
m_object_clipper->set_mesh(*m_mesh);
|
m_c->m_object_clipper->set_mesh(*m_c->mesh());
|
||||||
}
|
}
|
||||||
m_object_clipper->set_plane(*m_clipping_plane);
|
m_c->m_object_clipper->set_plane(*m_clipping_plane);
|
||||||
m_object_clipper->set_transformation(trafo);
|
m_c->m_object_clipper->set_transformation(trafo);
|
||||||
|
|
||||||
|
|
||||||
// Next, ask the backend if supports are already calculated. If so, we are gonna cut them too.
|
// Next, ask the backend if supports are already calculated. If so, we are gonna cut them too.
|
||||||
// First we need a pointer to the respective SLAPrintObject. The index into objects vector is
|
// First we need a pointer to the respective SLAPrintObject. The index into objects vector is
|
||||||
// cached so we don't have todo it on each render. We only search for the po if needed:
|
// cached so we don't have todo it on each render. We only search for the po if needed:
|
||||||
if (m_print_object_idx < 0 || (int)m_parent.sla_print()->objects().size() != m_print_objects_count) {
|
if (m_c->m_print_object_idx < 0 || (int)m_parent.sla_print()->objects().size() != m_c->m_print_objects_count) {
|
||||||
m_print_objects_count = m_parent.sla_print()->objects().size();
|
m_c->m_print_objects_count = m_parent.sla_print()->objects().size();
|
||||||
m_print_object_idx = -1;
|
m_c->m_print_object_idx = -1;
|
||||||
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
|
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
|
||||||
++m_print_object_idx;
|
++m_c->m_print_object_idx;
|
||||||
if (po->model_object()->id() == m_model_object->id())
|
if (po->model_object()->id() == m_c->m_model_object->id())
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (m_print_object_idx >= 0) {
|
if (m_c->m_print_object_idx >= 0) {
|
||||||
const SLAPrintObject* print_object = m_parent.sla_print()->objects()[m_print_object_idx];
|
const SLAPrintObject* print_object = m_parent.sla_print()->objects()[m_c->m_print_object_idx];
|
||||||
|
|
||||||
if (print_object->is_step_done(slaposSupportTree)) {
|
if (print_object->is_step_done(slaposSupportTree) && !print_object->get_mesh(slaposSupportTree).empty()) {
|
||||||
// If the supports are already calculated, save the timestamp of the respective step
|
// If the supports are already calculated, save the timestamp of the respective step
|
||||||
// so we can later tell they were recalculated.
|
// so we can later tell they were recalculated.
|
||||||
size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp;
|
size_t timestamp = print_object->step_state_with_timestamp(slaposSupportTree).timestamp;
|
||||||
|
|
||||||
if (! m_supports_clipper || (int)timestamp != m_old_timestamp) {
|
if (! m_c->m_supports_clipper || (int)timestamp != m_c->m_old_timestamp) {
|
||||||
// The timestamp has changed.
|
// The timestamp has changed.
|
||||||
m_supports_clipper.reset(new MeshClipper);
|
m_c->m_supports_clipper.reset(new MeshClipper);
|
||||||
// The mesh should already have the shared vertices calculated.
|
// The mesh should already have the shared vertices calculated.
|
||||||
m_supports_clipper->set_mesh(print_object->support_mesh());
|
m_c->m_supports_clipper->set_mesh(print_object->support_mesh());
|
||||||
m_old_timestamp = timestamp;
|
m_c->m_old_timestamp = timestamp;
|
||||||
}
|
}
|
||||||
m_supports_clipper->set_plane(*m_clipping_plane);
|
m_c->m_supports_clipper->set_plane(*m_clipping_plane);
|
||||||
m_supports_clipper->set_transformation(supports_trafo);
|
m_c->m_supports_clipper->set_transformation(supports_trafo);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
// The supports are not valid. We better dump the cached data.
|
// The supports are not valid. We better dump the cached data.
|
||||||
m_supports_clipper.reset();
|
m_c->m_supports_clipper.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point we have the triangulated cuts for both the object and supports - let's render.
|
// At this point we have the triangulated cuts for both the object and supports - let's render.
|
||||||
if (! m_object_clipper->get_triangles().empty()) {
|
if (! m_c->m_object_clipper->get_triangles().empty()) {
|
||||||
::glPushMatrix();
|
::glPushMatrix();
|
||||||
::glColor3f(1.0f, 0.37f, 0.0f);
|
::glColor3f(1.0f, 0.37f, 0.0f);
|
||||||
::glBegin(GL_TRIANGLES);
|
::glBegin(GL_TRIANGLES);
|
||||||
for (const Vec3f& point : m_object_clipper->get_triangles())
|
for (const Vec3f& point : m_c->m_object_clipper->get_triangles())
|
||||||
::glVertex3f(point(0), point(1), point(2));
|
::glVertex3f(point(0), point(1), point(2));
|
||||||
::glEnd();
|
::glEnd();
|
||||||
::glPopMatrix();
|
::glPopMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_supports_clipper && ! m_supports_clipper->get_triangles().empty() && !m_editing_mode) {
|
if (m_c->m_supports_clipper && ! m_c->m_supports_clipper->get_triangles().empty() && !m_editing_mode) {
|
||||||
// The supports are hidden in the editing mode, so it makes no sense to render the cuts.
|
// The supports are hidden in the editing mode, so it makes no sense to render the cuts.
|
||||||
::glPushMatrix();
|
::glPushMatrix();
|
||||||
::glColor3f(1.0f, 0.f, 0.37f);
|
::glColor3f(1.0f, 0.f, 0.37f);
|
||||||
::glBegin(GL_TRIANGLES);
|
::glBegin(GL_TRIANGLES);
|
||||||
for (const Vec3f& point : m_supports_clipper->get_triangles())
|
for (const Vec3f& point : m_c->m_supports_clipper->get_triangles())
|
||||||
::glVertex3f(point(0), point(1), point(2));
|
::glVertex3f(point(0), point(1), point(2));
|
||||||
::glEnd();
|
::glEnd();
|
||||||
::glPopMatrix();
|
::glPopMatrix();
|
||||||
|
|
@ -230,6 +260,7 @@ void GLGizmoSlaSupports::on_render_for_picking() const
|
||||||
|
|
||||||
glsafe(::glEnable(GL_DEPTH_TEST));
|
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||||
render_points(selection, true);
|
render_points(selection, true);
|
||||||
|
render_hollowed_mesh();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const
|
void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking) const
|
||||||
|
|
@ -298,7 +329,7 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
|
||||||
if (m_editing_mode) {
|
if (m_editing_mode) {
|
||||||
// in case the normal is not yet cached, find and cache it
|
// in case the normal is not yet cached, find and cache it
|
||||||
if (m_editing_cache[i].normal == Vec3f::Zero())
|
if (m_editing_cache[i].normal == Vec3f::Zero())
|
||||||
m_mesh_raycaster->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal);
|
m_c->m_mesh_raycaster->get_closest_point(m_editing_cache[i].support_point.pos, &m_editing_cache[i].normal);
|
||||||
|
|
||||||
Eigen::Quaterniond q;
|
Eigen::Quaterniond q;
|
||||||
q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast<double>());
|
q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * m_editing_cache[i].normal.cast<double>());
|
||||||
|
|
@ -327,6 +358,44 @@ void GLGizmoSlaSupports::render_points(const Selection& selection, bool picking)
|
||||||
glsafe(::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive));
|
glsafe(::glMaterialfv(GL_FRONT, GL_EMISSION, render_color_emissive));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now render the drain holes:
|
||||||
|
if (! m_c->m_cavity_mesh) {
|
||||||
|
render_color[0] = 0.7f;
|
||||||
|
render_color[1] = 0.7f;
|
||||||
|
render_color[2] = 0.7f;
|
||||||
|
render_color[3] = 0.7f;
|
||||||
|
glsafe(::glColor4fv(render_color));
|
||||||
|
for (const sla::DrainHole& drain_hole : m_c->m_model_object->sla_drain_holes) {
|
||||||
|
// Inverse matrix of the instance scaling is applied so that the mark does not scale with the object.
|
||||||
|
glsafe(::glPushMatrix());
|
||||||
|
glsafe(::glTranslatef(drain_hole.pos(0), drain_hole.pos(1), drain_hole.pos(2)));
|
||||||
|
glsafe(::glMultMatrixd(instance_scaling_matrix_inverse.data()));
|
||||||
|
|
||||||
|
if (vol->is_left_handed())
|
||||||
|
glFrontFace(GL_CW);
|
||||||
|
|
||||||
|
// Matrices set, we can render the point mark now.
|
||||||
|
|
||||||
|
Eigen::Quaterniond q;
|
||||||
|
q.setFromTwoVectors(Vec3d{0., 0., 1.}, instance_scaling_matrix_inverse * (-drain_hole.normal).cast<double>());
|
||||||
|
Eigen::AngleAxisd aa(q);
|
||||||
|
glsafe(::glRotated(aa.angle() * (180. / M_PI), aa.axis()(0), aa.axis()(1), aa.axis()(2)));
|
||||||
|
glsafe(::glPushMatrix());
|
||||||
|
glsafe(::glTranslated(0., 0., -drain_hole.height));
|
||||||
|
::gluCylinder(m_quadric, drain_hole.radius, drain_hole.radius, drain_hole.height, 24, 1);
|
||||||
|
glsafe(::glTranslated(0., 0., drain_hole.height));
|
||||||
|
::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1);
|
||||||
|
glsafe(::glTranslated(0., 0., -drain_hole.height));
|
||||||
|
glsafe(::glRotatef(180.f, 1.f, 0.f, 0.f));
|
||||||
|
::gluDisk(m_quadric, 0.0, drain_hole.radius, 24, 1);
|
||||||
|
glsafe(::glPopMatrix());
|
||||||
|
|
||||||
|
if (vol->is_left_handed())
|
||||||
|
glFrontFace(GL_CCW);
|
||||||
|
glsafe(::glPopMatrix());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!picking)
|
if (!picking)
|
||||||
glsafe(::glDisable(GL_LIGHTING));
|
glsafe(::glDisable(GL_LIGHTING));
|
||||||
|
|
||||||
|
|
@ -340,7 +409,7 @@ bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const
|
||||||
if (m_clipping_plane_distance == 0.f)
|
if (m_clipping_plane_distance == 0.f)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Vec3d transformed_point = m_model_object->instances.front()->get_transformation().get_matrix() * point;
|
Vec3d transformed_point = m_c->m_model_object->instances[m_c->m_active_instance]->get_transformation().get_matrix() * point;
|
||||||
transformed_point(2) += m_z_shift;
|
transformed_point(2) += m_z_shift;
|
||||||
return m_clipping_plane->is_point_clipped(transformed_point);
|
return m_clipping_plane->is_point_clipped(transformed_point);
|
||||||
}
|
}
|
||||||
|
|
@ -349,39 +418,38 @@ bool GLGizmoSlaSupports::is_mesh_point_clipped(const Vec3d& point) const
|
||||||
|
|
||||||
bool GLGizmoSlaSupports::is_mesh_update_necessary() const
|
bool GLGizmoSlaSupports::is_mesh_update_necessary() const
|
||||||
{
|
{
|
||||||
return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty())
|
return ((m_state == On) && (m_c->m_model_object != nullptr) && !m_c->m_model_object->instances.empty())
|
||||||
&& ((m_model_object->id() != m_model_object_id) || m_its == nullptr);
|
&& ((m_c->m_model_object->id() != m_c->m_model_object_id) || m_its == nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void GLGizmoSlaSupports::update_mesh()
|
void GLGizmoSlaSupports::update_mesh()
|
||||||
{
|
{
|
||||||
if (! m_model_object)
|
if (! m_c->m_model_object)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
wxBusyCursor wait;
|
wxBusyCursor wait;
|
||||||
// this way we can use that mesh directly.
|
// this way we can use that mesh directly.
|
||||||
// This mesh does not account for the possible Z up SLA offset.
|
// This mesh does not account for the possible Z up SLA offset.
|
||||||
m_mesh = &m_model_object->volumes.front()->mesh();
|
m_c->m_mesh = &m_c->m_model_object->volumes.front()->mesh();
|
||||||
m_its = &m_mesh->its;
|
m_its = &m_c->m_mesh->its;
|
||||||
|
|
||||||
// If this is different mesh than last time or if the AABB tree is uninitialized, recalculate it.
|
// If this is different mesh than last time or if the AABB tree is uninitialized, recalculate it.
|
||||||
if (m_model_object_id != m_model_object->id() || ! m_mesh_raycaster)
|
if (m_c->m_model_object_id != m_c->m_model_object->id() || ! m_c->m_mesh_raycaster)
|
||||||
m_mesh_raycaster.reset(new MeshRaycaster(*m_mesh));
|
m_c->m_mesh_raycaster.reset(new MeshRaycaster(*m_c->mesh()));
|
||||||
|
|
||||||
m_model_object_id = m_model_object->id();
|
m_c->m_model_object_id = m_c->m_model_object->id();
|
||||||
disable_editing_mode();
|
disable_editing_mode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal
|
// Unprojects the mouse position on the mesh and saves hit point and normal of the facet into pos_and_normal
|
||||||
// Return false if no intersection was found, true otherwise.
|
// Return false if no intersection was found, true otherwise.
|
||||||
bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal)
|
bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal)
|
||||||
{
|
{
|
||||||
// if the gizmo doesn't have the V, F structures for igl, calculate them first:
|
// if the gizmo doesn't have the V, F structures for igl, calculate them first:
|
||||||
if (! m_mesh_raycaster)
|
if (! m_c->m_mesh_raycaster)
|
||||||
update_mesh();
|
update_mesh();
|
||||||
|
|
||||||
const Camera& camera = m_parent.get_camera();
|
const Camera& camera = m_parent.get_camera();
|
||||||
|
|
@ -393,12 +461,27 @@ bool GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec
|
||||||
// The raycaster query
|
// The raycaster query
|
||||||
Vec3f hit;
|
Vec3f hit;
|
||||||
Vec3f normal;
|
Vec3f normal;
|
||||||
if (m_mesh_raycaster->unproject_on_mesh(mouse_pos, trafo.get_matrix(), camera, hit, normal, m_clipping_plane.get())) {
|
if (m_c->m_mesh_raycaster->unproject_on_mesh(mouse_pos, trafo.get_matrix(), camera, hit, normal, m_clipping_plane.get())) {
|
||||||
|
// Check whether the hit is in a hole
|
||||||
|
bool in_hole = false;
|
||||||
|
// In case the hollowed and drilled mesh is available, we can allow
|
||||||
|
// placing points in holes, because they should never end up
|
||||||
|
// on surface that's been drilled away.
|
||||||
|
if (! m_c->m_cavity_mesh) {
|
||||||
|
for (const sla::DrainHole& hole : m_c->m_model_object->sla_drain_holes) {
|
||||||
|
if (hole.is_inside(hit)) {
|
||||||
|
in_hole = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (! in_hole) {
|
||||||
// Return both the point and the facet normal.
|
// Return both the point and the facet normal.
|
||||||
pos_and_normal = std::make_pair(hit, normal);
|
pos_and_normal = std::make_pair(hit, normal);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -459,7 +542,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||||
GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state();
|
GLSelectionRectangle::EState rectangle_status = m_selection_rectangle.get_state();
|
||||||
|
|
||||||
// First collect positions of all the points in world coordinates.
|
// First collect positions of all the points in world coordinates.
|
||||||
Geometry::Transformation trafo = m_model_object->instances[m_active_instance]->get_transformation();
|
Geometry::Transformation trafo = m_c->m_model_object->instances[m_c->m_active_instance]->get_transformation();
|
||||||
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
|
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., m_z_shift));
|
||||||
std::vector<Vec3d> points;
|
std::vector<Vec3d> points;
|
||||||
for (unsigned int i=0; i<m_editing_cache.size(); ++i)
|
for (unsigned int i=0; i<m_editing_cache.size(); ++i)
|
||||||
|
|
@ -472,7 +555,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
||||||
points_inside.push_back(points[idx].cast<float>());
|
points_inside.push_back(points[idx].cast<float>());
|
||||||
|
|
||||||
// Only select/deselect points that are actually visible
|
// Only select/deselect points that are actually visible
|
||||||
for (size_t idx : m_mesh_raycaster->get_unobscured_idxs(trafo, m_parent.get_camera(), points_inside, m_clipping_plane.get()))
|
for (size_t idx : m_c->m_mesh_raycaster->get_unobscured_idxs(trafo, m_parent.get_camera(), points_inside, m_clipping_plane.get()))
|
||||||
{
|
{
|
||||||
if (rectangle_status == GLSelectionRectangle::Deselect)
|
if (rectangle_status == GLSelectionRectangle::Deselect)
|
||||||
unselect_point(points_idxs[idx]);
|
unselect_point(points_idxs[idx]);
|
||||||
|
|
@ -610,10 +693,10 @@ std::vector<const ConfigOption*> GLGizmoSlaSupports::get_config_options(const st
|
||||||
{
|
{
|
||||||
std::vector<const ConfigOption*> out;
|
std::vector<const ConfigOption*> out;
|
||||||
|
|
||||||
if (!m_model_object)
|
if (!m_c->m_model_object)
|
||||||
return out;
|
return out;
|
||||||
|
|
||||||
const DynamicPrintConfig& object_cfg = m_model_object->config;
|
const DynamicPrintConfig& object_cfg = m_c->m_model_object->config;
|
||||||
const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
|
const DynamicPrintConfig& print_cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
|
||||||
std::unique_ptr<DynamicPrintConfig> default_cfg = nullptr;
|
std::unique_ptr<DynamicPrintConfig> default_cfg = nullptr;
|
||||||
|
|
||||||
|
|
@ -636,7 +719,7 @@ std::vector<const ConfigOption*> GLGizmoSlaSupports::get_config_options(const st
|
||||||
|
|
||||||
ClippingPlane GLGizmoSlaSupports::get_sla_clipping_plane() const
|
ClippingPlane GLGizmoSlaSupports::get_sla_clipping_plane() const
|
||||||
{
|
{
|
||||||
if (!m_model_object || m_state == Off || m_clipping_plane_distance == 0.f)
|
if (!m_c->m_model_object || m_state == Off || m_clipping_plane_distance == 0.f)
|
||||||
return ClippingPlane::ClipsNothing();
|
return ClippingPlane::ClipsNothing();
|
||||||
else
|
else
|
||||||
return ClippingPlane(-m_clipping_plane->get_normal(), m_clipping_plane->get_data()[3]);
|
return ClippingPlane(-m_clipping_plane->get_normal(), m_clipping_plane->get_data()[3]);
|
||||||
|
|
@ -665,7 +748,7 @@ void GLGizmoSlaSupports::find_intersecting_facets(const igl::AABB<Eigen::MatrixX
|
||||||
|
|
||||||
void GLGizmoSlaSupports::make_line_segments() const
|
void GLGizmoSlaSupports::make_line_segments() const
|
||||||
{
|
{
|
||||||
TriangleMeshSlicer tms(&m_model_object->volumes.front()->mesh);
|
TriangleMeshSlicer tms(&m_c->m_model_object->volumes.front()->mesh);
|
||||||
Vec3f normal(0.f, 1.f, 1.f);
|
Vec3f normal(0.f, 1.f, 1.f);
|
||||||
double d = 0.;
|
double d = 0.;
|
||||||
|
|
||||||
|
|
@ -689,7 +772,7 @@ void GLGizmoSlaSupports::on_render_input_window(float x, float y, float bottom_l
|
||||||
static float last_y = 0.0f;
|
static float last_y = 0.0f;
|
||||||
static float last_h = 0.0f;
|
static float last_h = 0.0f;
|
||||||
|
|
||||||
if (!m_model_object)
|
if (! m_c->m_model_object)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool first_run = true; // This is a hack to redraw the button when all points are removed,
|
bool first_run = true; // This is a hack to redraw the button when all points are removed,
|
||||||
|
|
@ -728,7 +811,6 @@ RENDER_AGAIN:
|
||||||
float window_width = minimal_slider_width + std::max(std::max(settings_sliders_left, clipping_slider_left), diameter_slider_left);
|
float window_width = minimal_slider_width + std::max(std::max(settings_sliders_left, clipping_slider_left), diameter_slider_left);
|
||||||
window_width = std::max(std::max(window_width, buttons_width_approx), lock_supports_width_approx);
|
window_width = std::max(std::max(window_width, buttons_width_approx), lock_supports_width_approx);
|
||||||
|
|
||||||
|
|
||||||
bool force_refresh = false;
|
bool force_refresh = false;
|
||||||
bool remove_selected = false;
|
bool remove_selected = false;
|
||||||
bool remove_all = false;
|
bool remove_all = false;
|
||||||
|
|
@ -827,15 +909,15 @@ RENDER_AGAIN:
|
||||||
m_density_stash = density;
|
m_density_stash = density;
|
||||||
}
|
}
|
||||||
if (slider_edited) {
|
if (slider_edited) {
|
||||||
m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance;
|
m_c->m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance;
|
||||||
m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density;
|
m_c->m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density;
|
||||||
}
|
}
|
||||||
if (slider_released) {
|
if (slider_released) {
|
||||||
m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = m_minimal_point_distance_stash;
|
m_c->m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = m_minimal_point_distance_stash;
|
||||||
m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)m_density_stash;
|
m_c->m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)m_density_stash;
|
||||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Support parameter change")));
|
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Support parameter change")));
|
||||||
m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance;
|
m_c->m_model_object->config.opt<ConfigOptionFloat>("support_points_minimal_distance", true)->value = minimal_point_distance;
|
||||||
m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density;
|
m_c->m_model_object->config.opt<ConfigOptionInt>("support_points_density_relative", true)->value = (int)density;
|
||||||
wxGetApp().obj_list()->update_and_show_object_settings_item();
|
wxGetApp().obj_list()->update_and_show_object_settings_item();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -853,10 +935,10 @@ RENDER_AGAIN:
|
||||||
m_imgui->disabled_end();
|
m_imgui->disabled_end();
|
||||||
|
|
||||||
// m_imgui->text("");
|
// m_imgui->text("");
|
||||||
// m_imgui->text(m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) :
|
// m_imgui->text(m_c->m_model_object->sla_points_status == sla::PointsStatus::NoPoints ? _(L("No points (will be autogenerated)")) :
|
||||||
// (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? _(L("Autogenerated points (no modifications)")) :
|
// (m_c->m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated ? _(L("Autogenerated points (no modifications)")) :
|
||||||
// (m_model_object->sla_points_status == sla::PointsStatus::UserModified ? _(L("User-modified points")) :
|
// (m_c->m_model_object->sla_points_status == sla::PointsStatus::UserModified ? _(L("User-modified points")) :
|
||||||
// (m_model_object->sla_points_status == sla::PointsStatus::Generating ? _(L("Generation in progress...")) : "UNKNOWN STATUS"))));
|
// (m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating ? _(L("Generation in progress...")) : "UNKNOWN STATUS"))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -890,12 +972,6 @@ RENDER_AGAIN:
|
||||||
|
|
||||||
m_imgui->end();
|
m_imgui->end();
|
||||||
|
|
||||||
if (m_editing_mode != m_old_editing_state) { // user toggled between editing/non-editing mode
|
|
||||||
m_parent.toggle_sla_auxiliaries_visibility(!m_editing_mode, m_model_object, m_active_instance);
|
|
||||||
force_refresh = true;
|
|
||||||
}
|
|
||||||
m_old_editing_state = m_editing_mode;
|
|
||||||
|
|
||||||
if (remove_selected || remove_all) {
|
if (remove_selected || remove_all) {
|
||||||
force_refresh = false;
|
force_refresh = false;
|
||||||
m_parent.set_as_dirty();
|
m_parent.set_as_dirty();
|
||||||
|
|
@ -952,12 +1028,12 @@ std::string GLGizmoSlaSupports::on_get_name() const
|
||||||
|
|
||||||
void GLGizmoSlaSupports::on_set_state()
|
void GLGizmoSlaSupports::on_set_state()
|
||||||
{
|
{
|
||||||
// m_model_object pointer can be invalid (for instance because of undo/redo action),
|
// m_c->m_model_object pointer can be invalid (for instance because of undo/redo action),
|
||||||
// we should recover it from the object id
|
// we should recover it from the object id
|
||||||
m_model_object = nullptr;
|
m_c->m_model_object = nullptr;
|
||||||
for (const auto mo : wxGetApp().model().objects) {
|
for (const auto mo : wxGetApp().model().objects) {
|
||||||
if (mo->id() == m_model_object_id) {
|
if (mo->id() == m_c->m_model_object_id) {
|
||||||
m_model_object = mo;
|
m_c->m_model_object = mo;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -971,19 +1047,20 @@ void GLGizmoSlaSupports::on_set_state()
|
||||||
update_mesh();
|
update_mesh();
|
||||||
|
|
||||||
// we'll now reload support points:
|
// we'll now reload support points:
|
||||||
if (m_model_object)
|
if (m_c->m_model_object)
|
||||||
reload_cache();
|
reload_cache();
|
||||||
|
|
||||||
m_parent.toggle_model_objects_visibility(false);
|
m_parent.toggle_model_objects_visibility(false);
|
||||||
if (m_model_object)
|
if (m_c->m_model_object && ! m_c->m_cavity_mesh)
|
||||||
m_parent.toggle_model_objects_visibility(true, m_model_object, m_active_instance);
|
m_parent.toggle_model_objects_visibility(true, m_c->m_model_object, m_c->m_active_instance);
|
||||||
|
m_parent.toggle_sla_auxiliaries_visibility(! m_editing_mode, m_c->m_model_object, m_c->m_active_instance);
|
||||||
|
|
||||||
// Set default head diameter from config.
|
// Set default head diameter from config.
|
||||||
const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
|
const DynamicPrintConfig& cfg = wxGetApp().preset_bundle->sla_prints.get_edited_preset().config;
|
||||||
m_new_point_head_diameter = static_cast<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value;
|
m_new_point_head_diameter = static_cast<const ConfigOptionFloat*>(cfg.option("support_head_front_diameter"))->value;
|
||||||
}
|
}
|
||||||
if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
|
if (m_state == Off && m_old_state != Off) { // the gizmo was just turned Off
|
||||||
bool will_ask = m_model_object && m_editing_mode && unsaved_changes();
|
bool will_ask = m_c->m_model_object && m_editing_mode && unsaved_changes();
|
||||||
if (will_ask) {
|
if (will_ask) {
|
||||||
wxGetApp().CallAfter([this]() {
|
wxGetApp().CallAfter([this]() {
|
||||||
// Following is called through CallAfter, because otherwise there was a problem
|
// Following is called through CallAfter, because otherwise there was a problem
|
||||||
|
|
@ -1005,11 +1082,12 @@ void GLGizmoSlaSupports::on_set_state()
|
||||||
m_parent.toggle_model_objects_visibility(true);
|
m_parent.toggle_model_objects_visibility(true);
|
||||||
m_normal_cache.clear();
|
m_normal_cache.clear();
|
||||||
m_clipping_plane_distance = 0.f;
|
m_clipping_plane_distance = 0.f;
|
||||||
|
update_clipping_plane();
|
||||||
// Release clippers and the AABB raycaster.
|
// Release clippers and the AABB raycaster.
|
||||||
m_its = nullptr;
|
m_its = nullptr;
|
||||||
m_object_clipper.reset();
|
m_c->m_object_clipper.reset();
|
||||||
m_supports_clipper.reset();
|
m_c->m_supports_clipper.reset();
|
||||||
m_mesh_raycaster.reset();
|
m_c->m_mesh_raycaster.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_old_state = m_state;
|
m_old_state = m_state;
|
||||||
|
|
@ -1051,7 +1129,7 @@ void GLGizmoSlaSupports::on_load(cereal::BinaryInputArchive& ar)
|
||||||
{
|
{
|
||||||
ar(m_clipping_plane_distance,
|
ar(m_clipping_plane_distance,
|
||||||
*m_clipping_plane,
|
*m_clipping_plane,
|
||||||
m_model_object_id,
|
m_c->m_model_object_id,
|
||||||
m_new_point_head_diameter,
|
m_new_point_head_diameter,
|
||||||
m_normal_cache,
|
m_normal_cache,
|
||||||
m_editing_cache,
|
m_editing_cache,
|
||||||
|
|
@ -1065,7 +1143,7 @@ void GLGizmoSlaSupports::on_save(cereal::BinaryOutputArchive& ar) const
|
||||||
{
|
{
|
||||||
ar(m_clipping_plane_distance,
|
ar(m_clipping_plane_distance,
|
||||||
*m_clipping_plane,
|
*m_clipping_plane,
|
||||||
m_model_object_id,
|
m_c->m_model_object_id,
|
||||||
m_new_point_head_diameter,
|
m_new_point_head_diameter,
|
||||||
m_normal_cache,
|
m_normal_cache,
|
||||||
m_editing_cache,
|
m_editing_cache,
|
||||||
|
|
@ -1143,9 +1221,9 @@ void GLGizmoSlaSupports::editing_mode_apply_changes()
|
||||||
for (const CacheEntry& ce : m_editing_cache)
|
for (const CacheEntry& ce : m_editing_cache)
|
||||||
m_normal_cache.push_back(ce.support_point);
|
m_normal_cache.push_back(ce.support_point);
|
||||||
|
|
||||||
m_model_object->sla_points_status = sla::PointsStatus::UserModified;
|
m_c->m_model_object->sla_points_status = sla::PointsStatus::UserModified;
|
||||||
m_model_object->sla_support_points.clear();
|
m_c->m_model_object->sla_support_points.clear();
|
||||||
m_model_object->sla_support_points = m_normal_cache;
|
m_c->m_model_object->sla_support_points = m_normal_cache;
|
||||||
|
|
||||||
reslice_SLA_supports();
|
reslice_SLA_supports();
|
||||||
}
|
}
|
||||||
|
|
@ -1156,10 +1234,10 @@ void GLGizmoSlaSupports::editing_mode_apply_changes()
|
||||||
void GLGizmoSlaSupports::reload_cache()
|
void GLGizmoSlaSupports::reload_cache()
|
||||||
{
|
{
|
||||||
m_normal_cache.clear();
|
m_normal_cache.clear();
|
||||||
if (m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated || m_model_object->sla_points_status == sla::PointsStatus::Generating)
|
if (m_c->m_model_object->sla_points_status == sla::PointsStatus::AutoGenerated || m_c->m_model_object->sla_points_status == sla::PointsStatus::Generating)
|
||||||
get_data_from_backend();
|
get_data_from_backend();
|
||||||
else
|
else
|
||||||
for (const sla::SupportPoint& point : m_model_object->sla_support_points)
|
for (const sla::SupportPoint& point : m_c->m_model_object->sla_support_points)
|
||||||
m_normal_cache.emplace_back(point);
|
m_normal_cache.emplace_back(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1168,7 +1246,7 @@ bool GLGizmoSlaSupports::has_backend_supports() const
|
||||||
{
|
{
|
||||||
// find SlaPrintObject with this ID
|
// find SlaPrintObject with this ID
|
||||||
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
|
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
|
||||||
if (po->model_object()->id() == m_model_object->id())
|
if (po->model_object()->id() == m_c->m_model_object->id())
|
||||||
return po->is_step_done(slaposSupportPoints);
|
return po->is_step_done(slaposSupportPoints);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1176,7 +1254,7 @@ bool GLGizmoSlaSupports::has_backend_supports() const
|
||||||
|
|
||||||
void GLGizmoSlaSupports::reslice_SLA_supports(bool postpone_error_messages) const
|
void GLGizmoSlaSupports::reslice_SLA_supports(bool postpone_error_messages) const
|
||||||
{
|
{
|
||||||
wxGetApp().CallAfter([this, postpone_error_messages]() { wxGetApp().plater()->reslice_SLA_supports(*m_model_object, postpone_error_messages); });
|
wxGetApp().CallAfter([this, postpone_error_messages]() { wxGetApp().plater()->reslice_SLA_supports(*m_c->m_model_object, postpone_error_messages); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLGizmoSlaSupports::get_data_from_backend()
|
void GLGizmoSlaSupports::get_data_from_backend()
|
||||||
|
|
@ -1186,14 +1264,14 @@ void GLGizmoSlaSupports::get_data_from_backend()
|
||||||
|
|
||||||
// find the respective SLAPrintObject, we need a pointer to it
|
// find the respective SLAPrintObject, we need a pointer to it
|
||||||
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
|
for (const SLAPrintObject* po : m_parent.sla_print()->objects()) {
|
||||||
if (po->model_object()->id() == m_model_object->id()) {
|
if (po->model_object()->id() == m_c->m_model_object->id()) {
|
||||||
m_normal_cache.clear();
|
m_normal_cache.clear();
|
||||||
const std::vector<sla::SupportPoint>& points = po->get_support_points();
|
const std::vector<sla::SupportPoint>& points = po->get_support_points();
|
||||||
auto mat = po->trafo().inverse().cast<float>();
|
auto mat = po->trafo().inverse().cast<float>();
|
||||||
for (unsigned int i=0; i<points.size();++i)
|
for (unsigned int i=0; i<points.size();++i)
|
||||||
m_normal_cache.emplace_back(sla::SupportPoint(mat * points[i].pos, points[i].head_front_radius, points[i].is_new_island));
|
m_normal_cache.emplace_back(sla::SupportPoint(mat * points[i].pos, points[i].head_front_radius, points[i].is_new_island));
|
||||||
|
|
||||||
m_model_object->sla_points_status = sla::PointsStatus::AutoGenerated;
|
m_c->m_model_object->sla_points_status = sla::PointsStatus::AutoGenerated;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1210,10 +1288,10 @@ void GLGizmoSlaSupports::auto_generate()
|
||||||
_(L("Are you sure you want to do it?")) + "\n",
|
_(L("Are you sure you want to do it?")) + "\n",
|
||||||
_(L("Warning")), wxICON_WARNING | wxYES | wxNO);
|
_(L("Warning")), wxICON_WARNING | wxYES | wxNO);
|
||||||
|
|
||||||
if (m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) {
|
if (m_c->m_model_object->sla_points_status != sla::PointsStatus::UserModified || m_normal_cache.empty() || dlg.ShowModal() == wxID_YES) {
|
||||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Autogenerate support points")));
|
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Autogenerate support points")));
|
||||||
wxGetApp().CallAfter([this]() { reslice_SLA_supports(); });
|
wxGetApp().CallAfter([this]() { reslice_SLA_supports(); });
|
||||||
m_model_object->sla_points_status = sla::PointsStatus::Generating;
|
m_c->m_model_object->sla_points_status = sla::PointsStatus::Generating;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1227,6 +1305,9 @@ void GLGizmoSlaSupports::switch_to_editing_mode()
|
||||||
for (const sla::SupportPoint& sp : m_normal_cache)
|
for (const sla::SupportPoint& sp : m_normal_cache)
|
||||||
m_editing_cache.emplace_back(sp);
|
m_editing_cache.emplace_back(sp);
|
||||||
select_point(NoPoints);
|
select_point(NoPoints);
|
||||||
|
|
||||||
|
m_parent.toggle_sla_auxiliaries_visibility(false, m_c->m_model_object, m_c->m_active_instance);
|
||||||
|
m_parent.set_as_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1235,6 +1316,8 @@ void GLGizmoSlaSupports::disable_editing_mode()
|
||||||
if (m_editing_mode) {
|
if (m_editing_mode) {
|
||||||
m_editing_mode = false;
|
m_editing_mode = false;
|
||||||
wxGetApp().plater()->leave_gizmos_stack();
|
wxGetApp().plater()->leave_gizmos_stack();
|
||||||
|
m_parent.toggle_sla_auxiliaries_visibility(true, m_c->m_model_object, m_c->m_active_instance);
|
||||||
|
m_parent.set_as_dirty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1258,9 +1341,9 @@ void GLGizmoSlaSupports::update_clipping_plane(bool keep_normal) const
|
||||||
Vec3d normal = (keep_normal && m_clipping_plane->get_normal() != Vec3d::Zero() ?
|
Vec3d normal = (keep_normal && m_clipping_plane->get_normal() != Vec3d::Zero() ?
|
||||||
m_clipping_plane->get_normal() : -m_parent.get_camera().get_dir_forward());
|
m_clipping_plane->get_normal() : -m_parent.get_camera().get_dir_forward());
|
||||||
|
|
||||||
const Vec3d& center = m_model_object->instances[m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift);
|
const Vec3d& center = m_c->m_model_object->instances[m_c->m_active_instance]->get_offset() + Vec3d(0., 0., m_z_shift);
|
||||||
float dist = normal.dot(center);
|
float dist = normal.dot(center);
|
||||||
*m_clipping_plane = ClippingPlane(normal, (dist - (-m_active_instance_bb_radius) - m_clipping_plane_distance * 2*m_active_instance_bb_radius));
|
*m_clipping_plane = ClippingPlane(normal, (dist - (-m_c->m_active_instance_bb_radius) - m_clipping_plane_distance * 2*m_c->m_active_instance_bb_radius));
|
||||||
m_parent.set_as_dirty();
|
m_parent.set_as_dirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
#include "GLGizmoBase.hpp"
|
#include "GLGizmoBase.hpp"
|
||||||
#include "slic3r/GUI/GLSelectionRectangle.hpp"
|
#include "slic3r/GUI/GLSelectionRectangle.hpp"
|
||||||
|
|
||||||
#include "libslic3r/SLA/SLACommon.hpp"
|
#include "libslic3r/SLA/Common.hpp"
|
||||||
#include <wx/dialog.h>
|
#include <wx/dialog.h>
|
||||||
|
|
||||||
#include <cereal/types/vector.hpp>
|
#include <cereal/types/vector.hpp>
|
||||||
|
|
@ -21,10 +21,10 @@ enum class SLAGizmoEventType : unsigned char;
|
||||||
class GLGizmoSlaSupports : public GLGizmoBase
|
class GLGizmoSlaSupports : public GLGizmoBase
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
ModelObject* m_model_object = nullptr;
|
//ModelObject* m_model_object = nullptr;
|
||||||
ObjectID m_model_object_id = 0;
|
//ObjectID m_model_object_id = 0;
|
||||||
int m_active_instance = -1;
|
//int m_active_instance = -1;
|
||||||
float m_active_instance_bb_radius; // to cache the bb
|
//float m_active_instance_bb_radius; // to cache the bb
|
||||||
mutable double m_z_shift = 0.f;
|
mutable double m_z_shift = 0.f;
|
||||||
bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal);
|
bool unproject_on_mesh(const Vec2d& mouse_pos, std::pair<Vec3f, Vec3f>& pos_and_normal);
|
||||||
|
|
||||||
|
|
@ -34,15 +34,12 @@ private:
|
||||||
typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
|
typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
|
||||||
typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
|
typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
|
||||||
|
|
||||||
std::unique_ptr<MeshRaycaster> m_mesh_raycaster;
|
//std::unique_ptr<MeshRaycaster> m_mesh_raycaster;
|
||||||
const TriangleMesh* m_mesh;
|
//const TriangleMesh* m_mesh;
|
||||||
const indexed_triangle_set* m_its;
|
const indexed_triangle_set* m_its;
|
||||||
mutable const TriangleMesh* m_supports_mesh;
|
//mutable int m_old_timestamp = -1;
|
||||||
mutable std::vector<Vec2f> m_triangles;
|
//mutable int m_print_object_idx = -1;
|
||||||
mutable std::vector<Vec2f> m_supports_triangles;
|
//mutable int m_print_objects_count = -1;
|
||||||
mutable int m_old_timestamp = -1;
|
|
||||||
mutable int m_print_object_idx = -1;
|
|
||||||
mutable int m_print_objects_count = -1;
|
|
||||||
|
|
||||||
class CacheEntry {
|
class CacheEntry {
|
||||||
public:
|
public:
|
||||||
|
|
@ -72,7 +69,7 @@ private:
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
|
GLGizmoSlaSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id, CommonGizmosData* cd);
|
||||||
~GLGizmoSlaSupports() override;
|
~GLGizmoSlaSupports() override;
|
||||||
void set_sla_support_data(ModelObject* model_object, const Selection& selection);
|
void set_sla_support_data(ModelObject* model_object, const Selection& selection);
|
||||||
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
bool gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_position, bool shift_down, bool alt_down, bool control_down);
|
||||||
|
|
@ -93,13 +90,13 @@ private:
|
||||||
//void render_selection_rectangle() const;
|
//void render_selection_rectangle() const;
|
||||||
void render_points(const Selection& selection, bool picking = false) const;
|
void render_points(const Selection& selection, bool picking = false) const;
|
||||||
void render_clipping_plane(const Selection& selection) const;
|
void render_clipping_plane(const Selection& selection) const;
|
||||||
|
void render_hollowed_mesh() const;
|
||||||
bool is_mesh_update_necessary() const;
|
bool is_mesh_update_necessary() const;
|
||||||
void update_mesh();
|
void update_mesh();
|
||||||
bool unsaved_changes() const;
|
bool unsaved_changes() const;
|
||||||
|
|
||||||
bool m_lock_unique_islands = false;
|
bool m_lock_unique_islands = false;
|
||||||
bool m_editing_mode = false; // Is editing mode active?
|
bool m_editing_mode = false; // Is editing mode active?
|
||||||
bool m_old_editing_state = false; // To keep track of whether the user toggled between the modes (needed for imgui refreshes).
|
|
||||||
float m_new_point_head_diameter; // Size of a new point.
|
float m_new_point_head_diameter; // Size of a new point.
|
||||||
CacheEntry m_point_before_drag; // undo/redo - so we know what state was edited
|
CacheEntry m_point_before_drag; // undo/redo - so we know what state was edited
|
||||||
float m_old_point_head_diameter = 0.; // the same
|
float m_old_point_head_diameter = 0.; // the same
|
||||||
|
|
@ -121,11 +118,12 @@ private:
|
||||||
bool m_selection_empty = true;
|
bool m_selection_empty = true;
|
||||||
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
|
EState m_old_state = Off; // to be able to see that the gizmo has just been closed (see on_set_state)
|
||||||
|
|
||||||
mutable std::unique_ptr<MeshClipper> m_object_clipper;
|
//mutable std::unique_ptr<MeshClipper> m_object_clipper;
|
||||||
mutable std::unique_ptr<MeshClipper> m_supports_clipper;
|
//mutable std::unique_ptr<MeshClipper> m_supports_clipper;
|
||||||
|
|
||||||
std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const;
|
std::vector<const ConfigOption*> get_config_options(const std::vector<std::string>& keys) const;
|
||||||
bool is_mesh_point_clipped(const Vec3d& point) const;
|
bool is_mesh_point_clipped(const Vec3d& point) const;
|
||||||
|
bool is_point_in_hole(const Vec3f& pt) const;
|
||||||
//void find_intersecting_facets(const igl::AABB<Eigen::MatrixXf, 3>* aabb, const Vec3f& normal, double offset, std::vector<unsigned int>& out) const;
|
//void find_intersecting_facets(const igl::AABB<Eigen::MatrixXf, 3>* aabb, const Vec3f& normal, double offset, std::vector<unsigned int>& out) const;
|
||||||
|
|
||||||
// Methods that do the model_object and editing cache synchronization,
|
// Methods that do the model_object and editing cache synchronization,
|
||||||
|
|
|
||||||
|
|
@ -32,5 +32,6 @@ enum class SLAGizmoEventType : unsigned char {
|
||||||
#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp"
|
#include "slic3r/GUI/Gizmos/GLGizmoFlatten.hpp"
|
||||||
#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp"
|
#include "slic3r/GUI/Gizmos/GLGizmoSlaSupports.hpp"
|
||||||
#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp"
|
#include "slic3r/GUI/Gizmos/GLGizmoCut.hpp"
|
||||||
|
#include "slic3r/GUI/Gizmos/GLGizmoHollow.hpp"
|
||||||
|
|
||||||
#endif //slic3r_GLGizmos_hpp_
|
#endif //slic3r_GLGizmos_hpp_
|
||||||
|
|
|
||||||
|
|
@ -83,12 +83,16 @@ bool GLGizmosManager::init()
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_common_gizmos_data.reset(new CommonGizmosData());
|
||||||
|
|
||||||
|
// Order of gizmos in the vector must match order in EType!
|
||||||
m_gizmos.emplace_back(new GLGizmoMove3D(m_parent, "move.svg", 0));
|
m_gizmos.emplace_back(new GLGizmoMove3D(m_parent, "move.svg", 0));
|
||||||
m_gizmos.emplace_back(new GLGizmoScale3D(m_parent, "scale.svg", 1));
|
m_gizmos.emplace_back(new GLGizmoScale3D(m_parent, "scale.svg", 1));
|
||||||
m_gizmos.emplace_back(new GLGizmoRotate3D(m_parent, "rotate.svg", 2));
|
m_gizmos.emplace_back(new GLGizmoRotate3D(m_parent, "rotate.svg", 2));
|
||||||
m_gizmos.emplace_back(new GLGizmoFlatten(m_parent, "place.svg", 3));
|
m_gizmos.emplace_back(new GLGizmoFlatten(m_parent, "place.svg", 3));
|
||||||
m_gizmos.emplace_back(new GLGizmoCut(m_parent, "cut.svg", 4));
|
m_gizmos.emplace_back(new GLGizmoCut(m_parent, "cut.svg", 4));
|
||||||
m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 5));
|
m_gizmos.emplace_back(new GLGizmoHollow(m_parent, "hollow.svg", 5, m_common_gizmos_data.get()));
|
||||||
|
m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 6, m_common_gizmos_data.get()));
|
||||||
|
|
||||||
for (auto& gizmo : m_gizmos) {
|
for (auto& gizmo : m_gizmos) {
|
||||||
if (! gizmo->init()) {
|
if (! gizmo->init()) {
|
||||||
|
|
@ -345,6 +349,7 @@ void GLGizmosManager::set_sla_support_data(ModelObject* model_object)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->set_sla_support_data(model_object, m_parent.get_selection());
|
dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->set_sla_support_data(model_object, m_parent.get_selection());
|
||||||
|
dynamic_cast<GLGizmoHollow*>(m_gizmos[Hollow].get())->set_sla_support_data(model_object, m_parent.get_selection());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the gizmo used the event to do something, false otherwise.
|
// Returns true if the gizmo used the event to do something, false otherwise.
|
||||||
|
|
@ -353,15 +358,22 @@ bool GLGizmosManager::gizmo_event(SLAGizmoEventType action, const Vec2d& mouse_p
|
||||||
if (!m_enabled || m_gizmos.empty())
|
if (!m_enabled || m_gizmos.empty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if (m_current == SlaSupports)
|
||||||
return dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
return dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||||
|
if (m_current == Hollow)
|
||||||
|
return dynamic_cast<GLGizmoHollow*>(m_gizmos[Hollow].get())->gizmo_event(action, mouse_position, shift_down, alt_down, control_down);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClippingPlane GLGizmosManager::get_sla_clipping_plane() const
|
ClippingPlane GLGizmosManager::get_sla_clipping_plane() const
|
||||||
{
|
{
|
||||||
if (!m_enabled || m_current != SlaSupports || m_gizmos.empty())
|
if (!m_enabled || (m_current != SlaSupports && m_current != Hollow) || m_gizmos.empty())
|
||||||
return ClippingPlane::ClipsNothing();
|
return ClippingPlane::ClipsNothing();
|
||||||
|
|
||||||
|
if (m_current == SlaSupports)
|
||||||
return dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->get_sla_clipping_plane();
|
return dynamic_cast<GLGizmoSlaSupports*>(m_gizmos[SlaSupports].get())->get_sla_clipping_plane();
|
||||||
|
else
|
||||||
|
return dynamic_cast<GLGizmoHollow*>(m_gizmos[Hollow].get())->get_sla_clipping_plane();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GLGizmosManager::wants_reslice_supports_on_undo() const
|
bool GLGizmosManager::wants_reslice_supports_on_undo() const
|
||||||
|
|
@ -401,7 +413,7 @@ bool GLGizmosManager::on_mouse_wheel(wxMouseEvent& evt)
|
||||||
{
|
{
|
||||||
bool processed = false;
|
bool processed = false;
|
||||||
|
|
||||||
if (m_current == SlaSupports) {
|
if (m_current == SlaSupports || m_current == Hollow) {
|
||||||
float rot = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta();
|
float rot = (float)evt.GetWheelRotation() / (float)evt.GetWheelDelta();
|
||||||
if (gizmo_event((rot > 0.f ? SLAGizmoEventType::MouseWheelUp : SLAGizmoEventType::MouseWheelDown), Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
|
if (gizmo_event((rot > 0.f ? SLAGizmoEventType::MouseWheelUp : SLAGizmoEventType::MouseWheelDown), Vec2d::Zero(), evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
|
||||||
processed = true;
|
processed = true;
|
||||||
|
|
@ -459,7 +471,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
|
||||||
|
|
||||||
if (evt.LeftDown())
|
if (evt.LeftDown())
|
||||||
{
|
{
|
||||||
if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
|
if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::LeftDown, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
|
||||||
// the gizmo got the event and took some action, there is no need to do anything more
|
// the gizmo got the event and took some action, there is no need to do anything more
|
||||||
processed = true;
|
processed = true;
|
||||||
else if (!selection.is_empty() && grabber_contains_mouse()) {
|
else if (!selection.is_empty() && grabber_contains_mouse()) {
|
||||||
|
|
@ -477,17 +489,17 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
|
||||||
processed = true;
|
processed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::RightDown))
|
else if (evt.RightDown() && (selected_object_idx != -1) && (m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::RightDown))
|
||||||
{
|
{
|
||||||
// we need to set the following right up as processed to avoid showing the context menu if the user release the mouse over the object
|
// we need to set the following right up as processed to avoid showing the context menu if the user release the mouse over the object
|
||||||
pending_right_up = true;
|
pending_right_up = true;
|
||||||
// event was taken care of by the SlaSupports gizmo
|
// event was taken care of by the SlaSupports gizmo
|
||||||
processed = true;
|
processed = true;
|
||||||
}
|
}
|
||||||
else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports))
|
else if (evt.Dragging() && (m_parent.get_move_volume_id() != -1) && (m_current == SlaSupports || m_current == Hollow))
|
||||||
// don't allow dragging objects with the Sla gizmo on
|
// don't allow dragging objects with the Sla gizmo on
|
||||||
processed = true;
|
processed = true;
|
||||||
else if (evt.Dragging() && (m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
|
else if (evt.Dragging() && (m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::Dragging, mouse_pos, evt.ShiftDown(), evt.AltDown(), evt.ControlDown()))
|
||||||
{
|
{
|
||||||
// the gizmo got the event and took some action, no need to do anything more here
|
// the gizmo got the event and took some action, no need to do anything more here
|
||||||
m_parent.set_as_dirty();
|
m_parent.set_as_dirty();
|
||||||
|
|
@ -563,7 +575,7 @@ bool GLGizmosManager::on_mouse(wxMouseEvent& evt)
|
||||||
|
|
||||||
processed = true;
|
processed = true;
|
||||||
}
|
}
|
||||||
else if (evt.LeftUp() && (m_current == SlaSupports) && !m_parent.is_mouse_dragging())
|
else if (evt.LeftUp() && (m_current == SlaSupports || m_current == Hollow) && !m_parent.is_mouse_dragging())
|
||||||
{
|
{
|
||||||
// in case SLA gizmo is selected, we just pass the LeftUp event and stop processing - neither
|
// in case SLA gizmo is selected, we just pass the LeftUp event and stop processing - neither
|
||||||
// object moving or selecting is suppressed in that case
|
// object moving or selecting is suppressed in that case
|
||||||
|
|
@ -628,7 +640,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
|
||||||
#endif /* __APPLE__ */
|
#endif /* __APPLE__ */
|
||||||
{
|
{
|
||||||
// Sla gizmo selects all support points
|
// Sla gizmo selects all support points
|
||||||
if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::SelectAll))
|
if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::SelectAll))
|
||||||
processed = true;
|
processed = true;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -662,7 +674,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
|
||||||
case 'r' :
|
case 'r' :
|
||||||
case 'R' :
|
case 'R' :
|
||||||
{
|
{
|
||||||
if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::ResetClippingPlane))
|
if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::ResetClippingPlane))
|
||||||
processed = true;
|
processed = true;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -674,7 +686,7 @@ bool GLGizmosManager::on_char(wxKeyEvent& evt)
|
||||||
case WXK_DELETE:
|
case WXK_DELETE:
|
||||||
#endif /* __APPLE__ */
|
#endif /* __APPLE__ */
|
||||||
{
|
{
|
||||||
if ((m_current == SlaSupports) && gizmo_event(SLAGizmoEventType::Delete))
|
if ((m_current == SlaSupports || m_current == Hollow) && gizmo_event(SLAGizmoEventType::Delete))
|
||||||
processed = true;
|
processed = true;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
@ -736,20 +748,31 @@ bool GLGizmosManager::on_key(wxKeyEvent& evt)
|
||||||
|
|
||||||
if (evt.GetEventType() == wxEVT_KEY_UP)
|
if (evt.GetEventType() == wxEVT_KEY_UP)
|
||||||
{
|
{
|
||||||
if (m_current == SlaSupports)
|
if (m_current == SlaSupports || m_current == Hollow)
|
||||||
{
|
{
|
||||||
|
bool is_editing = true;
|
||||||
|
bool is_rectangle_dragging = false;
|
||||||
|
|
||||||
|
if (m_current == SlaSupports) {
|
||||||
GLGizmoSlaSupports* gizmo = dynamic_cast<GLGizmoSlaSupports*>(get_current());
|
GLGizmoSlaSupports* gizmo = dynamic_cast<GLGizmoSlaSupports*>(get_current());
|
||||||
|
is_editing = gizmo->is_in_editing_mode();
|
||||||
|
is_rectangle_dragging = gizmo->is_selection_rectangle_dragging();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
GLGizmoHollow* gizmo = dynamic_cast<GLGizmoHollow*>(get_current());
|
||||||
|
is_rectangle_dragging = gizmo->is_selection_rectangle_dragging();
|
||||||
|
}
|
||||||
|
|
||||||
if (keyCode == WXK_SHIFT)
|
if (keyCode == WXK_SHIFT)
|
||||||
{
|
{
|
||||||
// shift has been just released - SLA gizmo might want to close rectangular selection.
|
// shift has been just released - SLA gizmo might want to close rectangular selection.
|
||||||
if (gizmo_event(SLAGizmoEventType::ShiftUp) || (gizmo->is_in_editing_mode() && gizmo->is_selection_rectangle_dragging()))
|
if (gizmo_event(SLAGizmoEventType::ShiftUp) || (is_editing && is_rectangle_dragging))
|
||||||
processed = true;
|
processed = true;
|
||||||
}
|
}
|
||||||
else if (keyCode == WXK_ALT)
|
else if (keyCode == WXK_ALT)
|
||||||
{
|
{
|
||||||
// alt has been just released - SLA gizmo might want to close rectangular selection.
|
// alt has been just released - SLA gizmo might want to close rectangular selection.
|
||||||
if (gizmo_event(SLAGizmoEventType::AltUp) || (gizmo->is_in_editing_mode() && gizmo->is_selection_rectangle_dragging()))
|
if (gizmo_event(SLAGizmoEventType::AltUp) || (is_editing && is_rectangle_dragging))
|
||||||
processed = true;
|
processed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -54,11 +54,13 @@ public:
|
||||||
|
|
||||||
enum EType : unsigned char
|
enum EType : unsigned char
|
||||||
{
|
{
|
||||||
|
// Order must match index in m_gizmos!
|
||||||
Move,
|
Move,
|
||||||
Scale,
|
Scale,
|
||||||
Rotate,
|
Rotate,
|
||||||
Flatten,
|
Flatten,
|
||||||
Cut,
|
Cut,
|
||||||
|
Hollow,
|
||||||
SlaSupports,
|
SlaSupports,
|
||||||
Undefined
|
Undefined
|
||||||
};
|
};
|
||||||
|
|
@ -111,6 +113,7 @@ private:
|
||||||
MouseCapture m_mouse_capture;
|
MouseCapture m_mouse_capture;
|
||||||
std::string m_tooltip;
|
std::string m_tooltip;
|
||||||
bool m_serializing;
|
bool m_serializing;
|
||||||
|
std::unique_ptr<CommonGizmosData> m_common_gizmos_data;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit GLGizmosManager(GLCanvas3D& parent);
|
explicit GLGizmosManager(GLCanvas3D& parent);
|
||||||
|
|
@ -168,6 +171,7 @@ public:
|
||||||
void update_data();
|
void update_data();
|
||||||
|
|
||||||
EType get_current_type() const { return m_current; }
|
EType get_current_type() const { return m_current; }
|
||||||
|
GLGizmoBase* get_current() const;
|
||||||
|
|
||||||
bool is_running() const;
|
bool is_running() const;
|
||||||
bool handle_shortcut(int key);
|
bool handle_shortcut(int key);
|
||||||
|
|
@ -216,8 +220,6 @@ private:
|
||||||
float get_scaled_total_height() const;
|
float get_scaled_total_height() const;
|
||||||
float get_scaled_total_width() const;
|
float get_scaled_total_width() const;
|
||||||
|
|
||||||
GLGizmoBase* get_current() const;
|
|
||||||
|
|
||||||
bool generate_icons_texture() const;
|
bool generate_icons_texture() const;
|
||||||
|
|
||||||
void update_on_off_state(const Vec2d& mouse_pos);
|
void update_on_off_state(const Vec2d& mouse_pos);
|
||||||
|
|
|
||||||
155
src/slic3r/GUI/Job.hpp
Normal file
155
src/slic3r/GUI/Job.hpp
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
#ifndef JOB_HPP
|
||||||
|
#define JOB_HPP
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#include <slic3r/Utils/Thread.hpp>
|
||||||
|
#include <slic3r/GUI/I18N.hpp>
|
||||||
|
#include <slic3r/GUI/ProgressIndicator.hpp>
|
||||||
|
|
||||||
|
#include <wx/event.h>
|
||||||
|
|
||||||
|
#include <boost/thread.hpp>
|
||||||
|
|
||||||
|
namespace Slic3r { namespace GUI {
|
||||||
|
|
||||||
|
// A class to handle UI jobs like arranging and optimizing rotation.
|
||||||
|
// These are not instant jobs, the user has to be informed about their
|
||||||
|
// state in the status progress indicator. On the other hand they are
|
||||||
|
// separated from the background slicing process. Ideally, these jobs should
|
||||||
|
// run when the background process is not running.
|
||||||
|
//
|
||||||
|
// TODO: A mechanism would be useful for blocking the plater interactions:
|
||||||
|
// objects would be frozen for the user. In case of arrange, an animation
|
||||||
|
// could be shown, or with the optimize orientations, partial results
|
||||||
|
// could be displayed.
|
||||||
|
class Job : public wxEvtHandler
|
||||||
|
{
|
||||||
|
int m_range = 100;
|
||||||
|
boost::thread m_thread;
|
||||||
|
std::atomic<bool> m_running{false}, m_canceled{false};
|
||||||
|
bool m_finalized = false;
|
||||||
|
std::shared_ptr<ProgressIndicator> m_progress;
|
||||||
|
|
||||||
|
void run()
|
||||||
|
{
|
||||||
|
m_running.store(true);
|
||||||
|
process();
|
||||||
|
m_running.store(false);
|
||||||
|
|
||||||
|
// ensure to call the last status to finalize the job
|
||||||
|
update_status(status_range(), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// status range for a particular job
|
||||||
|
virtual int status_range() const { return 100; }
|
||||||
|
|
||||||
|
// status update, to be used from the work thread (process() method)
|
||||||
|
void update_status(int st, const wxString &msg = "")
|
||||||
|
{
|
||||||
|
auto evt = new wxThreadEvent();
|
||||||
|
evt->SetInt(st);
|
||||||
|
evt->SetString(msg);
|
||||||
|
wxQueueEvent(this, evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool was_canceled() const { return m_canceled.load(); }
|
||||||
|
|
||||||
|
// Launched just before start(), a job can use it to prepare internals
|
||||||
|
virtual void prepare() {}
|
||||||
|
|
||||||
|
// Launched when the job is finished. It refreshes the 3Dscene by def.
|
||||||
|
virtual void finalize() { m_finalized = true; }
|
||||||
|
|
||||||
|
|
||||||
|
public:
|
||||||
|
Job(std::shared_ptr<ProgressIndicator> pri) : m_progress(pri)
|
||||||
|
{
|
||||||
|
Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) {
|
||||||
|
auto msg = evt.GetString();
|
||||||
|
if (!msg.empty())
|
||||||
|
m_progress->set_status_text(msg.ToUTF8().data());
|
||||||
|
|
||||||
|
if (m_finalized) return;
|
||||||
|
|
||||||
|
m_progress->set_progress(evt.GetInt());
|
||||||
|
if (evt.GetInt() == status_range()) {
|
||||||
|
// set back the original range and cancel callback
|
||||||
|
m_progress->set_range(m_range);
|
||||||
|
m_progress->set_cancel_callback();
|
||||||
|
wxEndBusyCursor();
|
||||||
|
|
||||||
|
finalize();
|
||||||
|
|
||||||
|
// dont do finalization again for the same process
|
||||||
|
m_finalized = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_finalized() const { return m_finalized; }
|
||||||
|
|
||||||
|
Job(const Job &) = delete;
|
||||||
|
Job(Job &&) = delete;
|
||||||
|
Job &operator=(const Job &) = delete;
|
||||||
|
Job &operator=(Job &&) = delete;
|
||||||
|
|
||||||
|
virtual void process() = 0;
|
||||||
|
|
||||||
|
void start()
|
||||||
|
{ // Start the job. No effect if the job is already running
|
||||||
|
if (!m_running.load()) {
|
||||||
|
prepare();
|
||||||
|
|
||||||
|
// Save the current status indicatior range and push the new one
|
||||||
|
m_range = m_progress->get_range();
|
||||||
|
m_progress->set_range(status_range());
|
||||||
|
|
||||||
|
// init cancellation flag and set the cancel callback
|
||||||
|
m_canceled.store(false);
|
||||||
|
m_progress->set_cancel_callback(
|
||||||
|
[this]() { m_canceled.store(true); });
|
||||||
|
|
||||||
|
m_finalized = false;
|
||||||
|
|
||||||
|
// Changing cursor to busy
|
||||||
|
wxBeginBusyCursor();
|
||||||
|
|
||||||
|
try { // Execute the job
|
||||||
|
m_thread = create_thread([this] { this->run(); });
|
||||||
|
} catch (std::exception &) {
|
||||||
|
update_status(status_range(),
|
||||||
|
_(L("ERROR: not enough resources to "
|
||||||
|
"execute a new job.")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The state changes will be undone when the process hits the
|
||||||
|
// last status value, in the status update handler (see ctor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// To wait for the running job and join the threads. False is
|
||||||
|
// returned if the timeout has been reached and the job is still
|
||||||
|
// running. Call cancel() before this fn if you want to explicitly
|
||||||
|
// end the job.
|
||||||
|
bool join(int timeout_ms = 0)
|
||||||
|
{
|
||||||
|
if (!m_thread.joinable()) return true;
|
||||||
|
|
||||||
|
if (timeout_ms <= 0)
|
||||||
|
m_thread.join();
|
||||||
|
else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_running() const { return m_running.load(); }
|
||||||
|
void cancel() { m_canceled.store(true); }
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // JOB_HPP
|
||||||
|
|
@ -58,7 +58,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
|
|
||||||
// initialize status bar
|
// initialize status bar
|
||||||
m_statusbar.reset(new ProgressStatusBar(this));
|
m_statusbar = std::make_shared<ProgressStatusBar>(this);
|
||||||
|
m_statusbar->set_font(GUI::wxGetApp().normal_font());
|
||||||
m_statusbar->embed(this);
|
m_statusbar->embed(this);
|
||||||
m_statusbar->set_status_text(_(L("Version")) + " " +
|
m_statusbar->set_status_text(_(L("Version")) + " " +
|
||||||
SLIC3R_VERSION +
|
SLIC3R_VERSION +
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ public:
|
||||||
Plater* m_plater { nullptr };
|
Plater* m_plater { nullptr };
|
||||||
wxNotebook* m_tabpanel { nullptr };
|
wxNotebook* m_tabpanel { nullptr };
|
||||||
wxProgressDialog* m_progress_dialog { nullptr };
|
wxProgressDialog* m_progress_dialog { nullptr };
|
||||||
std::unique_ptr<ProgressStatusBar> m_statusbar;
|
std::shared_ptr<ProgressStatusBar> m_statusbar;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // GUI
|
} // GUI
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,6 @@
|
||||||
|
|
||||||
#include "slic3r/GUI/Camera.hpp"
|
#include "slic3r/GUI/Camera.hpp"
|
||||||
|
|
||||||
// There is an L function in igl that would be overridden by our localization macro.
|
|
||||||
#undef L
|
|
||||||
#include <igl/AABB.h>
|
|
||||||
|
|
||||||
#include <GL/glew.h>
|
#include <GL/glew.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -99,57 +95,6 @@ void MeshClipper::recalculate_triangles()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class MeshRaycaster::AABBWrapper {
|
|
||||||
public:
|
|
||||||
AABBWrapper(const TriangleMesh* mesh);
|
|
||||||
~AABBWrapper() { m_AABB.deinit(); }
|
|
||||||
|
|
||||||
typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
|
|
||||||
typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
|
|
||||||
igl::AABB<MapMatrixXfUnaligned, 3> m_AABB;
|
|
||||||
|
|
||||||
Vec3f get_hit_pos(const igl::Hit& hit) const;
|
|
||||||
Vec3f get_hit_normal(const igl::Hit& hit) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
const TriangleMesh* m_mesh;
|
|
||||||
};
|
|
||||||
|
|
||||||
MeshRaycaster::AABBWrapper::AABBWrapper(const TriangleMesh* mesh)
|
|
||||||
: m_mesh(mesh)
|
|
||||||
{
|
|
||||||
m_AABB.init(
|
|
||||||
MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3),
|
|
||||||
MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
MeshRaycaster::MeshRaycaster(const TriangleMesh& mesh)
|
|
||||||
: m_AABB_wrapper(new AABBWrapper(&mesh)), m_mesh(&mesh)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define the default destructor here. This is needed for the PIMPL with
|
|
||||||
// unique_ptr to work, the AABBWrapper is complete here.
|
|
||||||
MeshRaycaster::~MeshRaycaster() = default;
|
|
||||||
|
|
||||||
Vec3f MeshRaycaster::AABBWrapper::get_hit_pos(const igl::Hit& hit) const
|
|
||||||
{
|
|
||||||
const stl_triangle_vertex_indices& indices = m_mesh->its.indices[hit.id];
|
|
||||||
return Vec3f((1-hit.u-hit.v) * m_mesh->its.vertices[indices(0)]
|
|
||||||
+ hit.u * m_mesh->its.vertices[indices(1)]
|
|
||||||
+ hit.v * m_mesh->its.vertices[indices(2)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Vec3f MeshRaycaster::AABBWrapper::get_hit_normal(const igl::Hit& hit) const
|
|
||||||
{
|
|
||||||
const stl_triangle_vertex_indices& indices = m_mesh->its.indices[hit.id];
|
|
||||||
Vec3f a(m_mesh->its.vertices[indices(1)] - m_mesh->its.vertices[indices(0)]);
|
|
||||||
Vec3f b(m_mesh->its.vertices[indices(2)] - m_mesh->its.vertices[indices(0)]);
|
|
||||||
return Vec3f(a.cross(b));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
|
bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
|
||||||
Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane) const
|
Vec3f& position, Vec3f& normal, const ClippingPlane* clipping_plane) const
|
||||||
|
|
@ -163,27 +108,20 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
|
||||||
::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 0., model_mat.data(), proj_mat.data(), viewport.data(), &pt1(0), &pt1(1), &pt1(2));
|
::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 0., model_mat.data(), proj_mat.data(), viewport.data(), &pt1(0), &pt1(1), &pt1(2));
|
||||||
::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 1., model_mat.data(), proj_mat.data(), viewport.data(), &pt2(0), &pt2(1), &pt2(2));
|
::gluUnProject(mouse_pos(0), viewport[3] - mouse_pos(1), 1., model_mat.data(), proj_mat.data(), viewport.data(), &pt2(0), &pt2(1), &pt2(2));
|
||||||
|
|
||||||
std::vector<igl::Hit> hits;
|
|
||||||
|
|
||||||
Transform3d inv = trafo.inverse();
|
Transform3d inv = trafo.inverse();
|
||||||
|
|
||||||
pt1 = inv * pt1;
|
pt1 = inv * pt1;
|
||||||
pt2 = inv * pt2;
|
pt2 = inv * pt2;
|
||||||
|
|
||||||
if (! m_AABB_wrapper->m_AABB.intersect_ray(
|
std::vector<sla::EigenMesh3D::hit_result> hits = m_emesh.query_ray_hits(pt1, pt2-pt1);
|
||||||
AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3),
|
if (hits.empty())
|
||||||
AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3),
|
|
||||||
pt1.cast<float>(), (pt2-pt1).cast<float>(), hits))
|
|
||||||
return false; // no intersection found
|
return false; // no intersection found
|
||||||
|
|
||||||
std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
|
|
||||||
|
|
||||||
unsigned i = 0;
|
unsigned i = 0;
|
||||||
|
|
||||||
// Remove points that are obscured or cut by the clipping plane
|
// Remove points that are obscured or cut by the clipping plane
|
||||||
if (clipping_plane) {
|
if (clipping_plane) {
|
||||||
for (i=0; i<hits.size(); ++i)
|
for (i=0; i<hits.size(); ++i)
|
||||||
if (! clipping_plane->is_point_clipped(trafo * m_AABB_wrapper->get_hit_pos(hits[i]).cast<double>()))
|
if (! clipping_plane->is_point_clipped(trafo * hits[i].position()))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (i==hits.size() || (hits.size()-i) % 2 != 0) {
|
if (i==hits.size() || (hits.size()-i) % 2 != 0) {
|
||||||
|
|
@ -194,8 +132,8 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now stuff the points in the provided vector and calculate normals if asked about them:
|
// Now stuff the points in the provided vector and calculate normals if asked about them:
|
||||||
position = m_AABB_wrapper->get_hit_pos(hits[i]);
|
position = hits[i].position().cast<float>();
|
||||||
normal = m_AABB_wrapper->get_hit_normal(hits[i]);
|
normal = hits[i].normal().cast<float>();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -219,24 +157,21 @@ std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo
|
||||||
|
|
||||||
bool is_obscured = false;
|
bool is_obscured = false;
|
||||||
// Cast a ray in the direction of the camera and look for intersection with the mesh:
|
// Cast a ray in the direction of the camera and look for intersection with the mesh:
|
||||||
std::vector<igl::Hit> hits;
|
std::vector<sla::EigenMesh3D::hit_result> hits;
|
||||||
// Offset the start of the ray by EPSILON to account for numerical inaccuracies.
|
// Offset the start of the ray by EPSILON to account for numerical inaccuracies.
|
||||||
if (m_AABB_wrapper->m_AABB.intersect_ray(
|
hits = m_emesh.query_ray_hits((inverse_trafo * pt + direction_to_camera_mesh * EPSILON).cast<double>(),
|
||||||
AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3),
|
direction_to_camera.cast<double>());
|
||||||
AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3),
|
|
||||||
inverse_trafo * pt + direction_to_camera_mesh * EPSILON, direction_to_camera_mesh, hits)) {
|
|
||||||
|
|
||||||
std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; });
|
|
||||||
|
|
||||||
|
if (! hits.empty()) {
|
||||||
// If the closest hit facet normal points in the same direction as the ray,
|
// If the closest hit facet normal points in the same direction as the ray,
|
||||||
// we are looking through the mesh and should therefore discard the point:
|
// we are looking through the mesh and should therefore discard the point:
|
||||||
if (m_AABB_wrapper->get_hit_normal(hits.front()).dot(direction_to_camera_mesh) > 0.f)
|
if (hits.front().normal().dot(direction_to_camera_mesh.cast<double>()) > 0)
|
||||||
is_obscured = true;
|
is_obscured = true;
|
||||||
|
|
||||||
// Eradicate all hits that the caller wants to ignore
|
// Eradicate all hits that the caller wants to ignore
|
||||||
for (unsigned j=0; j<hits.size(); ++j) {
|
for (unsigned j=0; j<hits.size(); ++j) {
|
||||||
const igl::Hit& hit = hits[j];
|
if (clipping_plane && clipping_plane->is_point_clipped(trafo.get_matrix() * hits[j].position())) {
|
||||||
if (clipping_plane && clipping_plane->is_point_clipped(trafo.get_matrix() * m_AABB_wrapper->get_hit_pos(hit).cast<double>())) {
|
|
||||||
hits.erase(hits.begin()+j);
|
hits.erase(hits.begin()+j);
|
||||||
--j;
|
--j;
|
||||||
}
|
}
|
||||||
|
|
@ -257,17 +192,15 @@ std::vector<unsigned> MeshRaycaster::get_unobscured_idxs(const Geometry::Transfo
|
||||||
Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const
|
Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const
|
||||||
{
|
{
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
Eigen::Matrix<float, 1, 3> closest_point;
|
Vec3d closest_point;
|
||||||
m_AABB_wrapper->m_AABB.squared_distance(
|
m_emesh.squared_distance(point.cast<double>(), idx, closest_point);
|
||||||
AABBWrapper::MapMatrixXfUnaligned(m_mesh->its.vertices.front().data(), m_mesh->its.vertices.size(), 3),
|
|
||||||
AABBWrapper::MapMatrixXiUnaligned(m_mesh->its.indices.front().data(), m_mesh->its.indices.size(), 3),
|
|
||||||
point, idx, closest_point);
|
|
||||||
if (normal) {
|
if (normal) {
|
||||||
igl::Hit imag_hit;
|
auto indices = m_emesh.F().row(idx);
|
||||||
imag_hit.id = idx;
|
Vec3d a(m_emesh.V().row(indices(1)) - m_emesh.V().row(indices(0)));
|
||||||
*normal = m_AABB_wrapper->get_hit_normal(imag_hit);
|
Vec3d b(m_emesh.V().row(indices(2)) - m_emesh.V().row(indices(0)));
|
||||||
|
*normal = Vec3f(a.cross(b).cast<float>());
|
||||||
}
|
}
|
||||||
return closest_point;
|
return closest_point.cast<float>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "libslic3r/Point.hpp"
|
#include "libslic3r/Point.hpp"
|
||||||
#include "libslic3r/Geometry.hpp"
|
#include "libslic3r/Geometry.hpp"
|
||||||
|
#include "libslic3r/SLA/EigenMesh3D.hpp"
|
||||||
|
|
||||||
|
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
|
|
@ -46,7 +47,7 @@ public:
|
||||||
bool operator!=(const ClippingPlane& cp) const { return ! (*this==cp); }
|
bool operator!=(const ClippingPlane& cp) const { return ! (*this==cp); }
|
||||||
|
|
||||||
double distance(const Vec3d& pt) const {
|
double distance(const Vec3d& pt) const {
|
||||||
assert(is_approx(get_normal().norm(), 1.));
|
// FIXME: this fails: assert(is_approx(get_normal().norm(), 1.));
|
||||||
return (-get_normal().dot(pt) + m_data[3]);
|
return (-get_normal().dot(pt) + m_data[3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,11 +105,11 @@ private:
|
||||||
// whether certain points are visible or obscured by the mesh etc.
|
// whether certain points are visible or obscured by the mesh etc.
|
||||||
class MeshRaycaster {
|
class MeshRaycaster {
|
||||||
public:
|
public:
|
||||||
// The class saves a const* to the mesh, called is responsible
|
// The class makes a copy of the mesh as EigenMesh3D.
|
||||||
// for making sure it does not get invalid.
|
// The pointer can be invalidated after constructor returns.
|
||||||
MeshRaycaster(const TriangleMesh& mesh);
|
MeshRaycaster(const TriangleMesh& mesh)
|
||||||
|
: m_emesh(mesh)
|
||||||
~MeshRaycaster();
|
{}
|
||||||
|
|
||||||
// Given a mouse position, this returns true in case it is on the mesh.
|
// Given a mouse position, this returns true in case it is on the mesh.
|
||||||
bool unproject_on_mesh(
|
bool unproject_on_mesh(
|
||||||
|
|
@ -136,10 +137,7 @@ public:
|
||||||
Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const;
|
Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// PIMPL wrapper around igl::AABB so I don't have to include the header-only IGL here
|
sla::EigenMesh3D m_emesh;
|
||||||
class AABBWrapper;
|
|
||||||
std::unique_ptr<AABBWrapper> m_AABB_wrapper;
|
|
||||||
const TriangleMesh* m_mesh = nullptr;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,11 +39,13 @@
|
||||||
#include "libslic3r/GCode/ThumbnailData.hpp"
|
#include "libslic3r/GCode/ThumbnailData.hpp"
|
||||||
#endif // ENABLE_THUMBNAIL_GENERATOR
|
#endif // ENABLE_THUMBNAIL_GENERATOR
|
||||||
#include "libslic3r/Model.hpp"
|
#include "libslic3r/Model.hpp"
|
||||||
|
#include "libslic3r/SLA/Hollowing.hpp"
|
||||||
|
#include "libslic3r/SLA/Rotfinder.hpp"
|
||||||
|
#include "libslic3r/SLA/SupportPoint.hpp"
|
||||||
#include "libslic3r/Polygon.hpp"
|
#include "libslic3r/Polygon.hpp"
|
||||||
#include "libslic3r/Print.hpp"
|
#include "libslic3r/Print.hpp"
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
#include "libslic3r/SLAPrint.hpp"
|
#include "libslic3r/SLAPrint.hpp"
|
||||||
#include "libslic3r/SLA/SLARotfinder.hpp"
|
|
||||||
#include "libslic3r/Utils.hpp"
|
#include "libslic3r/Utils.hpp"
|
||||||
|
|
||||||
//#include "libslic3r/ClipperUtils.hpp"
|
//#include "libslic3r/ClipperUtils.hpp"
|
||||||
|
|
@ -70,6 +72,7 @@
|
||||||
#include "Camera.hpp"
|
#include "Camera.hpp"
|
||||||
#include "Mouse3DController.hpp"
|
#include "Mouse3DController.hpp"
|
||||||
#include "Tab.hpp"
|
#include "Tab.hpp"
|
||||||
|
#include "Job.hpp"
|
||||||
#include "PresetBundle.hpp"
|
#include "PresetBundle.hpp"
|
||||||
#include "BackgroundSlicingProcess.hpp"
|
#include "BackgroundSlicingProcess.hpp"
|
||||||
#include "ProgressStatusBar.hpp"
|
#include "ProgressStatusBar.hpp"
|
||||||
|
|
@ -1503,144 +1506,39 @@ struct Plater::priv
|
||||||
// objects would be frozen for the user. In case of arrange, an animation
|
// objects would be frozen for the user. In case of arrange, an animation
|
||||||
// could be shown, or with the optimize orientations, partial results
|
// could be shown, or with the optimize orientations, partial results
|
||||||
// could be displayed.
|
// could be displayed.
|
||||||
class Job : public wxEvtHandler
|
class PlaterJob: public Job
|
||||||
{
|
{
|
||||||
int m_range = 100;
|
priv *m_plater;
|
||||||
boost::thread m_thread;
|
|
||||||
priv * m_plater = nullptr;
|
|
||||||
std::atomic<bool> m_running{false}, m_canceled{false};
|
|
||||||
bool m_finalized = false;
|
|
||||||
|
|
||||||
void run()
|
|
||||||
{
|
|
||||||
m_running.store(true);
|
|
||||||
process();
|
|
||||||
m_running.store(false);
|
|
||||||
|
|
||||||
// ensure to call the last status to finalize the job
|
|
||||||
update_status(status_range(), "");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// status range for a particular job
|
|
||||||
virtual int status_range() const { return 100; }
|
|
||||||
|
|
||||||
// status update, to be used from the work thread (process() method)
|
|
||||||
void update_status(int st, const wxString &msg = "")
|
|
||||||
{
|
|
||||||
auto evt = new wxThreadEvent();
|
|
||||||
evt->SetInt(st);
|
|
||||||
evt->SetString(msg);
|
|
||||||
wxQueueEvent(this, evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
priv & plater() { return *m_plater; }
|
priv & plater() { return *m_plater; }
|
||||||
const priv &plater() const { return *m_plater; }
|
const priv &plater() const { return *m_plater; }
|
||||||
bool was_canceled() const { return m_canceled.load(); }
|
|
||||||
|
|
||||||
// Launched just before start(), a job can use it to prepare internals
|
|
||||||
virtual void prepare() {}
|
|
||||||
|
|
||||||
// Launched when the job is finished. It refreshes the 3Dscene by def.
|
// Launched when the job is finished. It refreshes the 3Dscene by def.
|
||||||
virtual void finalize()
|
void finalize() override
|
||||||
{
|
{
|
||||||
// Do a full refresh of scene tree, including regenerating
|
// Do a full refresh of scene tree, including regenerating
|
||||||
// all the GLVolumes. FIXME The update function shall just
|
// all the GLVolumes. FIXME The update function shall just
|
||||||
// reload the modified matrices.
|
// reload the modified matrices.
|
||||||
if (!was_canceled())
|
if (!Job::was_canceled())
|
||||||
plater().update(unsigned(UpdateParams::FORCE_FULL_SCREEN_REFRESH));
|
plater().update(unsigned(UpdateParams::FORCE_FULL_SCREEN_REFRESH));
|
||||||
|
|
||||||
|
Job::finalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Job(priv *_plater) : m_plater(_plater)
|
PlaterJob(priv *_plater)
|
||||||
{
|
: Job(_plater->statusbar()), m_plater(_plater)
|
||||||
Bind(wxEVT_THREAD, [this](const wxThreadEvent &evt) {
|
{}
|
||||||
auto msg = evt.GetString();
|
|
||||||
if (!msg.empty())
|
|
||||||
plater().statusbar()->set_status_text(msg);
|
|
||||||
|
|
||||||
if (m_finalized) return;
|
|
||||||
|
|
||||||
plater().statusbar()->set_progress(evt.GetInt());
|
|
||||||
if (evt.GetInt() == status_range()) {
|
|
||||||
// set back the original range and cancel callback
|
|
||||||
plater().statusbar()->set_range(m_range);
|
|
||||||
plater().statusbar()->set_cancel_callback();
|
|
||||||
wxEndBusyCursor();
|
|
||||||
|
|
||||||
finalize();
|
|
||||||
|
|
||||||
// dont do finalization again for the same process
|
|
||||||
m_finalized = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Job(const Job &) = delete;
|
|
||||||
Job(Job &&) = delete;
|
|
||||||
Job &operator=(const Job &) = delete;
|
|
||||||
Job &operator=(Job &&) = delete;
|
|
||||||
|
|
||||||
virtual void process() = 0;
|
|
||||||
|
|
||||||
void start()
|
|
||||||
{ // Start the job. No effect if the job is already running
|
|
||||||
if (!m_running.load()) {
|
|
||||||
prepare();
|
|
||||||
|
|
||||||
// Save the current status indicatior range and push the new one
|
|
||||||
m_range = plater().statusbar()->get_range();
|
|
||||||
plater().statusbar()->set_range(status_range());
|
|
||||||
|
|
||||||
// init cancellation flag and set the cancel callback
|
|
||||||
m_canceled.store(false);
|
|
||||||
plater().statusbar()->set_cancel_callback(
|
|
||||||
[this]() { m_canceled.store(true); });
|
|
||||||
|
|
||||||
m_finalized = false;
|
|
||||||
|
|
||||||
// Changing cursor to busy
|
|
||||||
wxBeginBusyCursor();
|
|
||||||
|
|
||||||
try { // Execute the job
|
|
||||||
m_thread = create_thread([this] { this->run(); });
|
|
||||||
} catch (std::exception &) {
|
|
||||||
update_status(status_range(),
|
|
||||||
_(L("ERROR: not enough resources to "
|
|
||||||
"execute a new job.")));
|
|
||||||
}
|
|
||||||
|
|
||||||
// The state changes will be undone when the process hits the
|
|
||||||
// last status value, in the status update handler (see ctor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// To wait for the running job and join the threads. False is
|
|
||||||
// returned if the timeout has been reached and the job is still
|
|
||||||
// running. Call cancel() before this fn if you want to explicitly
|
|
||||||
// end the job.
|
|
||||||
bool join(int timeout_ms = 0)
|
|
||||||
{
|
|
||||||
if (!m_thread.joinable()) return true;
|
|
||||||
|
|
||||||
if (timeout_ms <= 0)
|
|
||||||
m_thread.join();
|
|
||||||
else if (!m_thread.try_join_for(boost::chrono::milliseconds(timeout_ms)))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool is_running() const { return m_running.load(); }
|
|
||||||
void cancel() { m_canceled.store(true); }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class Jobs : size_t {
|
enum class Jobs : size_t {
|
||||||
Arrange,
|
Arrange,
|
||||||
Rotoptimize
|
Rotoptimize,
|
||||||
|
Hollow
|
||||||
};
|
};
|
||||||
|
|
||||||
class ArrangeJob : public Job
|
class ArrangeJob : public PlaterJob
|
||||||
{
|
{
|
||||||
using ArrangePolygon = arrangement::ArrangePolygon;
|
using ArrangePolygon = arrangement::ArrangePolygon;
|
||||||
using ArrangePolygons = arrangement::ArrangePolygons;
|
using ArrangePolygons = arrangement::ArrangePolygons;
|
||||||
|
|
@ -1757,7 +1655,7 @@ struct Plater::priv
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using Job::Job;
|
using PlaterJob::PlaterJob;
|
||||||
|
|
||||||
int status_range() const override { return int(m_selected.size()); }
|
int status_range() const override { return int(m_selected.size()); }
|
||||||
|
|
||||||
|
|
@ -1774,13 +1672,30 @@ struct Plater::priv
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class RotoptimizeJob : public Job
|
class RotoptimizeJob : public PlaterJob
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
using Job::Job;
|
using PlaterJob::PlaterJob;
|
||||||
void process() override;
|
void process() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class HollowJob : public PlaterJob
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using PlaterJob::PlaterJob;
|
||||||
|
void prepare() override;
|
||||||
|
void process() override;
|
||||||
|
void finalize() override;
|
||||||
|
private:
|
||||||
|
GLGizmoHollow * get_gizmo();
|
||||||
|
const GLGizmoHollow * get_gizmo() const;
|
||||||
|
|
||||||
|
std::unique_ptr<TriangleMesh> m_output_mesh;
|
||||||
|
std::unique_ptr<MeshRaycaster> m_output_raycaster;
|
||||||
|
const TriangleMesh* m_object_mesh = nullptr;
|
||||||
|
sla::HollowingConfig m_cfg;
|
||||||
|
};
|
||||||
|
|
||||||
// Jobs defined inside the group class will be managed so that only one can
|
// Jobs defined inside the group class will be managed so that only one can
|
||||||
// run at a time. Also, the background process will be stopped if a job is
|
// run at a time. Also, the background process will be stopped if a job is
|
||||||
// started.
|
// started.
|
||||||
|
|
@ -1792,6 +1707,7 @@ struct Plater::priv
|
||||||
|
|
||||||
ArrangeJob arrange_job{m_plater};
|
ArrangeJob arrange_job{m_plater};
|
||||||
RotoptimizeJob rotoptimize_job{m_plater};
|
RotoptimizeJob rotoptimize_job{m_plater};
|
||||||
|
HollowJob hollow_job{m_plater};
|
||||||
|
|
||||||
// To create a new job, just define a new subclass of Job, implement
|
// To create a new job, just define a new subclass of Job, implement
|
||||||
// the process and the optional prepare() and finalize() methods
|
// the process and the optional prepare() and finalize() methods
|
||||||
|
|
@ -1799,7 +1715,8 @@ struct Plater::priv
|
||||||
// if it cannot run concurrently with other jobs in this group
|
// if it cannot run concurrently with other jobs in this group
|
||||||
|
|
||||||
std::vector<std::reference_wrapper<Job>> m_jobs{arrange_job,
|
std::vector<std::reference_wrapper<Job>> m_jobs{arrange_job,
|
||||||
rotoptimize_job};
|
rotoptimize_job,
|
||||||
|
hollow_job};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {}
|
ExclusiveJobGroup(priv *_plater) : m_plater(_plater) {}
|
||||||
|
|
@ -1876,7 +1793,7 @@ struct Plater::priv
|
||||||
|
|
||||||
void reset_all_gizmos();
|
void reset_all_gizmos();
|
||||||
void update_ui_from_settings();
|
void update_ui_from_settings();
|
||||||
ProgressStatusBar* statusbar();
|
std::shared_ptr<ProgressStatusBar> statusbar();
|
||||||
std::string get_config(const std::string &key) const;
|
std::string get_config(const std::string &key) const;
|
||||||
BoundingBoxf bed_shape_bb() const;
|
BoundingBoxf bed_shape_bb() const;
|
||||||
BoundingBox scaled_bed_shape_bb() const;
|
BoundingBox scaled_bed_shape_bb() const;
|
||||||
|
|
@ -1901,6 +1818,7 @@ struct Plater::priv
|
||||||
void reset();
|
void reset();
|
||||||
void mirror(Axis axis);
|
void mirror(Axis axis);
|
||||||
void arrange();
|
void arrange();
|
||||||
|
void hollow();
|
||||||
void sla_optimize_rotation();
|
void sla_optimize_rotation();
|
||||||
void split_object();
|
void split_object();
|
||||||
void split_volume();
|
void split_volume();
|
||||||
|
|
@ -2291,9 +2209,9 @@ void Plater::priv::update_ui_from_settings()
|
||||||
preview->get_canvas3d()->update_ui_from_settings();
|
preview->get_canvas3d()->update_ui_from_settings();
|
||||||
}
|
}
|
||||||
|
|
||||||
ProgressStatusBar* Plater::priv::statusbar()
|
std::shared_ptr<ProgressStatusBar> Plater::priv::statusbar()
|
||||||
{
|
{
|
||||||
return main_frame->m_statusbar.get();
|
return main_frame->m_statusbar;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Plater::priv::get_config(const std::string &key) const
|
std::string Plater::priv::get_config(const std::string &key) const
|
||||||
|
|
@ -2815,6 +2733,12 @@ void Plater::priv::arrange()
|
||||||
m_ui_jobs.start(Jobs::Arrange);
|
m_ui_jobs.start(Jobs::Arrange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Plater::priv::hollow()
|
||||||
|
{
|
||||||
|
this->take_snapshot(_(L("Hollow")));
|
||||||
|
m_ui_jobs.start(Jobs::Hollow);
|
||||||
|
}
|
||||||
|
|
||||||
// This method will find an optimal orientation for the currently selected item
|
// This method will find an optimal orientation for the currently selected item
|
||||||
// Very similar in nature to the arrange method above...
|
// Very similar in nature to the arrange method above...
|
||||||
void Plater::priv::sla_optimize_rotation() {
|
void Plater::priv::sla_optimize_rotation() {
|
||||||
|
|
@ -2946,6 +2870,68 @@ void Plater::priv::RotoptimizeJob::process()
|
||||||
: _(L("Orientation found.")));
|
: _(L("Orientation found.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Plater::priv::HollowJob::prepare()
|
||||||
|
{
|
||||||
|
const GLGizmosManager& gizmo_manager = plater().q->canvas3D()->get_gizmos_manager();
|
||||||
|
const GLGizmoHollow* gizmo_hollow = dynamic_cast<const GLGizmoHollow*>(gizmo_manager.get_current());
|
||||||
|
assert(gizmo_hollow);
|
||||||
|
auto hlw_data = gizmo_hollow->get_hollowing_parameters();
|
||||||
|
m_object_mesh = hlw_data.first;
|
||||||
|
m_cfg = hlw_data.second;
|
||||||
|
m_output_mesh.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Plater::priv::HollowJob::process()
|
||||||
|
{
|
||||||
|
sla::JobController ctl;
|
||||||
|
ctl.stopcondition = [this]{ return was_canceled(); };
|
||||||
|
ctl.statuscb = [this](unsigned st, const std::string &s) {
|
||||||
|
if (st < 100) update_status(int(st), s);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<TriangleMesh> omesh =
|
||||||
|
sla::generate_interior(*m_object_mesh, m_cfg, ctl);
|
||||||
|
|
||||||
|
if (omesh && !omesh->empty()) {
|
||||||
|
m_output_mesh.reset(new TriangleMesh{*m_object_mesh});
|
||||||
|
m_output_mesh->merge(*omesh);
|
||||||
|
m_output_mesh->require_shared_vertices();
|
||||||
|
|
||||||
|
update_status(90, _(L("Indexing hollowed object")));
|
||||||
|
|
||||||
|
m_output_raycaster.reset(new MeshRaycaster(*m_output_mesh));
|
||||||
|
|
||||||
|
update_status(100, was_canceled() ? _(L("Hollowing cancelled.")) :
|
||||||
|
_(L("Hollowing done.")));
|
||||||
|
} else {
|
||||||
|
update_status(100, _(L("Hollowing failed.")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Plater::priv::HollowJob::finalize()
|
||||||
|
{
|
||||||
|
if (auto gizmo = get_gizmo()) {
|
||||||
|
gizmo->update_mesh_raycaster(std::move(m_output_raycaster));
|
||||||
|
gizmo->update_hollowed_mesh(std::move(m_output_mesh));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GLGizmoHollow *Plater::priv::HollowJob::get_gizmo()
|
||||||
|
{
|
||||||
|
const GLGizmosManager& gizmo_manager = plater().q->canvas3D()->get_gizmos_manager();
|
||||||
|
auto ret = dynamic_cast<GLGizmoHollow*>(gizmo_manager.get_current());
|
||||||
|
assert(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GLGizmoHollow *Plater::priv::HollowJob::get_gizmo() const
|
||||||
|
{
|
||||||
|
const GLGizmosManager& gizmo_manager = plater().q->canvas3D()->get_gizmos_manager();
|
||||||
|
auto ret = dynamic_cast<const GLGizmoHollow*>(gizmo_manager.get_current());
|
||||||
|
assert(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
void Plater::priv::split_object()
|
void Plater::priv::split_object()
|
||||||
{
|
{
|
||||||
int obj_idx = get_selected_object_idx();
|
int obj_idx = get_selected_object_idx();
|
||||||
|
|
@ -5066,6 +5052,11 @@ void Plater::export_toolpaths_to_obj() const
|
||||||
p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str());
|
p->preview->get_canvas3d()->export_toolpaths_to_obj(into_u8(path).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Plater::hollow()
|
||||||
|
{
|
||||||
|
p->hollow();
|
||||||
|
}
|
||||||
|
|
||||||
void Plater::reslice()
|
void Plater::reslice()
|
||||||
{
|
{
|
||||||
// Stop arrange and (or) optimize rotation tasks.
|
// Stop arrange and (or) optimize rotation tasks.
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,7 @@ public:
|
||||||
void reload_from_disk();
|
void reload_from_disk();
|
||||||
bool has_toolpaths_to_export() const;
|
bool has_toolpaths_to_export() const;
|
||||||
void export_toolpaths_to_obj() const;
|
void export_toolpaths_to_obj() const;
|
||||||
|
void hollow();
|
||||||
void reslice();
|
void reslice();
|
||||||
void reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages = false);
|
void reslice_SLA_supports(const ModelObject &object, bool postpone_error_messages = false);
|
||||||
void changed_object(int obj_idx);
|
void changed_object(int obj_idx);
|
||||||
|
|
|
||||||
|
|
@ -505,6 +505,10 @@ const std::vector<std::string>& Preset::sla_print_options()
|
||||||
"pad_object_connector_stride",
|
"pad_object_connector_stride",
|
||||||
"pad_object_connector_width",
|
"pad_object_connector_width",
|
||||||
"pad_object_connector_penetration",
|
"pad_object_connector_penetration",
|
||||||
|
"hollowing_enable",
|
||||||
|
"hollowing_min_thickness",
|
||||||
|
"hollowing_quality",
|
||||||
|
"hollowing_closing_distance",
|
||||||
"output_filename_format",
|
"output_filename_format",
|
||||||
"default_sla_print_profile",
|
"default_sla_print_profile",
|
||||||
"compatible_printers",
|
"compatible_printers",
|
||||||
|
|
|
||||||
|
|
@ -11,58 +11,17 @@ namespace Slic3r {
|
||||||
*/
|
*/
|
||||||
class ProgressIndicator {
|
class ProgressIndicator {
|
||||||
public:
|
public:
|
||||||
using CancelFn = std::function<void(void)>; // Cancel function signature.
|
|
||||||
|
|
||||||
private:
|
/// Cancel callback function type
|
||||||
float m_state = .0f, m_max = 1.f, m_step;
|
using CancelFn = std::function<void()>;
|
||||||
CancelFn m_cancelfunc = [](){};
|
|
||||||
|
|
||||||
public:
|
virtual ~ProgressIndicator() = default;
|
||||||
|
|
||||||
inline virtual ~ProgressIndicator() {}
|
virtual void set_range(int range) = 0;
|
||||||
|
virtual void set_cancel_callback(CancelFn = CancelFn()) = 0;
|
||||||
/// Get the maximum of the progress range.
|
virtual void set_progress(int pr) = 0;
|
||||||
float max() const { return m_max; }
|
virtual void set_status_text(const char *) = 0; // utf8 char array
|
||||||
|
virtual int get_range() const = 0;
|
||||||
/// Get the current progress state
|
|
||||||
float state() const { return m_state; }
|
|
||||||
|
|
||||||
/// Set the maximum of the progress range
|
|
||||||
virtual void max(float maxval) { m_max = maxval; }
|
|
||||||
|
|
||||||
/// Set the current state of the progress.
|
|
||||||
virtual void state(float val) { m_state = val; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Number of states int the progress. Can be used instead of giving a
|
|
||||||
* maximum value.
|
|
||||||
*/
|
|
||||||
virtual void states(unsigned statenum) {
|
|
||||||
m_step = m_max / statenum;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Message shown on the next status update.
|
|
||||||
virtual void message(const std::string&) = 0;
|
|
||||||
|
|
||||||
/// Title of the operation.
|
|
||||||
virtual void title(const std::string&) = 0;
|
|
||||||
|
|
||||||
/// Formatted message for the next status update. Works just like sprintf.
|
|
||||||
virtual void message_fmt(const std::string& fmt, ...);
|
|
||||||
|
|
||||||
/// Set up a cancel callback for the operation if feasible.
|
|
||||||
virtual void on_cancel(CancelFn func = CancelFn()) { m_cancelfunc = func; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Explicitly shut down the progress indicator and call the associated
|
|
||||||
* callback.
|
|
||||||
*/
|
|
||||||
virtual void cancel() { m_cancelfunc(); }
|
|
||||||
|
|
||||||
/// Convenience function to call message and status update in one function.
|
|
||||||
void update(float st, const std::string& msg) {
|
|
||||||
message(msg); state(st);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,7 @@
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id)
|
ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id)
|
||||||
: self{new wxStatusBar(parent ? parent : GUI::wxGetApp().mainframe,
|
: self{new wxStatusBar(parent, id == -1 ? wxID_ANY : id)}
|
||||||
id == -1 ? wxID_ANY : id)}
|
|
||||||
, m_prog{new wxGauge(self,
|
, m_prog{new wxGauge(self,
|
||||||
wxGA_HORIZONTAL,
|
wxGA_HORIZONTAL,
|
||||||
100,
|
100,
|
||||||
|
|
@ -32,7 +31,6 @@ ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id)
|
||||||
m_prog->Hide();
|
m_prog->Hide();
|
||||||
m_cancelbutton->Hide();
|
m_cancelbutton->Hide();
|
||||||
|
|
||||||
self->SetFont(GUI::wxGetApp().normal_font());
|
|
||||||
self->SetFieldsCount(3);
|
self->SetFieldsCount(3);
|
||||||
int w[] = {-1, 150, 155};
|
int w[] = {-1, 150, 155};
|
||||||
self->SetStatusWidths(3, w);
|
self->SetStatusWidths(3, w);
|
||||||
|
|
@ -149,8 +147,7 @@ void ProgressStatusBar::run(int rate)
|
||||||
|
|
||||||
void ProgressStatusBar::embed(wxFrame *frame)
|
void ProgressStatusBar::embed(wxFrame *frame)
|
||||||
{
|
{
|
||||||
wxFrame* mf = frame ? frame : GUI::wxGetApp().mainframe;
|
if(frame) frame->SetStatusBar(self);
|
||||||
if(mf) mf->SetStatusBar(self);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ProgressStatusBar::set_status_text(const wxString& txt)
|
void ProgressStatusBar::set_status_text(const wxString& txt)
|
||||||
|
|
@ -173,6 +170,11 @@ wxString ProgressStatusBar::get_status_text() const
|
||||||
return self->GetStatusText();
|
return self->GetStatusText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ProgressStatusBar::set_font(const wxFont &font)
|
||||||
|
{
|
||||||
|
self->SetFont(font);
|
||||||
|
}
|
||||||
|
|
||||||
void ProgressStatusBar::show_cancel_button()
|
void ProgressStatusBar::show_cancel_button()
|
||||||
{
|
{
|
||||||
if(m_cancelbutton) m_cancelbutton->Show();
|
if(m_cancelbutton) m_cancelbutton->Show();
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "ProgressIndicator.hpp"
|
||||||
|
|
||||||
class wxTimer;
|
class wxTimer;
|
||||||
class wxGauge;
|
class wxGauge;
|
||||||
class wxButton;
|
class wxButton;
|
||||||
|
|
@ -14,6 +16,7 @@ class wxStatusBar;
|
||||||
class wxWindow;
|
class wxWindow;
|
||||||
class wxFrame;
|
class wxFrame;
|
||||||
class wxString;
|
class wxString;
|
||||||
|
class wxFont;
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
|
|
@ -22,7 +25,7 @@ namespace Slic3r {
|
||||||
* of the Slicer main window. It consists of a message area to the left and a
|
* of the Slicer main window. It consists of a message area to the left and a
|
||||||
* progress indication area to the right with an optional cancel button.
|
* progress indication area to the right with an optional cancel button.
|
||||||
*/
|
*/
|
||||||
class ProgressStatusBar
|
class ProgressStatusBar : public ProgressIndicator
|
||||||
{
|
{
|
||||||
wxStatusBar *self; // we cheat! It should be the base class but: perl!
|
wxStatusBar *self; // we cheat! It should be the base class but: perl!
|
||||||
wxGauge *m_prog;
|
wxGauge *m_prog;
|
||||||
|
|
@ -30,30 +33,29 @@ class ProgressStatusBar
|
||||||
std::unique_ptr<wxTimer> m_timer;
|
std::unique_ptr<wxTimer> m_timer;
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/// Cancel callback function type
|
|
||||||
using CancelFn = std::function<void()>;
|
|
||||||
|
|
||||||
ProgressStatusBar(wxWindow *parent = nullptr, int id = -1);
|
ProgressStatusBar(wxWindow *parent = nullptr, int id = -1);
|
||||||
~ProgressStatusBar();
|
~ProgressStatusBar() override;
|
||||||
|
|
||||||
int get_progress() const;
|
int get_progress() const;
|
||||||
// if the argument is less than 0 it shows the last state or
|
// if the argument is less than 0 it shows the last state or
|
||||||
// pulses if no state was set before.
|
// pulses if no state was set before.
|
||||||
void set_progress(int);
|
void set_progress(int) override;
|
||||||
int get_range() const;
|
int get_range() const override;
|
||||||
void set_range(int = 100);
|
void set_range(int = 100) override;
|
||||||
void show_progress(bool);
|
void show_progress(bool);
|
||||||
void start_busy(int = 100);
|
void start_busy(int = 100);
|
||||||
void stop_busy();
|
void stop_busy();
|
||||||
inline bool is_busy() const { return m_busy; }
|
inline bool is_busy() const { return m_busy; }
|
||||||
void set_cancel_callback(CancelFn = CancelFn());
|
void set_cancel_callback(CancelFn = CancelFn()) override;
|
||||||
inline void reset_cancel_callback() { set_cancel_callback(); }
|
inline void reset_cancel_callback() { set_cancel_callback(); }
|
||||||
void run(int rate);
|
void run(int rate);
|
||||||
void embed(wxFrame *frame = nullptr);
|
void embed(wxFrame *frame = nullptr);
|
||||||
void set_status_text(const wxString& txt);
|
void set_status_text(const wxString& txt);
|
||||||
void set_status_text(const std::string& txt);
|
void set_status_text(const std::string& txt);
|
||||||
void set_status_text(const char *txt);
|
void set_status_text(const char *txt) override;
|
||||||
wxString get_status_text() const;
|
wxString get_status_text() const;
|
||||||
|
void set_font(const wxFont &font);
|
||||||
|
|
||||||
// Temporary methods to satisfy Perl side
|
// Temporary methods to satisfy Perl side
|
||||||
void show_cancel_button();
|
void show_cancel_button();
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue